From ce186e6cc1006cd33ed7f01448504d050210d259 Mon Sep 17 00:00:00 2001 From: Rane <60792108+Elijahrane@users.noreply.github.com> Date: Thu, 28 Apr 2022 23:41:03 -0400 Subject: [PATCH] Mime Powers (Vow + Invisible Wall) (#7653) --- Content.Client/Entry/IgnoredComponents.cs | 1 + .../Abilities/Mime/InvisibleWallComponent.cs | 12 ++ .../Abilities/Mime/MimePowersComponent.cs | 63 ++++++++ .../Abilities/Mime/MimePowersSystem.cs | 147 ++++++++++++++++++ Content.Server/Alert/Click/BreakVow.cs | 20 +++ Content.Server/Alert/Click/RetakeVow.cs | 20 +++ Content.Shared/Alert/AlertType.cs | 2 + Resources/Locale/en-US/abilities/mime.ftl | 7 + Resources/Prototypes/Alerts/alerts.yml | 14 ++ .../Entities/Structures/Walls/walls.yml | 22 +++ .../Prototypes/Roles/Jobs/Civilian/mime.yml | 4 + .../Interface/Alerts/Abilities/silenced.png | Bin 0 -> 6027 bytes 12 files changed, 312 insertions(+) create mode 100644 Content.Server/Abilities/Mime/InvisibleWallComponent.cs create mode 100644 Content.Server/Abilities/Mime/MimePowersComponent.cs create mode 100644 Content.Server/Abilities/Mime/MimePowersSystem.cs create mode 100644 Content.Server/Alert/Click/BreakVow.cs create mode 100644 Content.Server/Alert/Click/RetakeVow.cs create mode 100644 Resources/Locale/en-US/abilities/mime.ftl create mode 100644 Resources/Textures/Interface/Alerts/Abilities/silenced.png diff --git a/Content.Client/Entry/IgnoredComponents.cs b/Content.Client/Entry/IgnoredComponents.cs index a9a5a765b8..9e03181b48 100644 --- a/Content.Client/Entry/IgnoredComponents.cs +++ b/Content.Client/Entry/IgnoredComponents.cs @@ -57,6 +57,7 @@ namespace Content.Client.Entry "ResearchPointSource", "ResearchClient", "IdCardConsole", + "MimePowers", "ThermalRegulator", "DiseaseMachineRunning", "DiseaseMachine", diff --git a/Content.Server/Abilities/Mime/InvisibleWallComponent.cs b/Content.Server/Abilities/Mime/InvisibleWallComponent.cs new file mode 100644 index 0000000000..fafa2c8e83 --- /dev/null +++ b/Content.Server/Abilities/Mime/InvisibleWallComponent.cs @@ -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); + } +} diff --git a/Content.Server/Abilities/Mime/MimePowersComponent.cs b/Content.Server/Abilities/Mime/MimePowersComponent.cs new file mode 100644 index 0000000000..3b56a07a6d --- /dev/null +++ b/Content.Server/Abilities/Mime/MimePowersComponent.cs @@ -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 +{ + /// + /// Lets its owner entity use mime powers, like placing invisible walls. + /// + [RegisterComponent] + public sealed class MimePowersComponent : Component + { + /// + /// Whether this component is active or not. + /// + [ViewVariables] + [DataField("enabled")] + public bool Enabled = true; + + /// + /// The wall prototype to use. + /// + [DataField("wallPrototype", customTypeSerializer: typeof(PrototypeIdSerializer))] + 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; + + + /// + /// Whether this mime is ready to take the vow again. + /// Note that if they already have the vow, this is also false. + /// + public bool ReadyToRepent = false; + + /// + /// Accumulator for when the mime breaks their vows + /// + + [DataField("accumulator")] + public float Accumulator = 0f; + + /// + /// How long it takes the mime to get their powers back + + [DataField("vowCooldown")] + public TimeSpan VowCooldown = TimeSpan.FromMinutes(5); + } +} diff --git a/Content.Server/Abilities/Mime/MimePowersSystem.cs b/Content.Server/Abilities/Mime/MimePowersSystem.cs new file mode 100644 index 0000000000..b21090f6f9 --- /dev/null +++ b/Content.Server/Abilities/Mime/MimePowersSystem.cs @@ -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(OnComponentInit); + SubscribeLocalEvent(OnSpeakAttempt); + SubscribeLocalEvent(OnInvisibleWall); + } + public override void Update(float frameTime) + { + base.Update(frameTime); + /// Queue to despawn invis walls + foreach (var invisWall in EntityQuery()) + { + 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()) + { + 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(); + } + + /// + /// Creates an invisible wall in a free space after some checks. + /// + 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(entity) && entity != uid) || /// Is it a mob? + ((Resolve(entity, ref physics, false) && (physics.CollisionLayer & (int) CollisionGroup.Impassable) != 0) // Is it impassable? + && !(TryComp(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(wall); + /// Handle args so cooldown works + args.Handled = true; + } + + /// + /// Break this mime's vow to not speak. + /// + 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); + } + + /// + /// Retake this mime's vow to not speak. + /// + 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 {} +} diff --git a/Content.Server/Alert/Click/BreakVow.cs b/Content.Server/Alert/Click/BreakVow.cs new file mode 100644 index 0000000000..67ac15bea8 --- /dev/null +++ b/Content.Server/Alert/Click/BreakVow.cs @@ -0,0 +1,20 @@ +using Content.Shared.Alert; +using Content.Server.Abilities.Mime; + +namespace Content.Server.Alert.Click +{ + /// + /// Break your mime vows + /// + [DataDefinition] + public sealed class BreakVow : IAlertClick + { + public void AlertClicked(EntityUid player) + { + if (IoCManager.Resolve().TryGetComponent(player, out var mimePowers)) + { + EntitySystem.Get().BreakVow(player, mimePowers); + } + } + } +} diff --git a/Content.Server/Alert/Click/RetakeVow.cs b/Content.Server/Alert/Click/RetakeVow.cs new file mode 100644 index 0000000000..06c97697fb --- /dev/null +++ b/Content.Server/Alert/Click/RetakeVow.cs @@ -0,0 +1,20 @@ +using Content.Shared.Alert; +using Content.Server.Abilities.Mime; + +namespace Content.Server.Alert.Click +{ + /// + /// Retake your mime vows + /// + [DataDefinition] + public sealed class RetakeVow : IAlertClick + { + public void AlertClicked(EntityUid player) + { + if (IoCManager.Resolve().TryGetComponent(player, out var mimePowers)) + { + EntitySystem.Get().RetakeVow(player, mimePowers); + } + } + } +} diff --git a/Content.Shared/Alert/AlertType.cs b/Content.Shared/Alert/AlertType.cs index b6a7ef8763..35409a2548 100644 --- a/Content.Shared/Alert/AlertType.cs +++ b/Content.Shared/Alert/AlertType.cs @@ -32,6 +32,8 @@ Pulling, Magboots, Toxins, + VowOfSilence, + VowBroken, Debug1, Debug2, Debug3, diff --git a/Resources/Locale/en-US/abilities/mime.ftl b/Resources/Locale/en-US/abilities/mime.ftl new file mode 100644 index 0000000000..da03cd76b7 --- /dev/null +++ b/Resources/Locale/en-US/abilities/mime.ftl @@ -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. diff --git a/Resources/Prototypes/Alerts/alerts.yml b/Resources/Prototypes/Alerts/alerts.yml index 73c16846c2..f4668347ed 100644 --- a/Resources/Prototypes/Alerts/alerts.yml +++ b/Resources/Prototypes/Alerts/alerts.yml @@ -191,6 +191,20 @@ name: "[color=red]Parched[/color]" 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 id: Pulled icon: /Textures/Interface/Alerts/Pull/pulled.png diff --git a/Resources/Prototypes/Entities/Structures/Walls/walls.yml b/Resources/Prototypes/Entities/Structures/Walls/walls.yml index eb978ac692..4d247fc014 100644 --- a/Resources/Prototypes/Entities/Structures/Walls/walls.yml +++ b/Resources/Prototypes/Entities/Structures/Walls/walls.yml @@ -649,3 +649,25 @@ - type: IconSmooth key: walls 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 diff --git a/Resources/Prototypes/Roles/Jobs/Civilian/mime.yml b/Resources/Prototypes/Roles/Jobs/Civilian/mime.yml index 43ab456936..9571cdedaa 100644 --- a/Resources/Prototypes/Roles/Jobs/Civilian/mime.yml +++ b/Resources/Prototypes/Roles/Jobs/Civilian/mime.yml @@ -9,6 +9,10 @@ access: - Theatre - Maintenance + special: + - !type:AddComponentSpecial + components: + - type: MimePowers - type: startingGear id: MimeGear diff --git a/Resources/Textures/Interface/Alerts/Abilities/silenced.png b/Resources/Textures/Interface/Alerts/Abilities/silenced.png new file mode 100644 index 0000000000000000000000000000000000000000..373a90abe642c5317c4f347fddf74e73aa374d0c GIT binary patch literal 6027 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 YGTb|1Zd8XF3JwT_YVBxMX0a#sKMeYBR{#J2 literal 0 HcmV?d00001