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

@@ -57,6 +57,7 @@ namespace Content.Client.Entry
"ResearchPointSource", "ResearchPointSource",
"ResearchClient", "ResearchClient",
"IdCardConsole", "IdCardConsole",
"MimePowers",
"ThermalRegulator", "ThermalRegulator",
"DiseaseMachineRunning", "DiseaseMachineRunning",
"DiseaseMachine", "DiseaseMachine",

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 {}
}

View File

@@ -0,0 +1,20 @@
using Content.Shared.Alert;
using Content.Server.Abilities.Mime;
namespace Content.Server.Alert.Click
{
///<summary>
/// Break your mime vows
///</summary>
[DataDefinition]
public sealed class BreakVow : IAlertClick
{
public void AlertClicked(EntityUid player)
{
if (IoCManager.Resolve<IEntityManager>().TryGetComponent<MimePowersComponent?>(player, out var mimePowers))
{
EntitySystem.Get<MimePowersSystem>().BreakVow(player, mimePowers);
}
}
}
}

View File

@@ -0,0 +1,20 @@
using Content.Shared.Alert;
using Content.Server.Abilities.Mime;
namespace Content.Server.Alert.Click
{
///<summary>
/// Retake your mime vows
///</summary>
[DataDefinition]
public sealed class RetakeVow : IAlertClick
{
public void AlertClicked(EntityUid player)
{
if (IoCManager.Resolve<IEntityManager>().TryGetComponent<MimePowersComponent?>(player, out var mimePowers))
{
EntitySystem.Get<MimePowersSystem>().RetakeVow(player, mimePowers);
}
}
}
}

View File

@@ -32,6 +32,8 @@
Pulling, Pulling,
Magboots, Magboots,
Toxins, Toxins,
VowOfSilence,
VowBroken,
Debug1, Debug1,
Debug2, Debug2,
Debug3, Debug3,

View File

@@ -0,0 +1,7 @@
mime-cant-speak = Your vow of silence prevents you from speaking.
mime-invisible-wall = Create Invisible Wall
mime-invisible-wall-desc = Create an invisible wall in front of you, if placeable there.
mime-invisible-wall-popup = {CAPITALIZE(THE($mime))} brushes up against an invisible wall!
mime-invisible-wall-failed = You can't create an invisible wall there.
mime-not-ready-repent = You aren't ready to repent for your broken vow yet.
mime-ready-to-repent = You feel ready to take your vows again.

View File

@@ -191,6 +191,20 @@
name: "[color=red]Parched[/color]" name: "[color=red]Parched[/color]"
description: You're severely thirsty. The thirst makes moving around a chore. description: You're severely thirsty. The thirst makes moving around a chore.
- type: alert
id: VowOfSilence
icon: /Textures/Interface/Alerts/Abilities/silenced.png
name: Vow of Silence
onClick: !type:BreakVow { }
description: You have taken a vow of silence as part of initiation into the Mystiko Tagma Mimon. Click to break your vow.
- type: alert
id: VowBroken
icon: /Textures/Interface/Actions/scream.png
name: Vow Broken
onClick: !type:RetakeVow { }
description: You've broken your vows to Mimes everywhere. You can speak, but you've lost your powers for at least 5 entire minutes!!! Click to try and retake your vow.
- type: alert - type: alert
id: Pulled id: Pulled
icon: /Textures/Interface/Alerts/Pull/pulled.png icon: /Textures/Interface/Alerts/Pull/pulled.png

View File

@@ -649,3 +649,25 @@
- type: IconSmooth - type: IconSmooth
key: walls key: walls
base: wood base: wood
- type: entity
id: WallInvisible
name: Invisible Wall
components:
- type: Tag
tags:
- Wall
- type: Physics
bodyType: Static
- type: Fixtures
fixtures:
- shape:
!type:PhysShapeAabb
bounds: "-0.5,-0.5,0.5,0.5"
layer:
- Impassable
- VaultImpassable
- SmallImpassable
mask:
- Impassable
- type: Airtight

View File

@@ -9,6 +9,10 @@
access: access:
- Theatre - Theatre
- Maintenance - Maintenance
special:
- !type:AddComponentSpecial
components:
- type: MimePowers
- type: startingGear - type: startingGear
id: MimeGear id: MimeGear

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB