Ports Devils from Goobstation (#4639)
* or your debil * Woop * Whoops, Missed this * Whoops, Suicide System * Hush... Extended tests... Hush * AAAAAA * No-longer jaunt whilst dead * :) * Update Content.Shared/_Lavaland/Chasm/PreventChasmFallingSystem.cs Co-authored-by: Tobias Berger <toby@tobot.dev> Signed-off-by: William Lemon <William.Lemon2@gmail.com> * Update Content.Server/_Goobstation/Possession/PossessionSystem.cs Co-authored-by: Tobias Berger <toby@tobot.dev> Signed-off-by: William Lemon <William.Lemon2@gmail.com> * Please tobysenpai forgive me --------- Signed-off-by: William Lemon <William.Lemon2@gmail.com> Co-authored-by: Tobias Berger <toby@tobot.dev>
This commit is contained in:
parent
496cb0f7cc
commit
f563f2c566
|
|
@ -0,0 +1,52 @@
|
|||
// SPDX-FileCopyrightText: 2025 GoobBot <uristmchands@proton.me>
|
||||
// SPDX-FileCopyrightText: 2025 SolsticeOfTheWinter <solsticeofthewinter@gmail.com>
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
using Content.Shared._Goobstation.Devil.UI;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.UserInterface;
|
||||
|
||||
namespace Content.Client._Goobstation.Devil.UI;
|
||||
|
||||
[UsedImplicitly]
|
||||
public sealed class RevivalContractBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
private RevivalContractMenu? _menu;
|
||||
|
||||
public RevivalContractBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
}
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
|
||||
_menu = this.CreateWindow<RevivalContractMenu>();
|
||||
_menu.SetEntity(Owner);
|
||||
_menu.Accepted += OnAccepted;
|
||||
_menu.Rejected += OnRejected;
|
||||
_menu.OnClose += OnClosed;
|
||||
|
||||
_menu.OpenCentered();
|
||||
}
|
||||
|
||||
private void OnAccepted()
|
||||
{
|
||||
SendPredictedMessage(new RevivalContractMessage(true));
|
||||
Close();
|
||||
}
|
||||
|
||||
private void OnRejected()
|
||||
{
|
||||
SendPredictedMessage(new RevivalContractMessage(false));
|
||||
Close();
|
||||
}
|
||||
|
||||
private void OnClosed()
|
||||
{
|
||||
SendPredictedMessage(new RevivalContractMessage(false));
|
||||
Close();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
<DefaultWindow xmlns="https://spacestation14.io"
|
||||
xmlns:customControls="clr-namespace:Content.Client.Administration.UI.CustomControls"
|
||||
Title="{Loc 'revival-contract-menu-title'}"
|
||||
MinWidth="300">
|
||||
<BoxContainer Orientation="Vertical" Margin="4">
|
||||
<Label Name="RevivalPrompt"
|
||||
Text="{Loc 'revival-contract-menu-prompt'}"
|
||||
Align="Left"
|
||||
ClipText="True"/>
|
||||
<Button Name="AcceptButton"
|
||||
Text="{Loc 'revival-contract-prompt-accept'}"
|
||||
ClipText="True"/>
|
||||
<Button Name="RejectButton"
|
||||
Text="{Loc 'revival-contract-prompt-reject'}"
|
||||
ClipText="True"/>
|
||||
</BoxContainer>
|
||||
</DefaultWindow>
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
// SPDX-FileCopyrightText: 2025 GoobBot <uristmchands@proton.me>
|
||||
// SPDX-FileCopyrightText: 2025 SolsticeOfTheWinter <solsticeofthewinter@gmail.com>
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
|
||||
namespace Content.Client._Goobstation.Devil.UI;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class RevivalContractMenu : DefaultWindow
|
||||
{
|
||||
public event Action? Accepted;
|
||||
public event Action? Rejected;
|
||||
|
||||
public EntityUid? Entity { get; private set; }
|
||||
|
||||
public RevivalContractMenu()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
AcceptButton.OnPressed += OnAcceptPressed;
|
||||
RejectButton.OnPressed += OnRejectPressed;
|
||||
}
|
||||
|
||||
private void OnAcceptPressed(BaseButton.ButtonEventArgs args)
|
||||
{
|
||||
Accepted?.Invoke();
|
||||
Close();
|
||||
}
|
||||
|
||||
private void OnRejectPressed(BaseButton.ButtonEventArgs args)
|
||||
{
|
||||
Rejected?.Invoke();
|
||||
Close();
|
||||
}
|
||||
|
||||
public void SetEntity(EntityUid ent)
|
||||
{
|
||||
Entity = ent;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
using Content.Server.Administration.Logs;
|
||||
using Content.Server.Atmos.Components;
|
||||
using Content.Shared._Goobstation.Atmos;
|
||||
using Content.Shared.Alert;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Damage;
|
||||
|
|
@ -144,7 +145,7 @@ namespace Content.Server.Atmos.EntitySystems
|
|||
/// </summary>
|
||||
public float GetFeltLowPressure(EntityUid uid, BarotraumaComponent barotrauma, float environmentPressure)
|
||||
{
|
||||
if (barotrauma.HasImmunity)
|
||||
if (barotrauma.HasImmunity || HasComp<SpecialPressureImmunityComponent>(uid)) // Goobstation - Special Immunity
|
||||
{
|
||||
return Atmospherics.OneAtmosphere;
|
||||
}
|
||||
|
|
@ -158,7 +159,7 @@ namespace Content.Server.Atmos.EntitySystems
|
|||
/// </summary>
|
||||
public float GetFeltHighPressure(EntityUid uid, BarotraumaComponent barotrauma, float environmentPressure)
|
||||
{
|
||||
if (barotrauma.HasImmunity)
|
||||
if (barotrauma.HasImmunity || HasComp<SpecialPressureImmunityComponent>(uid)) // Goobstation - Special Immunity
|
||||
{
|
||||
return Atmospherics.OneAtmosphere;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using Content.Shared.Damage;
|
||||
using Content.Shared.FixedPoint; // Goobstation
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
|
|
@ -39,6 +40,18 @@ namespace Content.Server.Bible.Components
|
|||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public DamageSpecifier DamageOnUntrainedUse = default!;
|
||||
|
||||
/// <summary>
|
||||
/// How much damage to deal to the entity being smitten - Goob
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public DamageSpecifier SmiteDamage = new() { DamageDict = new Dictionary<string, FixedPoint2>() { { "Holy", 25 } } }; // Ungodly
|
||||
|
||||
/// <summary>
|
||||
/// How long to stun the entity being smitten - Goob
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public TimeSpan SmiteStunDuration = TimeSpan.FromSeconds(8);
|
||||
|
||||
/// <summary>
|
||||
/// Chance the bible will fail to heal someone with no helmet
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ public sealed class BodySystem : SharedBodySystem
|
|||
}
|
||||
}
|
||||
|
||||
protected override void RemovePart(
|
||||
public override void RemovePart( // DeltaV - Made public
|
||||
Entity<BodyComponent?> bodyEnt,
|
||||
Entity<BodyPartComponent> partEnt,
|
||||
string slotId)
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ using JetBrains.Annotations;
|
|||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
using Content.Shared._DV.CosmicCult.Components; // DeltaV
|
||||
using Content.Shared._Goobstation.Body.Components; // Goobstation
|
||||
|
||||
|
||||
namespace Content.Server.Body.Systems;
|
||||
|
|
@ -80,7 +81,7 @@ public sealed class RespiratorSystem : EntitySystem
|
|||
|
||||
respirator.NextUpdate += respirator.AdjustedUpdateInterval;
|
||||
|
||||
if (_mobState.IsDead(uid) || HasComp<BreathingImmunityComponent>(uid)) // Shitmed: BreathingImmunity
|
||||
if (_mobState.IsDead(uid) || HasComp<BreathingImmunityComponent>(uid) || HasComp<SpecialBreathingImmunityComponent>(uid)) // Shitmed: BreathingImmunity Goob immunity too
|
||||
continue;
|
||||
|
||||
// Begin DeltaV Additions
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ public sealed class SuicideSystem : EntitySystem
|
|||
[Dependency] private readonly SharedSuicideSystem _suicide = default!;
|
||||
|
||||
private static readonly ProtoId<TagPrototype> CannotSuicideTag = "CannotSuicide";
|
||||
private static readonly ProtoId<TagPrototype> CannotSuicideAny = "CannotSuicideAny"; // DeltaV / Goob, ProtoID Tag
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
|
|
@ -49,7 +50,7 @@ public sealed class SuicideSystem : EntitySystem
|
|||
public bool Suicide(EntityUid victim)
|
||||
{
|
||||
// Can't suicide if we're already dead
|
||||
if (!TryComp<MobStateComponent>(victim, out var mobState) || _mobState.IsDead(victim, mobState))
|
||||
if (!TryComp<MobStateComponent>(victim, out var mobState) || _mobState.IsDead(victim, mobState) || _tagSystem.HasTag(victim, CannotSuicideAny)) // Goobstation - DeltaV Use ProtoId
|
||||
return false;
|
||||
|
||||
_adminLogger.Add(LogType.Mind, $"{ToPrettyString(victim):player} is attempting to suicide");
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ using Content.Server.Ghost;
|
|||
using Content.Server.Morgue.Components;
|
||||
using Content.Server.Storage.Components;
|
||||
using Content.Server.Storage.EntitySystems;
|
||||
using Content.Shared._Goobstation.CrematorImmune;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.IdentityManagement;
|
||||
|
|
@ -133,6 +134,8 @@ public sealed class CrematoriumSystem : EntitySystem
|
|||
for (var i = storage.Contents.ContainedEntities.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var item = storage.Contents.ContainedEntities[i];
|
||||
if (HasComp<CrematoriumImmuneComponent>(item)) // GOOBCODE ALERT //
|
||||
continue;
|
||||
_containers.Remove(item, storage.Contents);
|
||||
Del(item);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,8 +13,9 @@ using Content.Shared.Rejuvenate;
|
|||
using Content.Shared.Temperature;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Physics.Events;
|
||||
using Content.Shared.Projectiles;
|
||||
using Content.Shared._Goobstation.Temperature.Components;
|
||||
using Content.Shared._Goobstation.Temperature;
|
||||
|
||||
namespace Content.Server.Temperature.Systems;
|
||||
|
||||
|
|
@ -52,6 +53,9 @@ public sealed class TemperatureSystem : EntitySystem
|
|||
|
||||
SubscribeLocalEvent<ChangeTemperatureOnCollideComponent, ProjectileHitEvent>(ChangeTemperatureOnCollide);
|
||||
|
||||
SubscribeLocalEvent<SpecialLowTempImmunityComponent, TemperatureImmunityEvent>(OnCheckLowTemperatureImmunity); // Goob edit
|
||||
SubscribeLocalEvent<SpecialHighTempImmunityComponent, TemperatureImmunityEvent>(OnCheckHighTemperatureImmunity); // Goob edit
|
||||
|
||||
// Allows overriding thresholds based on the parent's thresholds.
|
||||
SubscribeLocalEvent<TemperatureComponent, EntParentChangedMessage>(OnParentChange);
|
||||
SubscribeLocalEvent<ContainerTemperatureDamageThresholdsComponent, ComponentStartup>(
|
||||
|
|
@ -116,14 +120,36 @@ public sealed class TemperatureSystem : EntitySystem
|
|||
ShouldUpdateDamage.Clear();
|
||||
}
|
||||
|
||||
// Goob start
|
||||
private void OnCheckLowTemperatureImmunity(Entity<SpecialLowTempImmunityComponent> ent, ref TemperatureImmunityEvent args)
|
||||
{
|
||||
if (args.CurrentTemperature < args.IdealTemperature)
|
||||
args.CurrentTemperature = args.IdealTemperature;
|
||||
}
|
||||
|
||||
private void OnCheckHighTemperatureImmunity(Entity<SpecialHighTempImmunityComponent> ent, ref TemperatureImmunityEvent args)
|
||||
{
|
||||
if (args.CurrentTemperature > args.IdealTemperature)
|
||||
args.CurrentTemperature = args.IdealTemperature;
|
||||
}
|
||||
// Goob end
|
||||
|
||||
public void ForceChangeTemperature(EntityUid uid, float temp, TemperatureComponent? temperature = null)
|
||||
{
|
||||
if (!Resolve(uid, ref temperature))
|
||||
return;
|
||||
|
||||
float lastTemp = temperature.CurrentTemperature;
|
||||
float delta = temperature.CurrentTemperature - temp;
|
||||
temperature.CurrentTemperature = temp;
|
||||
|
||||
// Goob start
|
||||
var tempEv = new TemperatureImmunityEvent(temperature.CurrentTemperature);
|
||||
RaiseLocalEvent(uid, tempEv);
|
||||
temperature.CurrentTemperature = tempEv.CurrentTemperature;
|
||||
|
||||
float delta = temperature.CurrentTemperature - temp;
|
||||
// Goob end
|
||||
|
||||
RaiseLocalEvent(uid, new OnTemperatureChangeEvent(temperature.CurrentTemperature, lastTemp, delta),
|
||||
true);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,67 @@
|
|||
// SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
|
||||
// SPDX-FileCopyrightText: 2025 GoobBot <uristmchands@proton.me>
|
||||
// SPDX-FileCopyrightText: 2025 Misandry <mary@thughunt.ing>
|
||||
// SPDX-FileCopyrightText: 2025 Roudenn <romabond091@gmail.com>
|
||||
// SPDX-FileCopyrightText: 2025 Solstice <solsticeofthewinter@gmail.com>
|
||||
// SPDX-FileCopyrightText: 2025 SolsticeOfTheWinter <solsticeofthewinter@gmail.com>
|
||||
// SPDX-FileCopyrightText: 2025 gus <august.eymann@gmail.com>
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Content.Server._Goobstation.Devil.GameTicking.Rules;
|
||||
using Content.Server.Administration.Managers;
|
||||
using Content.Server.Antag;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Mind.Components;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server._Goobstation.Administration.Systems;
|
||||
|
||||
public sealed partial class GoobAdminVerbSystem
|
||||
{
|
||||
[Dependency] private readonly AntagSelectionSystem _antag = default!;
|
||||
[Dependency] private readonly IAdminManager _admin = default!;
|
||||
|
||||
private void AddAntagVerbs(GetVerbsEvent<Verb> args)
|
||||
{
|
||||
if (!AntagVerbAllowed(args, out var targetPlayer))
|
||||
return;
|
||||
// Devil
|
||||
Verb devilAntag = new()
|
||||
{
|
||||
Text = Loc.GetString("admin-verb-text-make-devil"),
|
||||
Category = VerbCategory.Antag,
|
||||
Icon = new SpriteSpecifier.Rsi(new("_Goobstation/Actions/devil.rsi"), "summon-contract"),
|
||||
Act = () =>
|
||||
{
|
||||
_antag.ForceMakeAntag<DevilRuleComponent>(targetPlayer, "Devil");
|
||||
},
|
||||
Impact = LogImpact.High,
|
||||
Message = Loc.GetString("admin-verb-make-devil"),
|
||||
};
|
||||
args.Verbs.Add(devilAntag);
|
||||
}
|
||||
|
||||
public bool AntagVerbAllowed(GetVerbsEvent<Verb> args, [NotNullWhen(true)] out ICommonSession? target)
|
||||
{
|
||||
target = null;
|
||||
|
||||
if (!TryComp<ActorComponent>(args.User, out var actor))
|
||||
return false;
|
||||
|
||||
var player = actor.PlayerSession;
|
||||
|
||||
if (!_admin.HasAdminFlag(player, AdminFlags.Fun))
|
||||
return false;
|
||||
|
||||
if (!HasComp<MindContainerComponent>(args.Target) || !TryComp<ActorComponent>(args.Target, out var targetActor))
|
||||
return false;
|
||||
|
||||
target = targetActor.PlayerSession;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
// SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
|
||||
// SPDX-FileCopyrightText: 2025 Conchelle <mary@thughunt.ing>
|
||||
// SPDX-FileCopyrightText: 2025 GoobBot <uristmchands@proton.me>
|
||||
// SPDX-FileCopyrightText: 2025 Misandry <mary@thughunt.ing>
|
||||
// SPDX-FileCopyrightText: 2025 gus <august.eymann@gmail.com>
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
using Content.Shared.Verbs;
|
||||
|
||||
namespace Content.Server._Goobstation.Administration.Systems;
|
||||
|
||||
public sealed partial class GoobAdminVerbSystem : EntitySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<GetVerbsEvent<Verb>>(GetVerbs);
|
||||
}
|
||||
|
||||
private void GetVerbs(GetVerbsEvent<Verb> args)
|
||||
{
|
||||
AddAntagVerbs(args);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
// SPDX-FileCopyrightText: 2025 GoobBot <uristmchands@proton.me>
|
||||
// SPDX-FileCopyrightText: 2025 Solstice <solsticeofthewinter@gmail.com>
|
||||
// SPDX-FileCopyrightText: 2025 SolsticeOfTheWinter <solsticeofthewinter@gmail.com>
|
||||
// SPDX-FileCopyrightText: 2025 TheBorzoiMustConsume <197824988+TheBorzoiMustConsume@users.noreply.github.com>
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
using Content.Shared._Goobstation.Religion;
|
||||
using Content.Server.Bible.Components;
|
||||
using Content.Shared._Goobstation.Devil;
|
||||
using Content.Shared._Goobstation.Exorcism;
|
||||
using Content.Shared._Goobstation.Religion;
|
||||
using Content.Shared._Shitmed.Targeting;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Stunnable;
|
||||
using Content.Shared.Timing;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server._Goobstation.Bible;
|
||||
|
||||
public sealed partial class GoobBibleSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly DamageableSystem _damageableSystem = default!;
|
||||
[Dependency] private readonly MobStateSystem _mobStateSystem = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly UseDelaySystem _delay = default!;
|
||||
[Dependency] private readonly SharedStunSystem _stun = default!;
|
||||
[Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly INetManager _netManager = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
}
|
||||
|
||||
public bool TryDoSmite(EntityUid bible, EntityUid performer, EntityUid target, UseDelayComponent? useDelay = null, BibleComponent? bibleComp = null)
|
||||
{
|
||||
if (!Resolve(bible, ref useDelay, ref bibleComp))
|
||||
return false;
|
||||
|
||||
if (!TryComp<WeakToHolyComponent>(target, out var weakToHoly)
|
||||
|| weakToHoly is { AlwaysTakeHoly: false }
|
||||
|| !HasComp<BibleUserComponent>(performer)
|
||||
|| !_timing.IsFirstTimePredicted
|
||||
|| _delay.IsDelayed(bible)
|
||||
|| !_netManager.IsServer)
|
||||
return false;
|
||||
|
||||
var multiplier = 1f;
|
||||
var isDevil = false;
|
||||
|
||||
if (TryComp<DevilComponent>(target, out var devil))
|
||||
{
|
||||
isDevil = true;
|
||||
multiplier = devil.BibleUserDamageMultiplier;
|
||||
}
|
||||
|
||||
if (!_mobStateSystem.IsIncapacitated(target))
|
||||
{
|
||||
var popup = Loc.GetString("weaktoholy-component-bible-sizzle", ("target", target), ("item", bible));
|
||||
_popupSystem.PopupPredicted(popup, target, performer, PopupType.LargeCaution);
|
||||
_audio.PlayPvs(bibleComp.SizzleSoundPath, target);
|
||||
_damageableSystem.TryChangeDamage(target, bibleComp.SmiteDamage * multiplier, true, origin: bible, targetPart: TargetBodyPart.All);
|
||||
_stun.TryParalyze(target, bibleComp.SmiteStunDuration * multiplier, false);
|
||||
_delay.TryResetDelay((bible, useDelay));
|
||||
}
|
||||
else if (isDevil && HasComp<BibleUserComponent>(performer))
|
||||
{
|
||||
var doAfterArgs = new DoAfterArgs(
|
||||
EntityManager,
|
||||
performer,
|
||||
TimeSpan.FromSeconds(10f),
|
||||
new ExorcismDoAfterEvent(),
|
||||
eventTarget: target,
|
||||
target: target)
|
||||
{
|
||||
BreakOnMove = true,
|
||||
NeedHand = true,
|
||||
BlockDuplicate = true,
|
||||
BreakOnDropItem = true,
|
||||
};
|
||||
|
||||
_doAfter.TryStartDoAfter(doAfterArgs);
|
||||
var popup = Loc.GetString("devil-banish-begin", ("target", target), ("user", performer));
|
||||
_popupSystem.PopupEntity(popup, target, PopupType.LargeCaution);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,145 @@
|
|||
// 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 Content.Shared._Goobstation.DelayedDeath;
|
||||
using Content.Shared._Goobstation.CheatDeath;
|
||||
using Content.Shared._Goobstation.Devour.Events;
|
||||
using Content.Server._Shitmed.DelayedDeath;
|
||||
using Content.Server.Actions;
|
||||
using Content.Server.Administration.Systems;
|
||||
using Content.Server.Jittering;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Traits.Assorted;
|
||||
using Robust.Shared.Network;
|
||||
|
||||
namespace Content.Server._Goobstation.Devil.CheatDeath;
|
||||
|
||||
public sealed partial class CheatDeathSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly RejuvenateSystem _rejuvenateSystem = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
|
||||
[Dependency] private readonly MobStateSystem _mobStateSystem = default!;
|
||||
[Dependency] private readonly ActionsSystem _actionsSystem = default!;
|
||||
[Dependency] private readonly JitteringSystem _jitter = default!;
|
||||
[Dependency] private readonly MobThresholdSystem _thresholdSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<CheatDeathComponent, MapInitEvent>(OnInit);
|
||||
SubscribeLocalEvent<CheatDeathComponent, ComponentRemove>(OnRemoval);
|
||||
|
||||
SubscribeLocalEvent<CheatDeathComponent, CheatDeathEvent>(OnDeathCheatAttempt);
|
||||
|
||||
SubscribeLocalEvent<CheatDeathComponent, ExaminedEvent>(OnExamined);
|
||||
|
||||
SubscribeLocalEvent<CheatDeathComponent, DelayedDeathEvent>(OnDelayedDeath);
|
||||
}
|
||||
|
||||
private void OnInit(Entity<CheatDeathComponent> ent, ref MapInitEvent args) =>
|
||||
_actionsSystem.AddAction(ent, ref ent.Comp.ActionEntity, ent.Comp.ActionCheatDeath);
|
||||
|
||||
private void OnRemoval(Entity<CheatDeathComponent> ent, ref ComponentRemove args) =>
|
||||
_actionsSystem.RemoveAction(ent.Owner, ent.Comp.ActionEntity);
|
||||
|
||||
private void OnExamined(Entity<CheatDeathComponent> ent, ref ExaminedEvent args)
|
||||
{
|
||||
if (args.Examined != args.Examiner)
|
||||
return;
|
||||
|
||||
if (ent.Comp.InfiniteRevives)
|
||||
{
|
||||
var unlimited = Loc.GetString("cheat-death-component-remaining-revives-unlimited");
|
||||
args.PushMarkup(unlimited);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var remaining = Loc.GetString("cheat-death-component-remaining-revives", ("amount", ent.Comp.ReviveAmount));
|
||||
args.PushMarkup(remaining);
|
||||
|
||||
}
|
||||
|
||||
private void OnDelayedDeath(Entity<CheatDeathComponent> ent, ref DelayedDeathEvent args)
|
||||
{
|
||||
if (args.PreventRevive)
|
||||
RemCompDeferred(ent.Owner, ent.Comp);
|
||||
}
|
||||
|
||||
|
||||
private void OnDeathCheatAttempt(Entity<CheatDeathComponent> ent, ref CheatDeathEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
if (!_mobStateSystem.IsDead(ent) && !ent.Comp.CanCheatStanding)
|
||||
{
|
||||
var failPopup = Loc.GetString("action-cheat-death-fail-not-dead");
|
||||
_popupSystem.PopupEntity(failPopup, ent, ent, PopupType.LargeCaution);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// check if we're allowed to revive
|
||||
var reviveEv = new BeforeSelfRevivalEvent(ent, "self-revive-fail");
|
||||
RaiseLocalEvent(ent, ref reviveEv);
|
||||
|
||||
if (reviveEv.Cancelled)
|
||||
return;
|
||||
|
||||
// If the entity is out of revives, or if they are unrevivable, return.
|
||||
if (ent.Comp.ReviveAmount <= 0 || HasComp<UnrevivableComponent>(ent))
|
||||
{
|
||||
var failPopup = Loc.GetString("action-cheat-death-fail-no-lives");
|
||||
_popupSystem.PopupEntity(failPopup, ent, ent, PopupType.LargeCaution);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// If the holy damage exceeds the crit state, do not allow revives.
|
||||
if (!TryComp<DamageableComponent>(ent, out var damageable)
|
||||
|| !_thresholdSystem.TryGetIncapThreshold(ent, out var incapThreshold)
|
||||
|| damageable.Damage.DamageDict["Holy"] >= incapThreshold)
|
||||
{
|
||||
var failPopup = Loc.GetString("action-cheat-death-holy-damage");
|
||||
_popupSystem.PopupEntity(failPopup, ent, ent, PopupType.LargeCaution);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Show popup
|
||||
if (_mobStateSystem.IsDead(ent) && !ent.Comp.CanCheatStanding)
|
||||
{
|
||||
var popup = Loc.GetString("action-cheated-death-dead", ("name", Name(ent)));
|
||||
_popupSystem.PopupEntity(popup, ent, PopupType.LargeCaution);
|
||||
}
|
||||
else
|
||||
{
|
||||
var popup = Loc.GetString("action-cheated-death-alive", ("name", Name(ent)));
|
||||
_popupSystem.PopupEntity(popup, ent, PopupType.LargeCaution);
|
||||
}
|
||||
|
||||
// Revive entity
|
||||
_rejuvenateSystem.PerformRejuvenate(ent);
|
||||
_jitter.DoJitter(ent, TimeSpan.FromSeconds(5), true);
|
||||
|
||||
// Decrement remaining revives.
|
||||
if (!ent.Comp.InfiniteRevives)
|
||||
ent.Comp.ReviveAmount--;
|
||||
|
||||
// remove comp if at zero
|
||||
if (ent.Comp.ReviveAmount <= 0 && !ent.Comp.InfiniteRevives)
|
||||
RemComp(ent.Owner, ent.Comp);
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
// 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 Content.Shared._Goobstation.CheatDeath;
|
||||
using Content.Shared._Goobstation.Devil.Condemned;
|
||||
using Content.Shared.Mobs;
|
||||
|
||||
namespace Content.Server._Goobstation.Devil.Condemned;
|
||||
public sealed partial class CondemnedSystem
|
||||
{
|
||||
public void InitializeOnDeath()
|
||||
{
|
||||
SubscribeLocalEvent<CondemnedComponent, MobStateChangedEvent>(OnMobStateChanged);
|
||||
}
|
||||
|
||||
private void OnMobStateChanged(EntityUid uid, CondemnedComponent comp, MobStateChangedEvent args)
|
||||
{
|
||||
if (args.NewMobState != MobState.Dead
|
||||
|| comp.SoulOwnedNotDevil
|
||||
|| !comp.CondemnOnDeath)
|
||||
return;
|
||||
|
||||
if (TryComp<CheatDeathComponent>(uid, out var cheatDeath)
|
||||
&& cheatDeath.ReviveAmount > 0 || cheatDeath is { InfiniteRevives: true })
|
||||
return;
|
||||
|
||||
StartCondemnation(uid, behavior: CondemnedBehavior.Delete);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,187 @@
|
|||
// 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 Content.Shared._Goobstation.Devil;
|
||||
using Content.Shared._Goobstation.Devil.Condemned;
|
||||
using Content.Shared._Goobstation.Religion;
|
||||
using Content.Server.IdentityManagement;
|
||||
using Content.Server.Polymorph.Systems;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Interaction.Components;
|
||||
using Content.Shared.Movement.Events;
|
||||
using Content.Shared.Polymorph;
|
||||
using Content.Shared.Popups;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Spawners;
|
||||
using Content.Server.Forensics;
|
||||
|
||||
namespace Content.Server._Goobstation.Devil.Condemned;
|
||||
|
||||
public sealed partial class CondemnedSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly PolymorphSystem _poly = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
[Dependency] private readonly ForensicsSystem _forensics = default!; // DeltaV - Forensics DNA
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<CondemnedComponent, ExaminedEvent>(OnExamined);
|
||||
SubscribeLocalEvent<CondemnedComponent, MapInitEvent>(OnStartup);
|
||||
SubscribeLocalEvent<CondemnedComponent, ComponentRemove>(OnRemoved);
|
||||
SubscribeLocalEvent<CondemnedComponent, UpdateCanMoveEvent>(OnMoveAttempt);
|
||||
InitializeOnDeath();
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
var query = EntityQueryEnumerator<CondemnedComponent>();
|
||||
while (query.MoveNext(out var uid, out var comp))
|
||||
{
|
||||
switch (comp.CurrentPhase)
|
||||
{
|
||||
case CondemnedPhase.PentagramActive:
|
||||
UpdatePentagramPhase(uid, frameTime, comp);
|
||||
break;
|
||||
case CondemnedPhase.HandActive:
|
||||
UpdateHandPhase(uid, frameTime, comp);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnStartup(Entity<CondemnedComponent> condemned, ref MapInitEvent args)
|
||||
{
|
||||
if (condemned.Comp.SoulOwnedNotDevil)
|
||||
return;
|
||||
|
||||
if (HasComp<WeakToHolyComponent>(condemned))
|
||||
condemned.Comp.WasWeakToHoly = true;
|
||||
else
|
||||
EnsureComp<WeakToHolyComponent>(condemned).AlwaysTakeHoly = true;
|
||||
}
|
||||
|
||||
private void OnRemoved(Entity<CondemnedComponent> condemned, ref ComponentRemove args)
|
||||
{
|
||||
if (condemned.Comp.SoulOwnedNotDevil)
|
||||
return;
|
||||
|
||||
if (!condemned.Comp.WasWeakToHoly)
|
||||
RemComp<WeakToHolyComponent>(condemned);
|
||||
}
|
||||
|
||||
private void OnMoveAttempt(Entity<CondemnedComponent> condemned, ref UpdateCanMoveEvent args)
|
||||
{
|
||||
if (!condemned.Comp.FreezeDuringCondemnation
|
||||
|| condemned.Comp.CurrentPhase != CondemnedPhase.Waiting)
|
||||
return;
|
||||
|
||||
args.Cancel();
|
||||
}
|
||||
|
||||
public void StartCondemnation(
|
||||
EntityUid uid,
|
||||
bool freezeEntity = true,
|
||||
bool doFlavor = true,
|
||||
CondemnedBehavior behavior = CondemnedBehavior.Delete)
|
||||
{
|
||||
var comp = EnsureComp<CondemnedComponent>(uid);
|
||||
comp.CondemnOnDeath = false;
|
||||
|
||||
if (freezeEntity)
|
||||
comp.FreezeDuringCondemnation = true;
|
||||
|
||||
var coords = Transform(uid).Coordinates;
|
||||
Spawn(comp.PentagramProto, coords);
|
||||
_audio.PlayPvs(comp.SoundEffect, coords);
|
||||
|
||||
if (comp.CondemnedBehavior == CondemnedBehavior.Delete && doFlavor)
|
||||
_popup.PopupCoordinates(Loc.GetString("condemned-start", ("target", uid)), coords, PopupType.LargeCaution);
|
||||
|
||||
comp.CurrentPhase = CondemnedPhase.PentagramActive;
|
||||
comp.PhaseTimer = 0f;
|
||||
comp.CondemnedBehavior = behavior;
|
||||
}
|
||||
|
||||
private void UpdatePentagramPhase(EntityUid uid, float frameTime, CondemnedComponent? comp = null)
|
||||
{
|
||||
if (!Resolve(uid, ref comp))
|
||||
return;
|
||||
|
||||
comp.PhaseTimer += frameTime;
|
||||
|
||||
if (comp.PhaseTimer < 3f)
|
||||
return;
|
||||
|
||||
var coords = Transform(uid).Coordinates;
|
||||
var handEntity = Spawn(comp.HandProto, coords);
|
||||
|
||||
comp.HandDuration = TryComp<TimedDespawnComponent>(handEntity, out var timedDespawn)
|
||||
? timedDespawn.Lifetime
|
||||
: 1f;
|
||||
|
||||
comp.CurrentPhase = CondemnedPhase.HandActive;
|
||||
comp.PhaseTimer = 0f;
|
||||
}
|
||||
|
||||
private void UpdateHandPhase(EntityUid uid, float frameTime, CondemnedComponent? comp = null)
|
||||
{
|
||||
if (!Resolve(uid, ref comp))
|
||||
return;
|
||||
|
||||
comp.PhaseTimer += frameTime;
|
||||
|
||||
if (comp.PhaseTimer < comp.HandDuration)
|
||||
return;
|
||||
|
||||
DoCondemnedBehavior(uid, comp.ScrambleAfterBanish);
|
||||
|
||||
comp.CurrentPhase = CondemnedPhase.Complete;
|
||||
}
|
||||
|
||||
private void DoCondemnedBehavior(EntityUid uid, bool scramble = true, CondemnedComponent? comp = null)
|
||||
{
|
||||
if (!Resolve(uid, ref comp))
|
||||
return;
|
||||
|
||||
switch (comp)
|
||||
{
|
||||
case { CondemnedBehavior: CondemnedBehavior.Delete }:
|
||||
QueueDel(uid);
|
||||
break;
|
||||
case { CondemnedBehavior: CondemnedBehavior.Banish }:
|
||||
if (scramble)
|
||||
{
|
||||
_forensics.RandomizeDNA(uid); // DeltaV - was ScrambleSystem, now Forensics.
|
||||
_forensics.RandomizeFingerprint(uid);
|
||||
}
|
||||
_poly.PolymorphEntity(uid, comp.BanishProto);
|
||||
break;
|
||||
}
|
||||
|
||||
RemCompDeferred(uid, comp);
|
||||
}
|
||||
|
||||
private void OnExamined(Entity<CondemnedComponent> condemned, ref ExaminedEvent args)
|
||||
{
|
||||
if (!args.IsInDetailsRange
|
||||
|| condemned.Comp.SoulOwnedNotDevil)
|
||||
return;
|
||||
|
||||
var ev = new IsEyesCoveredCheckEvent();
|
||||
RaiseLocalEvent(condemned, ev);
|
||||
|
||||
if (ev.IsEyesProtected)
|
||||
return;
|
||||
|
||||
args.PushMarkup(Loc.GetString("condemned-component-examined", ("target", Identity.Entity(condemned, EntityManager) )));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,105 @@
|
|||
// SPDX-FileCopyrightText: 2025 GoobBot <uristmchands@proton.me>
|
||||
// SPDX-FileCopyrightText: 2025 Solstice <solsticeofthewinter@gmail.com>
|
||||
// SPDX-FileCopyrightText: 2025 SolsticeOfTheWinter <solsticeofthewinter@gmail.com>
|
||||
// SPDX-FileCopyrightText: 2025 gluesniffler <159397573+gluesniffler@users.noreply.github.com>
|
||||
// SPDX-FileCopyrightText: 2025 gluesniffler <linebarrelerenthusiast@gmail.com>
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
using System.Linq;
|
||||
using Content.Shared._Goobstation.Devil;
|
||||
using Content.Server.Body.Components;
|
||||
using Content.Shared.Body.Components;
|
||||
using Content.Shared.Body.Part;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server._Goobstation.Devil.Contract;
|
||||
|
||||
public sealed partial class DevilContractSystem
|
||||
{
|
||||
private void InitializeSpecialActions()
|
||||
{
|
||||
SubscribeLocalEvent<DevilContractSoulOwnershipEvent>(OnSoulOwnership);
|
||||
SubscribeLocalEvent<DevilContractLoseHandEvent>(OnLoseHand);
|
||||
SubscribeLocalEvent<DevilContractLoseLegEvent>(OnLoseLeg);
|
||||
SubscribeLocalEvent<DevilContractLoseOrganEvent>(OnLoseOrgan);
|
||||
SubscribeLocalEvent<DevilContractChanceEvent>(OnChance);
|
||||
}
|
||||
private void OnSoulOwnership(DevilContractSoulOwnershipEvent args)
|
||||
{
|
||||
if (args.Contract?.ContractOwner is not { } contractOwner)
|
||||
return;
|
||||
|
||||
TryTransferSouls(contractOwner, args.Target, 1);
|
||||
}
|
||||
|
||||
private void OnLoseHand(DevilContractLoseHandEvent args)
|
||||
{
|
||||
if (!TryComp<BodyComponent>(args.Target, out var body))
|
||||
return;
|
||||
|
||||
var hands = _bodySystem.GetBodyChildrenOfType(args.Target, BodyPartType.Hand, body).ToList();
|
||||
|
||||
if (hands.Count <= 0)
|
||||
return;
|
||||
|
||||
var pick = _random.Pick(hands);
|
||||
|
||||
if (!TryComp<BodyPartComponent>(pick.Id, out var woundable)
|
||||
|| !woundable.CanSever)
|
||||
return;
|
||||
|
||||
_bodySystem.RemovePart(new(args.Target, body), pick, _bodySystem.GetSlotFromBodyPart(pick.Component));
|
||||
QueueDel(pick.Id);
|
||||
|
||||
Dirty(args.Target, body);
|
||||
Log.Debug($"Removed part {ToPrettyString(pick.Id)} from {ToPrettyString(args.Target)}"); // DeltaV - Use EntitySystem Logger intead of _sawmill
|
||||
QueueDel(pick.Id);
|
||||
}
|
||||
|
||||
private void OnLoseLeg(DevilContractLoseLegEvent args)
|
||||
{
|
||||
if (!TryComp<BodyComponent>(args.Target, out var body))
|
||||
return;
|
||||
|
||||
var legs = _bodySystem.GetBodyChildrenOfType(args.Target, BodyPartType.Leg, body).ToList();
|
||||
|
||||
if (legs.Count <= 0)
|
||||
return;
|
||||
|
||||
var pick = _random.Pick(legs);
|
||||
|
||||
if (!TryComp<BodyPartComponent>(pick.Id, out var woundable)
|
||||
|| !woundable.CanSever)
|
||||
return;
|
||||
|
||||
_bodySystem.RemovePart(new(args.Target, body), pick, _bodySystem.GetSlotFromBodyPart(pick.Component));
|
||||
|
||||
Dirty(args.Target, body);
|
||||
Log.Debug($"Removed part {ToPrettyString(pick.Id)} from {ToPrettyString(args.Target)}"); // DeltaV - Use EntitySystem Logger intead of _sawmill
|
||||
QueueDel(pick.Id);
|
||||
}
|
||||
|
||||
private void OnLoseOrgan(DevilContractLoseOrganEvent args)
|
||||
{
|
||||
// don't remove the brain, as funny as that is.
|
||||
var eligibleOrgans = _bodySystem.GetBodyOrgans(args.Target)
|
||||
.Where(o => !HasComp<BrainComponent>(o.Id))
|
||||
.ToList();
|
||||
|
||||
if (eligibleOrgans.Count <= 0)
|
||||
return;
|
||||
|
||||
var pick = _random.Pick(eligibleOrgans);
|
||||
|
||||
_bodySystem.RemoveOrgan(pick.Id, pick.Component);
|
||||
Log.Debug($"Removed part {ToPrettyString(pick.Id)} from {ToPrettyString(args.Target)}"); // DeltaV - Use EntitySystem Logger intead of _sawmill
|
||||
QueueDel(pick.Id);
|
||||
}
|
||||
|
||||
// LETS GO GAMBLING!!!!!
|
||||
private void OnChance(DevilContractChanceEvent args)
|
||||
{
|
||||
AddRandomClause(args.Target);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,499 @@
|
|||
// SPDX-FileCopyrightText: 2025 GoobBot <uristmchands@proton.me>
|
||||
// SPDX-FileCopyrightText: 2025 Solstice <solsticeofthewinter@gmail.com>
|
||||
// SPDX-FileCopyrightText: 2025 SolsticeOfTheWinter <solsticeofthewinter@gmail.com>
|
||||
// SPDX-FileCopyrightText: 2025 coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
|
||||
// SPDX-FileCopyrightText: 2025 gluesniffler <159397573+gluesniffler@users.noreply.github.com>
|
||||
// SPDX-FileCopyrightText: 2025 gluesniffler <linebarrelerenthusiast@gmail.com>
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using Content.Shared._Goobstation.Paper;
|
||||
using Content.Server._Goobstation.Devil.Objectives.Components;
|
||||
using Content.Server._Goobstation.Possession;
|
||||
using Content.Shared._Goobstation.Devil;
|
||||
using Content.Shared._Goobstation.Devil.Condemned;
|
||||
using Content.Shared._Goobstation.Devil.Contract;
|
||||
using Content.Server.Body.Systems;
|
||||
using Content.Server.Explosion.EntitySystems;
|
||||
using Content.Server.Hands.Systems;
|
||||
using Content.Server.Implants;
|
||||
using Content.Server.Mind;
|
||||
using Content.Server.Polymorph.Systems;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Mindshield.Components;
|
||||
using Content.Shared.Nutrition;
|
||||
using Content.Shared.Paper;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Silicons.Borgs.Components;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Utility;
|
||||
using Content.Shared._EE.Silicon.Components;
|
||||
|
||||
namespace Content.Server._Goobstation.Devil.Contract;
|
||||
|
||||
public sealed partial class DevilContractSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SharedPopupSystem _popupSystem = null!;
|
||||
[Dependency] private readonly DamageableSystem _damageable = null!;
|
||||
[Dependency] private readonly HandsSystem _hands = null!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = null!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = null!;
|
||||
[Dependency] private readonly BodySystem _bodySystem = null!;
|
||||
[Dependency] private readonly IRobustRandom _random = null!;
|
||||
[Dependency] private readonly SubdermalImplantSystem _implant = null!;
|
||||
[Dependency] private readonly PolymorphSystem _polymorph = null!;
|
||||
[Dependency] private readonly ExplosionSystem _explosion = null!;
|
||||
[Dependency] private readonly MindSystem _mind = null!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
InitializeRegex();
|
||||
InitializeSpecialActions();
|
||||
|
||||
SubscribeLocalEvent<DevilContractComponent, BeingSignedAttemptEvent>(OnContractSignAttempt);
|
||||
SubscribeLocalEvent<DevilContractComponent, ExaminedEvent>(OnExamined);
|
||||
SubscribeLocalEvent<DevilContractComponent, GetVerbsEvent<AlternativeVerb>>(OnGetVerbs);
|
||||
SubscribeLocalEvent<DevilContractComponent, SignSuccessfulEvent>(OnSignStep);
|
||||
SubscribeLocalEvent<DevilContractComponent, AfterFullyEatenEvent>(OnEaten);
|
||||
}
|
||||
|
||||
private readonly Dictionary<LocId, Func<DevilContractComponent, EntityUid?>> _targetResolvers = new()
|
||||
{
|
||||
// The contractee is who is making the deal.
|
||||
["devil-contract-contractee"] = comp => comp.Signer,
|
||||
// The contractor is the entity offering the deal.
|
||||
["devil-contract-contractor"] = comp => comp.ContractOwner,
|
||||
};
|
||||
|
||||
private Regex _clauseRegex = null!;
|
||||
|
||||
private void InitializeRegex()
|
||||
{
|
||||
var escapedPatterns = _targetResolvers.Keys.Select(locId => Loc.GetString(locId)).ToList(); // malicious linq and regex
|
||||
var targetPattern = string.Join("|", escapedPatterns);
|
||||
|
||||
_clauseRegex = new Regex($@"^\s*(?<target>{targetPattern})\s*:\s*(?<clause>.+?)\s*$",
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.Multiline);
|
||||
}
|
||||
private void OnGetVerbs(Entity<DevilContractComponent> contract, ref GetVerbsEvent<AlternativeVerb> args)
|
||||
{
|
||||
if (!args.CanInteract
|
||||
|| !args.CanAccess
|
||||
|| !TryComp<DevilComponent>(args.User, out var devilComp))
|
||||
return;
|
||||
|
||||
var user = args.User;
|
||||
|
||||
AlternativeVerb burnVerb = new()
|
||||
{
|
||||
Act = () => TryBurnContract(contract, (user, devilComp)),
|
||||
Text = Loc.GetString("burn-contract-prompt"),
|
||||
Icon = new SpriteSpecifier.Rsi(new ("/Textures/Effects/fire.rsi"), "fire"),
|
||||
};
|
||||
|
||||
args.Verbs.Add(burnVerb);
|
||||
}
|
||||
|
||||
private void TryBurnContract(Entity<DevilContractComponent> contract, Entity<DevilComponent> devil)
|
||||
{
|
||||
var coordinates = Transform(contract).Coordinates;
|
||||
|
||||
if (contract.Comp is not { IsContractFullySigned: true})
|
||||
{
|
||||
Spawn(devil.Comp.FireEffectProto, coordinates);
|
||||
_audio.PlayPvs(devil.Comp.FwooshPath, coordinates, new AudioParams(-2f, 1f, SharedAudioSystem.DefaultSoundRange, 1f, false, 0f));
|
||||
QueueDel(contract);
|
||||
}
|
||||
else
|
||||
_popupSystem.PopupCoordinates(Loc.GetString("burn-contract-popup-fail"), coordinates, devil, PopupType.MediumCaution);
|
||||
}
|
||||
|
||||
private void OnExamined(Entity<DevilContractComponent> contract, ref ExaminedEvent args)
|
||||
{
|
||||
if (!args.IsInDetailsRange)
|
||||
return;
|
||||
|
||||
UpdateContractWeight(contract);
|
||||
args.PushMarkup(Loc.GetString("devil-contract-examined", ("weight", contract.Comp.ContractWeight)));
|
||||
}
|
||||
|
||||
private void OnEaten(Entity<DevilContractComponent> contract, ref AfterFullyEatenEvent args)
|
||||
{
|
||||
_explosion.QueueExplosion(
|
||||
args.User,
|
||||
typeId: "Default",
|
||||
totalIntensity: 1, // contract explosions should not cause any kind of major structural damage. you should at worst need to weld a window or repair a table.
|
||||
slope: 1,
|
||||
maxTileIntensity: 1,
|
||||
maxTileBreak: 0,
|
||||
addLog: false); // DeltaV - Use EntitySystem Logger intead of _sawmill
|
||||
}
|
||||
|
||||
#region Signing Steps
|
||||
|
||||
private void OnContractSignAttempt(Entity<DevilContractComponent> contract, ref BeingSignedAttemptEvent args)
|
||||
{
|
||||
// Make sure that weight is set properly!
|
||||
UpdateContractWeight(contract);
|
||||
|
||||
// Don't allow mortals to sign contracts for other people.
|
||||
if (contract.Comp.IsVictimSigned && args.Signer != contract.Comp.ContractOwner)
|
||||
{
|
||||
var invalidUserPopup = Loc.GetString("devil-sign-invalid-user");
|
||||
_popupSystem.PopupEntity(invalidUserPopup, contract, args.Signer);
|
||||
|
||||
args.Cancelled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Only handle unsigned contracts.
|
||||
if (contract.Comp.IsVictimSigned || contract.Comp.IsDevilSigned)
|
||||
return;
|
||||
|
||||
if (!IsUserValid(args.Signer, out var failReason))
|
||||
{
|
||||
_popupSystem.PopupEntity(failReason, contract, args.Signer, PopupType.MediumCaution);
|
||||
|
||||
args.Cancelled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the weight is too low
|
||||
if (!contract.Comp.IsContractSignable)
|
||||
{
|
||||
var difference = Math.Abs(contract.Comp.ContractWeight);
|
||||
|
||||
var unevenOddsPopup = Loc.GetString("contract-uneven-odds", ("number", difference));
|
||||
_popupSystem.PopupEntity(unevenOddsPopup, contract, args.Signer, PopupType.MediumCaution);
|
||||
|
||||
args.Cancelled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if devil is trying to sign first
|
||||
if (args.Signer == contract.Comp.ContractOwner)
|
||||
{
|
||||
var tooEarlyPopup = Loc.GetString("devil-contract-early-sign-failed");
|
||||
_popupSystem.PopupEntity(tooEarlyPopup, args.Signer, args.Signer, PopupType.MediumCaution);
|
||||
|
||||
args.Cancelled = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void OnSignStep(Entity<DevilContractComponent> contract, ref SignSuccessfulEvent args)
|
||||
{
|
||||
// Determine signing phase
|
||||
if (!contract.Comp.IsVictimSigned)
|
||||
HandleVictimSign(contract, args.Paper, args.User);
|
||||
else if (!contract.Comp.IsDevilSigned)
|
||||
HandleDevilSign(contract, args.Paper, args.User);
|
||||
|
||||
// Final activation check
|
||||
if (contract.Comp.IsContractFullySigned)
|
||||
HandleBothPartiesSigned(contract);
|
||||
}
|
||||
|
||||
private void HandleVictimSign(Entity<DevilContractComponent> contract, EntityUid signed, EntityUid signer)
|
||||
{
|
||||
contract.Comp.Signer = signer;
|
||||
contract.Comp.IsVictimSigned = true;
|
||||
|
||||
if (TryComp<PaperComponent>(contract, out var paper))
|
||||
paper.EditingDisabled = true;
|
||||
|
||||
_popupSystem.PopupEntity(Loc.GetString("contract-victim-signed"), signed, signer);
|
||||
}
|
||||
|
||||
private void HandleDevilSign(Entity<DevilContractComponent> contract, EntityUid signed, EntityUid signer)
|
||||
{
|
||||
contract.Comp.IsDevilSigned = true;
|
||||
_popupSystem.PopupEntity(Loc.GetString("contract-devil-signed"), signed, signer);
|
||||
|
||||
if (_mind.TryGetMind(signer, out var mindId, out var mind) &&
|
||||
_mind.TryGetObjectiveComp<MeetContractWeightConditionComponent>(mindId, out var objectiveComp, mind))
|
||||
objectiveComp.ContractWeight += contract.Comp.ContractWeight;
|
||||
}
|
||||
|
||||
private void HandleBothPartiesSigned(Entity<DevilContractComponent> contract)
|
||||
{
|
||||
UpdateContractWeight(contract);
|
||||
DoContractEffects(contract);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Helper Events
|
||||
|
||||
public bool IsUserValid(EntityUid user, out string failReason)
|
||||
{
|
||||
if (HasComp<CondemnedComponent>(user)
|
||||
|| HasComp<SiliconComponent>(user)
|
||||
|| HasComp<BorgChassisComponent>(user))
|
||||
{
|
||||
failReason = Loc.GetString("devil-contract-no-soul-sign-failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (HasComp<MindShieldComponent>(user)
|
||||
&& !HasComp<DevilComponent>(user))
|
||||
{
|
||||
failReason = Loc.GetString("devil-contract-mind-shielded-failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (HasComp<PossessedComponent>(user))
|
||||
{
|
||||
failReason = Loc.GetString("devil-contract-early-sign-failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
failReason = string.Empty;
|
||||
return true;
|
||||
}
|
||||
public bool TryTransferSouls(EntityUid devil, EntityUid contractee, int added)
|
||||
{
|
||||
// Can't sell what doesn't exist.
|
||||
if (HasComp<CondemnedComponent>(contractee)
|
||||
|| devil == contractee)
|
||||
return false;
|
||||
|
||||
var ev = new SoulAmountChangedEvent(devil, contractee, added);
|
||||
RaiseLocalEvent(devil, ref ev);
|
||||
|
||||
var condemned = EnsureComp<CondemnedComponent>(contractee);
|
||||
condemned.SoulOwner = devil;
|
||||
condemned.CondemnOnDeath = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void UpdateContractWeight(Entity<DevilContractComponent> contract, PaperComponent? paper = null)
|
||||
{
|
||||
if (!Resolve(contract, ref paper))
|
||||
return;
|
||||
|
||||
contract.Comp.CurrentClauses.Clear();
|
||||
var newWeight = 0;
|
||||
|
||||
var matches = _clauseRegex.Matches(paper.Content);
|
||||
foreach (Match match in matches)
|
||||
{
|
||||
if (!match.Success)
|
||||
continue;
|
||||
|
||||
var clauseKey = match.Groups["clause"].Value.Trim().ToLowerInvariant().Replace(" ", "");
|
||||
|
||||
if (!_prototypeManager.TryIndex(clauseKey, out DevilClausePrototype? clauseProto)
|
||||
|| !contract.Comp.CurrentClauses.Add(clauseProto))
|
||||
continue;
|
||||
|
||||
newWeight += clauseProto.ClauseWeight;
|
||||
}
|
||||
|
||||
contract.Comp.ContractWeight = newWeight;
|
||||
}
|
||||
|
||||
private void DoContractEffects(Entity<DevilContractComponent> contract, PaperComponent? paper = null)
|
||||
{
|
||||
if (!Resolve(contract, ref paper))
|
||||
return;
|
||||
|
||||
UpdateContractWeight(contract);
|
||||
|
||||
var matches = _clauseRegex.Matches(paper.Content);
|
||||
var processedClauses = new HashSet<string>();
|
||||
|
||||
foreach (Match match in matches)
|
||||
{
|
||||
if (!match.Success)
|
||||
continue;
|
||||
|
||||
var targetKey = match.Groups["target"].Value.Trim().ToLowerInvariant().Replace(" ", "");
|
||||
var clauseKey = match.Groups["clause"].Value.Trim().ToLowerInvariant().Replace(" ", "");
|
||||
|
||||
var locId = _targetResolvers.Keys.FirstOrDefault(id => Loc.GetString(id).Equals(targetKey, StringComparison.OrdinalIgnoreCase));
|
||||
var resolver = _targetResolvers[locId];
|
||||
|
||||
if (resolver(contract.Comp) == null)
|
||||
{
|
||||
Log.Warning($"Unknown resolver: {resolver(contract.Comp)}"); // DeltaV - Use EntitySystem Logger intead of _sawmill
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!_prototypeManager.TryIndex(clauseKey, out DevilClausePrototype? clause))
|
||||
{
|
||||
Log.Warning($"Unknown contract clause: {clauseKey}"); // DeltaV - Use EntitySystem Logger intead of _sawmill
|
||||
continue;
|
||||
}
|
||||
|
||||
// no duplicates
|
||||
if (!processedClauses.Add(clauseKey))
|
||||
{
|
||||
Log.Warning($"Attempted to apply duplicate clause: {clauseKey} on contract {ToPrettyString(contract)}"); // DeltaV - Use EntitySystem Logger intead of _sawmill
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
var targetEntity = resolver(contract.Comp);
|
||||
|
||||
if (targetEntity is not null)
|
||||
ApplyEffectToTarget(targetEntity.Value, clause, contract);
|
||||
else
|
||||
Log.Warning($"Invalid target entity from resolver for clause {clauseKey} in contract {ToPrettyString(contract)}"); // DeltaV - Use EntitySystem Logger intead of _sawmill
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyEffectToTarget(EntityUid target, DevilClausePrototype clause, Entity<DevilContractComponent>? contract)
|
||||
{
|
||||
|
||||
DoPolymorphs(target, clause);
|
||||
|
||||
RemoveComponents(target, clause);
|
||||
|
||||
AddComponents(target, clause);
|
||||
|
||||
OverrideComponents(target, clause); // DeltaV - Fix component modifications
|
||||
|
||||
ChangeDamageModifier(target, clause);
|
||||
|
||||
AddImplants(target, clause);
|
||||
|
||||
SpawnItems(target, clause);
|
||||
|
||||
DoSpecialActions(target, contract, clause);
|
||||
}
|
||||
|
||||
private void ChangeDamageModifier(EntityUid target, DevilClausePrototype clause)
|
||||
{
|
||||
if (clause.DamageModifierSet == null)
|
||||
return;
|
||||
|
||||
_damageable.SetDamageModifierSetId(target, clause.DamageModifierSet);
|
||||
}
|
||||
|
||||
private void RemoveComponents(EntityUid target, DevilClausePrototype clause)
|
||||
{
|
||||
if (clause.RemovedComponents == null)
|
||||
return;
|
||||
|
||||
EntityManager.RemoveComponents(target, clause.RemovedComponents);
|
||||
}
|
||||
|
||||
private void AddImplants(EntityUid target, DevilClausePrototype clause)
|
||||
{
|
||||
if (clause.Implants == null)
|
||||
return;
|
||||
|
||||
_implant.AddImplants(target, clause.Implants);
|
||||
}
|
||||
|
||||
private void AddComponents(EntityUid target, DevilClausePrototype clause)
|
||||
{
|
||||
if (clause.AddedComponents == null)
|
||||
return;
|
||||
|
||||
EntityManager.AddComponents(target, clause.AddedComponents, false);
|
||||
}
|
||||
|
||||
// Begin DeltaV Addition - Fix component modifications
|
||||
private void OverrideComponents(EntityUid target, DevilClausePrototype clause)
|
||||
{
|
||||
if (clause.OverriddenComponents == null)
|
||||
return;
|
||||
|
||||
EntityManager.AddComponents(target, clause.OverriddenComponents, true);
|
||||
}
|
||||
// End DeltaV Addition
|
||||
|
||||
private void SpawnItems(EntityUid target, DevilClausePrototype clause)
|
||||
{
|
||||
if (clause.SpawnedItems == null)
|
||||
return;
|
||||
|
||||
foreach (var item in clause.SpawnedItems)
|
||||
{
|
||||
if (!_prototypeManager.TryIndex(item, out _))
|
||||
continue;
|
||||
|
||||
var spawnedItem = SpawnNextToOrDrop(item, target);
|
||||
_hands.TryPickupAnyHand(target, spawnedItem, false, false, false);
|
||||
}
|
||||
}
|
||||
|
||||
private void DoPolymorphs(EntityUid target, DevilClausePrototype clause)
|
||||
{
|
||||
if (clause.Polymorph == null)
|
||||
return;
|
||||
|
||||
_polymorph.PolymorphEntity(target, clause.Polymorph.Value);
|
||||
}
|
||||
|
||||
private void DoSpecialActions(EntityUid target, Entity<DevilContractComponent>? contract, DevilClausePrototype clause)
|
||||
{
|
||||
if (clause.Event == null)
|
||||
return;
|
||||
|
||||
var ev = clause.Event;
|
||||
ev.Target = target;
|
||||
|
||||
if (contract is not null)
|
||||
ev.Contract = contract;
|
||||
|
||||
// you gotta cast this shit to object, don't ask me vro idk either
|
||||
RaiseLocalEvent(target, (object)ev, true);
|
||||
}
|
||||
|
||||
public void AddRandomNegativeClause(EntityUid target)
|
||||
{
|
||||
var negativeClauses = _prototypeManager.EnumeratePrototypes<DevilClausePrototype>()
|
||||
.Where(c => c.ClauseWeight >= 0)
|
||||
.ToList();
|
||||
|
||||
if (negativeClauses.Count == 0)
|
||||
return;
|
||||
|
||||
var selectedClause = _random.Pick(negativeClauses);
|
||||
ApplyEffectToTarget(target, selectedClause, null);
|
||||
|
||||
Log.Debug($"Selected {selectedClause.ID} effect for {ToPrettyString(target)}"); // DeltaV - Use EntitySystem Logger intead of _sawmill
|
||||
}
|
||||
|
||||
public void AddRandomPositiveClause(EntityUid target)
|
||||
{
|
||||
var positiveClauses = _prototypeManager.EnumeratePrototypes<DevilClausePrototype>()
|
||||
.Where(c => c.ClauseWeight <= 0)
|
||||
.ToList();
|
||||
|
||||
if (positiveClauses.Count == 0)
|
||||
return;
|
||||
|
||||
var selectedClause = _random.Pick(positiveClauses);
|
||||
ApplyEffectToTarget(target, selectedClause, null);
|
||||
|
||||
Log.Debug($"Selected {selectedClause.ID} effect for {ToPrettyString(target)}"); // DeltaV - Use EntitySystem Logger intead of _sawmill
|
||||
}
|
||||
|
||||
public void AddRandomClause(EntityUid target)
|
||||
{
|
||||
var clauses = _prototypeManager.EnumeratePrototypes<DevilClausePrototype>().ToList();
|
||||
|
||||
if (clauses.Count == 0)
|
||||
return;
|
||||
|
||||
var selectedClause = _random.Pick(clauses);
|
||||
ApplyEffectToTarget(target, selectedClause, null);
|
||||
|
||||
Log.Debug($"Selected {selectedClause.ID} effect for {ToPrettyString(target)}"); // DeltaV - Use EntitySystem Logger intead of _sawmill
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
// 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
|
||||
|
||||
namespace Content.Server._Goobstation.Devil.Contract.Revival;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class PendingRevivalContractComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The entity being revived.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public EntityUid? Contractee;
|
||||
|
||||
/// <summary>
|
||||
/// The entity offering revival
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public EntityUid? Offerer;
|
||||
|
||||
/// <summary>
|
||||
/// The contract attached to this player.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public EntityUid? Contract;
|
||||
|
||||
/// <summary>
|
||||
/// The MindId of the player.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public EntityUid MindId;
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
// 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
|
||||
|
||||
namespace Content.Server._Goobstation.Devil.Contract.Revival;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class RevivalContractComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The entity who signed the paper, AKA, the entity who has the effects applied.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public EntityUid? Signer;
|
||||
|
||||
/// <summary>
|
||||
/// The entity who created the contract, AKA, the entity who gains the soul.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public EntityUid? ContractOwner;
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,123 @@
|
|||
// 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 Content.Shared._Goobstation.Devil;
|
||||
using Content.Shared._Goobstation.Devil.Condemned;
|
||||
using Content.Shared._Goobstation.Devil.UI;
|
||||
using Content.Server.Administration.Systems;
|
||||
using Content.Server.Mind;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Mobs;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Popups;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.Player;
|
||||
|
||||
namespace Content.Server._Goobstation.Devil.Contract.Revival;
|
||||
public sealed partial class PendingRevivalContractSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
|
||||
[Dependency] private readonly UserInterfaceSystem _userInterface = default!;
|
||||
[Dependency] private readonly RejuvenateSystem _rejuvenate = default!;
|
||||
[Dependency] private readonly DevilContractSystem _contract = default!;
|
||||
[Dependency] private readonly MindSystem _mind = default!;
|
||||
[Dependency] private readonly IPlayerManager _player = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<RevivalContractComponent, AfterInteractEvent>(AfterInteract);
|
||||
SubscribeLocalEvent<PendingRevivalContractComponent, RevivalContractMessage>(OnMessage);
|
||||
}
|
||||
|
||||
private void AfterInteract(Entity<RevivalContractComponent> ent, ref AfterInteractEvent args)
|
||||
{
|
||||
if (args.Target is not { } target
|
||||
|| !TryComp<MobStateComponent>(target, out var mobState)
|
||||
|| mobState.CurrentState != MobState.Dead)
|
||||
return;
|
||||
|
||||
// Non-devils can't offer deals silly.
|
||||
if (!HasComp<DevilComponent>(args.User))
|
||||
{
|
||||
_popupSystem.PopupEntity(Loc.GetString("devil-sign-invalid-user"), args.User, PopupType.MediumCaution);
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure the mind actually exists
|
||||
if (!_mind.TryGetMind(target, out var mindId, out var mindComp) || mindComp.CurrentEntity is not { } ghost)
|
||||
{
|
||||
_popupSystem.PopupEntity(Loc.GetString("revival-contract-no-mind"), args.User, args.User);
|
||||
return;
|
||||
}
|
||||
|
||||
// You can't offer two deals at once.
|
||||
if (HasComp<PendingRevivalContractComponent>(ghost) || HasComp<CondemnedComponent>(target))
|
||||
{
|
||||
var failedPopup = Loc.GetString("revival-contract-use-failed", ("target", target)); // DeltaV - Added target param
|
||||
_popupSystem.PopupEntity(failedPopup, args.User, args.User);
|
||||
return;
|
||||
}
|
||||
|
||||
// Create pending contract
|
||||
var pending = EnsureComp<PendingRevivalContractComponent>(ghost);
|
||||
pending.Contractee = target;
|
||||
pending.Offerer = args.User;
|
||||
pending.Contract = ent;
|
||||
pending.MindId = mindId;
|
||||
|
||||
// Show confirmation
|
||||
var successPopup = Loc.GetString("revival-contract-use-success", ("target", target));
|
||||
_popupSystem.PopupEntity(successPopup, args.User, args.User);
|
||||
|
||||
ent.Comp.Signer = target;
|
||||
ent.Comp.ContractOwner = args.User;
|
||||
|
||||
TryOpenUi(ghost);
|
||||
}
|
||||
|
||||
private bool TryOpenUi(EntityUid target)
|
||||
{
|
||||
if (!_userInterface.HasUi(target, RevivalContractUiKey.Key))
|
||||
return false;
|
||||
|
||||
if (_mind.TryGetMind(target, out _, out var mindComp) &&
|
||||
_player.TryGetSessionById(mindComp.UserId, out var session) &&
|
||||
session is { } insession)
|
||||
_userInterface.OpenUi(target, RevivalContractUiKey.Key, insession);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void OnMessage(Entity<PendingRevivalContractComponent> ent, ref RevivalContractMessage args)
|
||||
{
|
||||
if (args.Accepted && ent.Comp.Contractee is { } contractee)
|
||||
{
|
||||
TryReviveAndTransferSoul(contractee, ent.Comp);
|
||||
_mind.UnVisit(ent.Comp.MindId);
|
||||
}
|
||||
|
||||
RemComp<PendingRevivalContractComponent>(args.Actor);
|
||||
}
|
||||
|
||||
private bool TryReviveAndTransferSoul(EntityUid target, PendingRevivalContractComponent pending)
|
||||
{
|
||||
if (TerminatingOrDeleted(target))
|
||||
return false;
|
||||
|
||||
if (TryComp<RevivalContractComponent>(pending.Contract, out var contract) && contract is { ContractOwner: { Valid: true } contractOwner, Signer: { } signer })
|
||||
{
|
||||
_rejuvenate.PerformRejuvenate(target);
|
||||
_popupSystem.PopupEntity(Loc.GetString("revival-contract-accepted"), target, target);
|
||||
_contract.TryTransferSouls(contractOwner, signer, 1);
|
||||
}
|
||||
|
||||
RemComp(target, pending);
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,124 @@
|
|||
// 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 Content.Server._Goobstation.Devil.Contract;
|
||||
using Content.Server._Goobstation.Devil.Contract.Revival;
|
||||
using Content.Server._Goobstation.Devil.Grip;
|
||||
using Content.Shared._Goobstation.Devil;
|
||||
using Content.Shared._Goobstation.Devil.Actions;
|
||||
using Content.Shared._Goobstation.Devil.Condemned;
|
||||
using Content.Shared._Goobstation.Devil.Contract;
|
||||
using Content.Shared.Cuffs.Components;
|
||||
using Content.Shared.IdentityManagement;
|
||||
|
||||
namespace Content.Server._Goobstation.Devil;
|
||||
|
||||
public sealed partial class DevilSystem
|
||||
{
|
||||
private void SubscribeAbilities()
|
||||
{
|
||||
SubscribeLocalEvent<DevilComponent, CreateContractEvent>(OnContractCreated);
|
||||
SubscribeLocalEvent<DevilComponent, CreateRevivalContractEvent>(OnRevivalContractCreated);
|
||||
SubscribeLocalEvent<DevilComponent, ShadowJauntEvent>(OnShadowJaunt);
|
||||
SubscribeLocalEvent<DevilComponent, DevilGripEvent>(OnDevilGrip);
|
||||
SubscribeLocalEvent<DevilComponent, DevilPossessionEvent>(OnPossess);
|
||||
}
|
||||
|
||||
private void OnContractCreated(Entity<DevilComponent> devil, ref CreateContractEvent args)
|
||||
{
|
||||
if (!TryUseAbility(args))
|
||||
return;
|
||||
|
||||
var contract = Spawn(devil.Comp.ContractPrototype, Transform(devil).Coordinates);
|
||||
_hands.TryPickupAnyHand(devil, contract);
|
||||
|
||||
if (!TryComp<DevilContractComponent>(contract, out var contractComponent))
|
||||
return;
|
||||
|
||||
contractComponent.ContractOwner = args.Performer;
|
||||
|
||||
PlayFwooshSound(devil);
|
||||
DoContractFlavor(devil, Identity.Name(devil, EntityManager));
|
||||
}
|
||||
|
||||
private void OnRevivalContractCreated(Entity<DevilComponent> devil, ref CreateRevivalContractEvent args)
|
||||
{
|
||||
if (!TryUseAbility(args))
|
||||
return;
|
||||
|
||||
var contract = Spawn(devil.Comp.RevivalContractPrototype, Transform(devil).Coordinates);
|
||||
_hands.TryPickupAnyHand(devil, contract);
|
||||
|
||||
if (!TryComp<RevivalContractComponent>(contract, out var contractComponent))
|
||||
return;
|
||||
|
||||
contractComponent.ContractOwner = args.Performer;
|
||||
|
||||
PlayFwooshSound(devil);
|
||||
DoContractFlavor(devil, Identity.Name(devil, EntityManager));
|
||||
}
|
||||
|
||||
private void OnShadowJaunt(Entity<DevilComponent> devil, ref ShadowJauntEvent args)
|
||||
{
|
||||
if (!TryUseAbility(args))
|
||||
return;
|
||||
|
||||
Spawn(devil.Comp.JauntAnimationProto, Transform(devil).Coordinates);
|
||||
Spawn(devil.Comp.PentagramEffectProto, Transform(devil).Coordinates);
|
||||
|
||||
if (TryComp<CuffableComponent>(devil, out var cuffableComponent))
|
||||
_container.EmptyContainer(cuffableComponent.Container, true);
|
||||
|
||||
_poly.PolymorphEntity(devil, devil.Comp.JauntEntityProto);
|
||||
}
|
||||
|
||||
private void OnDevilGrip(Entity<DevilComponent> devil, ref DevilGripEvent args)
|
||||
{
|
||||
if (!TryUseAbility(args))
|
||||
return;
|
||||
|
||||
if (devil.Comp.DevilGrip != null)
|
||||
{
|
||||
foreach (var item in _hands.EnumerateHeld(devil.Owner))
|
||||
{
|
||||
if (!HasComp<DevilGripComponent>(item))
|
||||
continue;
|
||||
|
||||
QueueDel(item);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var grasp = Spawn(devil.Comp.GripPrototype, Transform(devil).Coordinates);
|
||||
if (!_hands.TryPickupAnyHand(devil, grasp))
|
||||
QueueDel(grasp);
|
||||
|
||||
devil.Comp.DevilGrip = args.Action.Owner;
|
||||
}
|
||||
|
||||
private void OnPossess(Entity<DevilComponent> devil, ref DevilPossessionEvent args)
|
||||
{
|
||||
if (!TryComp<CondemnedComponent>(args.Target, out var condemned) || condemned.SoulOwnedNotDevil)
|
||||
{
|
||||
var message = Loc.GetString("invalid-possession-target");
|
||||
_popup.PopupEntity(message, devil, devil);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TryUseAbility(args))
|
||||
return;
|
||||
|
||||
if (devil.Comp.PowerLevel != DevilPowerLevel.None)
|
||||
devil.Comp.PossessionDuration *= (int)devil.Comp.PowerLevel;
|
||||
|
||||
if (_possession.TryPossessTarget(args.Target, args.Performer, devil.Comp.PossessionDuration, true, polymorphPossessor: true))
|
||||
{
|
||||
Spawn(devil.Comp.JauntAnimationProto, Transform(args.Target).Coordinates);
|
||||
Spawn(devil.Comp.PentagramEffectProto, Transform(args.Target).Coordinates);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,120 @@
|
|||
// SPDX-FileCopyrightText: 2025 GoobBot <uristmchands@proton.me>
|
||||
// SPDX-FileCopyrightText: 2025 Solstice <solsticeofthewinter@gmail.com>
|
||||
// SPDX-FileCopyrightText: 2025 SolsticeOfTheWinter <solsticeofthewinter@gmail.com>
|
||||
// SPDX-FileCopyrightText: 2025 gus <august.eymann@gmail.com>
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
using Content.Shared._Goobstation.CheatDeath;
|
||||
using Content.Shared._Goobstation.Devil;
|
||||
using Content.Shared._Goobstation.Devil.Condemned;
|
||||
using Content.Shared.Body.Part;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server._Goobstation.Devil;
|
||||
|
||||
public sealed partial class DevilSystem
|
||||
{
|
||||
private void InitializeHandshakeSystem()
|
||||
{
|
||||
SubscribeLocalEvent<DevilComponent, GetVerbsEvent<InnateVerb>>(OnGetVerbs);
|
||||
SubscribeLocalEvent<PendingHandshakeComponent, GetVerbsEvent<InnateVerb>>(OnGetVerbsPending);
|
||||
}
|
||||
private void OnGetVerbs(EntityUid uid, DevilComponent comp, GetVerbsEvent<InnateVerb> args)
|
||||
{
|
||||
// Can't shake your own hand, and you can't shake from a distance
|
||||
if (!args.CanAccess
|
||||
|| !args.CanInteract
|
||||
|| _state.IsIncapacitated(args.Target)
|
||||
|| !HasComp<MobStateComponent>(args.Target)
|
||||
|| HasComp<CondemnedComponent>(args.Target)
|
||||
|| args.Target == args.User
|
||||
|| !_body.BodyHasPartType(uid, BodyPartType.Hand) // cant shake if you have no hands
|
||||
|| !_body.BodyHasPartType(args.Target, BodyPartType.Hand) // or if they have none
|
||||
|| !_contract.IsUserValid(args.Target, out _))
|
||||
return;
|
||||
|
||||
InnateVerb handshakeVerb = new()
|
||||
{
|
||||
Act = () => OfferHandshake(args.User, args.Target),
|
||||
Text = Loc.GetString("hand-shake-prompt-verb", ("target", args.Target)),
|
||||
Icon = new SpriteSpecifier.Rsi(new("_Goobstation/Actions/devil.rsi"), "summon-contract"),
|
||||
Priority = 1 // Higher priority than default verbs
|
||||
};
|
||||
args.Verbs.Add(handshakeVerb);
|
||||
}
|
||||
|
||||
private void OnGetVerbsPending(EntityUid uid, PendingHandshakeComponent comp, GetVerbsEvent<InnateVerb> args)
|
||||
{
|
||||
if (!args.CanAccess
|
||||
|| !args.CanInteract
|
||||
|| _state.IsIncapacitated(args.Target)
|
||||
|| !HasComp<MobStateComponent>(args.Target)
|
||||
|| args.Target != comp.Offerer)
|
||||
return;
|
||||
|
||||
InnateVerb handshakeVerb = new()
|
||||
{
|
||||
Act = () => HandleHandshake(args.Target, args.User),
|
||||
Text = Loc.GetString("hand-shake-accept-verb", ("target", args.Target)),
|
||||
Icon = new SpriteSpecifier.Rsi(new("_Goobstation/Actions/devil.rsi"), "summon-contract"),
|
||||
Priority = 1 // Higher priority than default verbs
|
||||
};
|
||||
args.Verbs.Add(handshakeVerb);
|
||||
}
|
||||
|
||||
private void OfferHandshake(EntityUid user, EntityUid target)
|
||||
{
|
||||
if (HasComp<DevilComponent>(target)
|
||||
|| HasComp<PendingHandshakeComponent>(target)
|
||||
|| !_contract.IsUserValid(target, out _))
|
||||
return;
|
||||
|
||||
var pending = AddComp<PendingHandshakeComponent>(target);
|
||||
pending.Offerer = user;
|
||||
pending.ExpiryTime = _timing.CurTime + TimeSpan.FromSeconds(15);
|
||||
|
||||
// Notify target
|
||||
var popupMessage = Loc.GetString("handshake-offer-popup", ("user", user));
|
||||
_popup.PopupEntity(popupMessage, target, target);
|
||||
|
||||
// Notify self
|
||||
var selfPopup = Loc.GetString("handshake-offer-popup-self", ("target", target));
|
||||
_popup.PopupEntity(selfPopup, user, user);
|
||||
}
|
||||
private void HandleHandshake(EntityUid user, EntityUid target)
|
||||
{
|
||||
if (!_contract.TryTransferSouls(user, target, 1))
|
||||
{
|
||||
var handshakeFail = Loc.GetString("handshake-fail", ("user", user));
|
||||
_popup.PopupEntity(handshakeFail, user, user);
|
||||
return;
|
||||
}
|
||||
|
||||
var handshakeSucess = Loc.GetString("handshake-success", ("user", user));
|
||||
_popup.PopupEntity(handshakeSucess, target, target);
|
||||
_rejuvenate.PerformRejuvenate(target);
|
||||
|
||||
var cheatdeath = EnsureComp<CheatDeathComponent>(target);
|
||||
cheatdeath.ReviveAmount = 1;
|
||||
|
||||
_contract.AddRandomNegativeClause(target);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
var query = EntityQueryEnumerator<PendingHandshakeComponent>();
|
||||
while (query.MoveNext(out var uid, out var comp))
|
||||
{
|
||||
if (comp.ExpiryTime > _timing.CurTime)
|
||||
continue;
|
||||
|
||||
RemCompDeferred(uid, comp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,302 @@
|
|||
// 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.Server.Administration.Systems;
|
||||
using Content.Server.Antag.Components;
|
||||
using Content.Server.Atmos.Components;
|
||||
using Content.Server.Body.Systems;
|
||||
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.Server.Speech;
|
||||
using Content.Server.Speech.Components;
|
||||
using Content.Server.Stunnable;
|
||||
using Content.Server.Temperature.Components;
|
||||
using Content.Server.Zombies;
|
||||
using Content.Shared._Lavaland.Chasm;
|
||||
using Content.Shared._Shitmed.Body.Components;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.CombatMode;
|
||||
using Content.Shared.Damage;
|
||||
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.Shared.Body.Part;
|
||||
using Content.Server.Bible.Components;
|
||||
using Content.Shared._EE.Silicon.Components;
|
||||
|
||||
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);
|
||||
|
||||
// Adjust stats
|
||||
EnsureComp<ZombieImmuneComponent>(devil);
|
||||
EnsureComp<BreathingImmunityComponent>(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
|
||||
if (TryComp<DamageableComponent>(devil, out var damageableComp))
|
||||
_damageable.SetDamageModifierSetId(devil, devil.Comp.DevilDamageModifierSet, damageableComp);
|
||||
|
||||
// No decapitating the devil
|
||||
foreach (var part in _body.GetBodyChildren(devil))
|
||||
{
|
||||
if (!TryComp(part.Id, out BodyPartComponent? woundable)) // DeltaV - Use Bodypart instead of woundable.
|
||||
continue;
|
||||
|
||||
woundable.CanSever = false; // DeltaV - Use bodypart instead of Woundable
|
||||
Dirty(part.Id, woundable);
|
||||
}
|
||||
|
||||
// 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, devil.Comp.DamageOnTrueName * devil.Comp.BibleUserDamageMultiplier, true);
|
||||
_stun.TryParalyze(devil, devil.Comp.ParalyzeDurationOnTrueName * devil.Comp.BibleUserDamageMultiplier, false);
|
||||
|
||||
var popup = Loc.GetString("devil-true-name-heard-chaplain", ("speaker", args.Source), ("target", devil));
|
||||
_popup.PopupEntity(popup, devil, PopupType.LargeCaution);
|
||||
}
|
||||
else
|
||||
{
|
||||
_stun.TryParalyze(devil, devil.Comp.ParalyzeDurationOnTrueName, false);
|
||||
_damageable.TryChangeDamage(devil, 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
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
// 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 Content.Shared.NPC.Prototypes;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server._Goobstation.Devil.GameTicking.Rules;
|
||||
|
||||
[RegisterComponent, Access(typeof(DevilRuleSystem))]
|
||||
public sealed partial class DevilRuleComponent : Component
|
||||
{
|
||||
[DataField]
|
||||
public SoundPathSpecifier BriefingSound = new("/Audio/_Goobstation/Ambience/Antag/devil_start.ogg");
|
||||
|
||||
[DataField]
|
||||
public ProtoId<NpcFactionPrototype> DevilFaction = "DevilFaction"; // DeltaV - Don't use literals.
|
||||
|
||||
[DataField]
|
||||
public ProtoId<NpcFactionPrototype> NanotrasenFaction = "NanoTrasen"; // DeltaV - Don't use literals.
|
||||
|
||||
[DataField]
|
||||
public EntProtoId DevilMindRole = "DevilMindRole";
|
||||
}
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
// 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;
|
||||
using Content.Server._Goobstation.Devil.Roles;
|
||||
using Content.Shared._Goobstation.Devil;
|
||||
using Content.Server.Antag;
|
||||
using Content.Server.GameTicking.Rules;
|
||||
using Content.Server.Mind;
|
||||
using Content.Server.Objectives;
|
||||
using Content.Server.Roles;
|
||||
using Content.Shared.NPC.Prototypes;
|
||||
using Content.Shared.NPC.Systems;
|
||||
using Content.Shared.Roles;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server._Goobstation.Devil.GameTicking.Rules;
|
||||
|
||||
public sealed class DevilRuleSystem : GameRuleSystem<DevilRuleComponent>
|
||||
{
|
||||
[Dependency] private readonly MindSystem _mind = default!;
|
||||
[Dependency] private readonly AntagSelectionSystem _antag = default!;
|
||||
[Dependency] private readonly NpcFactionSystem _npcFaction = default!;
|
||||
[Dependency] private readonly ObjectivesSystem _objective = default!;
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<DevilRuleComponent, AfterAntagEntitySelectedEvent>(OnSelectAntag);
|
||||
SubscribeLocalEvent<DevilRuleComponent, ObjectivesTextPrependEvent>(OnTextPrepend);
|
||||
SubscribeLocalEvent<DevilRoleComponent, GetBriefingEvent>(OnGetBrief);
|
||||
}
|
||||
|
||||
private void OnSelectAntag(EntityUid uid, DevilRuleComponent comp, ref AfterAntagEntitySelectedEvent args)
|
||||
{
|
||||
MakeDevil(args.EntityUid, comp);
|
||||
}
|
||||
|
||||
private bool MakeDevil(EntityUid target, DevilRuleComponent rule)
|
||||
{
|
||||
var devilComp = EnsureComp<DevilComponent>(target);
|
||||
|
||||
var briefing = Loc.GetString("devil-role-greeting", ("trueName", devilComp.TrueName), ("playerName", Name(target)));
|
||||
_antag.SendBriefing(target, briefing, Color.DarkRed, rule.BriefingSound);
|
||||
|
||||
_npcFaction.RemoveFaction(target, rule.NanotrasenFaction);
|
||||
_npcFaction.AddFaction(target, rule.DevilFaction);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void OnGetBrief(Entity<DevilRoleComponent> role, ref GetBriefingEvent args)
|
||||
{
|
||||
var ent = args.Mind.Comp.OwnedEntity;
|
||||
|
||||
if (ent is null)
|
||||
return;
|
||||
|
||||
args.Append(MakeBriefing(ent.Value));
|
||||
}
|
||||
|
||||
private string MakeBriefing(EntityUid ent)
|
||||
{
|
||||
return !TryComp<DevilComponent>(ent, out var devilComp)
|
||||
? null!
|
||||
: Loc.GetString("devil-role-greeting", ("trueName", devilComp.TrueName), ("playerName", Name(ent)));
|
||||
}
|
||||
|
||||
private void OnTextPrepend(EntityUid uid, DevilRuleComponent comp, ref ObjectivesTextPrependEvent args)
|
||||
|
||||
{
|
||||
var mostContractsName = string.Empty;
|
||||
var mostContracts = 0f;
|
||||
|
||||
var query = EntityQueryEnumerator<DevilComponent>();
|
||||
while (query.MoveNext(out var devil, out var devilComp))
|
||||
{
|
||||
if (!_mind.TryGetMind(devil, out var mindId, out var mind))
|
||||
continue;
|
||||
|
||||
var metaData = MetaData(devil);
|
||||
if (devilComp.Souls < mostContracts)
|
||||
continue;
|
||||
|
||||
mostContracts = devilComp.Souls;
|
||||
mostContractsName = _objective.GetTitle((mindId, mind), metaData.EntityName);
|
||||
}
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine(Loc.GetString($"roundend-prepend-devil-contracts{(!string.IsNullOrWhiteSpace(mostContractsName) ? "-named" : "")}", ("name", mostContractsName), ("number", mostContracts)));
|
||||
args.Text = sb.ToString();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
// SPDX-FileCopyrightText: 2025 GoobBot <uristmchands@proton.me>
|
||||
// SPDX-FileCopyrightText: 2025 SolsticeOfTheWinter <solsticeofthewinter@gmail.com>
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
using Content.Shared.Whitelist;
|
||||
using Robust.Shared.Audio;
|
||||
|
||||
namespace Content.Server._Goobstation.Devil.Grip;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class DevilGripComponent : Component
|
||||
{
|
||||
[DataField]
|
||||
public TimeSpan CooldownAfterUse = TimeSpan.FromSeconds(20);
|
||||
|
||||
/// <summary>
|
||||
/// A blacklist of entities that cannot be gripped by the devil.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public EntityWhitelist Blacklist = new();
|
||||
|
||||
[DataField]
|
||||
public TimeSpan KnockdownTime = TimeSpan.FromSeconds(5f);
|
||||
|
||||
[DataField]
|
||||
public float StaminaDamage = 80f;
|
||||
|
||||
[DataField]
|
||||
public TimeSpan SpeechTime = TimeSpan.FromSeconds(10f);
|
||||
|
||||
[DataField]
|
||||
public SoundSpecifier? Sound = new SoundPathSpecifier("/Audio/_Goobstation/Effects/bone_crack.ogg");
|
||||
|
||||
[DataField]
|
||||
public LocId Invocation = "devil-speech-grip";
|
||||
}
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
// SPDX-FileCopyrightText: 2025 GoobBot <uristmchands@proton.me>
|
||||
// SPDX-FileCopyrightText: 2025 Solstice <solsticeofthewinter@gmail.com>
|
||||
// SPDX-FileCopyrightText: 2025 SolsticeOfTheWinter <solsticeofthewinter@gmail.com>
|
||||
// SPDX-FileCopyrightText: 2025 TheBorzoiMustConsume <197824988+TheBorzoiMustConsume@users.noreply.github.com>
|
||||
// SPDX-FileCopyrightText: 2025 Tim <timfalken@hotmail.com>
|
||||
// SPDX-FileCopyrightText: 2025 Timfa <timfalken@hotmail.com>
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
using Content.Shared._Goobstation.Devil;
|
||||
using Content.Shared._Goobstation.Religion;
|
||||
using Content.Server.Chat.Systems;
|
||||
using Content.Server.Speech.EntitySystems;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Chat;
|
||||
using Content.Shared.Damage.Systems;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.StatusEffect;
|
||||
using Content.Shared.Stunnable;
|
||||
using Content.Shared.Whitelist;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
|
||||
namespace Content.Server._Goobstation.Devil.Grip;
|
||||
|
||||
public sealed class DevilGripSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SharedStaminaSystem _stamina = default!;
|
||||
[Dependency] private readonly SharedStunSystem _stun = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly ChatSystem _chat = default!;
|
||||
[Dependency] private readonly RatvarianLanguageSystem _language = default!;
|
||||
[Dependency] private readonly EntityWhitelistSystem _whitelist = default!;
|
||||
[Dependency] private readonly SharedActionsSystem _actions = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<DevilGripComponent, AfterInteractEvent>(OnAfterInteract);
|
||||
}
|
||||
|
||||
private void OnAfterInteract(Entity<DevilGripComponent> ent, ref AfterInteractEvent args)
|
||||
{
|
||||
if (!args.CanReach
|
||||
|| args.Target is not { } target
|
||||
|| args.Target == args.User
|
||||
|| _whitelist.IsBlacklistPass(ent.Comp.Blacklist, target)
|
||||
|| !TryComp<DevilComponent>(args.User, out var devilComp))
|
||||
return;
|
||||
|
||||
// DeltaV - No Recall or Intervention can work in this place, there is no escape.
|
||||
// if (_divineIntervention.ShouldDeny(target))
|
||||
// {
|
||||
// _actions.SetCooldown(devilComp.DevilGrip, ent.Comp.CooldownAfterUse);
|
||||
// devilComp.DevilGrip = null;
|
||||
// InvokeGrasp(args.User, ent);
|
||||
// QueueDel(ent);
|
||||
// args.Handled = true;
|
||||
// return;
|
||||
// }
|
||||
|
||||
if (TryComp(target, out StatusEffectsComponent? status))
|
||||
{
|
||||
_ = _stun.TryKnockdown(target, ent.Comp.KnockdownTime, true, status) || _stun.TryStun(target, ent.Comp.KnockdownTime, true, status);
|
||||
_stamina.TakeStaminaDamage(target, ent.Comp.StaminaDamage);
|
||||
_language.DoRatvarian(target, ent.Comp.SpeechTime, true, status);
|
||||
}
|
||||
|
||||
_actions.SetCooldown(devilComp.DevilGrip, ent.Comp.CooldownAfterUse);
|
||||
devilComp.DevilGrip = null;
|
||||
InvokeGrasp(args.User, ent);
|
||||
QueueDel(ent);
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
public void InvokeGrasp(EntityUid user, Entity<DevilGripComponent> ent)
|
||||
{
|
||||
_audio.PlayPvs(ent.Comp.Sound, user);
|
||||
_chat.TrySendInGameICMessage(user, Loc.GetString(ent.Comp.Invocation), InGameICChatType.Speak, false);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
// 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 Content.Server._Goobstation.Devil.Contract;
|
||||
using Content.Server._Goobstation.Devil.Objectives.Systems;
|
||||
|
||||
namespace Content.Server._Goobstation.Devil.Objectives.Components;
|
||||
|
||||
[RegisterComponent, Access(typeof(DevilContractSystem), typeof(DevilObjectiveSystem))]
|
||||
|
||||
public sealed partial class MeetContractWeightConditionComponent : Component
|
||||
{
|
||||
[DataField]
|
||||
public int ContractWeight;
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
// 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 Content.Server._Goobstation.Devil.Objectives.Systems;
|
||||
|
||||
namespace Content.Server._Goobstation.Devil.Objectives.Components;
|
||||
|
||||
[RegisterComponent, Access(typeof(DevilSystem), typeof(DevilObjectiveSystem))]
|
||||
|
||||
public sealed partial class SignContractConditionComponent : Component
|
||||
{
|
||||
[DataField]
|
||||
public int ContractsSigned;
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
// 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 Content.Server._Goobstation.Devil.Objectives.Components;
|
||||
using Content.Server.Objectives.Systems;
|
||||
using Content.Shared.Objectives.Components;
|
||||
|
||||
namespace Content.Server._Goobstation.Devil.Objectives.Systems;
|
||||
|
||||
public sealed partial class DevilObjectiveSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly NumberObjectiveSystem _number = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<SignContractConditionComponent, ObjectiveGetProgressEvent>(OnContractGetProgress);
|
||||
SubscribeLocalEvent<MeetContractWeightConditionComponent, ObjectiveGetProgressEvent>(OnWeightGetProgress);
|
||||
}
|
||||
|
||||
private void OnContractGetProgress(EntityUid uid, SignContractConditionComponent comp, ref ObjectiveGetProgressEvent args)
|
||||
{
|
||||
var target = _number.GetTarget(uid);
|
||||
args.Progress = target != 0 ? MathF.Min((float)comp.ContractsSigned / target, 1f) : 1f;
|
||||
}
|
||||
|
||||
private void OnWeightGetProgress(EntityUid uid, MeetContractWeightConditionComponent comp, ref ObjectiveGetProgressEvent args)
|
||||
{
|
||||
var target = _number.GetTarget(uid);
|
||||
args.Progress = target != 0 ? MathF.Min((float)comp.ContractWeight / target, 1f) : 1f;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
// SPDX-FileCopyrightText: 2025 GoobBot <uristmchands@proton.me>
|
||||
// SPDX-FileCopyrightText: 2025 Solstice <solsticeofthewinter@gmail.com>
|
||||
// SPDX-FileCopyrightText: 2025 SolsticeOfTheWinter <solsticeofthewinter@gmail.com>
|
||||
// SPDX-FileCopyrightText: 2025 gus <august.eymann@gmail.com>
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
using Content.Shared.Roles;
|
||||
|
||||
namespace Content.Server._Goobstation.Devil.Roles;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class DevilRoleComponent : BaseMindRoleComponent;
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
// 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 Content.Shared.Actions;
|
||||
using Content.Shared.Actions.Components;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Polymorph;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server._Goobstation.Possession;
|
||||
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class PossessedComponent : Component
|
||||
{
|
||||
[ViewVariables]
|
||||
public EntityUid OriginalMindId;
|
||||
|
||||
[ViewVariables]
|
||||
public EntityUid OriginalEntity;
|
||||
|
||||
[ViewVariables]
|
||||
public EntityUid PossessorMindId;
|
||||
|
||||
[ViewVariables]
|
||||
public EntityUid PossessorOriginalEntity;
|
||||
|
||||
[ViewVariables]
|
||||
public TimeSpan PossessionEndTime;
|
||||
|
||||
[ViewVariables]
|
||||
public TimeSpan PossessionTimeRemaining;
|
||||
|
||||
[ViewVariables]
|
||||
public bool WasPacified;
|
||||
|
||||
[ViewVariables]
|
||||
public bool WasWeakToHoly;
|
||||
|
||||
[ViewVariables]
|
||||
public Container PossessedContainer;
|
||||
|
||||
[DataField]
|
||||
public EntProtoId<ActionComponent> EndPossessionAction = "ActionEndPossession";
|
||||
|
||||
[DataField]
|
||||
public bool HideActions = true;
|
||||
|
||||
[ViewVariables]
|
||||
public EntityUid? ActionEntity = null;
|
||||
|
||||
[ViewVariables]
|
||||
public EntityUid[] HiddenActions;
|
||||
|
||||
[DataField]
|
||||
public bool PolymorphEntity = true;
|
||||
|
||||
[DataField]
|
||||
public ProtoId<PolymorphPrototype> Polymorph = new ("ShadowJauntPermanent");
|
||||
|
||||
[ViewVariables]
|
||||
public readonly SoundPathSpecifier PossessionSoundPath = new ("/Audio/_Goobstation/Effects/bone_crack.ogg");
|
||||
}
|
||||
|
|
@ -0,0 +1,281 @@
|
|||
// 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 Content.Shared._Goobstation.Devil;
|
||||
using Content.Shared._Goobstation.Possession;
|
||||
using Content.Server.Actions;
|
||||
using Content.Server.Polymorph.Components;
|
||||
using Content.Server.Polymorph.Systems;
|
||||
using Content.Server.Stunnable;
|
||||
using Content.Shared.Administration.Logs;
|
||||
using Content.Shared.CombatMode.Pacification;
|
||||
using Content.Shared.Coordinates;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Ghost;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Mindshield.Components;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Tag;
|
||||
using Content.Shared.Zombies;
|
||||
using Robust.Server.Containers;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Spawners;
|
||||
using Robust.Shared.Timing;
|
||||
using Content.Server.Bible.Components;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Content.Shared._Goobstation.Religion;
|
||||
|
||||
namespace Content.Server._Goobstation.Possession;
|
||||
|
||||
public sealed partial class PossessionSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly MobStateSystem _mobState = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
[Dependency] private readonly SharedMindSystem _mind = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly StunSystem _stun = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
[Dependency] private readonly ContainerSystem _container = default!;
|
||||
[Dependency] private readonly ISharedAdminLogManager _admin = default!;
|
||||
[Dependency] private readonly ActionsSystem _action = default!;
|
||||
[Dependency] private readonly PolymorphSystem _polymorph = default!;
|
||||
[Dependency] private readonly TagSystem _tag = default!;
|
||||
|
||||
private static readonly ProtoId<TagPrototype> CannotSuicideAnyTag = "CannotSuicideAny"; // DeltaV - Don't use literals.
|
||||
private static readonly EntProtoId LolipopProto = "FoodLollipop"; // DeltaV - Don't use literals.
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<PossessedComponent, MapInitEvent>(OnInit);
|
||||
SubscribeLocalEvent<PossessedComponent, ComponentRemove>(OnComponentRemoved);
|
||||
|
||||
SubscribeLocalEvent<PossessedComponent, ExaminedEvent>(OnExamined);
|
||||
|
||||
SubscribeLocalEvent<PossessedComponent, EndPossessionEarlyEvent>(OnEarlyEnd);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
var query = EntityQueryEnumerator<PossessedComponent>();
|
||||
while (query.MoveNext(out var uid, out var comp))
|
||||
{
|
||||
if (_timing.CurTime >= comp.PossessionEndTime)
|
||||
RemComp<PossessedComponent>(uid);
|
||||
|
||||
comp.PossessionTimeRemaining = comp.PossessionEndTime - _timing.CurTime;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnInit(Entity<PossessedComponent> possessed, ref MapInitEvent args)
|
||||
{
|
||||
if (!HasComp<WeakToHolyComponent>(possessed))
|
||||
AddComp<WeakToHolyComponent>(possessed).AlwaysTakeHoly = true;
|
||||
else
|
||||
possessed.Comp.WasWeakToHoly = true;
|
||||
|
||||
if (possessed.Comp.HideActions)
|
||||
possessed.Comp.HiddenActions = _action.HideActions(possessed);
|
||||
|
||||
_action.AddAction(possessed, ref possessed.Comp.ActionEntity, possessed.Comp.EndPossessionAction);
|
||||
|
||||
_tag.AddTag(possessed, CannotSuicideAnyTag); // DeltaV - Use ProtoId.
|
||||
|
||||
possessed.Comp.PossessedContainer = _container.EnsureContainer<Container>(possessed, "PossessedContainer");
|
||||
}
|
||||
|
||||
private void OnEarlyEnd(EntityUid uid, PossessedComponent comp, ref EndPossessionEarlyEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
// if polymorphed, undo
|
||||
_polymorph.Revert(uid);
|
||||
RemCompDeferred(uid, comp);
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
private void OnComponentRemoved(Entity<PossessedComponent> possessed, ref ComponentRemove args)
|
||||
{
|
||||
MapCoordinates? coordinates = null;
|
||||
|
||||
_action.RemoveAction(possessed.Owner, possessed.Comp.ActionEntity);
|
||||
|
||||
if (possessed.Comp.HideActions)
|
||||
_action.UnHideActions(possessed, possessed.Comp.HiddenActions);
|
||||
|
||||
if (possessed.Comp.PolymorphEntity && HasComp<PolymorphedEntityComponent>(possessed))
|
||||
_polymorph.Revert(possessed.Owner);
|
||||
|
||||
_tag.RemoveTag(possessed, CannotSuicideAnyTag); // DeltaV - Use ProtoId.
|
||||
|
||||
// Remove associated components.
|
||||
if (!possessed.Comp.WasPacified)
|
||||
RemComp<PacifiedComponent>(possessed.Comp.OriginalEntity);
|
||||
|
||||
if (!possessed.Comp.WasWeakToHoly)
|
||||
RemComp<WeakToHolyComponent>(possessed.Comp.OriginalEntity);
|
||||
|
||||
// Return the possessors mind to their body, and the target to theirs.
|
||||
if (!TerminatingOrDeleted(possessed.Comp.PossessorMindId))
|
||||
_mind.TransferTo(possessed.Comp.PossessorMindId, possessed.Comp.PossessorOriginalEntity);
|
||||
if (!TerminatingOrDeleted(possessed.Comp.OriginalMindId))
|
||||
_mind.TransferTo(possessed.Comp.OriginalMindId, possessed.Comp.OriginalEntity);
|
||||
|
||||
if (!TerminatingOrDeleted(possessed.Comp.OriginalEntity))
|
||||
coordinates = _transform.ToMapCoordinates(possessed.Comp.OriginalEntity.ToCoordinates());
|
||||
|
||||
// Paralyze, so you can't just magdump them.
|
||||
_stun.TryParalyze(possessed, TimeSpan.FromSeconds(10), false);
|
||||
_popup.PopupEntity(Loc.GetString("possession-end-popup", ("target", possessed)), possessed, PopupType.LargeCaution);
|
||||
|
||||
// Teleport to the entity, kinda like you're popping out of their head!
|
||||
if (!TerminatingOrDeleted(possessed.Comp.PossessorOriginalEntity) && coordinates is not null)
|
||||
_transform.SetMapCoordinates(possessed.Comp.PossessorOriginalEntity, coordinates.Value);
|
||||
|
||||
_container.CleanContainer(possessed.Comp.PossessedContainer);
|
||||
}
|
||||
|
||||
private void OnExamined(Entity<PossessedComponent> possessed, ref ExaminedEvent args)
|
||||
{
|
||||
if (!args.IsInDetailsRange
|
||||
|| args.Examined != args.Examiner)
|
||||
return;
|
||||
|
||||
var timeRemaining = Math.Floor(possessed.Comp.PossessionTimeRemaining.TotalSeconds);
|
||||
args.PushMarkup(Loc.GetString("possessed-component-examined", ("timeremaining", timeRemaining)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to temporarily possess a target.
|
||||
/// </summary>
|
||||
/// <param name="possessed">The entity being possessed.</param>
|
||||
/// <param name="possessor">The entity possessing the previous entity.</param>
|
||||
/// <param name="possessionDuration">How long does the possession last in seconds.</param>
|
||||
/// <param name="pacifyPossessed">Should the possessor be pacified while inside the possessed body?</param>
|
||||
/// <param name="doesMindshieldBlock">Does having a mindshield block being possessed?</param>
|
||||
/// <param name="doesChaplainBlock">Is the chaplain immune to this possession?</param>
|
||||
/// <param name="HideActions">Should all actions be hidden during?</param>
|
||||
public bool TryPossessTarget(EntityUid possessed, EntityUid possessor, TimeSpan possessionDuration, bool pacifyPossessed, bool doesMindshieldBlock = false, bool doesChaplainBlock = true, bool hideActions = true, bool polymorphPossessor = true)
|
||||
{
|
||||
// Possessing a dead guy? What.
|
||||
if (_mobState.IsIncapacitated(possessed) || HasComp<ZombieComponent>(possessed))
|
||||
{
|
||||
_popup.PopupClient(Loc.GetString("possession-fail-target-dead"), possessor, possessor);
|
||||
return false;
|
||||
}
|
||||
|
||||
// if you ever wanted to prevent this
|
||||
if (doesMindshieldBlock && HasComp<MindShieldComponent>(possessed))
|
||||
{
|
||||
_popup.PopupClient(Loc.GetString("possession-fail-target-shielded"), possessor, possessor);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (doesChaplainBlock && HasComp<BibleUserComponent>(possessed))
|
||||
{
|
||||
_popup.PopupClient(Loc.GetString("possession-fail-target-chaplain"), possessor, possessor);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (HasComp<PossessedComponent>(possessed))
|
||||
{
|
||||
_popup.PopupClient(Loc.GetString("possession-fail-target-already-possessed"), possessor, possessor);
|
||||
return false;
|
||||
}
|
||||
|
||||
List<(Type, string)> blockers =
|
||||
[
|
||||
(typeof(DevilComponent), "devil"),
|
||||
(typeof(GhostComponent), "ghost"),
|
||||
(typeof(SpectralComponent), "ghost"),
|
||||
(typeof(TimedDespawnComponent), "temporary"),
|
||||
];
|
||||
|
||||
foreach (var (item1, item2) in blockers)
|
||||
{
|
||||
if (CheckMindswapBlocker(item1, item2, possessed, possessor))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!_mind.TryGetMind(possessor, out var possessorMind, out _))
|
||||
return false;
|
||||
|
||||
DoPossess(possessed, possessor, possessionDuration, possessorMind, pacifyPossessed, hideActions, polymorphPossessor);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void DoPossess(EntityUid? possessedNullable, EntityUid possessor, TimeSpan possessionDuration, EntityUid possessorMind, bool pacifyPossessed, bool hideActions, bool polymorphPossessor)
|
||||
{
|
||||
if (possessedNullable is not { } possessed)
|
||||
return;
|
||||
|
||||
var possessedComp = EnsureComp<PossessedComponent>(possessed);
|
||||
possessedComp.HideActions = hideActions;
|
||||
|
||||
if (pacifyPossessed)
|
||||
{
|
||||
if (!HasComp<PacifiedComponent>(possessed))
|
||||
EnsureComp<PacifiedComponent>(possessed);
|
||||
else
|
||||
possessedComp.WasPacified = true;
|
||||
}
|
||||
|
||||
possessedComp.PolymorphEntity = polymorphPossessor;
|
||||
if (polymorphPossessor)
|
||||
_polymorph.PolymorphEntity(possessor, possessedComp.Polymorph);
|
||||
|
||||
// Get the possession time.
|
||||
possessedComp.PossessionEndTime = _timing.CurTime + possessionDuration;
|
||||
|
||||
// Store possessors original information.
|
||||
possessedComp.PossessorOriginalEntity = possessor;
|
||||
possessedComp.PossessorMindId = possessorMind;
|
||||
|
||||
// Store possessed original info
|
||||
possessedComp.OriginalEntity = possessed;
|
||||
|
||||
if (_mind.TryGetMind(possessed, out var possessedMind, out _))
|
||||
{
|
||||
possessedComp.OriginalMindId = possessedMind;
|
||||
|
||||
// Nobodies gonna know.
|
||||
var dummy = Spawn(LolipopProto, MapCoordinates.Nullspace);
|
||||
_container.Insert(dummy, possessedComp.PossessedContainer);
|
||||
|
||||
_mind.TransferTo(possessedMind, dummy);
|
||||
}
|
||||
|
||||
// Transfer into target
|
||||
_mind.TransferTo(possessorMind, possessed);
|
||||
|
||||
// SFX
|
||||
_popup.PopupEntity(Loc.GetString("possession-popup-self"), possessedMind, possessedMind, PopupType.LargeCaution);
|
||||
_popup.PopupEntity(Loc.GetString("possession-popup-others", ("target", possessed)), possessed, PopupType.MediumCaution);
|
||||
_audio.PlayPvs(possessedComp.PossessionSoundPath, possessed);
|
||||
|
||||
Log.Info($"{ToPrettyString(possessor)} possessed {ToPrettyString(possessed)}");
|
||||
_admin.Add(LogType.Mind, LogImpact.High, $"{ToPrettyString(possessor)} possessed {ToPrettyString(possessed)}");
|
||||
}
|
||||
|
||||
private bool CheckMindswapBlocker(Type type, string message, EntityUid possessed, EntityUid possessor)
|
||||
{
|
||||
if (!HasComp(possessed, type))
|
||||
return false;
|
||||
|
||||
_popup.PopupClient(Loc.GetString($"possession-fail-{message}"), possessor, possessor);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,119 @@
|
|||
// SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
|
||||
// SPDX-FileCopyrightText: 2025 GoobBot <uristmchands@proton.me>
|
||||
// SPDX-FileCopyrightText: 2025 Solstice <solsticeofthewinter@gmail.com>
|
||||
// SPDX-FileCopyrightText: 2025 SolsticeOfTheWinter <solsticeofthewinter@gmail.com>
|
||||
// SPDX-FileCopyrightText: 2025 TheBorzoiMustConsume <197824988+TheBorzoiMustConsume@users.noreply.github.com>
|
||||
// SPDX-FileCopyrightText: 2025 gluesniffler <159397573+gluesniffler@users.noreply.github.com>
|
||||
// SPDX-FileCopyrightText: 2025 gluesniffler <linebarrelerenthusiast@gmail.com>
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
using System.Linq;
|
||||
using Content.Shared._Goobstation.Religion;
|
||||
using Content.Server._Goobstation.Bible;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Inventory;
|
||||
using Robust.Shared.Physics.Events;
|
||||
using Robust.Shared.Timing;
|
||||
using Content.Shared._Shitmed.Targeting;
|
||||
using Content.Shared.Body.Components;
|
||||
using Content.Shared.Body.Systems;
|
||||
using Content.Shared.Timing;
|
||||
using Content.Server.Bible.Components;
|
||||
using Content.Shared.Damage.Prototypes;
|
||||
using Robust.Shared.Prototypes; // Shitmed Change
|
||||
|
||||
namespace Content.Server._Goobstation.Religion;
|
||||
|
||||
public sealed class WeakToHolySystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly DamageableSystem _damageableSystem = default!;
|
||||
[Dependency] private readonly InventorySystem _inventorySystem = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly GoobBibleSystem _goobBible = default!;
|
||||
[Dependency] private readonly SharedBodySystem _body = default!;
|
||||
[Dependency] private readonly UseDelaySystem _useDelay = default!;
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<WeakToHolyComponent, InteractUsingEvent>(AfterBibleUse);
|
||||
SubscribeLocalEvent<WeakToHolyComponent, MapInitEvent>(OnInit); // DeltaV - Add damage set
|
||||
SubscribeLocalEvent<WeakToHolyComponent, ComponentRemove>(OnRemove); // DeltaV - Clean up on removal
|
||||
}
|
||||
|
||||
|
||||
// Begin DeltaV Additions - Holy Weakness
|
||||
private void OnInit(Entity<WeakToHolyComponent> ent, ref MapInitEvent args)
|
||||
{
|
||||
if (!TryComp<DamageableComponent>(ent, out var damageable))
|
||||
return;
|
||||
|
||||
var dmg = damageable.Damage;
|
||||
if (dmg.DamageDict.ContainsKey("Holy"))
|
||||
{
|
||||
ent.Comp.HadHolyWeakness = true;
|
||||
return;
|
||||
}
|
||||
dmg.DamageDict["Holy"] = 0;
|
||||
}
|
||||
|
||||
private void OnRemove(Entity<WeakToHolyComponent> ent, ref ComponentRemove args)
|
||||
{
|
||||
if (!TryComp<DamageableComponent>(ent, out var damageable))
|
||||
return;
|
||||
|
||||
if (!ent.Comp.HadHolyWeakness)
|
||||
{
|
||||
var dmg = damageable.Damage;
|
||||
dmg.DamageDict.Remove("Holy");
|
||||
}
|
||||
}
|
||||
// End DeltaV additions
|
||||
|
||||
private void AfterBibleUse(Entity<WeakToHolyComponent> ent, ref InteractUsingEvent args)
|
||||
{
|
||||
if (!TryComp<BibleComponent>(args.Used, out var bibleComp))
|
||||
return;
|
||||
|
||||
if (!TryComp(args.Used, out UseDelayComponent? useDelay)
|
||||
|| _useDelay.IsDelayed((args.Used, useDelay))
|
||||
|| !HasComp<BibleUserComponent>(args.User))
|
||||
return;
|
||||
|
||||
_goobBible.TryDoSmite(args.Used, args.User, args.Target, useDelay);
|
||||
}
|
||||
|
||||
#region Holy Healing
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
// Holy damage healing.
|
||||
var query = EntityQueryEnumerator<WeakToHolyComponent, BodyComponent>();
|
||||
while (query.MoveNext(out var uid, out var weakToHoly, out var body))
|
||||
{
|
||||
if (weakToHoly.NextPassiveHealTick > _timing.CurTime)
|
||||
continue;
|
||||
weakToHoly.NextPassiveHealTick = _timing.CurTime + weakToHoly.HealTickDelay;
|
||||
|
||||
if (!TryComp<DamageableComponent>(uid, out var damageable))
|
||||
continue;
|
||||
|
||||
if (TerminatingOrDeleted(uid)
|
||||
|| _body.GetRootPartOrNull(uid, body: body) is not { }
|
||||
|| !damageable.Damage.DamageDict.TryGetValue("Holy", out _))
|
||||
continue;
|
||||
|
||||
// Rune healing.
|
||||
if (weakToHoly.IsColliding)
|
||||
_damageableSystem.TryChangeDamage(uid, weakToHoly.HealAmount, ignoreResistances: true, targetPart: TargetBodyPart.All);
|
||||
|
||||
// Passive healing.
|
||||
_damageableSystem.TryChangeDamage(uid, weakToHoly.PassiveAmount, ignoreResistances: true, targetPart: TargetBodyPart.All);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
|
@ -13,4 +13,25 @@ public sealed partial class DelayedDeathComponent : Component
|
|||
/// How long it has been since the delayed death timer started.
|
||||
/// </summary>
|
||||
public float DeathTimer;
|
||||
|
||||
// Goobstation additions below
|
||||
/// <summary>
|
||||
/// If true, will prevent *almost* all types of revival.
|
||||
/// Right now, this just means it won't allow devils to revive.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool PreventAllRevives;
|
||||
|
||||
/// <summary>
|
||||
/// What message is displayed when the time runs out - Goobstation
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public LocId DeathMessageId;
|
||||
|
||||
/// <summary>
|
||||
/// What the defib displays when attempting to revive this entity. - Goobstation
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public LocId DefibFailMessageId = "defibrillator-missing-organs";
|
||||
// End Goobstation additions
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,18 @@
|
|||
using Content.Server.Chat.Systems;
|
||||
using Content.Shared._Goobstation.DelayedDeath;
|
||||
using Content.Shared.Medical;
|
||||
using Content.Shared.Mobs;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.Popups;
|
||||
|
||||
namespace Content.Server._Shitmed.DelayedDeath;
|
||||
|
||||
public partial class DelayedDeathSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly ChatSystem _chat = default!; // Goobstation
|
||||
[Dependency] private readonly MobStateSystem _mobState = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popupSystem = default!; // Goobstation
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
|
|
@ -30,6 +35,19 @@ public partial class DelayedDeathSystem : EntitySystem
|
|||
// go crit then dead so deathgasp can happen
|
||||
_mobState.ChangeMobState(ent, MobState.Critical, mob);
|
||||
_mobState.ChangeMobState(ent, MobState.Dead, mob);
|
||||
|
||||
// goob code
|
||||
var ev = new DelayedDeathEvent(ent, PreventRevive: comp.PreventAllRevives);
|
||||
RaiseLocalEvent(ent, ref ev);
|
||||
|
||||
if (ev.Cancelled)
|
||||
{
|
||||
RemCompDeferred<DelayedDeathComponent>(ent);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(comp.DeathMessageId)) // Goobstation
|
||||
_popupSystem.PopupEntity(Loc.GetString(comp.DeathMessageId), ent, ent, PopupType.LargeCaution);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -38,5 +56,8 @@ public partial class DelayedDeathSystem : EntitySystem
|
|||
{
|
||||
// can't defib someone without a heart or brain pal
|
||||
args.Cancel();
|
||||
|
||||
var failPopup = Loc.GetString(ent.Comp.DefibFailMessageId); // Goobstation
|
||||
_chat.TrySendInGameICMessage(args.Defib, failPopup, InGameICChatType.Speak, true);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1037,4 +1037,27 @@ public abstract class SharedActionsSystem : EntitySystem
|
|||
ent.Comp.Temporary = temporary;
|
||||
Dirty(ent);
|
||||
}
|
||||
|
||||
// Shitmed Change Start - Starlight Abductors
|
||||
public EntityUid[] HideActions(EntityUid performer, ActionsComponent? comp = null)
|
||||
{
|
||||
if (!Resolve(performer, ref comp, false))
|
||||
return [];
|
||||
|
||||
var actions = comp.Actions.ToArray();
|
||||
comp.Actions.Clear();
|
||||
Dirty(performer, comp);
|
||||
return actions;
|
||||
}
|
||||
|
||||
public void UnHideActions(EntityUid performer, EntityUid[] actions, ActionsComponent? comp = null)
|
||||
{
|
||||
if (!Resolve(performer, ref comp, false))
|
||||
return;
|
||||
|
||||
foreach (var action in actions)
|
||||
comp.Actions.Add(action);
|
||||
Dirty(performer, comp);
|
||||
}
|
||||
// Shitmed Change End
|
||||
}
|
||||
|
|
|
|||
|
|
@ -340,7 +340,7 @@ public partial class SharedBodySystem
|
|||
AddLeg(partEnt, bodyEnt);
|
||||
}
|
||||
|
||||
protected virtual void RemovePart(
|
||||
public virtual void RemovePart( // DeltaV - Made public
|
||||
Entity<BodyComponent?> bodyEnt,
|
||||
Entity<BodyPartComponent> partEnt,
|
||||
string slotId)
|
||||
|
|
|
|||
|
|
@ -43,6 +43,17 @@ public sealed class ChasmSystem : EntitySystem
|
|||
if (_timing.CurTime < chasm.NextDeletionTime)
|
||||
continue;
|
||||
|
||||
// Lavaland Change start: Jaunter
|
||||
var ev = new _Lavaland.Chasm.BeforeChasmFallingEvent(uid);
|
||||
RaiseLocalEvent(uid, ref ev);
|
||||
if (ev.Cancelled)
|
||||
{
|
||||
RemComp<ChasmFallingComponent>(uid);
|
||||
_blocker.UpdateCanMove(uid);
|
||||
continue;
|
||||
}
|
||||
// Lavaland Change end: Jaunter
|
||||
|
||||
QueueDel(uid);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
using Content.Shared._Goobstation.Devour;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Body.Events;
|
||||
using Content.Shared.Body.Systems;
|
||||
|
|
@ -115,6 +116,13 @@ public sealed class DevourSystem : EntitySystem
|
|||
if (args.Args.Target != null && _whitelistSystem.IsWhitelistPass(ent.Comp.StomachStorageWhitelist, (EntityUid)args.Args.Target))
|
||||
{
|
||||
_containerSystem.Insert(args.Args.Target.Value, ent.Comp.Stomach);
|
||||
|
||||
// Goobstation start
|
||||
|
||||
if (HasComp<MobStateComponent>(args.Args.Target.Value)) // can be cases where objects are also whitelisted, which wont need this
|
||||
EnsureComp<PreventSelfRevivalComponent>(args.Args.Target.Value);
|
||||
|
||||
// Goobstation end
|
||||
}
|
||||
//TODO: Figure out a better way of removing structures via devour that still entails standing still and waiting for a DoAfter. Somehow.
|
||||
//If it's not alive, it must be a structure.
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ using Content.Shared.Verbs;
|
|||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Content.Shared._Goobstation.Devil;
|
||||
using Content.Shared._Goobstation.Paper;
|
||||
|
||||
namespace Content.Shared._DV.Paper;
|
||||
|
||||
|
|
@ -61,6 +63,11 @@ public sealed class SignatureSystem : EntitySystem
|
|||
if (ev.Cancelled)
|
||||
return false;
|
||||
|
||||
var paperEvent = new BeingSignedAttemptEvent(paper, signer); // Goobstation
|
||||
RaiseLocalEvent(paper.Owner, ref paperEvent);
|
||||
if (paperEvent.Cancelled)
|
||||
return false;
|
||||
|
||||
var signatureName = DetermineEntitySignature(signer);
|
||||
|
||||
var stampInfo = new StampDisplayInfo()
|
||||
|
|
@ -78,20 +85,30 @@ public sealed class SignatureSystem : EntitySystem
|
|||
return false;
|
||||
}
|
||||
|
||||
// Show popups and play a paper writing sound
|
||||
var signedOtherMessage = Loc.GetString("paper-signed-other", ("user", signer), ("target", paper.Owner));
|
||||
_popup.PopupEntity(signedOtherMessage, signer, Filter.PvsExcept(signer, entityManager: EntityManager), true);
|
||||
if (!HasComp<DevilComponent>(signer)) // Goobstation - Don't display popups for devils, it covers the others.
|
||||
{
|
||||
// Show popups and play a paper writing sound
|
||||
var signedOtherMessage = Loc.GetString("paper-signed-other", ("user", signer), ("target", paper.Owner));
|
||||
_popup.PopupEntity(signedOtherMessage, signer, Filter.PvsExcept(signer, entityManager: EntityManager), true);
|
||||
|
||||
var signedSelfMessage = Loc.GetString("paper-signed-self", ("target", paper.Owner));
|
||||
_popup.PopupClient(signedSelfMessage, signer, signer);
|
||||
var signedSelfMessage = Loc.GetString("paper-signed-self", ("target", paper.Owner));
|
||||
_popup.PopupClient(signedSelfMessage, signer, signer);
|
||||
}
|
||||
|
||||
_audio.PlayPredicted(comp.Sound, signer, signer);
|
||||
|
||||
var evSignSucessfulEvent = new SignSuccessfulEvent(paper, signer); // Goobstation - Devil Antagonist
|
||||
RaiseLocalEvent(paper, ref evSignSucessfulEvent); // Goobstation - Devil Antagonist
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private string DetermineEntitySignature(EntityUid uid)
|
||||
{
|
||||
// Goobstation - Allow devils to sign their true name.
|
||||
if (TryComp<DevilComponent>(uid, out var devilComp) && !string.IsNullOrWhiteSpace(devilComp.TrueName))
|
||||
return devilComp.TrueName;
|
||||
|
||||
// If the entity has an ID, use the name on it.
|
||||
if (_idCard.TryFindIdCard(uid, out var id) && !string.IsNullOrWhiteSpace(id.Comp.FullName))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
// SPDX-FileCopyrightText: 2025 GoobBot <uristmchands@proton.me>
|
||||
// SPDX-FileCopyrightText: 2025 Marcus F <marcus2008stoke@gmail.com>
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
namespace Content.Shared._Goobstation.Atmos;
|
||||
|
||||
/// <summary>
|
||||
/// Used to ensure that PressureImmunityComponent is not overriden.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class SpecialPressureImmunityComponent : Component;
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
// SPDX-FileCopyrightText: 2025 GoobBot <uristmchands@proton.me>
|
||||
// SPDX-FileCopyrightText: 2025 Marcus F <199992874+thebiggestbruh@users.noreply.github.com>
|
||||
// SPDX-FileCopyrightText: 2025 Marcus F <marcus2008stoke@gmail.com>
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
namespace Content.Shared._Goobstation.Body.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Used to ensure that BreathingImmunityComponent is not overriden.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class SpecialBreathingImmunityComponent : Component;
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
// 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 Content.Shared.Actions;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared._Goobstation.CheatDeath;
|
||||
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class CheatDeathComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// How many revives does this entity have remaining.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public int ReviveAmount = 1;
|
||||
|
||||
/// <summary>
|
||||
/// Self-explanatory.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool InfiniteRevives;
|
||||
|
||||
/// <summary>
|
||||
/// Can this entity heal themselves while not being dead?
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool CanCheatStanding;
|
||||
|
||||
[DataField]
|
||||
public EntProtoId ActionCheatDeath = "ActionCheatDeath";
|
||||
|
||||
[DataField]
|
||||
public EntityUid? ActionEntity;
|
||||
|
||||
}
|
||||
|
||||
public sealed partial class CheatDeathEvent : InstantActionEvent { }
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
namespace Content.Shared._Goobstation.CrematorImmune;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class CrematoriumImmuneComponent : Component
|
||||
{
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
// 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
|
||||
|
||||
namespace Content.Shared._Goobstation.DelayedDeath;
|
||||
|
||||
/// <summary>
|
||||
/// Raised on a user when delayed death is triggered on them.
|
||||
/// (E.G, they die to it.)
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public record struct DelayedDeathEvent(EntityUid User, bool Cancelled = false, bool PreventRevive = true);
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
// 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 Content.Shared.Actions;
|
||||
|
||||
namespace Content.Shared._Goobstation.Devil.Actions;
|
||||
|
||||
public sealed partial class CreateContractEvent : InstantActionEvent;
|
||||
|
||||
public sealed partial class CreateRevivalContractEvent : InstantActionEvent;
|
||||
|
||||
public sealed partial class ShadowJauntEvent : InstantActionEvent;
|
||||
|
||||
public sealed partial class DevilGripEvent : InstantActionEvent;
|
||||
|
||||
public sealed partial class DevilPossessionEvent : EntityTargetActionEvent;
|
||||
|
|
@ -0,0 +1,114 @@
|
|||
// 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 Content.Shared.Polymorph;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared._Goobstation.Devil.Condemned;
|
||||
|
||||
/// <summary>
|
||||
/// Marks an entity as having sold their soul.
|
||||
/// When you die, do NOT collect 200, do NOT pass go. Go directly to hell
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This should *really* be two components.
|
||||
/// </remarks>
|
||||
[RegisterComponent]
|
||||
public sealed partial class CondemnedComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The current phase of the condemnation animation.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public CondemnedPhase CurrentPhase = CondemnedPhase.Waiting;
|
||||
|
||||
/// <summary>
|
||||
/// Who owns this entities soul
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public EntityUid? SoulOwner;
|
||||
|
||||
/// <summary>
|
||||
/// The elapsed time of the phase.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float PhaseTimer;
|
||||
|
||||
/// <summary>
|
||||
/// How long the hand effect will last
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float HandDuration;
|
||||
|
||||
/// <summary>
|
||||
/// Should the examine message show when examining someone with this component?
|
||||
/// </summary>
|
||||
[DataField("showExamine")]
|
||||
public bool ShowExamineMessage = true;
|
||||
|
||||
/// <summary>
|
||||
/// Is this entities soul owned, but not by a devil?
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool SoulOwnedNotDevil;
|
||||
|
||||
/// <summary>
|
||||
/// Should this entity be sent to hell on death?
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool CondemnOnDeath;
|
||||
|
||||
/// <summary>
|
||||
/// Was this target already weak to holy before becoming condemned?
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool WasWeakToHoly;
|
||||
|
||||
/// <summary>
|
||||
/// Should movement be locked during the animation?
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool FreezeDuringCondemnation;
|
||||
|
||||
/// <summary>
|
||||
/// If true, scrambles the targets DNA after banishing them.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool ScrambleAfterBanish = true;
|
||||
|
||||
/// <summary>
|
||||
/// Should this entity be banished (sent to limbo for several minutes) or should they just be deleted?
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public CondemnedBehavior CondemnedBehavior = CondemnedBehavior.Delete;
|
||||
|
||||
[DataField]
|
||||
public EntProtoId PentagramProto = "Pentagram";
|
||||
|
||||
[DataField]
|
||||
public EntProtoId HandProto = "HellHand";
|
||||
|
||||
[DataField]
|
||||
public SoundPathSpecifier SoundEffect = new("/Audio/_Goobstation/Effects/earth_quake.ogg");
|
||||
|
||||
[DataField]
|
||||
public ProtoId<PolymorphPrototype> BanishProto = "ShadowJaunt180";
|
||||
}
|
||||
|
||||
public enum CondemnedPhase : byte
|
||||
{
|
||||
Waiting,
|
||||
PentagramActive,
|
||||
HandActive,
|
||||
Complete
|
||||
}
|
||||
|
||||
public enum CondemnedBehavior : byte
|
||||
{
|
||||
Delete,
|
||||
Banish,
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
// 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
|
||||
namespace Content.Shared._Goobstation.Devil.Contract;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class ContractSignerComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The contract entity itself.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public EntityUid? Contract;
|
||||
|
||||
/// <summary>
|
||||
/// The contract component.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public DevilContractComponent ContractComponent;
|
||||
|
||||
/// <summary>
|
||||
/// All current clauses the entity is under the effect of.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public List<DevilClausePrototype> CurrentClauses = [];
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
// 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 Content.Shared.Polymorph;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared._Goobstation.Devil.Contract;
|
||||
|
||||
[Prototype("clause")]
|
||||
public sealed class DevilClausePrototype : IPrototype
|
||||
{
|
||||
[IdDataField]
|
||||
public string ID { get; private init; } = default!;
|
||||
|
||||
[DataField(required: true)]
|
||||
public int ClauseWeight;
|
||||
|
||||
[DataField]
|
||||
public ComponentRegistry? AddedComponents;
|
||||
|
||||
[DataField]
|
||||
public ComponentRegistry? RemovedComponents;
|
||||
|
||||
[DataField]
|
||||
public ComponentRegistry? OverriddenComponents; // DeltaV - Added overridden components
|
||||
|
||||
[DataField]
|
||||
public string? DamageModifierSet;
|
||||
|
||||
[DataField]
|
||||
public BaseDevilContractEvent? Event;
|
||||
|
||||
[DataField]
|
||||
public List<EntProtoId>? Implants;
|
||||
|
||||
[DataField]
|
||||
public List<EntProtoId>? SpawnedItems;
|
||||
|
||||
[DataField]
|
||||
public ProtoId<PolymorphPrototype>? Polymorph;
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
// 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
|
||||
namespace Content.Shared._Goobstation.Devil.Contract;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class DevilContractComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The entity who signed the paper, AKA, the entity who has the effects applied.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public EntityUid? Signer;
|
||||
|
||||
/// <summary>
|
||||
/// The entity who created the contract, AKA, the entity who gains the soul.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public EntityUid? ContractOwner;
|
||||
|
||||
/// <summary>
|
||||
/// All current clauses.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public HashSet<DevilClausePrototype> CurrentClauses = [];
|
||||
|
||||
/// <summary>
|
||||
/// Has the contract been signed by the signer?
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool IsVictimSigned;
|
||||
|
||||
/// <summary>
|
||||
/// Has the contract been signed by the devil?
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool IsDevilSigned;
|
||||
|
||||
/// <summary>
|
||||
/// Has the contract been signed by both the devil and the victim?
|
||||
/// </summary>
|
||||
public bool IsContractFullySigned => IsVictimSigned && IsDevilSigned;
|
||||
|
||||
public bool IsContractSignable => ContractWeight >= 0;
|
||||
|
||||
public bool CanApplyEffects => IsContractFullySigned && IsContractSignable && Signer != null && ContractOwner != null;
|
||||
|
||||
/// <summary>
|
||||
/// Does the contract weigh positively or negatively?
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The higher it is, the more the cons.
|
||||
/// </remarks>
|
||||
[DataField]
|
||||
public int ContractWeight;
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared._Goobstation.Devil;
|
||||
|
||||
[Prototype("devilBranchPrototype")]
|
||||
public sealed class DevilBranchPrototype : IPrototype
|
||||
{
|
||||
[IdDataField]
|
||||
public string ID { get; set; } = default!;
|
||||
|
||||
[DataField("powerActions", required: true)]
|
||||
public Dictionary<DevilPowerLevel, List<EntProtoId>> PowerActions = new();
|
||||
}
|
||||
|
||||
public enum DevilPowerLevel : byte
|
||||
{
|
||||
None,
|
||||
Weak,
|
||||
Moderate,
|
||||
Powerful,
|
||||
}
|
||||
|
|
@ -0,0 +1,127 @@
|
|||
// SPDX-FileCopyrightText: 2025 GoobBot <uristmchands@proton.me>
|
||||
// SPDX-FileCopyrightText: 2025 Solstice <solsticeofthewinter@gmail.com>
|
||||
// SPDX-FileCopyrightText: 2025 SolsticeOfTheWinter <solsticeofthewinter@gmail.com>
|
||||
// SPDX-FileCopyrightText: 2025 gus <august.eymann@gmail.com>
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.Prototypes;
|
||||
using Content.Shared.Dataset;
|
||||
using Content.Shared.Polymorph;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Content.Shared.FixedPoint;
|
||||
|
||||
namespace Content.Shared._Goobstation.Devil;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class DevilComponent : Component
|
||||
{
|
||||
[DataField]
|
||||
public List<EntProtoId> BaseDevilActions = new()
|
||||
{
|
||||
"ActionCreateContract",
|
||||
"ActionShadowJaunt",
|
||||
"ActionDevilGrip",
|
||||
};
|
||||
|
||||
[DataField]
|
||||
public List<EntityUid>? ActionEntities;
|
||||
|
||||
/// <summary>
|
||||
/// The amount of souls or successful contracts the entity has.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public int Souls;
|
||||
|
||||
[DataField]
|
||||
public ProtoId<DevilBranchPrototype> DevilBranchPrototype = "BaseDevilBranch";
|
||||
|
||||
/// <summary>
|
||||
/// The true name of the devil.
|
||||
/// This is auto-generated from a list in the system.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public string TrueName = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The current power level of the devil.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public DevilPowerLevel PowerLevel = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Sound effect played when summoning a contract.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public SoundPathSpecifier FwooshPath = new ("/Audio/_Goobstation/Effects/fwoosh.ogg");
|
||||
|
||||
/// <summary>
|
||||
/// When the true-name stun was last triggered
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public TimeSpan LastTriggeredTime;
|
||||
|
||||
/// <summary>
|
||||
/// Minimum time between true-name triggers
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public TimeSpan CooldownDuration = TimeSpan.FromSeconds(30);
|
||||
|
||||
[DataField]
|
||||
public ProtoId<DatasetPrototype> FirstNameTrue = new("names_devil_first");
|
||||
|
||||
[DataField]
|
||||
public ProtoId<DatasetPrototype> LastNameTrue = new("names_devil_last");
|
||||
|
||||
/// <summary>
|
||||
/// How much damage taken when a true name is spoken. Doubled if spoken by the chaplain.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public DamageSpecifier DamageOnTrueName = new() {DamageDict = new Dictionary<string, FixedPoint2>() {{ "Holy", 15 }}};
|
||||
|
||||
/// <summary>
|
||||
/// Holy action damage multiplier if done by the chaplain. Also effects stums.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float BibleUserDamageMultiplier = 2f;
|
||||
|
||||
/// <summary>
|
||||
/// How long the Devil is stunned when their true name is spoken. Doubled if spoken by the chaplain.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public TimeSpan ParalyzeDurationOnTrueName = TimeSpan.FromSeconds(4);
|
||||
|
||||
[ViewVariables(VVAccess.ReadOnly)]
|
||||
public EntityUid? DevilGrip;
|
||||
|
||||
// abandom all hope, all ye who enter
|
||||
|
||||
[DataField]
|
||||
public TimeSpan PossessionDuration = TimeSpan.FromSeconds(30);
|
||||
|
||||
[DataField]
|
||||
public EntProtoId GripPrototype = "DevilGrip";
|
||||
|
||||
[DataField]
|
||||
public EntProtoId ContractPrototype = "PaperDevilContract";
|
||||
|
||||
[DataField]
|
||||
public EntProtoId RevivalContractPrototype = "PaperDevilContractRevival";
|
||||
|
||||
[DataField]
|
||||
public EntProtoId PentagramEffectProto = "Pentagram";
|
||||
|
||||
[DataField]
|
||||
public EntProtoId FireEffectProto = "FireEffect";
|
||||
|
||||
[DataField]
|
||||
public EntProtoId JauntAnimationProto = "PolymorphShadowJauntAnimation";
|
||||
|
||||
[DataField]
|
||||
public ProtoId<PolymorphPrototype> JauntEntityProto = "ShadowJaunt";
|
||||
|
||||
[DataField]
|
||||
public ProtoId<DamageModifierSetPrototype> DevilDamageModifierSet = "DevilDealPositive";
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
// 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 Content.Shared._Goobstation.Devil.Contract;
|
||||
using Content.Shared.Inventory;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared._Goobstation.Devil;
|
||||
|
||||
/// <summary>
|
||||
/// Raised on a devil when their power level changes.
|
||||
/// </summary>
|
||||
/// <param name="User">The Devil whos power level is changing</param>
|
||||
/// <param name="NewLevel">The new level they are reaching.</param>
|
||||
[ByRefEvent]
|
||||
public record struct PowerLevelChangedEvent(EntityUid User, DevilPowerLevel NewLevel);
|
||||
|
||||
/// <summary>
|
||||
/// Raised on a devil when the amount of souls in their storage changes.
|
||||
/// </summary>
|
||||
/// <param name="User">The Devil gaining souls.</param>
|
||||
/// <param name="Victim">The entity losing its soul.</param>
|
||||
/// <param name="Amount">How many souls they are gaining.</param>
|
||||
[ByRefEvent]
|
||||
public record struct SoulAmountChangedEvent(EntityUid User, EntityUid Victim, int Amount);
|
||||
|
||||
/// <summary>
|
||||
/// Raised on an entity to see if their eyes are covered.
|
||||
/// This just checks for the identity blocker comp.
|
||||
/// </summary>
|
||||
/// <param name="Target"></param>
|
||||
public sealed class IsEyesCoveredCheckEvent : EntityEventArgs, IInventoryRelayEvent
|
||||
{
|
||||
public SlotFlags TargetSlots => SlotFlags.EYES | SlotFlags.MASK | SlotFlags.HEAD;
|
||||
|
||||
public bool IsEyesProtected;
|
||||
}
|
||||
|
||||
// Contract Events
|
||||
|
||||
[ImplicitDataDefinitionForInheritors, DataDefinition]
|
||||
public abstract partial class BaseDevilContractEvent : EntityEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// The contract using this event.
|
||||
/// </summary>
|
||||
public DevilContractComponent? Contract;
|
||||
|
||||
/// <summary>
|
||||
/// The target affected by this contract.
|
||||
/// </summary>
|
||||
public EntityUid Target;
|
||||
}
|
||||
|
||||
[DataDefinition, Serializable]
|
||||
public sealed partial class DevilContractSoulOwnershipEvent : BaseDevilContractEvent;
|
||||
|
||||
[DataDefinition, Serializable]
|
||||
public sealed partial class DevilContractLoseHandEvent : BaseDevilContractEvent;
|
||||
|
||||
[DataDefinition, Serializable]
|
||||
public sealed partial class DevilContractLoseLegEvent : BaseDevilContractEvent;
|
||||
|
||||
[DataDefinition, Serializable]
|
||||
public sealed partial class DevilContractLoseOrganEvent : BaseDevilContractEvent;
|
||||
|
||||
[DataDefinition, Serializable]
|
||||
public sealed partial class DevilContractChanceEvent : BaseDevilContractEvent;
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
// 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
|
||||
|
||||
namespace Content.Shared._Goobstation.Devil;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class PendingHandshakeComponent : Component
|
||||
{
|
||||
[DataField]
|
||||
public EntityUid? Offerer;
|
||||
|
||||
[DataField]
|
||||
public TimeSpan ExpiryTime;
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared._Goobstation.Devil.UI;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class RevivalContractMessage(bool accepted) : BoundUserInterfaceMessage
|
||||
{
|
||||
public bool Accepted { get; } = accepted;
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
using Robust.Shared.Serialization;
|
||||
namespace Content.Shared._Goobstation.Devil.UI;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum RevivalContractUiKey
|
||||
{
|
||||
Key,
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
namespace Content.Shared._Goobstation.Devour.Events;
|
||||
|
||||
[ByRefEvent]
|
||||
public record struct BeforeSelfRevivalEvent(
|
||||
EntityUid Target,
|
||||
LocId PopupText,
|
||||
bool Handled = false,
|
||||
bool Cancelled = false);
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared._Goobstation.Devour;
|
||||
|
||||
/// <summary>
|
||||
/// Used to mark an entity as being unable to self-revive (e.g preventing Changelings from using their stasis)
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class PreventSelfRevivalComponent : Component;
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
using Content.Shared._Goobstation.Devour;
|
||||
using Content.Shared._Goobstation.Devour.Events;
|
||||
using Content.Shared.Popups;
|
||||
|
||||
namespace Content.Shared._Goobstation.Devour.Systems;
|
||||
|
||||
public sealed class PreventSelfRevivalSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<PreventSelfRevivalComponent, BeforeSelfRevivalEvent>(OnAttemptSelfRevive);
|
||||
}
|
||||
|
||||
private void OnAttemptSelfRevive(Entity<PreventSelfRevivalComponent> ent, ref BeforeSelfRevivalEvent args)
|
||||
{
|
||||
if (args.Handled || args.Cancelled)
|
||||
return;
|
||||
|
||||
_popup.PopupEntity(Loc.GetString(args.PopupText), args.Target, args.Target, PopupType.SmallCaution);
|
||||
args.Cancelled = true;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
using Content.Shared.DoAfter;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared._Goobstation.Exorcism;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed partial class ExorcismDoAfterEvent : SimpleDoAfterEvent;
|
||||
|
|
@ -9,6 +9,7 @@
|
|||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
using Content.Shared._Goobstation.Devil;
|
||||
using Content.Shared._Goobstation.Flashbang;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Inventory.Events;
|
||||
|
|
@ -23,6 +24,7 @@ public partial class GoobInventorySystem
|
|||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<InventoryComponent, FlashDurationMultiplierEvent>(RelayInventoryEvent);
|
||||
SubscribeLocalEvent<InventoryComponent, IsEyesCoveredCheckEvent>(RelayInventoryEvent);
|
||||
SubscribeLocalEvent<InventoryComponent, RefreshEquipmentHudEvent<Overlays.NightVisionComponent>>(RefRelayInventoryEvent);
|
||||
SubscribeLocalEvent<InventoryComponent, RefreshEquipmentHudEvent<Overlays.ThermalVisionComponent>>(RefRelayInventoryEvent);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
// 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
|
||||
|
||||
namespace Content.Shared._Goobstation.Paper;
|
||||
|
||||
/// <summary>
|
||||
/// Raised on the paper when a sign is attempted
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public record struct BeingSignedAttemptEvent(EntityUid Paper, EntityUid Signer, bool Cancelled = false);
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
// 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
|
||||
|
||||
namespace Content.Shared._Goobstation.Paper;
|
||||
|
||||
/// <summary>
|
||||
/// Raised on the paper when a sign is successful.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public record struct SignSuccessfulEvent(EntityUid Paper, EntityUid User);
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
// SPDX-FileCopyrightText: 2025 GoobBot <uristmchands@proton.me>
|
||||
// SPDX-FileCopyrightText: 2025 SolsticeOfTheWinter <solsticeofthewinter@gmail.com>
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
using Content.Shared.Actions;
|
||||
|
||||
namespace Content.Shared._Goobstation.Possession;
|
||||
|
||||
public sealed partial class EndPossessionEarlyEvent : InstantActionEvent;
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
// SPDX-FileCopyrightText: 2025 Aviu00 <93730715+Aviu00@users.noreply.github.com>
|
||||
// SPDX-FileCopyrightText: 2025 Aviu00 <aviu00@protonmail.com>
|
||||
// SPDX-FileCopyrightText: 2025 GoobBot <uristmchands@proton.me>
|
||||
// SPDX-FileCopyrightText: 2025 Solstice <solsticeofthewinter@gmail.com>
|
||||
// SPDX-FileCopyrightText: 2025 SolsticeOfTheWinter <solsticeofthewinter@gmail.com>
|
||||
// SPDX-FileCopyrightText: 2025 TheBorzoiMustConsume <197824988+TheBorzoiMustConsume@users.noreply.github.com>
|
||||
// SPDX-FileCopyrightText: 2025 gus <august.eymann@gmail.com>
|
||||
// SPDX-FileCopyrightText: 2025 mikusssssss <153551970+mikusssssss@users.noreply.github.com>
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.Prototypes;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
|
||||
namespace Content.Shared._Goobstation.Religion;
|
||||
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
public sealed partial class WeakToHolyComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Should this entity take holy damage no matter what?
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public bool AlwaysTakeHoly;
|
||||
|
||||
/// <summary>
|
||||
/// Is the entity currently standing on a rune?
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public bool IsColliding;
|
||||
|
||||
/// <summary>
|
||||
/// Duration between each heal tick.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public TimeSpan HealTickDelay = TimeSpan.FromSeconds(2);
|
||||
|
||||
/// <summary>
|
||||
/// Used for passive healing.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public TimeSpan NextPassiveHealTick;
|
||||
|
||||
/// <summary>
|
||||
/// DeltaV - Was this critter already holy damagable?
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool HadHolyWeakness = false;
|
||||
|
||||
/// <summary>
|
||||
/// How much the entity is healed by runes each tick.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public DamageSpecifier HealAmount = new()
|
||||
{
|
||||
DamageDict =
|
||||
{
|
||||
["Holy"] = -4,
|
||||
},
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// How much the entity is healed passively by each tick.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public DamageSpecifier PassiveAmount = new()
|
||||
{
|
||||
DamageDict =
|
||||
{
|
||||
["Holy"] = -0.5, // if its less it dont work du to limb damage
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
// SPDX-FileCopyrightText: 2025 GoobBot <uristmchands@proton.me>
|
||||
// SPDX-FileCopyrightText: 2025 Marcus F <199992874+thebiggestbruh@users.noreply.github.com>
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
namespace Content.Shared._Goobstation.Temperature.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Used to ensure that HighTempImmunityComponent is not overriden (when it is made eventually)
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class SpecialHighTempImmunityComponent : Component;
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
// SPDX-FileCopyrightText: 2025 GoobBot <uristmchands@proton.me>
|
||||
// SPDX-FileCopyrightText: 2025 Marcus F <marcus2008stoke@gmail.com>
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
namespace Content.Shared._Goobstation.Temperature.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Used to ensure that LowTempImmunityComponent is not overriden (when it is made eventually)
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class SpecialLowTempImmunityComponent : Component;
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
// SPDX-FileCopyrightText: 2025 GoobBot <uristmchands@proton.me>
|
||||
// SPDX-FileCopyrightText: 2025 Marcus F <199992874+thebiggestbruh@users.noreply.github.com>
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
using Content.Shared.Atmos;
|
||||
|
||||
namespace Content.Shared._Goobstation.Temperature;
|
||||
|
||||
public sealed class TemperatureImmunityEvent(float currentTemperature) : EntityEventArgs
|
||||
{
|
||||
public float CurrentTemperature = currentTemperature;
|
||||
public readonly float IdealTemperature = Atmospherics.NormalBodyTemperature;
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
// SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
|
||||
// SPDX-FileCopyrightText: 2025 Aidenkrz <aiden@djkraz.com>
|
||||
// SPDX-FileCopyrightText: 2025 Aineias1 <dmitri.s.kiselev@gmail.com>
|
||||
// SPDX-FileCopyrightText: 2025 FaDeOkno <143940725+FaDeOkno@users.noreply.github.com>
|
||||
// SPDX-FileCopyrightText: 2025 GoobBot <uristmchands@proton.me>
|
||||
// SPDX-FileCopyrightText: 2025 McBosserson <148172569+McBosserson@users.noreply.github.com>
|
||||
// SPDX-FileCopyrightText: 2025 Milon <plmilonpl@gmail.com>
|
||||
// SPDX-FileCopyrightText: 2025 Piras314 <p1r4s@proton.me>
|
||||
// SPDX-FileCopyrightText: 2025 Rouden <149893554+Roudenn@users.noreply.github.com>
|
||||
// SPDX-FileCopyrightText: 2025 TheBorzoiMustConsume <197824988+TheBorzoiMustConsume@users.noreply.github.com>
|
||||
// SPDX-FileCopyrightText: 2025 Unlumination <144041835+Unlumy@users.noreply.github.com>
|
||||
// SPDX-FileCopyrightText: 2025 coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
|
||||
// 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 gluesniffler <linebarrelerenthusiast@gmail.com>
|
||||
// SPDX-FileCopyrightText: 2025 username <113782077+whateverusername0@users.noreply.github.com>
|
||||
// SPDX-FileCopyrightText: 2025 whateverusername0 <whateveremail>
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
namespace Content.Shared._Lavaland.Chasm;
|
||||
|
||||
[ByRefEvent]
|
||||
public record struct BeforeChasmFallingEvent(EntityUid Entity, bool Cancelled = false);
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
// SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
|
||||
// SPDX-FileCopyrightText: 2025 Aidenkrz <aiden@djkraz.com>
|
||||
// SPDX-FileCopyrightText: 2025 Aineias1 <dmitri.s.kiselev@gmail.com>
|
||||
// SPDX-FileCopyrightText: 2025 FaDeOkno <143940725+FaDeOkno@users.noreply.github.com>
|
||||
// SPDX-FileCopyrightText: 2025 GoobBot <uristmchands@proton.me>
|
||||
// SPDX-FileCopyrightText: 2025 McBosserson <148172569+McBosserson@users.noreply.github.com>
|
||||
// SPDX-FileCopyrightText: 2025 Milon <plmilonpl@gmail.com>
|
||||
// SPDX-FileCopyrightText: 2025 Piras314 <p1r4s@proton.me>
|
||||
// SPDX-FileCopyrightText: 2025 Rouden <149893554+Roudenn@users.noreply.github.com>
|
||||
// SPDX-FileCopyrightText: 2025 TheBorzoiMustConsume <197824988+TheBorzoiMustConsume@users.noreply.github.com>
|
||||
// SPDX-FileCopyrightText: 2025 Unlumination <144041835+Unlumy@users.noreply.github.com>
|
||||
// SPDX-FileCopyrightText: 2025 coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
|
||||
// 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 gluesniffler <linebarrelerenthusiast@gmail.com>
|
||||
// SPDX-FileCopyrightText: 2025 username <113782077+whateverusername0@users.noreply.github.com>
|
||||
// SPDX-FileCopyrightText: 2025 whateverusername0 <whateveremail>
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
namespace Content.Shared._Lavaland.Chasm;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class PreventChasmFallingComponent : Component
|
||||
{
|
||||
[DataField]
|
||||
public bool DeleteOnUse = true;
|
||||
}
|
||||
|
|
@ -0,0 +1,115 @@
|
|||
// SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
|
||||
// SPDX-FileCopyrightText: 2025 Aidenkrz <aiden@djkraz.com>
|
||||
// SPDX-FileCopyrightText: 2025 Aineias1 <dmitri.s.kiselev@gmail.com>
|
||||
// SPDX-FileCopyrightText: 2025 FaDeOkno <143940725+FaDeOkno@users.noreply.github.com>
|
||||
// SPDX-FileCopyrightText: 2025 GoobBot <uristmchands@proton.me>
|
||||
// SPDX-FileCopyrightText: 2025 McBosserson <148172569+McBosserson@users.noreply.github.com>
|
||||
// SPDX-FileCopyrightText: 2025 Milon <plmilonpl@gmail.com>
|
||||
// SPDX-FileCopyrightText: 2025 Piras314 <p1r4s@proton.me>
|
||||
// SPDX-FileCopyrightText: 2025 Rouden <149893554+Roudenn@users.noreply.github.com>
|
||||
// SPDX-FileCopyrightText: 2025 TheBorzoiMustConsume <197824988+TheBorzoiMustConsume@users.noreply.github.com>
|
||||
// SPDX-FileCopyrightText: 2025 Unlumination <144041835+Unlumy@users.noreply.github.com>
|
||||
// SPDX-FileCopyrightText: 2025 coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
|
||||
// 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 gluesniffler <linebarrelerenthusiast@gmail.com>
|
||||
// SPDX-FileCopyrightText: 2025 pheenty <fedorlukin2006@gmail.com>
|
||||
// SPDX-FileCopyrightText: 2025 username <113782077+whateverusername0@users.noreply.github.com>
|
||||
// SPDX-FileCopyrightText: 2025 whateverusername0 <whateveremail>
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
using Content.Shared.Chasm;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Timing;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Shared._Lavaland.Chasm;
|
||||
|
||||
public sealed class PreventChasmFallingSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly SharedInteractionSystem _interaction = default!;
|
||||
[Dependency] private readonly EntityLookupSystem _lookup = default!;
|
||||
[Dependency] private readonly UseDelaySystem _delay = default!;
|
||||
|
||||
private static readonly ResolvedPathSpecifier FallSound = new("/Audio/Items/Mining/fultext_launch.ogg"); // DeltaV - Avoid literal
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<PreventChasmFallingComponent, BeforeChasmFallingEvent>(OnBeforeFall);
|
||||
SubscribeLocalEvent<InventoryComponent, BeforeChasmFallingEvent>(Relay);
|
||||
}
|
||||
|
||||
private void OnBeforeFall(EntityUid uid, PreventChasmFallingComponent comp, ref BeforeChasmFallingEvent args)
|
||||
{
|
||||
if (TryComp<UseDelayComponent>(uid, out var useDelay) && _delay.IsDelayed((uid, useDelay)))
|
||||
return;
|
||||
|
||||
args.Cancelled = true;
|
||||
var coordsValid = false;
|
||||
var coords = Transform(args.Entity).Coordinates;
|
||||
|
||||
const int attempts = 20;
|
||||
var curAttempts = 0;
|
||||
while (!coordsValid)
|
||||
{
|
||||
curAttempts++;
|
||||
if (curAttempts > attempts)
|
||||
return; // Just to be safe from stack overflow
|
||||
|
||||
var newCoords = new EntityCoordinates(Transform(args.Entity).ParentUid, coords.X + _random.NextFloat(-5f, 5f), coords.Y + _random.NextFloat(-5f, 5f));
|
||||
if (!_interaction.InRangeUnobstructed(args.Entity, newCoords, -1f) ||
|
||||
_lookup.GetEntitiesInRange<ChasmComponent>(newCoords, 1f).Count > 0)
|
||||
continue;
|
||||
|
||||
_transform.SetCoordinates(args.Entity, newCoords);
|
||||
_transform.AttachToGridOrMap(args.Entity, Transform(args.Entity));
|
||||
_audio.PlayPvs(FallSound, args.Entity); // DeltaV - Avoid literal
|
||||
if (args.Entity != uid && comp.DeleteOnUse)
|
||||
QueueDel(uid);
|
||||
else if (useDelay != null)
|
||||
_delay.TryResetDelay((uid, useDelay));
|
||||
|
||||
coordsValid = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void Relay(EntityUid uid, InventoryComponent comp, ref BeforeChasmFallingEvent args)
|
||||
{
|
||||
if (!HasComp<ContainerManagerComponent>(uid))
|
||||
return;
|
||||
|
||||
RelayEvent(uid, ref args);
|
||||
}
|
||||
|
||||
private void RelayEvent(EntityUid uid, ref BeforeChasmFallingEvent ev)
|
||||
{
|
||||
if (!TryComp<ContainerManagerComponent>(uid, out var containerManager))
|
||||
return;
|
||||
|
||||
foreach (var container in containerManager.Containers.Values)
|
||||
{
|
||||
if (ev.Cancelled)
|
||||
break;
|
||||
|
||||
foreach (var entity in container.ContainedEntities)
|
||||
{
|
||||
RaiseLocalEvent(entity, ref ev);
|
||||
if (ev.Cancelled)
|
||||
break;
|
||||
RelayEvent(entity, ref ev);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
# SPDX-FileCopyrightText: 2024 Fishbait <Fishbait@git.ml>
|
||||
# SPDX-FileCopyrightText: 2024 Piras314 <p1r4s@proton.me>
|
||||
# SPDX-FileCopyrightText: 2024 fishbait <gnesse@gmail.com>
|
||||
# SPDX-FileCopyrightText: 2024 username <113782077+whateverusername0@users.noreply.github.com>
|
||||
# SPDX-FileCopyrightText: 2024 whateverusername0 <whateveremail>
|
||||
# SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
|
||||
# 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
|
||||
|
||||
- files: ["devil_start.ogg"]
|
||||
license: "CC-BY-SA-4.0"
|
||||
copyright: "Created by SolsticeOfTheWinter"
|
||||
source: "https://github.com/SolsticeOfTheWinter"
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,15 @@
|
|||
# SPDX-FileCopyrightText: 2024 Piras314 <p1r4s@proton.me>
|
||||
# SPDX-FileCopyrightText: 2024 username <113782077+whateverusername0@users.noreply.github.com>
|
||||
# SPDX-FileCopyrightText: 2024 whateverusername0 <whateveremail>
|
||||
# SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
|
||||
# SPDX-FileCopyrightText: 2025 Aidenkrz <aiden@djkraz.com>
|
||||
# SPDX-FileCopyrightText: 2025 Armok <155400926+ARMOKS@users.noreply.github.com>
|
||||
# SPDX-FileCopyrightText: 2025 Aviu00 <93730715+Aviu00@users.noreply.github.com>
|
||||
# SPDX-FileCopyrightText: 2025 Solstice <solsticeofthewinter@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
- files: ["crack2.ogg"]
|
||||
license: "CC-BY-SA-3.0"
|
||||
copyright: "Taken from /tg/station"
|
||||
source: "https://github.com/tgstation/tgstation/tree/master/sound/effects/wounds/crack2.ogg"
|
||||
Binary file not shown.
|
|
@ -0,0 +1,2 @@
|
|||
admin-verb-text-make-devil = Make Devil
|
||||
admin-verb-make-devil = Make the target into a devil
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
figurines-devil-1 = Won't you shake a poor sinner's hand?
|
||||
figurines-devil-2 = Of course you can trust me! Just sign this contract!
|
||||
figurines-devil-3 = The chaplain is a scammer, don't you trust him!
|
||||
figurines-devil-4 = Have you heard of the next big investment crypto? It's called "Goobcoin" and it's going to the moon!
|
||||
figurines-devil-5 = Contractor? Contractee? It's really the same thing.
|
||||
figurines-devil-6 = CHAPLAIN!! I'M CREW!! I'M CREW!!
|
||||
figurines-devil-7 = Hey! Where are you going with my codex? Give that back!
|
||||
figurines-devil-8 = What? No, "Strength" will make you stronger, just trust me!
|
||||
figurines-devil-9 = I said I wanted your "Soul", not "Sole". Take the shoes back!
|
||||
figurines-devil-10 = No bitches? I can help with that.
|
||||
figurines-devil-11 = True name? You don't need to worry about that.
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
cheat-death-component-examined = [color=red] Something tells you {SUBJECT($target)} has no fear of death. [/color]
|
||||
cheat-death-component-remaining-revives = { $amount ->
|
||||
[0] You have no revives left.
|
||||
[one] You have one revive left.
|
||||
*[other] You have {$amount} revives left.
|
||||
}
|
||||
|
||||
cheat-death-component-remaining-revives-unlimited = You have an unlimited amount of revives.
|
||||
|
||||
action-cheat-death-fail-no-lives = You aren't slipping away this time.
|
||||
action-cheat-death-fail-not-dead = You can't cheat death while alive!
|
||||
action-cheat-death-holy-damage = Divine intervention prevents your revival.
|
||||
|
||||
action-cheated-death-dead = {$name} jerks awake. Bones mended, wounds sewn, organs arranged ever so perfectly.
|
||||
action-cheated-death-alive = {$name} violently shakes, all manners of wounds disappearing in a flash of flames.
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
devil-contract-contractee = contractee
|
||||
devil-contract-contractor = contractor
|
||||
|
||||
devil-contract-early-sign-failed = The contractor must wait for the contractee to sign first!
|
||||
devil-contract-no-soul-sign-failed = You do not have a soul to sell!
|
||||
devil-sign-invalid-user = You do not have the power to fulfill this contract!
|
||||
revival-contract-no-mind = They have no mind to accept a deal!
|
||||
devil-contract-mind-shielded-failed = A voice comes from your Nanotrasen™ mindshield. "Nice try."
|
||||
|
||||
contract-summon-flavor = {$name}'s hand bursts into flames before revealing a black roll of parchment...
|
||||
burn-contract-prompt = Burn contract.
|
||||
burn-contract-popup-success = The contract bursts into flames! It seems someones fate has been sealed...
|
||||
burn-contract-popup-fail = You cannot burn a contract during a deal!
|
||||
|
||||
|
||||
contract-victim-signed = A chill runs down your spine as you lift the pen...
|
||||
contract-devil-signed = The contract pulses with dark energy...
|
||||
contract-soul-added = You gain another soul in your possession...
|
||||
contract-uneven-odds = These odds aren't fair. You need {$number} more contract weight.
|
||||
devil-contract-examined = This contract has a weight of: {$weight}.
|
||||
|
||||
devil-deal-time-ran-out = Your vision goes black as your muscles fail you. You've run out of time.
|
||||
|
||||
revival-contract-accepted = You feel your connection to your body strengthen...
|
||||
revival-contract-rejected = You reject the unholy offer.
|
||||
revival-contract-expired = Times up.
|
||||
revival-contract-prompt = {$offerer} offers to restore your life for a price... Do you accept?
|
||||
revival-contract-use-success = You offer {$target} a deal no man could refuse...
|
||||
revival-contract-use-failed = You cannot offer {$target} this deal.
|
||||
|
||||
revival-contract-prompt-reject = Reject Offer
|
||||
revival-contract-prompt-accept = Accept Offer
|
||||
|
||||
revival-contract-menu-title = Contract From the Depths
|
||||
revival-contract-menu-prompt = Sell your soul for a second chance?
|
||||
|
||||
default-contract-content = This binding contract made this day between:
|
||||
|
||||
- The Infernal Party (Hereinafter "Contractor")
|
||||
- The Mortal Party (Hereinafter "Contractee")
|
||||
|
||||
{"["}bold]Clauses of Binding:{"["}/bold]
|
||||
{"["}italic]The following parties give up the following possessions{"["}/italic{"]"}
|
||||
|
||||
Contractee: Soul Ownership
|
||||
Contractee: Death
|
||||
|
||||
{"["}italic]All clauses come into effect upon signing of this paper by both parties.{"["}/italic]
|
||||
{"["}italic]{"["}color=#ac3a0d]Clauses may not be broken. Ever.{"["}/color]{"["}/italic]
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
devil-roundend-name = Devil
|
||||
objective-issuer-devil = Devil
|
||||
|
||||
roundend-prepend-devil-contracts = Someone has made [color=red]{$number}[/color] successful contracts.
|
||||
roundend-prepend-devil-contracts-named = [color=white]{$name}[/color] has made [color=red]{$number}[/color] successful contracts.
|
||||
|
||||
devil-component-examined = [color=darkred]{CAPITALIZE(POSS-ADJ($target))} eyes glow a faint red.[/color]
|
||||
condemned-component-examined = [color=chartreuse]{CAPITALIZE($target)}'s eyes are hollow and soulless.[/color]
|
||||
|
||||
devil-role-greeting = You are a devil from the depths of hell,
|
||||
who has possessed the body of {$playerName}
|
||||
Corrupt mortals and collect souls through the use of contracts.
|
||||
It is not in your interest to do tasks unrelated to your deals, they are beneath you.
|
||||
Your true name is {$trueName} - Try to keep it a secret, will you?
|
||||
|
||||
devil-true-name-heard = The act of {$speaker} commanding {$target}'s true name compels {POSS-ADJ($target)} to stop.
|
||||
devil-true-name-heard-chaplain = The act of {$speaker}'s holy words commanding {$target}'s true name name burns away at {POSS-ADJ($target)} very soul.
|
||||
|
||||
objective-condition-contract-title = Collect Souls
|
||||
objective-condition-contract-description = Collect as many souls as possible.
|
||||
|
||||
objective-condition-weight-title = Make profitable deals.
|
||||
objective-condition-weight-description = Ensure the deals benefit you as much as possible.
|
||||
|
||||
hand-shake-prompt-verb = Offer handshake to {$target}
|
||||
hand-shake-accept-verb = Shake {$target}'s hand
|
||||
|
||||
handshake-success = You feel slightly lighter as you shake {$user}'s hand.
|
||||
handshake-fail = You shake {POSS-ADJ($user)} hand, but nothing happens!
|
||||
|
||||
handshake-offer-popup = {$user} offers {POSS-ADJ($user)} hand to you, a sly grin on {POSS-ADJ($user)} face.
|
||||
handshake-offer-popup-self = You outstretch your hand to {$target}, a sly grin on your face.
|
||||
|
||||
weaktoholy-component-bible-sizzle = {$target}'s skin is singed with the {$item}!
|
||||
devil-banish-begin = {$user} begins to perform an exorcism on {$target}!
|
||||
devil-exorcised = The ground cracks and splinters beneath {$target} as they are forcefully returned to hell!
|
||||
|
||||
invalid-possession-target = You cannot possess this target!
|
||||
|
||||
devil-power-level-increase-weak = Bits of flame flare out from your fingertips. Your connection with hell has strengthened.
|
||||
devil-power-level-increase-moderate = You feel your power surge. Your connection with hell has strengthened.
|
||||
devil-power-level-increase-powerful = You've almost completed your tasks here. Just a few more souls.
|
||||
|
|
@ -0,0 +1 @@
|
|||
devil-speech-grip = Stupete et cadite!!
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
roles-antag-devil-name = Devil
|
||||
roles-antag-devil-description = Use your wit and silver tongue to trick crew members into devilish deals.
|
||||
|
|
@ -0,0 +1 @@
|
|||
self-revive-fail = Can't revive in this state!
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
# Ideally, subtype names should be short
|
||||
role-subtype-devil = Devil
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
possession-fail-changeling = There's no soul to tamper with!
|
||||
possession-fail-heretic = The power of the mansus prevents you from possessing them!
|
||||
possession-fail-ghoul = There's no mind for you to possess!
|
||||
possession-fail-devil = You cannot possess another devil!
|
||||
possession-fail-ghost = There's no mind for you to possess!
|
||||
possession-fail-temporary = There's no mind for you to possess!
|
||||
possession-fail-target-dead = There's no mind for you to possess!
|
||||
possession-fail-target-shielded = A pesky shield prevents you from prying at their mind!
|
||||
possession-fail-target-already-possessed = Someone else has already taken over this mind...
|
||||
possession-fail-target-chaplain = A holy force prevents you from possessing this target!
|
||||
|
||||
possession-popup-others = {$target}'s limbs contort in a strange way, {POSS-ADJ($target)} eyes rolling back in {POSS-ADJ($target)} head.
|
||||
possession-popup-self = Your soul is forced out of your body!
|
||||
possession-end-popup = {$target} seizes up and falls limp to the ground.
|
||||
|
||||
possessed-component-examined = You have {$timeremaining} seconds left in this body.
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
# SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
|
||||
# SPDX-FileCopyrightText: 2025 deltanedas <39013340+deltanedas@users.noreply.github.com>
|
||||
#
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
defibrillator-missing-organs = Patient lacks essential vital organs! Revival is impossible.
|
||||
|
|
@ -74,6 +74,11 @@
|
|||
radius: 6
|
||||
castShadows: false
|
||||
enabled: false
|
||||
- type: UserInterface
|
||||
interfaces:
|
||||
enum.RevivalContractUiKey.Key: # Goobstation - Devil
|
||||
type: RevivalContractBoundUserInterface
|
||||
requireInputValidation: false
|
||||
|
||||
# proto for player ghosts specifically
|
||||
- type: entity
|
||||
|
|
|
|||
|
|
@ -92,6 +92,8 @@
|
|||
orGroup: SpacemenFig
|
||||
- id: ToyFigurineNukieElite
|
||||
orGroup: SpacemenFig
|
||||
- id: ToyFigurineDevil # Goobstation
|
||||
orGroup: SpacemenFig
|
||||
- id: ToyFigurineFootsoldier
|
||||
orGroup: SpacemenFig
|
||||
- id: ToyFigurineWizardFake
|
||||
|
|
|
|||
|
|
@ -24,6 +24,8 @@
|
|||
prob: 0.5
|
||||
- id: SubWizard
|
||||
prob: 0.05
|
||||
- id: Devil # Goob
|
||||
prob: 0.02
|
||||
|
||||
- type: entity
|
||||
parent: BaseGameRule
|
||||
|
|
@ -33,6 +35,8 @@
|
|||
rules:
|
||||
- id: Thief
|
||||
prob: 0.5
|
||||
- id: Devil # Goob
|
||||
prob: 0.02
|
||||
|
||||
- type: entity
|
||||
parent: BaseGameRule
|
||||
|
|
@ -382,7 +386,7 @@
|
|||
selectionTime: IntraPlayerSpawn # Pre-selection before jobs; assignment doesn't really matter though, we only care about the pre-selection to block other antags.
|
||||
removeUponFailedSpawn: false
|
||||
definitions:
|
||||
- prefRoles: [ InitialInfected, Traitor, Thief, HeadRev ]
|
||||
- prefRoles: [ InitialInfected, Traitor, Thief, Devil, HeadRev ] # Goob - Inc devil
|
||||
max: 2
|
||||
playerRatio: 30
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
- SpaceNinja
|
||||
- Wizard
|
||||
- Zombies
|
||||
- Devil # Goob content
|
||||
- MinorAntagonists
|
||||
|
||||
- type: guideEntry
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@
|
|||
- !type:AddComponentSpecial
|
||||
components:
|
||||
- type: BibleUser #Lets them heal with bibles
|
||||
- type: Condemned
|
||||
soulOwnedNotDevil: true # This just means they won't suffer the effects of being condemned, while still not being able to sell their soul. - Goobstation
|
||||
- type: PsionicBonusChance # DeltaV - makes it more likely to become psionic.
|
||||
multiplier: 3
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,11 @@
|
|||
- Lawyer
|
||||
#- Brig # DeltaV - Removed brig access
|
||||
- Maintenance
|
||||
special: # Goobstation - Everyone knows lawyers are soulless.
|
||||
- !type:AddComponentSpecial
|
||||
components:
|
||||
- type: Condemned
|
||||
soulOwnedNotDevil: true
|
||||
|
||||
- type: startingGear
|
||||
id: LawyerGear
|
||||
|
|
|
|||
|
|
@ -37,6 +37,8 @@
|
|||
- type: CommandStaff
|
||||
- type: PsionicBonusChance # DeltaV - makes it more likely to become psionic.
|
||||
flatBonus: 0.025
|
||||
- type: Condemned # Goobstation - Nanotrasen owns your soul pal.
|
||||
soulOwnedNotDevil: true
|
||||
|
||||
- type: startingGear
|
||||
id: CaptainGear
|
||||
|
|
|
|||
|
|
@ -0,0 +1,131 @@
|
|||
# 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
|
||||
|
||||
# Devil Specific
|
||||
|
||||
- type: entity
|
||||
parent: BaseAction
|
||||
id: ActionCreateContract
|
||||
name: Summon infernal contract
|
||||
description: Creates an infernal contract in the Devil's hand.
|
||||
categories: [ HideSpawnMenu ]
|
||||
components:
|
||||
- type: Action
|
||||
useDelay: 10
|
||||
itemIconStyle: NoItem
|
||||
icon:
|
||||
sprite: _Goobstation/Actions/devil.rsi
|
||||
state: summon-contract
|
||||
- type: InstantAction
|
||||
event: !type:CreateContractEvent {}
|
||||
|
||||
- type: entity
|
||||
parent: BaseAction
|
||||
id: ActionCreateRevivalContract
|
||||
name: Summon revival contract
|
||||
description: Creates a revival contract in the Devil's hand.
|
||||
categories: [ HideSpawnMenu ]
|
||||
components:
|
||||
- type: Action
|
||||
useDelay: 30
|
||||
itemIconStyle: NoItem
|
||||
icon:
|
||||
sprite: _Goobstation/Actions/devil.rsi
|
||||
state: summon-contract-revival
|
||||
- type: InstantAction
|
||||
event: !type:CreateRevivalContractEvent {}
|
||||
|
||||
- type: entity
|
||||
parent: BaseAction
|
||||
id: ActionShadowJaunt
|
||||
name: Shadow jaunt
|
||||
description: Melt into the shadows.
|
||||
categories: [ HideSpawnMenu ]
|
||||
components:
|
||||
- type: Action
|
||||
useDelay: 40 # Stop running you cowardly fucks.
|
||||
itemIconStyle: NoItem
|
||||
checkCanInteract: false
|
||||
#checkConsciousness: true # DeltaV - Nuh-uh, no death teleporting
|
||||
icon:
|
||||
sprite: _Goobstation/Actions/devil.rsi
|
||||
state: shadow-jaunt
|
||||
- type: InstantAction
|
||||
event: !type:ShadowJauntEvent {}
|
||||
|
||||
- type: entity
|
||||
parent: BaseAction
|
||||
id: ActionDevilPossess
|
||||
name: Possess target
|
||||
description: Temporarily possess a target.
|
||||
categories: [ HideSpawnMenu ]
|
||||
components:
|
||||
- type: Action
|
||||
useDelay: 70
|
||||
itemIconStyle: NoItem
|
||||
icon:
|
||||
sprite: _Goobstation/Actions/devil.rsi
|
||||
state: possess
|
||||
- type: TargetAction
|
||||
interactOnMiss: false
|
||||
- type: EntityTargetAction
|
||||
canTargetSelf: false
|
||||
whitelist:
|
||||
components:
|
||||
- Body
|
||||
event: !type:DevilPossessionEvent {}
|
||||
|
||||
- type: entity
|
||||
parent: BaseAction
|
||||
id: ActionEndPossession
|
||||
name: End possession
|
||||
description: Cancel this possession early.
|
||||
categories: [ HideSpawnMenu ]
|
||||
components:
|
||||
- type: Action
|
||||
itemIconStyle: NoItem
|
||||
checkCanInteract: false
|
||||
checkConsciousness: false
|
||||
icon:
|
||||
sprite: _Goobstation/Actions/devil.rsi
|
||||
state: possess
|
||||
- type: InstantAction
|
||||
event: !type:EndPossessionEarlyEvent {}
|
||||
|
||||
- type: entity
|
||||
parent: BaseAction
|
||||
id: ActionDevilGrip
|
||||
name: Devils Grip
|
||||
description: What little fraction of your power remains in this realm, channeled into your hands.
|
||||
categories: [ HideSpawnMenu ]
|
||||
components:
|
||||
- type: Action
|
||||
itemIconStyle: NoItem
|
||||
icon:
|
||||
sprite: _Goobstation/Actions/devil.rsi
|
||||
state: devil-grip
|
||||
- type: InstantAction
|
||||
event: !type:DevilGripEvent {}
|
||||
|
||||
# Deals
|
||||
|
||||
- type: entity
|
||||
parent: BaseAction
|
||||
id: ActionCheatDeath
|
||||
name: Cheat Death
|
||||
description: Laugh in the Grim Reaper's face. You've still got time.
|
||||
categories: [ HideSpawnMenu ]
|
||||
components:
|
||||
- type: Action
|
||||
itemIconStyle: NoItem
|
||||
useDelay: 250
|
||||
checkCanInteract: false
|
||||
checkConsciousness: false
|
||||
icon:
|
||||
sprite: _Goobstation/Actions/devil.rsi
|
||||
state: cheat-death
|
||||
- type: InstantAction
|
||||
event: !type:CheatDeathEvent {}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
- type: damageModifierSet
|
||||
id: DevilDealPositive
|
||||
coefficients:
|
||||
Blunt: 0.50
|
||||
Slash: 0.50
|
||||
Piercing: 0.50
|
||||
Cold: 0.50
|
||||
Heat: 0.50
|
||||
Bloodloss: 0.50
|
||||
Poison: 0.50
|
||||
Radiation: 0.50
|
||||
Holy: 1.50
|
||||
|
||||
- type: damageModifierSet
|
||||
id: DevilDealNegative
|
||||
coefficients:
|
||||
Blunt: 2
|
||||
Slash: 2
|
||||
Piercing: 2
|
||||
Cold: 2
|
||||
Bloodloss: 2
|
||||
Poison: 2
|
||||
Radiation: 2
|
||||
Holy: 2
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue