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>
This commit is contained in:
parent
0efc1b8d93
commit
cc6bbfebd4
|
|
@ -0,0 +1,5 @@
|
|||
using Content.Shared.DeltaV.Chapel;
|
||||
|
||||
namespace Content.Client.DeltaV.Chapel;
|
||||
|
||||
public sealed class SacrificialAltarSystem : SharedSacrificialAltarSystem;
|
||||
|
|
@ -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<SacrificialAltarComponent, SacrificeDoAfterEvent>(OnDoAfter);
|
||||
}
|
||||
|
||||
private void OnDoAfter(Entity<SacrificialAltarComponent> 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<PsionicComponent>(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<BodyComponent>(target, out var body))
|
||||
_body.GibBody(target, gibOrgans: true, body, launchGibs: true);
|
||||
else
|
||||
QueueDel(target);
|
||||
}
|
||||
|
||||
protected override void AttemptSacrifice(Entity<SacrificialAltarComponent> 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<PsionicComponent>(user) && !HasComp<BibleUserComponent>(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<HumanoidAppearanceComponent>(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<ActorComponent>(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<PsionicComponent>(target))
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("altar-failure-reason-target", ("target", target)), ent, user, PopupType.SmallCaution);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!HasComp<HumanoidAppearanceComponent>(target) && !HasComp<MetempsychosisKarmaComponent>(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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Altar that lets you sacrifice psionics to lower glimmer by a large amount.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, Access(typeof(SharedSacrificialAltarSystem))]
|
||||
public sealed partial class SacrificialAltarComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// DoAfter for an active sacrifice.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public DoAfterId? DoAfter;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public TimeSpan SacrificeTime = TimeSpan.FromSeconds(8.35);
|
||||
|
||||
[DataField]
|
||||
public SoundSpecifier SacrificeSound = new SoundPathSpecifier("/Audio/DeltaV/Effects/clang2.ogg");
|
||||
|
||||
[DataField]
|
||||
public EntityUid? SacrificeStream;
|
||||
|
||||
/// <summary>
|
||||
/// Random amount to reduce glimmer by.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public MinMax GlimmerReduction = new(30, 60);
|
||||
|
||||
[DataField]
|
||||
public ProtoId<EntityTablePrototype> RewardPool = "PsionicSacrificeRewards";
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed partial class SacrificeDoAfterEvent : SimpleDoAfterEvent;
|
||||
|
|
@ -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<SacrificialAltarComponent, ExaminedEvent>(OnExamined);
|
||||
SubscribeLocalEvent<SacrificialAltarComponent, UnstrappedEvent>(OnUnstrapped);
|
||||
SubscribeLocalEvent<SacrificialAltarComponent, GetVerbsEvent<AlternativeVerb>>(OnGetVerbs);
|
||||
}
|
||||
|
||||
private void OnExamined(Entity<SacrificialAltarComponent> ent, ref ExaminedEvent args)
|
||||
{
|
||||
args.PushMarkup(Loc.GetString("altar-examine"));
|
||||
}
|
||||
|
||||
private void OnUnstrapped(Entity<SacrificialAltarComponent> ent, ref UnstrappedEvent args)
|
||||
{
|
||||
if (ent.Comp.DoAfter is {} id)
|
||||
{
|
||||
DoAfter.Cancel(id);
|
||||
ent.Comp.DoAfter = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnGetVerbs(Entity<SacrificialAltarComponent> ent, ref GetVerbsEvent<AlternativeVerb> args)
|
||||
{
|
||||
if (!args.CanAccess || !args.CanInteract || ent.Comp.DoAfter != null)
|
||||
return;
|
||||
|
||||
if (!TryComp<StrapComponent>(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<SacrificialAltarComponent> ent, EntityUid user, EntityUid target)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -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}!
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
<GuideEntityEmbed Entity="CrystalSoul" />
|
||||
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.
|
||||
|
||||
<GuideEntityEmbed Entity="PonderingOrbTelepathic" />
|
||||
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].
|
||||
<GuideEntityEmbed Entity="MaterialBluespace1" />
|
||||
<GuideEntityEmbed Entity="ClothingEyesTelegnosisSpectacles" />
|
||||
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
|
||||
<Box>
|
||||
<GuideEntityEmbed Entity="MobGolemCult"/>
|
||||
<GuideEntityEmbed Entity="MobGolemWood"/>
|
||||
</Box>
|
||||
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]
|
||||
</Document>
|
||||
|
|
|
|||
Loading…
Reference in New Issue