Familiars respawn (#7640)
This commit is contained in:
@@ -16,6 +16,7 @@ namespace Content.Server.Atmos.Components
|
||||
/// Accumulates time when yeeted by high pressure deltas.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
[DataField("accumulator")]
|
||||
public float Accumulator = 0f;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.MobState.Components;
|
||||
using Content.Shared.MobState;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Verbs;
|
||||
using Content.Shared.Tag;
|
||||
using Content.Shared.ActionBlocker;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Server.Cooldown;
|
||||
@@ -23,7 +23,6 @@ namespace Content.Server.Bible
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly DamageableSystem _damageableSystem = default!;
|
||||
[Dependency] private readonly PopupSystem _popupSystem = default!;
|
||||
[Dependency] private readonly TagSystem _tagSystem = default!;
|
||||
[Dependency] private readonly ActionBlockerSystem _blocker = default!;
|
||||
[Dependency] private readonly SharedActionsSystem _actionsSystem = default!;
|
||||
|
||||
@@ -35,6 +34,51 @@ namespace Content.Server.Bible
|
||||
SubscribeLocalEvent<SummonableComponent, GetVerbsEvent<AlternativeVerb>>(AddSummonVerb);
|
||||
SubscribeLocalEvent<SummonableComponent, GetItemActionsEvent>(GetSummonAction);
|
||||
SubscribeLocalEvent<SummonableComponent, SummonActionEvent>(OnSummon);
|
||||
SubscribeLocalEvent<FamiliarComponent, MobStateChangedEvent>(OnFamiliarDeath);
|
||||
}
|
||||
|
||||
private Queue<EntityUid> AddQueue = new();
|
||||
private 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();
|
||||
|
||||
foreach (var (respawning, summonableComp) in EntityQuery<SummonableRespawningComponent, SummonableComponent>())
|
||||
{
|
||||
summonableComp.Accumulator += frameTime;
|
||||
if (summonableComp.Accumulator < summonableComp.RespawnTime)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
/// Clean up the old body
|
||||
if (summonableComp.Summon != null)
|
||||
{
|
||||
EntityManager.DeleteEntity(summonableComp.Summon.Value);
|
||||
summonableComp.Summon = null;
|
||||
}
|
||||
summonableComp.AlreadySummoned = false;
|
||||
_popupSystem.PopupEntity(Loc.GetString("bible-summon-respawn-ready", ("book", summonableComp.Owner)), summonableComp.Owner, Filter.Pvs(summonableComp.Owner));
|
||||
SoundSystem.Play(Filter.Pvs(summonableComp.Owner), "/Audio/Effects/radpulse9.ogg", summonableComp.Owner, AudioParams.Default.WithVolume(-4f));
|
||||
/// Clean up the accumulator and respawn tracking component
|
||||
summonableComp.Accumulator = 0;
|
||||
RemQueue.Enqueue(respawning.Owner);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAfterInteract(EntityUid uid, BibleComponent component, AfterInteractEvent args)
|
||||
@@ -67,7 +111,8 @@ namespace Content.Server.Bible
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_invSystem.TryGetSlotEntity(args.Target.Value, "head", out var entityUid) && !_tagSystem.HasTag(args.Target.Value, "Familiar"))
|
||||
// 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 var entityUid) && !HasComp<FamiliarComponent>(args.Target.Value))
|
||||
{
|
||||
if (_random.Prob(component.FailChance))
|
||||
{
|
||||
@@ -125,6 +170,24 @@ namespace Content.Server.Bible
|
||||
{
|
||||
AttemptSummon(component, 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.Component.IsDead() || component.Source == null)
|
||||
return;
|
||||
|
||||
var source = component.Source;
|
||||
if (source != null && TryComp<SummonableComponent>(source, out var summonable))
|
||||
{
|
||||
AddQueue.Enqueue(summonable.Owner);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void AttemptSummon(SummonableComponent component, EntityUid user, TransformComponent? position)
|
||||
{
|
||||
if (component.AlreadySummoned || component.SpecialItemPrototype == null)
|
||||
@@ -138,7 +201,17 @@ namespace Content.Server.Bible
|
||||
if (!_blocker.CanInteract(user, component.Owner))
|
||||
return;
|
||||
|
||||
EntityManager.SpawnEntity(component.SpecialItemPrototype, position.Coordinates);
|
||||
// Make this familiar the component's summon
|
||||
var familiar = EntityManager.SpawnEntity(component.SpecialItemPrototype, position.Coordinates);
|
||||
component.Summon = familiar;
|
||||
|
||||
/// We only want to add the familiar component to mobs
|
||||
if (HasComp<MobStateComponent>(familiar))
|
||||
{
|
||||
/// Make this Summon the familiar's source
|
||||
var familiarComp = EnsureComp<FamiliarComponent>(familiar);
|
||||
familiarComp.Source = component.Owner;
|
||||
}
|
||||
component.AlreadySummoned = true;
|
||||
_actionsSystem.RemoveAction(user, component.SummonAction);
|
||||
}
|
||||
|
||||
17
Content.Server/Bible/Components/FamiliarComponent.cs
Normal file
17
Content.Server/Bible/Components/FamiliarComponent.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
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 class FamiliarComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The entity this familiar was summoned from.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public EntityUid? Source = null;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Content.Shared.Actions.ActionTypes;
|
||||
using Content.Server.Bible;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.Bible.Components
|
||||
@@ -22,6 +21,12 @@ namespace Content.Server.Bible.Components
|
||||
[DataField("requriesBibleUser")]
|
||||
public bool RequiresBibleUser = true;
|
||||
|
||||
/// <summary>
|
||||
/// The specific creature this summoned, if the SpecialItemPrototype has a mobstate.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public EntityUid? Summon = null;
|
||||
|
||||
[DataField("summonAction")]
|
||||
public InstantAction SummonAction = new()
|
||||
{
|
||||
@@ -30,5 +35,13 @@ namespace Content.Server.Bible.Components
|
||||
Description = "bible-summon-verb-desc",
|
||||
Event = new SummonActionEvent(),
|
||||
};
|
||||
|
||||
/// Used for respawning
|
||||
[ViewVariables]
|
||||
[DataField("accumulator")]
|
||||
public float Accumulator = 0f;
|
||||
[ViewVariables]
|
||||
[DataField("respawnTime")]
|
||||
public float RespawnTime = 180f;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace Content.Server.Bible.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// EntityQuery Tracking Component for Summonables that are counting up a respawn.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class SummonableRespawningComponent : Component
|
||||
{}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ namespace Content.Server.Cargo.Components
|
||||
/// How much time we've accumulated until next teleport.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
[DataField("accumulator")]
|
||||
public float Accumulator = 0f;
|
||||
|
||||
[ViewVariables]
|
||||
|
||||
@@ -16,6 +16,7 @@ namespace Content.Server.Disease.Components
|
||||
/// How much time we've accumulated processing
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
[DataField("accumulator")]
|
||||
public float Accumulator = 0f;
|
||||
/// <summary>
|
||||
/// The disease prototype currently being diagnosed
|
||||
|
||||
@@ -47,6 +47,7 @@ namespace Content.Server.Explosion.Components
|
||||
/// <summary>
|
||||
/// How much cooldown has elapsed (if relevant).
|
||||
/// </summary>
|
||||
[DataField("accumulator")]
|
||||
public float Accumulator = 0f;
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -42,6 +42,7 @@ namespace Content.Server.Fluids.Components
|
||||
/// <summary>
|
||||
/// The time accumulated since the start.
|
||||
/// </summary>
|
||||
[DataField("accumulator")]
|
||||
public float Accumulator = 0f;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,11 +32,13 @@ namespace Content.Server.Lathe.Components
|
||||
/// <summary>
|
||||
/// Update accumulator for the insertion time
|
||||
/// </suummary>
|
||||
[DataField("insertionAccumulator")]
|
||||
public float InsertionAccumulator = 0f;
|
||||
/// <summary>
|
||||
/// Production accumulator for the production time.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
[DataField("producingAccumulator")]
|
||||
public float ProducingAccumulator = 0f;
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -60,6 +60,7 @@ namespace Content.Server.Singularity.Components
|
||||
_ => 0
|
||||
};
|
||||
|
||||
[DataField("moveAccumulator")]
|
||||
public float MoveAccumulator;
|
||||
|
||||
// This is an interesting little workaround.
|
||||
|
||||
@@ -11,6 +11,8 @@ namespace Content.Shared.Emag.Components
|
||||
|
||||
[DataField("rechargeTime"), ViewVariables(VVAccess.ReadWrite)]
|
||||
public float RechargeTime = 90f;
|
||||
|
||||
[DataField("accumulator")]
|
||||
public float Accumulator = 0f;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ bible-heal-fail-others = {CAPITALIZE(THE($user))} hits {THE($target)} with {THE(
|
||||
bible-sizzle = The book sizzles in your hands!
|
||||
bible-summon-verb = Summon familiar
|
||||
bible-summon-verb-desc = Summon a familiar that will aid you and gain humanlike intelligence once inhabited by a soul.
|
||||
bible-summon-respawn-ready = {CAPITALIZE(THE($book))} surges with ethereal power. {CAPITALIZE(POSS-ADJ($book))} resident is home again.
|
||||
necro-heal-success-self = You hit {THE($target)} with {THE($bible)}, and {POSS-ADJ($target)} flesh warps as it melts!
|
||||
necro-heal-success-others = {CAPITALIZE(THE($user))} hits {THE($target)} with {THE($bible)}, and {POSS-ADJ($target)} flesh warps as it melts!
|
||||
necro-heal-fail-self = You hit {THE($target)} with {THE($bible)}, and it lands with a sad thwack, failing to smite {OBJECT($target)}.
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
proper: true
|
||||
- type: Tag
|
||||
tags:
|
||||
- Familiar
|
||||
- DoorBumpOpener
|
||||
- type: Access
|
||||
tags:
|
||||
@@ -64,7 +63,6 @@
|
||||
proper: true
|
||||
- type: Tag
|
||||
tags:
|
||||
- Familiar
|
||||
- DoorBumpOpener
|
||||
- type: Access
|
||||
tags:
|
||||
|
||||
@@ -57,6 +57,7 @@
|
||||
cooldownTime: 1.3
|
||||
- type: Summonable
|
||||
specialItem: MobCorgiCerberus
|
||||
respawnTime: 300
|
||||
- type: Sprite
|
||||
netsync: false
|
||||
sprite: Objects/Specific/Chapel/necronomicon.rsi
|
||||
|
||||
@@ -144,9 +144,6 @@
|
||||
- type: Tag
|
||||
id: ExplosivePassable
|
||||
|
||||
- type: Tag
|
||||
id: Familiar
|
||||
|
||||
- type: Tag
|
||||
id: FireAlarmElectronics
|
||||
|
||||
|
||||
Reference in New Issue
Block a user