From cc6bbfebd41049df61f38afdea0032a536ea7748 Mon Sep 17 00:00:00 2001 From: deltanedas <39013340+deltanedas@users.noreply.github.com> Date: Wed, 2 Oct 2024 13:04:35 +0000 Subject: [PATCH] port psionic sacraficing (#1883) * uncomment and update guidebook * update loot pool to use entity table and dont hardcode bluespace * add sacraficing to altars * :trollface: * :trollface: * :trollface: * 2 lines of sec * rename files * replace gamer loot with fake wizard outfit or a normality crystal --------- Co-authored-by: deltanedas <@deltanedas:kde.org> --- .../DeltaV/Chapel/SacrificialAltarSystem.cs | 5 + .../DeltaV/Chapel/SacrificialAltarSystem.cs | 139 ++++++++++++++++++ .../Chapel/SacrificialAltarComponent.cs | 47 ++++++ .../Chapel/SharedSacrificialAltarSystem.cs | 70 +++++++++ .../Locale/en-US/deltav/chapel/altar.ftl | 11 ++ .../Entities/Structures/Furniture/altar.yml | 11 +- Resources/Prototypes/Guidebook/science.yml | 2 +- .../Nyanotrasen/Guidebook/epistemics.yml | 8 +- .../Nyanotrasen/psionicArtifacts.yml | 44 +++--- .../Guidebook/Epistemics/Altar.xml | 17 +-- 10 files changed, 315 insertions(+), 39 deletions(-) create mode 100644 Content.Client/DeltaV/Chapel/SacrificialAltarSystem.cs create mode 100644 Content.Server/DeltaV/Chapel/SacrificialAltarSystem.cs create mode 100644 Content.Shared/DeltaV/Chapel/SacrificialAltarComponent.cs create mode 100644 Content.Shared/DeltaV/Chapel/SharedSacrificialAltarSystem.cs create mode 100644 Resources/Locale/en-US/deltav/chapel/altar.ftl diff --git a/Content.Client/DeltaV/Chapel/SacrificialAltarSystem.cs b/Content.Client/DeltaV/Chapel/SacrificialAltarSystem.cs new file mode 100644 index 0000000000..7b9b3757e3 --- /dev/null +++ b/Content.Client/DeltaV/Chapel/SacrificialAltarSystem.cs @@ -0,0 +1,5 @@ +using Content.Shared.DeltaV.Chapel; + +namespace Content.Client.DeltaV.Chapel; + +public sealed class SacrificialAltarSystem : SharedSacrificialAltarSystem; diff --git a/Content.Server/DeltaV/Chapel/SacrificialAltarSystem.cs b/Content.Server/DeltaV/Chapel/SacrificialAltarSystem.cs new file mode 100644 index 0000000000..a903d4124d --- /dev/null +++ b/Content.Server/DeltaV/Chapel/SacrificialAltarSystem.cs @@ -0,0 +1,139 @@ +using Content.Server.Bible.Components; +using Content.Server.Nyanotrasen.Cloning; +using Content.Shared.Abilities.Psionics; +using Content.Shared.Administration.Logs; +using Content.Shared.Body.Components; +using Content.Shared.Body.Systems; +using Content.Shared.Database; +using Content.Shared.DeltaV.Chapel; +using Content.Shared.DoAfter; +using Content.Shared.EntityTable; +using Content.Shared.Humanoid; +using Content.Shared.Mind; +using Content.Shared.Popups; +using Content.Shared.Psionics.Glimmer; +using Robust.Shared.Audio.Systems; +using Robust.Shared.Player; +using Robust.Shared.Prototypes; +using Robust.Shared.Random; + +namespace Content.Server.DeltaV.Chapel; + +public sealed class SacrificialAltarSystem : SharedSacrificialAltarSystem +{ + [Dependency] private readonly EntityTableSystem _entityTable = default!; + [Dependency] private readonly GlimmerSystem _glimmer = default!; + [Dependency] private readonly IPrototypeManager _proto = default!; + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly SharedBodySystem _body = default!; + [Dependency] private readonly SharedMindSystem _mind = default!; + [Dependency] private readonly SharedPopupSystem _popup = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnDoAfter); + } + + private void OnDoAfter(Entity ent, ref SacrificeDoAfterEvent args) + { + ent.Comp.SacrificeStream = _audio.Stop(ent.Comp.SacrificeStream); + ent.Comp.DoAfter = null; + + var user = args.Args.User; + + if (args.Cancelled || args.Handled || args.Args.Target is not {} target) + return; + + if (!_mind.TryGetMind(target, out var mindId, out var mind)) + return; + + // prevent starting the doafter then mindbreaking to double dip + if (!HasComp(target)) + return; + + _adminLogger.Add(LogType.Action, LogImpact.Extreme, $"{ToPrettyString(user):player} sacrificed {ToPrettyString(target):target} on {ToPrettyString(ent):altar}"); + + // lower glimmer by a random amount + _glimmer.Glimmer -= ent.Comp.GlimmerReduction.Next(_random); + + // spawn all the loot + var proto = _proto.Index(ent.Comp.RewardPool); + var coords = Transform(ent).Coordinates; + foreach (var id in _entityTable.GetSpawns(proto.Table)) + { + Spawn(id, coords); + } + + // TODO GOLEMS: create a soul crystal and transfer mind into it + + // finally gib the targets old body + if (TryComp(target, out var body)) + _body.GibBody(target, gibOrgans: true, body, launchGibs: true); + else + QueueDel(target); + } + + protected override void AttemptSacrifice(Entity ent, EntityUid user, EntityUid target) + { + if (ent.Comp.DoAfter != null) + return; + + // can't sacrifice yourself + if (user == target) + { + _popup.PopupEntity(Loc.GetString("altar-failure-reason-self"), ent, user, PopupType.SmallCaution); + return; + } + + // you need to be psionic OR bible user + if (!HasComp(user) && !HasComp(user)) + { + _popup.PopupEntity(Loc.GetString("altar-failure-reason-user"), ent, user, PopupType.SmallCaution); + return; + } + + // and no golems or familiars or whatever should be sacrificing + if (!HasComp(user)) + { + _popup.PopupEntity(Loc.GetString("altar-failure-reason-user-humanoid"), ent, user, PopupType.SmallCaution); + return; + } + + // prevent psichecking SSD people... + // notably there is no check in OnDoAfter so you can't alt f4 to survive being sacrificed + if (!HasComp(target) || _mind.GetMind(target) == null) + { + _popup.PopupEntity(Loc.GetString("altar-failure-reason-target-catatonic", ("target", target)), ent, user, PopupType.SmallCaution); + return; + } + + // TODO: there should be a penalty to the user for psichecking like this + if (!HasComp(target)) + { + _popup.PopupEntity(Loc.GetString("altar-failure-reason-target", ("target", target)), ent, user, PopupType.SmallCaution); + return; + } + + if (!HasComp(target) && !HasComp(target)) + { + _popup.PopupEntity(Loc.GetString("altar-failure-reason-target-humanoid", ("target", target)), ent, user, PopupType.SmallCaution); + return; + } + + _popup.PopupEntity(Loc.GetString("altar-sacrifice-popup", ("user", user), ("target", target)), ent, PopupType.LargeCaution); + + ent.Comp.SacrificeStream = _audio.PlayPvs(ent.Comp.SacrificeSound, ent)?.Entity; + + var ev = new SacrificeDoAfterEvent(); + var args = new DoAfterArgs(EntityManager, user, ent.Comp.SacrificeTime, ev, target: target, eventTarget: ent) + { + BreakOnDamage = true, + NeedHand = true + }; + DoAfter.TryStartDoAfter(args, out ent.Comp.DoAfter); + } +} diff --git a/Content.Shared/DeltaV/Chapel/SacrificialAltarComponent.cs b/Content.Shared/DeltaV/Chapel/SacrificialAltarComponent.cs new file mode 100644 index 0000000000..54c6ec3561 --- /dev/null +++ b/Content.Shared/DeltaV/Chapel/SacrificialAltarComponent.cs @@ -0,0 +1,47 @@ +using Content.Shared.Destructible.Thresholds; +using Content.Shared.DoAfter; +using Content.Shared.EntityTable; +using Robust.Shared.Audio; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; + +namespace Content.Shared.DeltaV.Chapel; + +/// +/// Altar that lets you sacrifice psionics to lower glimmer by a large amount. +/// +[RegisterComponent, NetworkedComponent, Access(typeof(SharedSacrificialAltarSystem))] +public sealed partial class SacrificialAltarComponent : Component +{ + /// + /// DoAfter for an active sacrifice. + /// + [DataField] + public DoAfterId? DoAfter; + + /// + /// How long it takes to sacrifice someone once they die. + /// This is the window to interrupt a sacrifice if you want glimmer to stay high, or need the psionic to be revived. + /// + [DataField] + public TimeSpan SacrificeTime = TimeSpan.FromSeconds(8.35); + + [DataField] + public SoundSpecifier SacrificeSound = new SoundPathSpecifier("/Audio/DeltaV/Effects/clang2.ogg"); + + [DataField] + public EntityUid? SacrificeStream; + + /// + /// Random amount to reduce glimmer by. + /// + [DataField] + public MinMax GlimmerReduction = new(30, 60); + + [DataField] + public ProtoId RewardPool = "PsionicSacrificeRewards"; +} + +[Serializable, NetSerializable] +public sealed partial class SacrificeDoAfterEvent : SimpleDoAfterEvent; diff --git a/Content.Shared/DeltaV/Chapel/SharedSacrificialAltarSystem.cs b/Content.Shared/DeltaV/Chapel/SharedSacrificialAltarSystem.cs new file mode 100644 index 0000000000..fe647c7d20 --- /dev/null +++ b/Content.Shared/DeltaV/Chapel/SharedSacrificialAltarSystem.cs @@ -0,0 +1,70 @@ +using Content.Shared.Buckle; +using Content.Shared.Buckle.Components; +using Content.Shared.DoAfter; +using Content.Shared.Examine; +using Content.Shared.Verbs; + +namespace Content.Shared.DeltaV.Chapel; + +public abstract class SharedSacrificialAltarSystem : EntitySystem +{ + [Dependency] private readonly SharedBuckleSystem _buckle = default!; + [Dependency] protected readonly SharedDoAfterSystem DoAfter = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnExamined); + SubscribeLocalEvent(OnUnstrapped); + SubscribeLocalEvent>(OnGetVerbs); + } + + private void OnExamined(Entity ent, ref ExaminedEvent args) + { + args.PushMarkup(Loc.GetString("altar-examine")); + } + + private void OnUnstrapped(Entity ent, ref UnstrappedEvent args) + { + if (ent.Comp.DoAfter is {} id) + { + DoAfter.Cancel(id); + ent.Comp.DoAfter = null; + } + } + + private void OnGetVerbs(Entity ent, ref GetVerbsEvent args) + { + if (!args.CanAccess || !args.CanInteract || ent.Comp.DoAfter != null) + return; + + if (!TryComp(ent, out var strap)) + return; + + if (GetFirstBuckled(strap) is not {} target) + return; + + var user = args.User; + args.Verbs.Add(new AlternativeVerb() + { + Act = () => AttemptSacrifice(ent, user, target), + Text = Loc.GetString("altar-sacrifice-verb"), + Priority = 2 + }); + } + + private EntityUid? GetFirstBuckled(StrapComponent strap) + { + foreach (var entity in strap.BuckledEntities) + { + return entity; + } + + return null; + } + + protected virtual void AttemptSacrifice(Entity ent, EntityUid user, EntityUid target) + { + } +} diff --git a/Resources/Locale/en-US/deltav/chapel/altar.ftl b/Resources/Locale/en-US/deltav/chapel/altar.ftl new file mode 100644 index 0000000000..ed031d638a --- /dev/null +++ b/Resources/Locale/en-US/deltav/chapel/altar.ftl @@ -0,0 +1,11 @@ +altar-examine = [color=purple]This altar can be used to sacrifice Psionics.[/color] +altar-sacrifice-verb = Sacrifice + +altar-failure-reason-self = You can't sacrifice yourself! +altar-failure-reason-user = You are not psionic or clerically trained! +altar-failure-reason-user-humanoid = You are not a humanoid! +altar-failure-reason-target = {CAPITALIZE(THE($target))} {CONJUGATE-BE($target)} not psionic! +altar-failure-reason-target-humanoid = {CAPITALIZE(THE($target))} {CONJUGATE-BE($target)} not a humanoid! +altar-failure-reason-target-catatonic = {CAPITALIZE(THE($target))} {CONJUGATE-BE($target)} braindead! + +altar-sacrifice-popup = {$user} starts to sacrifice {$target}! diff --git a/Resources/Prototypes/Entities/Structures/Furniture/altar.yml b/Resources/Prototypes/Entities/Structures/Furniture/altar.yml index c22f72d37d..7f21707670 100644 --- a/Resources/Prototypes/Entities/Structures/Furniture/altar.yml +++ b/Resources/Prototypes/Entities/Structures/Furniture/altar.yml @@ -25,8 +25,17 @@ - TableLayer - type: Sprite snapCardinals: true - - type: Climbable + #- type: Climbable # DeltaV: remove climbable since it conflicts with strap - type: Clickable + # Begin DeltaV additions: Sacrificing psionics + - type: SacrificialAltar + - type: Strap + position: Down + rotation: -90 + - type: GuideHelp + guides: + - AltarsGolemancy + # End DeltaV additions - type: entity id: AltarNanotrasen diff --git a/Resources/Prototypes/Guidebook/science.yml b/Resources/Prototypes/Guidebook/science.yml index 2832da6c41..a1bd65c5d2 100644 --- a/Resources/Prototypes/Guidebook/science.yml +++ b/Resources/Prototypes/Guidebook/science.yml @@ -8,7 +8,7 @@ - Xenoarchaeology - Robotics - Psionics # Nyanotrasen - Psionics guidebook - # - AltarsGolemancy # When it's added # Nyanotrasen - Golemancy guidebook + - AltarsGolemancy # Nyanotrasen - Golemancy guidebook - ReverseEngineering # Nyanotrasen - Reverse Engineering guidebook - GlimmerCreatures # DeltaV diff --git a/Resources/Prototypes/Nyanotrasen/Guidebook/epistemics.yml b/Resources/Prototypes/Nyanotrasen/Guidebook/epistemics.yml index 77b180d6ea..ed4d983d02 100644 --- a/Resources/Prototypes/Nyanotrasen/Guidebook/epistemics.yml +++ b/Resources/Prototypes/Nyanotrasen/Guidebook/epistemics.yml @@ -3,10 +3,10 @@ name: guide-entry-psionics text: "/ServerInfo/Nyanotrasen/Guidebook/Epistemics/Psionics.xml" -# - type: guideEntry # When it's added -# id: AltarsGolemancy -# name: guide-entry-altars-golemancy -# text: "/ServerInfo/Nyanotrasen/Guidebook/Epistemics/Altar.xml" +- type: guideEntry + id: AltarsGolemancy + name: guide-entry-altars-golemancy + text: "/ServerInfo/Nyanotrasen/Guidebook/Epistemics/Altar.xml" - type: guideEntry id: ReverseEngineering diff --git a/Resources/Prototypes/Nyanotrasen/psionicArtifacts.yml b/Resources/Prototypes/Nyanotrasen/psionicArtifacts.yml index 8f952b3905..96cfa0fcdf 100644 --- a/Resources/Prototypes/Nyanotrasen/psionicArtifacts.yml +++ b/Resources/Prototypes/Nyanotrasen/psionicArtifacts.yml @@ -1,21 +1,23 @@ -- type: weightedRandom - id: PsionicArtifactPool - weights: - ClothingHandsDispelGloves: 1 - ClothingEyesTelegnosisSpectacles: 1 - ClothingHandsGlovesColorYellowUnsulated: 1 - PonderingOrbTelepathic: 1 - ClothingShoesBootsMagBlinding: 0.5 - LidOSaws: 0.25 - BedsheetInvisibilityCloak: 0.15 - # WeaponWandPolymorphCarp: 0.05 - # WeaponWandPolymorphMonkey: 0.05 - # WeaponWandFireball: 0.025 - # WeaponWandDeath: 0.001 - # WeaponWandPolymorphDoor: 0.05 - SpawnSpellbook: 0.025 - ForceWallSpellbook: 0.025 - BlinkBook: 0.025 - SmiteBook: 0.00025 - KnockSpellbook: 0.025 - FireballSpellbook: 0.01 +# Items to spawn when sacrificing a psionic +- type: entityTable + id: PsionicSacrificeRewards + table: !type:AllSelector + children: + - id: MaterialBluespace1 + amount: !type:RangeNumberSelector + range: 1, 4 + - !type:NestedSelector + tableId: PsionicArtifacts + prob: 0.5 + +- type: entityTable + id: PsionicArtifacts + table: !type:GroupSelector + children: + - !type:AllSelector # fake wizard outfit + children: + - id: ClothingHeadHatWizardFake # the hat is fake so you can't sacrifice for gamer eva suit + - id: ClothingOuterWizard + - id: ClothingShoesWizard + - id: CrystalNormality + weight: 0.5 diff --git a/Resources/ServerInfo/Nyanotrasen/Guidebook/Epistemics/Altar.xml b/Resources/ServerInfo/Nyanotrasen/Guidebook/Epistemics/Altar.xml index c3b211d296..540e15a724 100644 --- a/Resources/ServerInfo/Nyanotrasen/Guidebook/Epistemics/Altar.xml +++ b/Resources/ServerInfo/Nyanotrasen/Guidebook/Epistemics/Altar.xml @@ -5,18 +5,11 @@ The chapel has a [color=#a4885c]sacrificial altar[/color]. To use it, a psionic humanoid must be placed on it, and someone with either psionics or clerical training must initiate the sacrifice. It appears in the context menu on the altar, which can be opened with the right mouse button by default. - -Once sacrificed, the psionic humanoid's soul is trapped inside a [color=#a4885c]soul crystal[/color]. This is not the end for them; they can still talk both vocally and telepathically, and this form is much harder to destroy. - - -10% of the time, the altar will spawn a powerful psionic item along with the soul crystal. This chance increases to 50% if the sacrifice was performed by someone with clerical training, such as the [color=#a4885c]chaplain[/color] or [color=#a4885c]mystagogue[/color]. + + +As a reward for sacrificing a psionic, glimmer will be lowered substantially and you will be given some loot. +Usually this is a few bluespace crystals, but you can also get a powerful psionic item. ## Golemancy - - - - -Soul crystals can be installed into [color=#a4885c]golems[/color] to give the soul a new body. Golems are bound to serve their master. As constructs, they do not need to eat, drink, or breathe. -Note that for wood golems, if plants are planted on top of their head, the plants will still need those things. - +[color=red]Note: Golemancy is not implemented yet. Once you sacrifice a psionic you can borg them with an MMI.[/color]