mirror of
https://github.com/space-syndicate/space-station-14.git
synced 2026-10-09 10:21:18 +03:00
Prediction + Refactor: BibleSytem (#44800)
* Prediction * Update * Update * Fix * Rename file * Fix * Update * Fix * cleanup * EntityUid? Source * Revert "EntityUid? Source" This reverts commit c169e1f030b1fced6951b8026dda8f4e01571424. * ftl and fix PVS * Update * review * Update --------- Co-authored-by: Pok245 <113675512+Pok27@users.noreply.github.com>
This commit is contained in:
parent
54986e0347
commit
c58331c4f3
19 changed files with 575 additions and 438 deletions
|
|
@ -1,256 +0,0 @@
|
|||
using Content.Server.Bible.Components;
|
||||
using Content.Server.Ghost.Roles.Events;
|
||||
using Content.Server.Popups;
|
||||
using Content.Shared.ActionBlocker;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Bible;
|
||||
using Content.Shared.Damage.Systems;
|
||||
using Content.Shared.Ghost.Roles.Components;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Mobs;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Timing;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.Bible
|
||||
{
|
||||
public sealed partial class BibleSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private IRobustRandom _random = default!;
|
||||
[Dependency] private ActionBlockerSystem _blocker = default!;
|
||||
[Dependency] private DamageableSystem _damageableSystem = default!;
|
||||
[Dependency] private InventorySystem _invSystem = default!;
|
||||
[Dependency] private MobStateSystem _mobStateSystem = default!;
|
||||
[Dependency] private PopupSystem _popupSystem = default!;
|
||||
[Dependency] private SharedActionsSystem _actionsSystem = default!;
|
||||
[Dependency] private SharedAudioSystem _audio = default!;
|
||||
[Dependency] private UseDelaySystem _delay = default!;
|
||||
[Dependency] private SharedTransformSystem _transform = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<BibleComponent, AfterInteractEvent>(OnAfterInteract);
|
||||
SubscribeLocalEvent<SummonableComponent, GetVerbsEvent<AlternativeVerb>>(AddSummonVerb);
|
||||
SubscribeLocalEvent<SummonableComponent, GetItemActionsEvent>(GetSummonAction);
|
||||
SubscribeLocalEvent<SummonableComponent, SummonActionEvent>(OnSummon);
|
||||
SubscribeLocalEvent<FamiliarComponent, MobStateChangedEvent>(OnFamiliarDeath);
|
||||
SubscribeLocalEvent<FamiliarComponent, GhostRoleSpawnerUsedEvent>(OnSpawned);
|
||||
}
|
||||
|
||||
private readonly Queue<EntityUid> _addQueue = new();
|
||||
private readonly Queue<EntityUid> _remQueue = new();
|
||||
|
||||
/// <summary>
|
||||
/// This handles familiar respawning.
|
||||
/// </summary>
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
foreach (var entity in _addQueue)
|
||||
{
|
||||
EnsureComp<SummonableRespawningComponent>(entity);
|
||||
}
|
||||
_addQueue.Clear();
|
||||
|
||||
foreach (var entity in _remQueue)
|
||||
{
|
||||
RemComp<SummonableRespawningComponent>(entity);
|
||||
}
|
||||
_remQueue.Clear();
|
||||
|
||||
var query = EntityQueryEnumerator<SummonableRespawningComponent, SummonableComponent>();
|
||||
while (query.MoveNext(out var uid, out var _, out var summonableComp))
|
||||
{
|
||||
summonableComp.Accumulator += frameTime;
|
||||
if (summonableComp.Accumulator < summonableComp.RespawnTime)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
// Clean up the old body
|
||||
if (summonableComp.Summon != null)
|
||||
{
|
||||
Del(summonableComp.Summon.Value);
|
||||
summonableComp.Summon = null;
|
||||
}
|
||||
summonableComp.AlreadySummoned = false;
|
||||
_popupSystem.PopupEntity(Loc.GetString("bible-summon-respawn-ready", ("book", uid)), uid, PopupType.Medium);
|
||||
_audio.PlayPvs(summonableComp.SummonSound, uid);
|
||||
// Clean up the accumulator and respawn tracking component
|
||||
summonableComp.Accumulator = 0;
|
||||
_remQueue.Enqueue(uid);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAfterInteract(EntityUid uid, BibleComponent component, AfterInteractEvent args)
|
||||
{
|
||||
if (!args.CanReach)
|
||||
return;
|
||||
|
||||
if (!TryComp(uid, out UseDelayComponent? useDelay) || _delay.IsDelayed((uid, useDelay)))
|
||||
return;
|
||||
|
||||
if (args.Target == null || args.Target == args.User || !_mobStateSystem.IsAlive(args.Target.Value))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!HasComp<BibleUserComponent>(args.User))
|
||||
{
|
||||
_popupSystem.PopupEntity(Loc.GetString("bible-sizzle"), args.User, args.User);
|
||||
|
||||
_audio.PlayPvs(component.SizzleSoundPath, args.User);
|
||||
_damageableSystem.TryChangeDamage(args.User, component.DamageOnUntrainedUse, true, origin: uid);
|
||||
_delay.TryResetDelay((uid, useDelay));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var userEnt = Identity.Entity(args.User, EntityManager);
|
||||
var targetEnt = Identity.Entity(args.Target.Value, EntityManager);
|
||||
|
||||
// This only has a chance to fail if the target is not wearing anything on their head and is not a familiar.
|
||||
if (!_invSystem.TryGetSlotEntity(args.Target.Value, "head", out _) && !HasComp<FamiliarComponent>(args.Target.Value))
|
||||
{
|
||||
if (_random.Prob(component.FailChance))
|
||||
{
|
||||
var othersFailMessage = Loc.GetString(component.LocPrefix + "-heal-fail-others", ("user", userEnt), ("target", targetEnt), ("bible", uid));
|
||||
_popupSystem.PopupEntity(othersFailMessage, args.User, Filter.PvsExcept(args.User), true, PopupType.SmallCaution);
|
||||
|
||||
var selfFailMessage = Loc.GetString(component.LocPrefix + "-heal-fail-self", ("target", targetEnt), ("bible", uid));
|
||||
_popupSystem.PopupEntity(selfFailMessage, args.User, args.User, PopupType.MediumCaution);
|
||||
|
||||
_audio.PlayPvs(component.BibleHitSound, args.User);
|
||||
_damageableSystem.TryChangeDamage(args.Target.Value, component.DamageOnFail, true, origin: uid);
|
||||
_delay.TryResetDelay((uid, useDelay));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
string othersMessage;
|
||||
string selfMessage;
|
||||
|
||||
if (_damageableSystem.TryChangeDamage(args.Target.Value, component.Damage, true, origin: uid))
|
||||
{
|
||||
othersMessage = Loc.GetString(component.LocPrefix + "-heal-success-others", ("user", userEnt), ("target", targetEnt), ("bible", uid));
|
||||
selfMessage = Loc.GetString(component.LocPrefix + "-heal-success-self", ("target", targetEnt), ("bible", uid));
|
||||
|
||||
_audio.PlayPvs(component.HealSoundPath, args.User);
|
||||
_delay.TryResetDelay((uid, useDelay));
|
||||
|
||||
if (component.HealingLightEffect.HasValue)
|
||||
Spawn(component.HealingLightEffect.Value, new EntityCoordinates(args.Target.Value, default));
|
||||
}
|
||||
else
|
||||
{
|
||||
othersMessage = Loc.GetString(component.LocPrefix + "-heal-success-none-others", ("user", userEnt), ("target", targetEnt), ("bible", uid));
|
||||
selfMessage = Loc.GetString(component.LocPrefix + "-heal-success-none-self", ("target", targetEnt), ("bible", uid));
|
||||
}
|
||||
|
||||
_popupSystem.PopupEntity(othersMessage, args.User, Filter.PvsExcept(args.User), true, PopupType.Medium);
|
||||
_popupSystem.PopupEntity(selfMessage, args.User, args.User, PopupType.Large);
|
||||
}
|
||||
|
||||
private void AddSummonVerb(EntityUid uid, SummonableComponent component, GetVerbsEvent<AlternativeVerb> args)
|
||||
{
|
||||
if (!args.CanInteract || !args.CanAccess || component.AlreadySummoned || component.SpecialItemPrototype == null)
|
||||
return;
|
||||
|
||||
if (component.RequiresBibleUser && !HasComp<BibleUserComponent>(args.User))
|
||||
return;
|
||||
|
||||
AlternativeVerb verb = new()
|
||||
{
|
||||
Act = () =>
|
||||
{
|
||||
if (!TryComp(args.User, out TransformComponent? userXform))
|
||||
return;
|
||||
|
||||
AttemptSummon((uid, component), args.User, userXform);
|
||||
},
|
||||
Text = Loc.GetString("bible-summon-verb"),
|
||||
Priority = 2
|
||||
};
|
||||
args.Verbs.Add(verb);
|
||||
}
|
||||
|
||||
private void GetSummonAction(EntityUid uid, SummonableComponent component, GetItemActionsEvent args)
|
||||
{
|
||||
if (component.AlreadySummoned)
|
||||
return;
|
||||
|
||||
args.AddAction(ref component.SummonActionEntity, component.SummonAction);
|
||||
}
|
||||
|
||||
private void OnSummon(Entity<SummonableComponent> ent, ref SummonActionEvent args)
|
||||
{
|
||||
AttemptSummon(ent, args.Performer, Transform(args.Performer));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts up the respawn stuff when
|
||||
/// the chaplain's familiar dies.
|
||||
/// </summary>
|
||||
private void OnFamiliarDeath(EntityUid uid, FamiliarComponent component, MobStateChangedEvent args)
|
||||
{
|
||||
if (args.NewMobState != MobState.Dead || component.Source == null)
|
||||
return;
|
||||
|
||||
var source = component.Source;
|
||||
if (source != null && HasComp<SummonableComponent>(source))
|
||||
{
|
||||
_addQueue.Enqueue(source.Value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When the familiar spawns, set its source to the bible.
|
||||
/// </summary>
|
||||
private void OnSpawned(EntityUid uid, FamiliarComponent component, GhostRoleSpawnerUsedEvent args)
|
||||
{
|
||||
var parent = Transform(args.Spawner).ParentUid;
|
||||
if (!TryComp<SummonableComponent>(parent, out var summonable))
|
||||
return;
|
||||
|
||||
component.Source = parent;
|
||||
summonable.Summon = uid;
|
||||
}
|
||||
|
||||
private void AttemptSummon(Entity<SummonableComponent> ent, EntityUid user, TransformComponent? position)
|
||||
{
|
||||
var (uid, component) = ent;
|
||||
if (component.AlreadySummoned || component.SpecialItemPrototype == null)
|
||||
return;
|
||||
if (component.RequiresBibleUser && !HasComp<BibleUserComponent>(user))
|
||||
return;
|
||||
if (!Resolve(user, ref position))
|
||||
return;
|
||||
if (component.Deleted || Deleted(uid))
|
||||
return;
|
||||
if (!_blocker.CanInteract(user, uid))
|
||||
return;
|
||||
|
||||
// Make this familiar the component's summon
|
||||
var familiar = Spawn(component.SpecialItemPrototype, position.Coordinates);
|
||||
component.Summon = familiar;
|
||||
|
||||
// If this is going to use a ghost role mob spawner, attach it to the bible.
|
||||
if (HasComp<GhostRoleMobSpawnerComponent>(familiar))
|
||||
{
|
||||
_popupSystem.PopupEntity(Loc.GetString("bible-summon-requested"), user, user, PopupType.Medium);
|
||||
_transform.SetParent(familiar, uid);
|
||||
}
|
||||
component.AlreadySummoned = true;
|
||||
_actionsSystem.RemoveAction(user, component.SummonActionEntity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,63 +0,0 @@
|
|||
using Content.Shared.Damage;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Bible.Components
|
||||
{
|
||||
[RegisterComponent]
|
||||
public sealed partial class BibleComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Default sound when bible hits somebody.
|
||||
/// </summary>
|
||||
private static readonly ProtoId<SoundCollectionPrototype> DefaultBibleHit = new("BibleHit");
|
||||
|
||||
/// <summary>
|
||||
/// Sound to play when bible hits somebody.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public SoundSpecifier BibleHitSound = new SoundCollectionSpecifier(DefaultBibleHit, AudioParams.Default.WithVolume(-4f));
|
||||
|
||||
/// <summary>
|
||||
/// Damage that will be healed on a success
|
||||
/// </summary>
|
||||
[DataField("damage", required: true)]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public DamageSpecifier Damage = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Damage that will be dealt on a failure
|
||||
/// </summary>
|
||||
[DataField("damageOnFail", required: true)]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public DamageSpecifier DamageOnFail = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Damage that will be dealt when a non-chaplain attempts to heal
|
||||
/// </summary>
|
||||
[DataField("damageOnUntrainedUse", required: true)]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public DamageSpecifier DamageOnUntrainedUse = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Chance the bible will fail to heal someone with no helmet
|
||||
/// </summary>
|
||||
[DataField("failChance")]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public float FailChance = 0.34f;
|
||||
|
||||
[DataField("sizzleSound")]
|
||||
public SoundSpecifier SizzleSoundPath = new SoundPathSpecifier("/Audio/Effects/lightburn.ogg");
|
||||
[DataField("healSound")]
|
||||
public SoundSpecifier HealSoundPath = new SoundPathSpecifier("/Audio/Effects/holy.ogg");
|
||||
|
||||
[DataField("locPrefix")]
|
||||
public string LocPrefix = "bible";
|
||||
|
||||
/// <summary>
|
||||
/// A short light effect to display when successfully healing someone
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public EntProtoId? HealingLightEffect = "HolyLightEffect";
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
namespace Content.Server.Bible.Components
|
||||
{
|
||||
[RegisterComponent]
|
||||
public sealed partial class BibleUserComponent : Component {}
|
||||
}
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
namespace Content.Server.Bible.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// This component is for the chaplain's familiars, and mostly
|
||||
/// used to track their current state and to give a component to check for
|
||||
/// if any special behavior is needed.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class FamiliarComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The entity this familiar was summoned from.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public EntityUid? Source = null;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Server.Bible.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// This lets you summon a mob or item with an alternative verb on the item
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class SummonableComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Default sound to play when entity is summoned.
|
||||
/// </summary>
|
||||
private static readonly ProtoId<SoundCollectionPrototype> DefaultSummonSound = new("Summon");
|
||||
|
||||
/// <summary>
|
||||
/// Sound to play when entity is summoned.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public SoundSpecifier SummonSound = new SoundCollectionSpecifier(DefaultSummonSound, AudioParams.Default.WithVolume(-4f));
|
||||
|
||||
/// <summary>
|
||||
/// Used for a special item only the Chaplain can summon. Usually a mob, but supports regular items too.
|
||||
/// </summary>
|
||||
[DataField("specialItem", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||
public string? SpecialItemPrototype = null;
|
||||
public bool AlreadySummoned = false;
|
||||
|
||||
[DataField("requiresBibleUser")]
|
||||
public bool RequiresBibleUser = true;
|
||||
|
||||
/// <summary>
|
||||
/// The specific creature this summoned, if the SpecialItemPrototype has a mobstate.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public EntityUid? Summon = null;
|
||||
|
||||
[DataField("summonAction", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||
public string SummonAction = "ActionBibleSummon";
|
||||
|
||||
[DataField("summonActionEntity")]
|
||||
public EntityUid? SummonActionEntity;
|
||||
|
||||
/// Used for respawning
|
||||
[DataField("accumulator")]
|
||||
public float Accumulator = 0f;
|
||||
[DataField("respawnTime")]
|
||||
public float RespawnTime = 180f;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
namespace Content.Server.Bible.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// EntityQuery Tracking Component for Summonables that are counting up a respawn.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class SummonableRespawningComponent : Component
|
||||
{}
|
||||
}
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
namespace Content.Server.Ghost.Roles.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// Raised on a spawned entity after they use a ghost role mob spawner.
|
||||
/// </summary>
|
||||
public sealed class GhostRoleSpawnerUsedEvent : EntityEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// The entity that spawned this.
|
||||
/// </summary>
|
||||
public EntityUid Spawner;
|
||||
|
||||
/// <summary>
|
||||
/// The entity spawned.
|
||||
/// </summary>
|
||||
public EntityUid Spawned;
|
||||
|
||||
public GhostRoleSpawnerUsedEvent(EntityUid spawner, EntityUid spawned)
|
||||
{
|
||||
Spawner = spawner;
|
||||
|
||||
Spawned = spawned;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,7 +4,6 @@ using Content.Server.Administration.Managers;
|
|||
using Content.Server.EUI;
|
||||
using Content.Server.GameTicking.Events;
|
||||
using Content.Server.Ghost.Roles.Components;
|
||||
using Content.Server.Ghost.Roles.Events;
|
||||
using Content.Shared.Ghost.Roles.Raffles;
|
||||
using Content.Server.Ghost.Roles.UI;
|
||||
using Content.Shared.Administration;
|
||||
|
|
@ -784,7 +783,7 @@ public sealed partial class GhostRoleSystem : EntitySystem
|
|||
var mob = Spawn(component.Prototype, spawnCoordinates.Value);
|
||||
|
||||
var spawnedEvent = new GhostRoleSpawnerUsedEvent(uid, mob);
|
||||
RaiseLocalEvent(mob, spawnedEvent);
|
||||
RaiseLocalEvent(mob, ref spawnedEvent);
|
||||
|
||||
if (ghostRole.MakeSentient)
|
||||
_mindSystem.MakeSentient(mob, ghostRole.AllowMovement, ghostRole.AllowSpeech);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
using Content.Server.Administration;
|
||||
using Content.Server.Administration.Logs;
|
||||
using Content.Server.Bible.Components;
|
||||
using Content.Server.Chat.Managers;
|
||||
using Content.Server.Popups;
|
||||
using Content.Shared.Database;
|
||||
|
|
@ -10,6 +9,7 @@ using Content.Shared.Prayer;
|
|||
using Content.Shared.Verbs;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Player;
|
||||
using Content.Shared.Bible.Components;
|
||||
|
||||
namespace Content.Server.Prayer;
|
||||
/// <summary>
|
||||
|
|
@ -48,7 +48,7 @@ public sealed partial class PrayerSystem : EntitySystem
|
|||
Icon = comp.VerbImage,
|
||||
Act = () =>
|
||||
{
|
||||
if (comp.BibleUserOnly && !TryComp<BibleUserComponent>(args.User, out var bibleUser))
|
||||
if (comp.BibleUserOnly && !HasComp<BibleUserComponent>(args.User))
|
||||
{
|
||||
_popupSystem.PopupEntity(Loc.GetString("prayer-popup-notify-pray-locked"), uid, actor.PlayerSession, PopupType.Large);
|
||||
return;
|
||||
|
|
|
|||
291
Content.Shared/Bible/BibleSystem.cs
Normal file
291
Content.Shared/Bible/BibleSystem.cs
Normal file
|
|
@ -0,0 +1,291 @@
|
|||
using Content.Shared.ActionBlocker;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Bible.Components;
|
||||
using Content.Shared.Damage.Systems;
|
||||
using Content.Shared.Ghost;
|
||||
using Content.Shared.Ghost.Roles.Components;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Mobs;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Random.Helpers;
|
||||
using Content.Shared.Timing;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Shared.Bible;
|
||||
|
||||
/// <summary>
|
||||
/// Handles bible healing on hit, and summoning/respawning of familiars via <see cref="SummonableComponent"/>.
|
||||
/// </summary>
|
||||
public sealed partial class BibleSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private ActionBlockerSystem _blocker = default!;
|
||||
[Dependency] private DamageableSystem _damageable = default!;
|
||||
[Dependency] private IGameTiming _timing = default!;
|
||||
[Dependency] private INetManager _net = default!;
|
||||
[Dependency] private InventorySystem _inventory = default!;
|
||||
[Dependency] private MobStateSystem _mobState = default!;
|
||||
[Dependency] private SharedActionsSystem _actions = default!;
|
||||
[Dependency] private SharedAudioSystem _audio = default!;
|
||||
[Dependency] private SharedContainerSystem _container = default!;
|
||||
[Dependency] private SharedPopupSystem _popup = default!;
|
||||
[Dependency] private SharedTransformSystem _transform = default!;
|
||||
[Dependency] private UseDelaySystem _delay = default!;
|
||||
|
||||
/// <summary>
|
||||
/// This handles familiar respawning.
|
||||
/// </summary>
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
var query = EntityQueryEnumerator<SummonableRespawningComponent, SummonableComponent>();
|
||||
while (query.MoveNext(out var uid, out var respawningComponent, out var summonableComp))
|
||||
{
|
||||
if (summonableComp.RespawnTime > _timing.CurTime)
|
||||
continue;
|
||||
|
||||
// Delete old summoned entity.
|
||||
if (Exists(summonableComp.SummonedEntity))
|
||||
PredictedDel(summonableComp.SummonedEntity);
|
||||
|
||||
summonableComp.CanSummon = true;
|
||||
Dirty(uid, summonableComp);
|
||||
|
||||
// Re-add summon action.
|
||||
if (summonableComp.SummonActionEntity is { } action
|
||||
&& _container.TryGetContainingContainer(uid, out var container))
|
||||
{
|
||||
_actions.GrantContainedAction(container.Owner, uid, action);
|
||||
}
|
||||
|
||||
if (_net.IsServer)
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString(summonableComp.SummonRespawnReadyText, ("book", uid)), uid, PopupType.Medium);
|
||||
_audio.PlayPvs(summonableComp.SummonSound, uid);
|
||||
}
|
||||
|
||||
RemCompDeferred(uid, respawningComponent);
|
||||
}
|
||||
}
|
||||
|
||||
[SubscribeLocalEvent]
|
||||
private void OnAfterInteract(Entity<BibleComponent> ent, ref AfterInteractEvent args)
|
||||
{
|
||||
if (!args.CanReach)
|
||||
return;
|
||||
|
||||
if (!TryComp<UseDelayComponent>(ent, out var useDelay) || _delay.IsDelayed((ent, useDelay)))
|
||||
return;
|
||||
|
||||
if (args.Target == null || args.Target == args.User || !_mobState.IsAlive(args.Target.Value))
|
||||
return;
|
||||
|
||||
var userEnt = Identity.Entity(args.User, EntityManager);
|
||||
var targetEnt = Identity.Entity(args.Target.Value, EntityManager);
|
||||
|
||||
// Sizzle user if they are not a Bible user.
|
||||
if (!HasComp<BibleUserComponent>(args.User))
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString(ent.Comp.SizzleText, ("user", userEnt), ("target", targetEnt), ("bible", ent)), args.User, args.User);
|
||||
|
||||
_audio.PlayPredicted(ent.Comp.SizzleSound, ent, args.User);
|
||||
_damageable.TryChangeDamage(args.User, ent.Comp.DamageOnUntrainedUse, true, origin: ent);
|
||||
_delay.TryResetDelay((ent, useDelay));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// This only has a chance to fail if the target is not wearing anything on their head and is not a familiar.
|
||||
if (!_inventory.TryGetSlotEntity(args.Target.Value, "head", out _) && !HasComp<FamiliarComponent>(args.Target.Value))
|
||||
{
|
||||
var rand = SharedRandomExtensions.PredictedRandom(_timing, GetNetEntity(ent));
|
||||
if (rand.Prob(ent.Comp.FailChance))
|
||||
{
|
||||
var othersFailMessage = Loc.GetString(ent.Comp.HealFailOthersText, ("user", userEnt), ("target", targetEnt), ("bible", ent));
|
||||
_popup.PopupEntity(othersFailMessage, args.User, Filter.PvsExcept(args.User), true, PopupType.SmallCaution);
|
||||
|
||||
var selfFailMessage = Loc.GetString(ent.Comp.HealFailSelfText, ("user", userEnt), ("target", targetEnt), ("bible", ent));
|
||||
_popup.PopupEntity(selfFailMessage, args.User, args.User, PopupType.MediumCaution);
|
||||
|
||||
_audio.PlayPredicted(ent.Comp.BibleHitSound, ent, args.User);
|
||||
_damageable.TryChangeDamage(args.Target.Value, ent.Comp.DamageOnFail, true, origin: ent);
|
||||
_delay.TryResetDelay((ent, useDelay));
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
string othersMessage;
|
||||
string selfMessage;
|
||||
|
||||
if (_damageable.TryChangeDamage(args.Target.Value, ent.Comp.Damage, true, origin: ent))
|
||||
{
|
||||
othersMessage = Loc.GetString(ent.Comp.HealSuccessOthersText, ("user", userEnt), ("target", targetEnt), ("bible", ent));
|
||||
selfMessage = Loc.GetString(ent.Comp.HealSuccessSelfText, ("user", userEnt), ("target", targetEnt), ("bible", ent));
|
||||
|
||||
_audio.PlayPredicted(ent.Comp.HealSound, ent, args.User);
|
||||
_delay.TryResetDelay((ent, useDelay));
|
||||
|
||||
if (ent.Comp.HealingLightEffect.HasValue)
|
||||
PredictedSpawnAtPosition(ent.Comp.HealingLightEffect.Value, Transform(args.Target.Value).Coordinates);
|
||||
}
|
||||
else
|
||||
{
|
||||
othersMessage = Loc.GetString(ent.Comp.HealSuccessNoneOthersText, ("user", userEnt), ("target", targetEnt), ("bible", ent));
|
||||
selfMessage = Loc.GetString(ent.Comp.HealSuccessNoneSelfText, ("user", userEnt), ("target", targetEnt), ("bible", ent));
|
||||
}
|
||||
|
||||
_popup.PopupEntity(othersMessage, args.User, Filter.PvsExcept(args.User), true, PopupType.Medium);
|
||||
_popup.PopupEntity(selfMessage, args.User, args.User, PopupType.Large);
|
||||
}
|
||||
|
||||
[SubscribeLocalEvent]
|
||||
private void AddSummonVerb(Entity<SummonableComponent> ent, ref GetVerbsEvent<AlternativeVerb> args)
|
||||
{
|
||||
if (!args.CanInteract || !args.CanAccess || !CanSummon(ent, args.User))
|
||||
return;
|
||||
|
||||
var user = args.User;
|
||||
AlternativeVerb verb = new()
|
||||
{
|
||||
Act = () =>
|
||||
{
|
||||
AttemptSummon(ent, user);
|
||||
},
|
||||
Text = Loc.GetString(ent.Comp.SummonVerbText),
|
||||
Priority = 2
|
||||
};
|
||||
args.Verbs.Add(verb);
|
||||
}
|
||||
|
||||
[SubscribeLocalEvent]
|
||||
private void GetSummonAction(Entity<SummonableComponent> ent, ref GetItemActionsEvent args)
|
||||
{
|
||||
if (!CanSummon(ent, args.User))
|
||||
return;
|
||||
|
||||
args.AddAction(ref ent.Comp.SummonActionEntity, ent.Comp.SummonActionPrototype);
|
||||
}
|
||||
|
||||
[SubscribeLocalEvent]
|
||||
private void OnSummon(Entity<SummonableComponent> ent, ref SummonActionEvent args)
|
||||
{
|
||||
AttemptSummon(ent, args.Performer);
|
||||
}
|
||||
|
||||
[SubscribeLocalEvent]
|
||||
private void OnFamiliarDeath(Entity<FamiliarComponent> ent, ref MobStateChangedEvent args)
|
||||
{
|
||||
if (args.NewMobState != MobState.Dead || ent.Comp.Source == null)
|
||||
return;
|
||||
|
||||
StartRespawnTimer(ent.Comp.Source.Value);
|
||||
}
|
||||
|
||||
[SubscribeLocalEvent]
|
||||
private void OnFamiliarRemoved(Entity<FamiliarComponent> ent, ref ComponentRemove args)
|
||||
{
|
||||
if (!TryComp<SummonableComponent>(ent.Comp.Source, out var summonable))
|
||||
return;
|
||||
|
||||
// If the entity is no longer a familiar, then there’s no need to keep track of it anymore
|
||||
summonable.SummonedEntity = null;
|
||||
Dirty(ent.Comp.Source.Value, summonable);
|
||||
|
||||
StartRespawnTimer((ent.Comp.Source.Value, summonable));
|
||||
}
|
||||
|
||||
[SubscribeLocalEvent]
|
||||
private void OnSummonableRemoved(Entity<SummonableComponent> ent, ref ComponentRemove args)
|
||||
{
|
||||
if (!TryComp<FamiliarComponent>(ent.Comp.SummonedEntity, out var familiar))
|
||||
return;
|
||||
|
||||
familiar.Source = null;
|
||||
Dirty(ent.Comp.SummonedEntity.Value, familiar);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When the familiar spawns, set its source to the bible.
|
||||
/// </summary>
|
||||
[SubscribeLocalEvent]
|
||||
private void OnSpawned(Entity<FamiliarComponent> ent, ref GhostRoleSpawnerUsedEvent args)
|
||||
{
|
||||
var parent = Transform(args.Spawner).ParentUid;
|
||||
if (!TryComp<SummonableComponent>(parent, out var summonable))
|
||||
return;
|
||||
|
||||
ent.Comp.Source = parent;
|
||||
Dirty(ent);
|
||||
|
||||
summonable.SummonedEntity = ent;
|
||||
Dirty(parent, summonable);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts up the respawn timer for the chaplain's familiar.
|
||||
/// </summary>
|
||||
private void StartRespawnTimer(Entity<SummonableComponent?> ent)
|
||||
{
|
||||
if (!Resolve(ent, ref ent.Comp, false))
|
||||
return;
|
||||
|
||||
EnsureComp<SummonableRespawningComponent>(ent);
|
||||
|
||||
ent.Comp.RespawnTime = _timing.CurTime + ent.Comp.RespawnCooldown;
|
||||
Dirty(ent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the summonable entity can be summoned by the given user.
|
||||
/// </summary>
|
||||
private bool CanSummon(Entity<SummonableComponent> ent, EntityUid user)
|
||||
{
|
||||
return ent.Comp.CanSummon
|
||||
&& ent.Comp.SummonEntityPrototype.HasValue
|
||||
&& (!ent.Comp.RequiresBibleUser || HasComp<BibleUserComponent>(user));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to summon a new familiar.
|
||||
/// </summary>
|
||||
private void AttemptSummon(Entity<SummonableComponent> ent, EntityUid user)
|
||||
{
|
||||
if (!CanSummon(ent, user))
|
||||
return;
|
||||
|
||||
if (!_blocker.CanInteract(user, ent))
|
||||
return;
|
||||
|
||||
// Make this familiar the component's summon
|
||||
var familiar = PredictedSpawnAtPosition(ent.Comp.SummonEntityPrototype, Transform(user).Coordinates);
|
||||
ent.Comp.SummonedEntity = familiar;
|
||||
|
||||
// If this is going to use a ghost role mob spawner, attach it to the bible.
|
||||
if (HasComp<GhostRoleMobSpawnerComponent>(familiar))
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString(ent.Comp.SummonRequestedText), user, user, PopupType.Medium);
|
||||
_transform.SetParent(familiar, ent);
|
||||
}
|
||||
else
|
||||
{
|
||||
EnsureComp<FamiliarComponent>(familiar, out var familiarComponent);
|
||||
familiarComponent.Source = ent;
|
||||
Dirty(familiar, familiarComponent);
|
||||
}
|
||||
|
||||
_actions.RemoveAction(user, ent.Comp.SummonActionEntity);
|
||||
|
||||
ent.Comp.CanSummon = false;
|
||||
Dirty(ent);
|
||||
}
|
||||
}
|
||||
119
Content.Shared/Bible/Components/BibleComponent.cs
Normal file
119
Content.Shared/Bible/Components/BibleComponent.cs
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
using Content.Shared.Damage;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Bible.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Marks an entity as Bible that heals somebody on interaction.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
[Access(typeof(BibleSystem))]
|
||||
public sealed partial class BibleComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Default sound when Bible hits somebody.
|
||||
/// </summary>
|
||||
private static readonly ProtoId<SoundCollectionPrototype> DefaultBibleHitSound = new("BibleHit");
|
||||
|
||||
/// <summary>
|
||||
/// Default sound when Bible fails to heal somebody.
|
||||
/// </summary>
|
||||
private static readonly ProtoId<SoundCollectionPrototype> DefaultBibleSizzleSound = new("BibleSizzle");
|
||||
|
||||
/// <summary>
|
||||
/// Default sound when Bible heals somebody.
|
||||
/// </summary>
|
||||
private static readonly ProtoId<SoundCollectionPrototype> DefaultBibleHealSound = new("BibleHeal");
|
||||
|
||||
/// <summary>
|
||||
/// Sound to play when Bible hits somebody.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public SoundSpecifier BibleHitSound = new SoundCollectionSpecifier(DefaultBibleHitSound, AudioParams.Default.WithVolume(-4f));
|
||||
|
||||
/// <summary>
|
||||
/// Sound to play when Bible fails to heal somebody.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public SoundSpecifier SizzleSound = new SoundCollectionSpecifier(DefaultBibleSizzleSound);
|
||||
|
||||
/// <summary>
|
||||
/// Sound to play when Bible heals somebody.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public SoundSpecifier HealSound = new SoundCollectionSpecifier(DefaultBibleHealSound);
|
||||
|
||||
/// <summary>
|
||||
/// Damage that will be healed on a success.
|
||||
/// </summary>
|
||||
[DataField(required: true)]
|
||||
public DamageSpecifier Damage = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Damage that will be dealt on a failure.
|
||||
/// </summary>
|
||||
[DataField(required: true)]
|
||||
public DamageSpecifier DamageOnFail = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Damage that will be dealt when a non-chaplain attempts to heal.
|
||||
/// </summary>
|
||||
[DataField(required: true)]
|
||||
public DamageSpecifier DamageOnUntrainedUse = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Chance theBbible will fail to heal someone with no helmet.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float FailChance = 0.34f;
|
||||
|
||||
/// <summary>
|
||||
/// Loc string played when a non-chaplain attempts to use the bible.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public string SizzleText = "bible-sizzle";
|
||||
|
||||
/// <summary>
|
||||
/// Loc string shown to others when the bible fails to heal the target.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public string HealFailOthersText = "bible-heal-fail-others";
|
||||
|
||||
/// <summary>
|
||||
/// Loc string shown to the user when the bible fails to heal the target.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public string HealFailSelfText = "bible-heal-fail-self";
|
||||
|
||||
/// <summary>
|
||||
/// Loc string shown to others when the bible successfully heals the target.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public string HealSuccessOthersText = "bible-heal-success-others";
|
||||
|
||||
/// <summary>
|
||||
/// Loc string shown to the user when the bible successfully heals the target.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public string HealSuccessSelfText = "bible-heal-success-self";
|
||||
|
||||
/// <summary>
|
||||
/// Loc string shown to others when the bible hits a target without wounds.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public string HealSuccessNoneOthersText = "bible-heal-success-none-others";
|
||||
|
||||
/// <summary>
|
||||
/// Loc string shown to the user when the bible hits a target without wounds.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public string HealSuccessNoneSelfText = "bible-heal-success-none-self";
|
||||
|
||||
/// <summary>
|
||||
/// A short light effect to display when successfully healing someone.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public EntProtoId? HealingLightEffect = "HolyLightEffect";
|
||||
}
|
||||
13
Content.Shared/Bible/Components/BibleUserComponent.cs
Normal file
13
Content.Shared/Bible/Components/BibleUserComponent.cs
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Bible.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Marks entity as bible user.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
[Access(typeof(BibleSystem))]
|
||||
public sealed partial class BibleUserComponent : Component
|
||||
{
|
||||
public override bool SendOnlyToOwner => true;
|
||||
}
|
||||
19
Content.Shared/Bible/Components/FamiliarComponent.cs
Normal file
19
Content.Shared/Bible/Components/FamiliarComponent.cs
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Bible.Components;
|
||||
|
||||
/// <summary>
|
||||
/// This component is for the Bible user's familiars, and mostly
|
||||
/// used to track their current state and to give a component to check for
|
||||
/// if any special behavior is needed.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
[Access(typeof(BibleSystem))]
|
||||
public sealed partial class FamiliarComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The entity this familiar was summoned from.
|
||||
/// </summary>
|
||||
[ViewVariables, AutoNetworkedField]
|
||||
public EntityUid? Source = null;
|
||||
}
|
||||
91
Content.Shared/Bible/Components/SummonableComponent.cs
Normal file
91
Content.Shared/Bible/Components/SummonableComponent.cs
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
|
||||
namespace Content.Shared.Bible.Components;
|
||||
|
||||
/// <summary>
|
||||
/// This lets you summon a mob or item with an alternative verb on the item
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, AutoGenerateComponentPause]
|
||||
[Access(typeof(BibleSystem))]
|
||||
public sealed partial class SummonableComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Default sound to play when entity is summoned.
|
||||
/// </summary>
|
||||
private static readonly ProtoId<SoundCollectionPrototype> DefaultSummonSound = new("Summon");
|
||||
|
||||
/// <summary>
|
||||
/// Sound to play when entity is summoned.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public SoundSpecifier SummonSound = new SoundCollectionSpecifier(DefaultSummonSound, AudioParams.Default.WithVolume(-4f));
|
||||
|
||||
/// <summary>
|
||||
/// Used for a special item only the Chaplain can summon. Usually a mob, but supports regular items too.
|
||||
/// </summary>
|
||||
[DataField("summonEntity")]
|
||||
public EntProtoId? SummonEntityPrototype;
|
||||
|
||||
/// <summary>
|
||||
/// The summon action for component to use.
|
||||
/// </summary>
|
||||
[DataField("summonAction")]
|
||||
public EntProtoId SummonActionPrototype = "ActionBibleSummon";
|
||||
|
||||
/// <summary>
|
||||
/// The specific creature this summoned, if the SpecialItemPrototype has a mobstate.
|
||||
/// </summary>
|
||||
[ViewVariables, AutoNetworkedField]
|
||||
public EntityUid? SummonedEntity = null;
|
||||
|
||||
/// <summary>
|
||||
/// The created summon action.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public EntityUid? SummonActionEntity;
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not user must have <see cref="BibleUserComponent"/>.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool RequiresBibleUser = true;
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not summoning is allowed at the moment.
|
||||
/// </summary>
|
||||
[ViewVariables, AutoNetworkedField]
|
||||
public bool CanSummon = true;
|
||||
|
||||
/// <summary>
|
||||
/// Loc string for the summon alt-verb text.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public string SummonVerbText = "bible-summon-verb";
|
||||
|
||||
/// <summary>
|
||||
/// Loc string shown when a summon has been requested and is awaiting a soul.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public string SummonRequestedText = "bible-summon-requested";
|
||||
|
||||
/// <summary>
|
||||
/// Loc string shown when the familiar has respawned and is ready to summon again.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public string SummonRespawnReadyText = "bible-summon-respawn-ready";
|
||||
|
||||
/// <summary>
|
||||
/// Cooldown before being able to summon a new entity.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public TimeSpan RespawnCooldown = TimeSpan.FromSeconds(180);
|
||||
|
||||
/// <summary>
|
||||
/// Time after which summoning will be allowed.
|
||||
/// </summary>
|
||||
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoPausedField, AutoNetworkedField]
|
||||
public TimeSpan RespawnTime = TimeSpan.Zero;
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Bible.Components;
|
||||
|
||||
/// <summary>
|
||||
/// EntityQuery tracking component for summonables that are waiting for a respawn.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
[Access(typeof(BibleSystem))]
|
||||
public sealed partial class SummonableRespawningComponent : Component;
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace Content.Shared.Bible;
|
||||
|
||||
public sealed partial class SummonActionEvent : InstantActionEvent
|
||||
{
|
||||
|
||||
}
|
||||
/// <summary>
|
||||
/// Action event for summoning.
|
||||
/// </summary>
|
||||
public sealed partial class SummonActionEvent : InstantActionEvent;
|
||||
|
|
|
|||
9
Content.Shared/Ghost/GhostRoleSpawnerUsedEvent.cs
Normal file
9
Content.Shared/Ghost/GhostRoleSpawnerUsedEvent.cs
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
namespace Content.Shared.Ghost;
|
||||
|
||||
/// <summary>
|
||||
/// Raised on a spawned entity after they use a ghost role mob spawner.
|
||||
/// </summary>
|
||||
/// <param name="Spawner">The entity that spawned this.</param>
|
||||
/// <param name="Spawned">The entity spawned.</param>
|
||||
[ByRefEvent]
|
||||
public readonly record struct GhostRoleSpawnerUsedEvent(EntityUid Spawner, EntityUid Spawned);
|
||||
|
|
@ -34,7 +34,7 @@
|
|||
- type: Prayable
|
||||
bibleUserOnly: true
|
||||
- type: Summonable
|
||||
specialItem: SpawnPointGhostRemilia
|
||||
summonEntity: SpawnPointGhostRemilia
|
||||
- type: ReactionMixer
|
||||
mixMessage: "bible-mixing-success"
|
||||
reactionTypes:
|
||||
|
|
@ -81,11 +81,14 @@
|
|||
types:
|
||||
Caustic: 50
|
||||
failChance: 0
|
||||
locPrefix: "necro"
|
||||
healFailOthersText: necro-heal-fail-others
|
||||
healFailSelfText: necro-heal-fail-self
|
||||
healSuccessOthersText: necro-heal-success-others
|
||||
healSuccessSelfText: necro-heal-success-self
|
||||
healSound: "/Audio/Effects/lightburn.ogg"
|
||||
healingLightEffect: EvilLightEffect
|
||||
- type: Summonable
|
||||
specialItem: SpawnPointGhostCerberus
|
||||
summonEntity: SpawnPointGhostCerberus
|
||||
respawnTime: 300
|
||||
- type: Sprite
|
||||
sprite: Objects/Specific/Chapel/necronomicon.rsi
|
||||
|
|
|
|||
|
|
@ -7,3 +7,13 @@
|
|||
id: BibleHit
|
||||
files:
|
||||
- /Audio/Effects/hit_kick.ogg
|
||||
|
||||
- type: soundCollection
|
||||
id: BibleSizzle
|
||||
files:
|
||||
- /Audio/Effects/lightburn.ogg
|
||||
|
||||
- type: soundCollection
|
||||
id: BibleHeal
|
||||
files:
|
||||
- /Audio/Effects/holy.ogg
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue