Mime Powers (Vow + Invisible Wall) (#7653)

This commit is contained in:
Rane
2022-04-28 23:41:03 -04:00
committed by GitHub
parent f74a47b7aa
commit ce186e6cc1
12 changed files with 312 additions and 0 deletions

View File

@@ -0,0 +1,12 @@
namespace Content.Server.Abilities.Mime
{
// Tracks invisible wall despawning
[RegisterComponent]
public sealed class InvisibleWallComponent : Component
{
[DataField("accumulator")]
public float Accumulator = 0f;
[DataField("despawnTime")]
public TimeSpan DespawnTime = TimeSpan.FromSeconds(30);
}
}

View File

@@ -0,0 +1,63 @@
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
using Robust.Shared.Prototypes;
using Content.Shared.Actions.ActionTypes;
using Robust.Shared.Utility;
namespace Content.Server.Abilities.Mime
{
/// <summary>
/// Lets its owner entity use mime powers, like placing invisible walls.
/// </summary>
[RegisterComponent]
public sealed class MimePowersComponent : Component
{
/// <summary>
/// Whether this component is active or not.
/// </summarY>
[ViewVariables]
[DataField("enabled")]
public bool Enabled = true;
/// <summary>
/// The wall prototype to use.
/// </summary>
[DataField("wallPrototype", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
public string WallPrototype = "WallInvisible";
[DataField("invisibleWallAction")]
public InstantAction InvisibleWallAction = new()
{
UseDelay = TimeSpan.FromSeconds(30),
Icon = new SpriteSpecifier.Texture(new ResourcePath("Structures/Walls/solid.rsi/full.png")),
Name = "mime-invisible-wall",
Description = "mime-invisible-wall-desc",
Priority = -1,
Event = new InvisibleWallActionEvent(),
};
/// The vow zone lies below
public bool VowBroken = false;
/// <summary>
/// Whether this mime is ready to take the vow again.
/// Note that if they already have the vow, this is also false.
/// </summary>
public bool ReadyToRepent = false;
/// <summary>
/// Accumulator for when the mime breaks their vows
/// </summary>
[DataField("accumulator")]
public float Accumulator = 0f;
/// <summary>
/// How long it takes the mime to get their powers back
[DataField("vowCooldown")]
public TimeSpan VowCooldown = TimeSpan.FromMinutes(5);
}
}

View File

@@ -0,0 +1,147 @@
using Content.Server.Popups;
using Content.Server.Coordinates.Helpers;
using Content.Shared.Speech;
using Content.Shared.Actions;
using Content.Shared.Alert;
using Content.Shared.Physics;
using Content.Shared.Doors.Components;
using Content.Shared.Maps;
using Content.Shared.MobState.Components;
using Content.Shared.Tag;
using Robust.Shared.Player;
using Robust.Shared.Physics;
namespace Content.Server.Abilities.Mime
{
public sealed class MimePowersSystem : EntitySystem
{
[Dependency] private readonly PopupSystem _popupSystem = default!;
[Dependency] private readonly SharedActionsSystem _actionsSystem = default!;
[Dependency] private readonly AlertsSystem _alertsSystem = default!;
[Dependency] private readonly TagSystem _tagSystem = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<MimePowersComponent, ComponentInit>(OnComponentInit);
SubscribeLocalEvent<MimePowersComponent, SpeakAttemptEvent>(OnSpeakAttempt);
SubscribeLocalEvent<MimePowersComponent, InvisibleWallActionEvent>(OnInvisibleWall);
}
public override void Update(float frameTime)
{
base.Update(frameTime);
/// Queue to despawn invis walls
foreach (var invisWall in EntityQuery<InvisibleWallComponent>())
{
invisWall.Accumulator += frameTime;
if (invisWall.Accumulator < invisWall.DespawnTime.TotalSeconds)
{
continue;
}
EntityManager.QueueDeleteEntity(invisWall.Owner);
}
/// Queue to track whether mimes can retake vows yet
foreach (var mime in EntityQuery<MimePowersComponent>())
{
if (!mime.VowBroken || mime.ReadyToRepent)
return;
mime.Accumulator += frameTime;
if (mime.Accumulator < mime.VowCooldown.TotalSeconds)
{
continue;
}
mime.ReadyToRepent = true;
_popupSystem.PopupEntity(Loc.GetString("mime-ready-to-repent"), mime.Owner, Filter.Entities(mime.Owner));
}
}
private void OnComponentInit(EntityUid uid, MimePowersComponent component, ComponentInit args)
{
_actionsSystem.AddAction(uid, component.InvisibleWallAction, uid);
_alertsSystem.ShowAlert(uid, AlertType.VowOfSilence);
}
private void OnSpeakAttempt(EntityUid uid, MimePowersComponent component, SpeakAttemptEvent args)
{
if (!component.Enabled)
return;
_popupSystem.PopupEntity(Loc.GetString("mime-cant-speak"), uid, Filter.Entities(uid));
args.Cancel();
}
/// <summary>
/// Creates an invisible wall in a free space after some checks.
/// </summary>
private void OnInvisibleWall(EntityUid uid, MimePowersComponent component, InvisibleWallActionEvent args)
{
if (!component.Enabled)
return;
var xform = Transform(uid);
/// Get the tile in front of the mime
var offsetValue = xform.LocalRotation.ToWorldVec().Normalized;
var coords = xform.Coordinates.Offset(offsetValue).SnapToGrid();
/// Check there are no walls or mobs there
foreach (var entity in coords.GetEntitiesInTile())
{
IPhysBody? physics = null; /// We use this to check if it's impassable
if ((HasComp<MobStateComponent>(entity) && entity != uid) || /// Is it a mob?
((Resolve(entity, ref physics, false) && (physics.CollisionLayer & (int) CollisionGroup.Impassable) != 0) // Is it impassable?
&& !(TryComp<DoorComponent>(entity, out var door) && door.State != DoorState.Closed))) // Is it a door that's open and so not actually impassable?
{
_popupSystem.PopupEntity(Loc.GetString("mime-invisible-wall-failed"), uid, Filter.Entities(uid));
return;
}
}
_popupSystem.PopupEntity(Loc.GetString("mime-invisible-wall-popup", ("mime", uid)), uid, Filter.Pvs(uid));
/// Make sure we set the invisible wall to despawn properly
var wall = EntityManager.SpawnEntity(component.WallPrototype, coords);
EnsureComp<InvisibleWallComponent>(wall);
/// Handle args so cooldown works
args.Handled = true;
}
/// <summary>
/// Break this mime's vow to not speak.
/// </summary>
public void BreakVow(EntityUid uid, MimePowersComponent? mimePowers = null)
{
if (!Resolve(uid, ref mimePowers))
return;
if (mimePowers.VowBroken)
return;
mimePowers.Enabled = false;
mimePowers.VowBroken = true;
_alertsSystem.ClearAlert(uid, AlertType.VowOfSilence);
_alertsSystem.ShowAlert(uid, AlertType.VowBroken);
_actionsSystem.RemoveAction(uid, mimePowers.InvisibleWallAction);
}
/// <summary>
/// Retake this mime's vow to not speak.
/// </summary>
public void RetakeVow(EntityUid uid, MimePowersComponent? mimePowers = null)
{
if (!Resolve(uid, ref mimePowers))
return;
if (!mimePowers.ReadyToRepent)
{
_popupSystem.PopupEntity(Loc.GetString("mime-not-ready-repent"), uid, Filter.Entities(uid));
return;
}
mimePowers.Enabled = true;
mimePowers.ReadyToRepent = false;
mimePowers.VowBroken = false;
mimePowers.Accumulator = 0f;
_alertsSystem.ClearAlert(uid, AlertType.VowBroken);
_alertsSystem.ShowAlert(uid, AlertType.VowOfSilence);
_actionsSystem.AddAction(uid, mimePowers.InvisibleWallAction, uid);
}
}
public sealed class InvisibleWallActionEvent : InstantActionEvent {}
}