From af926c5279599b417a56c0a537b1e8ef84449aa9 Mon Sep 17 00:00:00 2001 From: Alex Evgrashin Date: Tue, 17 May 2022 12:55:19 +0300 Subject: [PATCH] Typing indicator (typing chat bubble) (#8215) --- .../TypingIndicator/TypingIndicatorSystem.cs | 86 +++++ .../TypingIndicatorVisualizerSystem.cs | 50 +++ Content.Client/Chat/UI/ChatBox.xaml.cs | 7 + .../TypingIndicator/TypingIndicatorSystem.cs | 63 ++++ Content.Shared/CCVar/CCVars.cs | 3 + .../SharedTypingIndicatorSystem.cs | 9 + .../TypingIndicator/TypingChangedEvent.cs | 20 + .../TypingIndicatorComponent.cs | 20 + .../TypingIndicatorPrototype.cs | 27 ++ .../TypingIndicator/TypingIndicatorVisuals.cs | 15 + .../Prototypes/Entities/Mobs/NPCs/carp.yml | 6 + .../Prototypes/Entities/Mobs/NPCs/xeno.yml | 2 + .../Entities/Mobs/Player/guardian.yml | 4 + .../Prototypes/Entities/Objects/Fun/pai.yml | 2 + Resources/Prototypes/typing_indicator.yml | 19 + .../Textures/Effects/speech.rsi/alien0.png | Bin 0 -> 577 bytes .../Textures/Effects/speech.rsi/alien1.png | Bin 0 -> 267 bytes .../Textures/Effects/speech.rsi/alien2.png | Bin 0 -> 268 bytes .../Effects/speech.rsi/alienroyal0.png | Bin 0 -> 895 bytes .../Effects/speech.rsi/alienroyal1.png | Bin 0 -> 397 bytes .../Effects/speech.rsi/alienroyal2.png | Bin 0 -> 396 bytes .../Textures/Effects/speech.rsi/blob0.png | Bin 0 -> 524 bytes .../Textures/Effects/speech.rsi/blob1.png | Bin 0 -> 255 bytes .../Textures/Effects/speech.rsi/blob2.png | Bin 0 -> 246 bytes .../Textures/Effects/speech.rsi/clock0.png | Bin 0 -> 870 bytes .../Textures/Effects/speech.rsi/clock1.png | Bin 0 -> 1007 bytes .../Textures/Effects/speech.rsi/clock2.png | Bin 0 -> 1030 bytes .../Textures/Effects/speech.rsi/default0.png | Bin 0 -> 376 bytes .../Textures/Effects/speech.rsi/default1.png | Bin 0 -> 200 bytes .../Textures/Effects/speech.rsi/default2.png | Bin 0 -> 190 bytes .../Textures/Effects/speech.rsi/guardian0.png | Bin 0 -> 398 bytes .../Textures/Effects/speech.rsi/guardian1.png | Bin 0 -> 248 bytes .../Textures/Effects/speech.rsi/guardian2.png | Bin 0 -> 235 bytes .../Textures/Effects/speech.rsi/holo0.png | Bin 0 -> 506 bytes .../Textures/Effects/speech.rsi/holo1.png | Bin 0 -> 251 bytes .../Textures/Effects/speech.rsi/holo2.png | Bin 0 -> 241 bytes .../Textures/Effects/speech.rsi/lawyer0.png | Bin 0 -> 579 bytes .../Textures/Effects/speech.rsi/lawyer1.png | Bin 0 -> 822 bytes .../Textures/Effects/speech.rsi/lawyer2.png | Bin 0 -> 851 bytes .../Textures/Effects/speech.rsi/machine0.png | Bin 0 -> 342 bytes .../Textures/Effects/speech.rsi/machine1.png | Bin 0 -> 209 bytes .../Textures/Effects/speech.rsi/machine2.png | Bin 0 -> 186 bytes .../Textures/Effects/speech.rsi/meta.json | 345 ++++++++++++++++++ .../Textures/Effects/speech.rsi/robot0.png | Bin 0 -> 346 bytes .../Textures/Effects/speech.rsi/robot1.png | Bin 0 -> 210 bytes .../Textures/Effects/speech.rsi/robot2.png | Bin 0 -> 193 bytes .../Textures/Effects/speech.rsi/slime0.png | Bin 0 -> 659 bytes .../Textures/Effects/speech.rsi/slime1.png | Bin 0 -> 515 bytes .../Textures/Effects/speech.rsi/slime2.png | Bin 0 -> 493 bytes .../Textures/Effects/speech.rsi/swarmer0.png | Bin 0 -> 749 bytes .../Textures/Effects/speech.rsi/swarmer1.png | Bin 0 -> 880 bytes .../Textures/Effects/speech.rsi/swarmer2.png | Bin 0 -> 824 bytes .../Textures/Effects/speech.rsi/syndibot0.png | Bin 0 -> 346 bytes .../Textures/Effects/speech.rsi/syndibot1.png | Bin 0 -> 210 bytes .../Textures/Effects/speech.rsi/syndibot2.png | Bin 0 -> 193 bytes 55 files changed, 678 insertions(+) create mode 100644 Content.Client/Chat/TypingIndicator/TypingIndicatorSystem.cs create mode 100644 Content.Client/Chat/TypingIndicator/TypingIndicatorVisualizerSystem.cs create mode 100644 Content.Server/Chat/TypingIndicator/TypingIndicatorSystem.cs create mode 100644 Content.Shared/Chat/TypingIndicator/SharedTypingIndicatorSystem.cs create mode 100644 Content.Shared/Chat/TypingIndicator/TypingChangedEvent.cs create mode 100644 Content.Shared/Chat/TypingIndicator/TypingIndicatorComponent.cs create mode 100644 Content.Shared/Chat/TypingIndicator/TypingIndicatorPrototype.cs create mode 100644 Content.Shared/Chat/TypingIndicator/TypingIndicatorVisuals.cs create mode 100644 Resources/Prototypes/typing_indicator.yml create mode 100644 Resources/Textures/Effects/speech.rsi/alien0.png create mode 100644 Resources/Textures/Effects/speech.rsi/alien1.png create mode 100644 Resources/Textures/Effects/speech.rsi/alien2.png create mode 100644 Resources/Textures/Effects/speech.rsi/alienroyal0.png create mode 100644 Resources/Textures/Effects/speech.rsi/alienroyal1.png create mode 100644 Resources/Textures/Effects/speech.rsi/alienroyal2.png create mode 100644 Resources/Textures/Effects/speech.rsi/blob0.png create mode 100644 Resources/Textures/Effects/speech.rsi/blob1.png create mode 100644 Resources/Textures/Effects/speech.rsi/blob2.png create mode 100644 Resources/Textures/Effects/speech.rsi/clock0.png create mode 100644 Resources/Textures/Effects/speech.rsi/clock1.png create mode 100644 Resources/Textures/Effects/speech.rsi/clock2.png create mode 100644 Resources/Textures/Effects/speech.rsi/default0.png create mode 100644 Resources/Textures/Effects/speech.rsi/default1.png create mode 100644 Resources/Textures/Effects/speech.rsi/default2.png create mode 100644 Resources/Textures/Effects/speech.rsi/guardian0.png create mode 100644 Resources/Textures/Effects/speech.rsi/guardian1.png create mode 100644 Resources/Textures/Effects/speech.rsi/guardian2.png create mode 100644 Resources/Textures/Effects/speech.rsi/holo0.png create mode 100644 Resources/Textures/Effects/speech.rsi/holo1.png create mode 100644 Resources/Textures/Effects/speech.rsi/holo2.png create mode 100644 Resources/Textures/Effects/speech.rsi/lawyer0.png create mode 100644 Resources/Textures/Effects/speech.rsi/lawyer1.png create mode 100644 Resources/Textures/Effects/speech.rsi/lawyer2.png create mode 100644 Resources/Textures/Effects/speech.rsi/machine0.png create mode 100644 Resources/Textures/Effects/speech.rsi/machine1.png create mode 100644 Resources/Textures/Effects/speech.rsi/machine2.png create mode 100644 Resources/Textures/Effects/speech.rsi/meta.json create mode 100644 Resources/Textures/Effects/speech.rsi/robot0.png create mode 100644 Resources/Textures/Effects/speech.rsi/robot1.png create mode 100644 Resources/Textures/Effects/speech.rsi/robot2.png create mode 100644 Resources/Textures/Effects/speech.rsi/slime0.png create mode 100644 Resources/Textures/Effects/speech.rsi/slime1.png create mode 100644 Resources/Textures/Effects/speech.rsi/slime2.png create mode 100644 Resources/Textures/Effects/speech.rsi/swarmer0.png create mode 100644 Resources/Textures/Effects/speech.rsi/swarmer1.png create mode 100644 Resources/Textures/Effects/speech.rsi/swarmer2.png create mode 100644 Resources/Textures/Effects/speech.rsi/syndibot0.png create mode 100644 Resources/Textures/Effects/speech.rsi/syndibot1.png create mode 100644 Resources/Textures/Effects/speech.rsi/syndibot2.png diff --git a/Content.Client/Chat/TypingIndicator/TypingIndicatorSystem.cs b/Content.Client/Chat/TypingIndicator/TypingIndicatorSystem.cs new file mode 100644 index 0000000000..a6a1c93b07 --- /dev/null +++ b/Content.Client/Chat/TypingIndicator/TypingIndicatorSystem.cs @@ -0,0 +1,86 @@ +using Content.Shared.CCVar; +using Content.Shared.Chat.TypingIndicator; +using Robust.Client.Player; +using Robust.Shared.Configuration; +using Robust.Shared.Timing; + +namespace Content.Client.Chat.TypingIndicator; + +// Client-side typing system tracks user input in chat box +public sealed class TypingIndicatorSystem : SharedTypingIndicatorSystem +{ + [Dependency] private readonly IGameTiming _time = default!; + [Dependency] private readonly IPlayerManager _playerManager = default!; + [Dependency] private readonly IConfigurationManager _cfg = default!; + + private readonly TimeSpan _typingTimeout = TimeSpan.FromSeconds(2); + private TimeSpan _lastTextChange; + private bool _isClientTyping; + + public override void Initialize() + { + base.Initialize(); + _cfg.OnValueChanged(CCVars.ChatShowTypingIndicator, OnShowTypingChanged); + } + + public void ClientChangedChatText() + { + // don't update it if player don't want to show typing indicator + if (!_cfg.GetCVar(CCVars.ChatShowTypingIndicator)) + return; + + // client typed something - show typing indicator + ClientUpdateTyping(true); + _lastTextChange = _time.CurTime; + } + + public void ClientSubmittedChatText() + { + // don't update it if player don't want to show typing + if (!_cfg.GetCVar(CCVars.ChatShowTypingIndicator)) + return; + + // client submitted text - hide typing indicator + ClientUpdateTyping(false); + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + // check if client didn't changed chat text box for a long time + if (_isClientTyping) + { + var dif = _time.CurTime - _lastTextChange; + if (dif > _typingTimeout) + { + // client didn't typed anything for a long time - hide indicator + ClientUpdateTyping(false); + } + } + } + + private void ClientUpdateTyping(bool isClientTyping) + { + if (_isClientTyping == isClientTyping) + return; + _isClientTyping = isClientTyping; + + // check if player controls any pawn + var playerPawn = _playerManager.LocalPlayer?.ControlledEntity; + if (playerPawn == null) + return; + + // send a networked event to server + RaiseNetworkEvent(new TypingChangedEvent(playerPawn.Value, isClientTyping)); + } + + private void OnShowTypingChanged(bool showTyping) + { + // hide typing indicator immediately if player don't want to show it anymore + if (!showTyping) + { + ClientUpdateTyping(false); + } + } +} diff --git a/Content.Client/Chat/TypingIndicator/TypingIndicatorVisualizerSystem.cs b/Content.Client/Chat/TypingIndicator/TypingIndicatorVisualizerSystem.cs new file mode 100644 index 0000000000..0f261b69ef --- /dev/null +++ b/Content.Client/Chat/TypingIndicator/TypingIndicatorVisualizerSystem.cs @@ -0,0 +1,50 @@ +using Content.Shared.Chat.TypingIndicator; +using Robust.Client.GameObjects; +using Robust.Client.Graphics; +using Robust.Shared.Prototypes; + +namespace Content.Client.Chat.TypingIndicator; + +public sealed class TypingIndicatorVisualizerSystem : VisualizerSystem +{ + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnInit); + } + + private void OnInit(EntityUid uid, TypingIndicatorComponent component, ComponentInit args) + { + if (!TryComp(uid, out SpriteComponent? sprite)) + return; + + if (!_prototypeManager.TryIndex(component.Prototype, out var proto)) + { + Logger.Error($"Unknown typing indicator id: {component.Prototype}"); + return; + } + + var layer = sprite.LayerMapReserveBlank(TypingIndicatorLayers.Base); + sprite.LayerSetRSI(layer, proto.SpritePath); + sprite.LayerSetState(layer, proto.TypingState); + sprite.LayerSetShader(layer, proto.Shader); + sprite.LayerSetOffset(layer, proto.Offset); + sprite.LayerSetVisible(layer, false); + } + + protected override void OnAppearanceChange(EntityUid uid, TypingIndicatorComponent component, ref AppearanceChangeEvent args) + { + base.OnAppearanceChange(uid, component, ref args); + + if (!TryComp(uid, out SpriteComponent? sprite)) + return; + + args.Component.TryGetData(TypingIndicatorVisuals.IsTyping, out bool isTyping); + if (sprite.LayerMapTryGet(TypingIndicatorLayers.Base, out var layer)) + { + sprite.LayerSetVisible(layer, isTyping); + } + } +} diff --git a/Content.Client/Chat/UI/ChatBox.xaml.cs b/Content.Client/Chat/UI/ChatBox.xaml.cs index 5b31ca0526..d6ac16ee04 100644 --- a/Content.Client/Chat/UI/ChatBox.xaml.cs +++ b/Content.Client/Chat/UI/ChatBox.xaml.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using Content.Client.Alerts.UI; using Content.Client.Chat.Managers; +using Content.Client.Chat.TypingIndicator; using Content.Client.Resources; using Content.Client.Stylesheets; using Content.Shared.Chat; @@ -475,6 +476,9 @@ namespace Content.Client.Chat.UI { // Update channel select button to correct channel if we have a prefix. UpdateChannelSelectButton(); + + // Warn typing indicator about change + EntitySystem.Get().ClientChangedChatText(); } private static ChatSelectChannel GetChannelFromPrefix(char prefix) @@ -518,6 +522,9 @@ namespace Content.Client.Chat.UI private void Input_OnTextEntered(LineEdit.LineEditEventArgs args) { + // Warn typing indicator about entered text + EntitySystem.Get().ClientSubmittedChatText(); + if (!string.IsNullOrWhiteSpace(args.Text)) { var (prefixChannel, text) = SplitInputContents(); diff --git a/Content.Server/Chat/TypingIndicator/TypingIndicatorSystem.cs b/Content.Server/Chat/TypingIndicator/TypingIndicatorSystem.cs new file mode 100644 index 0000000000..1c919b7ecf --- /dev/null +++ b/Content.Server/Chat/TypingIndicator/TypingIndicatorSystem.cs @@ -0,0 +1,63 @@ +using Content.Shared.ActionBlocker; +using Content.Shared.Chat.TypingIndicator; +using Robust.Server.GameObjects; + +namespace Content.Server.Chat.TypingIndicator; + +// Server-side typing system +// It receives networked typing events from clients +// And sync typing indicator using appearance component +public sealed class TypingIndicatorSystem : SharedTypingIndicatorSystem +{ + [Dependency] private readonly ActionBlockerSystem _actionBlocker = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnPlayerAttached); + SubscribeLocalEvent(OnPlayerDetached); + SubscribeNetworkEvent(OnClientTypingChanged); + } + + private void OnPlayerAttached(PlayerAttachedEvent ev) + { + // when player poses entity we want to make sure that there is typing indicator + EnsureComp(ev.Entity); + // we also need appearance component to sync visual state + EnsureComp(ev.Entity); + } + + private void OnPlayerDetached(EntityUid uid, TypingIndicatorComponent component, PlayerDetachedEvent args) + { + // player left entity body - hide typing indicator + SetTypingIndicatorEnabled(uid, false); + } + + private void OnClientTypingChanged(TypingChangedEvent ev) + { + // make sure that this entity still exist + if (!Exists(ev.Uid)) + { + Logger.Warning($"Client attached entity {ev.Uid} from TypingChangedEvent doesn't exist on server."); + return; + } + + // check if this entity can speak or emote + if (!_actionBlocker.CanSpeak(ev.Uid) && !_actionBlocker.CanEmote(ev.Uid)) + { + // nah, make sure that typing indicator is disabled + SetTypingIndicatorEnabled(ev.Uid, false); + return; + } + + SetTypingIndicatorEnabled(ev.Uid, ev.IsTyping); + } + + private void SetTypingIndicatorEnabled(EntityUid uid, bool isEnabled, AppearanceComponent? appearance = null) + { + if (!Resolve(uid, ref appearance, false)) + return; + + appearance.SetData(TypingIndicatorVisuals.IsTyping, isEnabled); + } +} diff --git a/Content.Shared/CCVar/CCVars.cs b/Content.Shared/CCVar/CCVars.cs index 2457d997ed..dd747870f1 100644 --- a/Content.Shared/CCVar/CCVars.cs +++ b/Content.Shared/CCVar/CCVars.cs @@ -820,6 +820,9 @@ namespace Content.Shared.CCVar public static readonly CVarDef ChatSanitizerEnabled = CVarDef.Create("chat.chat_sanitizer_enabled", true, CVar.SERVERONLY); + public static readonly CVarDef ChatShowTypingIndicator = + CVarDef.Create("chat.show_typing_indicator", true, CVar.CLIENTONLY); + /* * AFK */ diff --git a/Content.Shared/Chat/TypingIndicator/SharedTypingIndicatorSystem.cs b/Content.Shared/Chat/TypingIndicator/SharedTypingIndicatorSystem.cs new file mode 100644 index 0000000000..4c5ac2ca80 --- /dev/null +++ b/Content.Shared/Chat/TypingIndicator/SharedTypingIndicatorSystem.cs @@ -0,0 +1,9 @@ +namespace Content.Shared.Chat.TypingIndicator; + +/// +/// Sync typing indicator icon between client and server. +/// +public abstract class SharedTypingIndicatorSystem : EntitySystem +{ + +} diff --git a/Content.Shared/Chat/TypingIndicator/TypingChangedEvent.cs b/Content.Shared/Chat/TypingIndicator/TypingChangedEvent.cs new file mode 100644 index 0000000000..5e364d395a --- /dev/null +++ b/Content.Shared/Chat/TypingIndicator/TypingChangedEvent.cs @@ -0,0 +1,20 @@ +using Robust.Shared.Serialization; + +namespace Content.Shared.Chat.TypingIndicator; + +/// +/// Networked event from client. +/// Send to server when client started/stopped typing in chat input field. +/// +[Serializable, NetSerializable] +public sealed class TypingChangedEvent : EntityEventArgs +{ + public readonly EntityUid Uid; + public readonly bool IsTyping; + + public TypingChangedEvent(EntityUid uid, bool isTyping) + { + Uid = uid; + IsTyping = isTyping; + } +} diff --git a/Content.Shared/Chat/TypingIndicator/TypingIndicatorComponent.cs b/Content.Shared/Chat/TypingIndicator/TypingIndicatorComponent.cs new file mode 100644 index 0000000000..9878628ef0 --- /dev/null +++ b/Content.Shared/Chat/TypingIndicator/TypingIndicatorComponent.cs @@ -0,0 +1,20 @@ +using Robust.Shared.GameStates; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; + +namespace Content.Shared.Chat.TypingIndicator; + +/// +/// Show typing indicator icon when player typing text in chat box. +/// Added automatically when player poses entity. +/// +[RegisterComponent, NetworkedComponent] +[Friend(typeof(SharedTypingIndicatorSystem))] +public sealed class TypingIndicatorComponent : Component +{ + /// + /// Prototype id that store all visual info about typing indicator. + /// + [ViewVariables(VVAccess.ReadOnly)] + [DataField("proto", customTypeSerializer: typeof(PrototypeIdSerializer))] + public string Prototype = "default"; +} diff --git a/Content.Shared/Chat/TypingIndicator/TypingIndicatorPrototype.cs b/Content.Shared/Chat/TypingIndicator/TypingIndicatorPrototype.cs new file mode 100644 index 0000000000..e693b9099d --- /dev/null +++ b/Content.Shared/Chat/TypingIndicator/TypingIndicatorPrototype.cs @@ -0,0 +1,27 @@ +using Robust.Shared.Prototypes; +using Robust.Shared.Utility; + +namespace Content.Shared.Chat.TypingIndicator; + +/// +/// Prototype to store chat typing indicator visuals. +/// +[Prototype("typingIndicator")] +public sealed class TypingIndicatorPrototype : IPrototype +{ + [IdDataFieldAttribute] + public string ID { get; } = default!; + + [DataField("spritePath")] + public ResourcePath SpritePath = new("/Textures/Effects/speech.rsi"); + + [DataField("typingState", required: true)] + public string TypingState = default!; + + [DataField("offset")] + public Vector2 Offset = new(0.5f, 0.5f); + + [DataField("shader")] + public string Shader = "unshaded"; + +} diff --git a/Content.Shared/Chat/TypingIndicator/TypingIndicatorVisuals.cs b/Content.Shared/Chat/TypingIndicator/TypingIndicatorVisuals.cs new file mode 100644 index 0000000000..0368819eff --- /dev/null +++ b/Content.Shared/Chat/TypingIndicator/TypingIndicatorVisuals.cs @@ -0,0 +1,15 @@ +using Robust.Shared.Serialization; + +namespace Content.Shared.Chat.TypingIndicator; + +[Serializable, NetSerializable] +public enum TypingIndicatorVisuals : byte +{ + IsTyping +} + +[Serializable] +public enum TypingIndicatorLayers : byte +{ + Base +} diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/carp.yml b/Resources/Prototypes/Entities/Mobs/NPCs/carp.yml index 4ade2dc49f..dd183bc2af 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/carp.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/carp.yml @@ -68,6 +68,8 @@ Otherwise, wreak havoc on the station! - type: ReplacementAccent accent: genericAggressive + - type: TypingIndicator + proto: alien - type: NoSlip - type: entity @@ -84,6 +86,8 @@ sprite: Mobs/Aliens/Carps/magic.rsi - type: GhostTakeoverAvailable name: magicarp + - type: TypingIndicator + proto: guardian - type: entity name: holocarp @@ -110,6 +114,8 @@ - Opaque - type: GhostTakeoverAvailable name: holocarp + - type: TypingIndicator + proto: robot - type: entity id: MobCarpSalvage diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml b/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml index d5ed41c9af..8c355b9442 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml @@ -79,6 +79,8 @@ rules: You are an antagonist, smack, slash, and wack! - type: ReplacementAccent accent: xeno + - type: TypingIndicator + proto: alien - type: Temperature heatDamageThreshold: 360 coldDamageThreshold: -150 diff --git a/Resources/Prototypes/Entities/Mobs/Player/guardian.yml b/Resources/Prototypes/Entities/Mobs/Player/guardian.yml index ed944f8e51..7597e72c68 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/guardian.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/guardian.yml @@ -56,6 +56,8 @@ - type: Internals - type: Examiner - type: Speech + - type: TypingIndicator + proto: guardian - type: Pullable - type: UnarmedCombat range: 2 @@ -89,6 +91,8 @@ description: Listen to your owner. Don't tank damage. Punch people hard. - type: NameIdentifier group: Holoparasite + - type: TypingIndicator + proto: holo - type: Sprite layers: - state: tech_base diff --git a/Resources/Prototypes/Entities/Objects/Fun/pai.yml b/Resources/Prototypes/Entities/Objects/Fun/pai.yml index 4721e1d6ac..3265295dfd 100644 --- a/Resources/Prototypes/Entities/Objects/Fun/pai.yml +++ b/Resources/Prototypes/Entities/Objects/Fun/pai.yml @@ -36,6 +36,8 @@ - type: GhostRadio - type: DoAfter - type: Actions + - type: TypingIndicator + proto: robot - type: Speech speechSounds: Pai # This has to be installed because otherwise they're not "alive", diff --git a/Resources/Prototypes/typing_indicator.yml b/Resources/Prototypes/typing_indicator.yml new file mode 100644 index 0000000000..bc6f32e31e --- /dev/null +++ b/Resources/Prototypes/typing_indicator.yml @@ -0,0 +1,19 @@ +- type: typingIndicator + id: default + typingState: default0 + +- type: typingIndicator + id: robot + typingState: robot0 + +- type: typingIndicator + id: alien + typingState: alien0 + +- type: typingIndicator + id: guardian + typingState: guardian0 + +- type: typingIndicator + id: holo + typingState: holo0 diff --git a/Resources/Textures/Effects/speech.rsi/alien0.png b/Resources/Textures/Effects/speech.rsi/alien0.png new file mode 100644 index 0000000000000000000000000000000000000000..e8b08e8c3b15c0d58d04e14fa48c8fc0567a26cb GIT binary patch literal 577 zcmeAS@N?(olHy`uVBq!ia0vp^2Y|SNgAGW=wxneHD9LR-0yM!N~pET>|Zvrul)X6 z9si;E_vu%CrF9Pl1-CzPzq>NtJ!U=IXPsQTU;Dqk-+lPo(n`B!#yh|1E5GYMrCYaT z|DAtN7mI&bf3v*(?c(bnJhrdOn046%WJRO?|XHAd%a}g-AC^v&wbTe0P@$DBS2O0vz|XL z5!Z0N_5YHMy!_sIJAHo_|F@5DiP?Uj<8|2gy9wp5t_xPiv3{9;`@NdQ!u@;xE&r%c zdFE^3;>mRqJMGw{U+VAvzid}l`}JpUZIAE%Uk&t|y^>wM{4_71_t8CMb4b5#%BG4b T-ul2a!NB0@>gTe~DWM4f5_$$+ literal 0 HcmV?d00001 diff --git a/Resources/Textures/Effects/speech.rsi/alien1.png b/Resources/Textures/Effects/speech.rsi/alien1.png new file mode 100644 index 0000000000000000000000000000000000000000..03703c2858ae1b082b04cc6544d46be65ccfa8b8 GIT binary patch literal 267 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=mpok@Ln`LHy}D7b$w1`TNB;AY zK>-mv9l74G(7n?r8FN(k#=%3gE-Og~L@r6-;^ugwF@^0N&;NMVJKCT2?h)Vw8UX`K z^tk!2O}V!Ha>cBe(vRmj8Io}{$%j%2~fDu#oPxH@pScbS?83{ F1OSu~Z!iD= literal 0 HcmV?d00001 diff --git a/Resources/Textures/Effects/speech.rsi/alien2.png b/Resources/Textures/Effects/speech.rsi/alien2.png new file mode 100644 index 0000000000000000000000000000000000000000..0362e4bc298d5d5edbf6d44ac4ec3679ec9ed778 GIT binary patch literal 268 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=mpxq^Ln`LHy=utUwWtDS?QUI*R@R=rV-nIHG5^*lDh9uqE$hGnGy?{f z=v{6tSN!H2TOt0adZOL?_FWNW)t^tl=`B|bv#zh!f3@zf+0hS5!OUN-nATk0x>QVN z)nC_?oTd{Nyr=Vv?RK8XUa>6On|E#a)ToMyD;cNp=Bq1)?mY2Xbi*5!75=6F^S;U- z?A1Gy|E}=VHp!L$*!F+YJCa;#wcO&DeROFB)D;d%?|e50D89_&dJhuuboFyt=akR{ E0C3}J`~Uy| literal 0 HcmV?d00001 diff --git a/Resources/Textures/Effects/speech.rsi/alienroyal0.png b/Resources/Textures/Effects/speech.rsi/alienroyal0.png new file mode 100644 index 0000000000000000000000000000000000000000..daa2836f391183a30789bb58ab261fbc92246405 GIT binary patch literal 895 zcmeAS@N?(olHy`uVBq!ia0vp^2Y|SNgAGW=wxne|CX?wqgCAv|wRY%hURYo8w>o-3 z@uAz-pY0I2_NDy0y?(shytMxH$Jd=*`G3{VMR)YN-q-zznSO}lZ~L$Qzt7h{y;eWf z>c_{Qb=PLkt-kx;t$x?z%AcPK&XxY(JeSiz_UvlU!t!ro3Q9>qch&~?pDDU-#?DHLK2A0973M_3YQS@6xq3`~L4WOJDiV4(Oc9Z}K%fxAsWO z%;wqo_HW-s`<{PK_!CQOirxIXZbVDnZZ-niA;Z4V_kY1J>w3<((An2|?%lsWcbD&} zPN1L5^BRAxcrRjpefv$l#`_EH|NncEd|@9j$Ra{#7xwk<*K@4@_jhjef_?kaYVG>> z>iPcP@S71BUxdJq#JZ(?e(s;Olt4l9ZR4^lKc8)kc`5Jr|NT1Q&A(q-DXqHpdfVL3 zr?(f?-0c7U{)N0-eGzNNk69OGFM0w6=iGi{@ABW^SGk%)@l0#geb@FdKe3Sb)qcJH zs+|Ea`gT8>KV6@{{ob1kTX(*=Jzt9Z@6WIAeHw~(YwY?~c>eVC_T@Jpzustmrc|@% z#r^sWoiER??%&Sj;!;zyPyPSLjiL))@4EBlrGIbhzdxV=_UYPP>T=}l*SGf{*QVFo zGOW&;d^L8#vj1EDeT#T<#lvuxIi{r{JiI_U2`@+u-qE3PsEl%zBk$Y_1Udw|9$)VZ+;ZxfhiY% zT*yEBJG;Ve`tt6W>~*X9U{Zb4T=_*k=YCsVRhy;~ozNSgx%=(G9PSm@A4CCNaE^cG(bxJO+YUqpUwa!< g?Sd72;n>IcReILS@VM3+z+h(ZboFyt=akR{02KtVkpKVy literal 0 HcmV?d00001 diff --git a/Resources/Textures/Effects/speech.rsi/alienroyal2.png b/Resources/Textures/Effects/speech.rsi/alienroyal2.png new file mode 100644 index 0000000000000000000000000000000000000000..76b4be2ca4e7456de1689992accba57294561a60 GIT binary patch literal 396 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7T#lEU<~$jaSW-L^Y)ry*C7XyV;}RU zugzhd$g#3TM&YPa7WexHKldCKi)Pm;VmPSvK%r^vS23g07Beqy$WdtiAHR5JqSL)S z-;E|VO#db(rIRkL?0L3w_Rr*X7xq|BYwEw@vQ;PA^Vsfhch6pLSNnhexOChGrX|sC ze;4e3_1^z?eeBw@cQLgYpRR1uuatf;ty1Gh>DF5}?_W53hsz_?_}<^ftjKK}AN<{D zmwkNQWu}Ff%5NT!4|UYKIh{#V!E^d+&0Pv&KQ@;pmu_c#nv>j>^YQr`@1Tjgw=PVy z44cb!V|MaB^W!#Ce18k^+}D_YO&U zCyO`Rr>|6Z_WJVt=KdY_te2iL@T@66?)%2{$d-Gu=9X^{e)>0MPtE@QHAmx|eRh8B z|Hl~j`dMX`&ei?}ihm`)m&;oLO}3ZMpa1v%jqKUY|Ni{?zlW)G`Ja+~>zwYYNY{4% z+w)s}Y0WSBh4%03usRr1u))NZmwnZyjP~T^pG!^87d^T>Km7lu`rxUq)i?6>7e|&} zUbcr*_V3TH`n$Ske^hKaTvt7_%4ms8{k*^3zk?NGPd_|RReq=B`u+F%@t^h7_tn*N z{CsL~4&(s5w@^tlc JS?83{1OSuK+i3s* literal 0 HcmV?d00001 diff --git a/Resources/Textures/Effects/speech.rsi/blob1.png b/Resources/Textures/Effects/speech.rsi/blob1.png new file mode 100644 index 0000000000000000000000000000000000000000..a7f6f38ee4c8f6bae75231f68c9f89105dabab95 GIT binary patch literal 255 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=$2?seLn`LHy=u?bWFT_<? z*t~Z2j~(;gdz)_(OJ@kiXcVD`f_e*ge|=H%o5KDVcLeUFvs)&i$^Ix%`WxuciImtULDWmq*(yJNGu+?S6vA({0jRE9c%? iw13ghFN{#@{y$@tm|=MPRUp?2kcg+NpUXO@geCwTjA0)D literal 0 HcmV?d00001 diff --git a/Resources/Textures/Effects/speech.rsi/clock0.png b/Resources/Textures/Effects/speech.rsi/clock0.png new file mode 100644 index 0000000000000000000000000000000000000000..30722223b7e2cc5e255f008e3b4f7f63221f877b GIT binary patch literal 870 zcmeAS@N?(olHy`uVBq!ia0vp^2SAvE4M+yv$zf+;VAl0?aSW-L^Y*4;wzQ`N+k=g| zk*%F!Zbx=GahkPFI971BGPdPF>B{%^zsh44JDoCRU)d}zo}Or-RJ>y1L$1W=TX)T* zHsAkja`=tk$GytD*4DFyfQDlOA2z?uEjtxB%ZzuHe(?tTJ-{`F(XVd~=O$ZV!KX z(3$LjJ!{it_r6|cax%b&?WpJavfMBG_Wt}{EY4sV$sj5|zssnc*K%(BKF+E4nO6KT z`)PPO*4gj=e!G@a`@ix9@a$gr?RV&2$Mn7T`Col}kpQHYl<`=_@B4E6@7G^DV%J1f zG6ksC@A(t$-*7Y5^hwq)6HdVs^49f>H`jeWK2hUI|6F-&{=_1-VD2|F>!%vNv)Rs0 z*SxE9>1v+WDffRBKj(`(SbANy*K6x-mgD<=>FbkyTPIJrWc7b~|MA}Ma{YWxPyW}~ zW$d{wT@p2WfAC%diLY7Bc5})ue|=J?&iXWL&Xv#o_sWB3rD&)%KYE+^_STEo>d)_= z>oF9q(rt*Zv(+jV`4lxLexIiK?F>CT(Why*%eJz9IDh~D*&?7s&HrBpw{sT%1cuA) zoZES?Wgk2&(66<4`)rl!?&FJ_zt-8WxVK>Qz4z=v)@*b2<9|K=S5rlH_5ZAaK7;Uc_A&#tz4)8=<&WUiKF8;$r~lir zKTdvocW8(3gcm=RZ&fj!dAG{`Q`WC9>KzJIby9P_&bH@s6|}WSck`Qj|8I9Qa6I~x Q@)soK>FVdQ&MBb@0KYqbzW@LL literal 0 HcmV?d00001 diff --git a/Resources/Textures/Effects/speech.rsi/clock1.png b/Resources/Textures/Effects/speech.rsi/clock1.png new file mode 100644 index 0000000000000000000000000000000000000000..dc4a80a73b8e707af1f25553fd7bceb780118199 GIT binary patch literal 1007 zcmeAS@N?(olHy`uVBq!ia0vp^2SAvE4M+yv$zf+;VBY5G;uumf=k3jn-ZGIg>>t)& zyz2BwOXxe7Fpram@D8=SE&-3}zy7)Z=bNotwuLi-jnj#1GsnEVo`)A6ckMMSG}hdF zXwUiOvnoG#eV6_A{od^DoIt~|fe*XS{;jzDKJ~_x%#x`!drqvr$@B8xiTJpGx0zmi zH@b84m08)Vb>*9^Epp#_d#L@qpR?Y~E;=e!L+StS=h?mI^QJwIo?V}~YvJyDCr@wB z{;Ohn!+Q0kl;q;VYmaxC-*xY<{J873|D?kQE2paLJw7+3VE^M;Q{Np6+Ey<2ZSs?h zdYhB~m+bv1Z(=IAwG`r%$3oi6GgYft|5qonZeDdC39Pt&LWKgShNJCCo@ zc1^q!=M#PF`o+t&&(`-%ar(FEGc&fJ!y>lj@|}M3zj1B8bKSyE9`D{W>+FPg{ePZ5 z-LKnFr+;{V+#2&TDgAS)hfcD&{VJur!0PqJyWL1Z7o+O=-cw!j&ifr{OMhn=>c^G;_AIin znE#~y&b>eUv!wj*#fPcC*I&I$z^izR{kpzy$58{(RPEz^*@2~&XkN-#s8-@eeVrJB>i#MOYj#gEsA z*4|CCmu>D_x9@)bI-ggMFCQ({_Gegj5}1svwH>}zT0Qx?p^j5^!u~gZdgrY(`EMD- z_4D*Oek=iqP3VGOvHiU-)skkVQn9M?`k%7QoSSa$1c&I{wRy?Q-&LvaZVq;Jc&+1O zUUJ>K@@LuF`6{5aG$|)X?_5=kfiN&M_`jW7)x3A}Dm|8r)yJ4tT-9dSa#f%Ez#D(g zcQwpFUE6>Eeat_7>R$WY@0SfhqWKKfi9!W74>E idFnI!;}{OD>;KOX!eSfy-Tq=TNYc~Q&t;ucLK6T88Os*{ literal 0 HcmV?d00001 diff --git a/Resources/Textures/Effects/speech.rsi/clock2.png b/Resources/Textures/Effects/speech.rsi/clock2.png new file mode 100644 index 0000000000000000000000000000000000000000..48b4f806ec7d3772fde50286ac7b44febd59cbd7 GIT binary patch literal 1030 zcmeAS@N?(olHy`uVBq!ia0vp^2SAvE4M+yv$zf+;U_R^V;uumf=j}~jZ<$CDwg<_7 zGMpB#O@4FkPz{%J4985Ks7Mb74j-|bDJSC3yf1jX$lIK8x?|IX)=Z{8sbqgo6^|2<$vuFQ5_M!9I zpA?(=yXU%+s|&M4w)VPYCu&`b*G)P4@!sKMD^9QSS$(%dT|YndXkq2gXDd$cGMX-> zpp=yU*&zPzjMHUi{GV5G@8T=l-uG$h*RXB7OQ)p#+xvEMO=hmu?b~)MYyVwh>v&=A zIJGs#e5KtVW=)$vg7%pWS9^D^Ipwr&Hc-o@pVO}`n|#7PaE{3R@89pweWtnhxBHj> zZ_3{JxVgCG|M#}D@H^^SY-3gad#1g7%~|&2rDd7YTgt!H?z6c3zMud2^R0InCDiJx ze%AkBJz>w+Zu6cm&xz}azjggecH^h=?m>$6w|?RZL-cTcmAS^^q}4^;=quLU74f+zx)O{rvPhN@GbMsfo?b!ks&;NgPG3Mpg?q?N3Kj-V7ofemU z#_x*R|K5kfo9DXO{CYfnjm5uz(Z!%ZMGeGNKB$2V3Didq-e%VQE;P2TsGb!bd!cm0 z_S#AMYkkwJZ>xJgyMNc}RQR>%oA0g#zWqGi%)wl=pb}u>XfA^PRdo zXFx)cQM3K5{-3JS?))Qs^d!)^f2!(@l$8GceEojU-}{AHdb1v1+K zT&84I{%dCS@Ayv#3+Kk(VtYHccE0|(FTWoj{#dD7&d_#pe%wF#I>t}^hZWE7Zk|1X z>F4Eh@vjd0{5(D}p7)+^2aY{c{RPN$!=I9e0S^b zwU=Di{ybT+`KzaJfVFv>|CVR!kAJ={3zNCs9lUr2@47on**ZFI+_X6pZvJf3+_Q)G zylPtDwUk%jVt$hrcVpm>`)m=w)VNBA#Vv7>h33BZ@Aq4u-TOPZeEK%W>h$~1yN&<; z`%zGn$KDb3>!G;9e~Uj8=9jbkGI&-Q1ph1KL3ion}$ z`mH87?Z_eig~59N*Z#KWl@~1RIUjb+%j?q?{QmyD!|Iw1j@fUY-!A^K zWFNz}#~VJUJarCOp7O7d{f3dTT)5-YPxF`FiO%+3-ynSTv-^=n^X>Cj%#%m98kPNH za^(F3k+ZjW*?#tVD$kv8%V}@G<0C~se_z{p`GcU~@gJ{vMZBL~x81X2In#+V)&HZ< z&Zz(T{K9<3xV%Qe?EUQgs-M4GANaN^_1CJ0?5dyp*Ei@IKQ0sv(fc2HYVX!iK5LsPE(t2qY}^mO%eS?83{1OW1CoRk0n literal 0 HcmV?d00001 diff --git a/Resources/Textures/Effects/speech.rsi/default1.png b/Resources/Textures/Effects/speech.rsi/default1.png new file mode 100644 index 0000000000000000000000000000000000000000..b4bd5456338f22b12d561be1b2ad93931e781bd2 GIT binary patch literal 200 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC={hlt4Ar*7pUUlR;WFWwL!AF1T z_xP>Lf9Y)rGFc?8yX?KA!pyRXHEl~wfr>#Oq2%hyvvY6#ye)n6bTUKnr04TjMM;~q zWE$_6-?z#yv^2{8&a1uWepCmqpONCZDaTnN)V$%^y4VRRKcA+3`(V_txPa4c&H2!) t*9uwQ`&p_>?qaqxeDawcWb^^SSccoT74Pho{i_V(d%F6$taD0e0sv2ZPM-h( literal 0 HcmV?d00001 diff --git a/Resources/Textures/Effects/speech.rsi/default2.png b/Resources/Textures/Effects/speech.rsi/default2.png new file mode 100644 index 0000000000000000000000000000000000000000..43c4aaaea95c793f7a9aad8304f140f2e80f3120 GIT binary patch literal 190 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=t)4E9Ar*7pUbW^rY{0`1pgle7 zo&C<`LDShTT~Um^74X#7;KZS3)jyfMK*b=iLXT~y-HEgJ@12O;#`|LC&wUz;wl4CW z5%$shqhMk5qx8BGqks2G_AJ?Umo4Sl-|5npr}LPvg)cDbe~@P9?h<`WR3`MM@#Zg_ iKU_|}=LZ?spk2k_dS3F*c|Xk-5Z}|)&t;ucLK6TEZApUw literal 0 HcmV?d00001 diff --git a/Resources/Textures/Effects/speech.rsi/guardian0.png b/Resources/Textures/Effects/speech.rsi/guardian0.png new file mode 100644 index 0000000000000000000000000000000000000000..859c4936bd1ef2fd03f8ee5c7f4f83e88867feba GIT binary patch literal 398 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7xzrU<~ziaSW-L^Y*S|*C7KDwg+0p zU6B>74IEACFD6zuF(yj95Z(2{GHvQ5x7QrZVW0Np2Mh0dAwO@z_9|aypee{;&+gcr zg;sILir)Wz{`#}&{LOnae%e3lzWmW>*_1Zx_r0d_PGW}t?fzE??DN#*{r*qVv~yPd z|L@vYd>C1lGjQeJp11v9TX+yd>$=zLqU#T9>+WjU_n!ZA={}LEzqfYHsMl(kYL>oB z^7<{d)Qg|juiE;zGVIGAWCx(K57fUs)mxo>N$KpF?rHUgmR}BXeBJoW`GnH5`DyI- z9QT(@X}iU);dVB^qNIRJVaIHyjN9LA>fM5$t^X^N$F<<~uGfDgeP{0fRvb~nSbJao z+3Oljt?${QhW|qtv~$mu7_ZyPduq}1_|WX%pVz$j$q2W+q5M0yN$s@NUe838L4uyH KelF{r5}E+I#G@tv literal 0 HcmV?d00001 diff --git a/Resources/Textures/Effects/speech.rsi/guardian1.png b/Resources/Textures/Effects/speech.rsi/guardian1.png new file mode 100644 index 0000000000000000000000000000000000000000..d35ecff026223aaaa8b91fad3de3872c4f8f985c GIT binary patch literal 248 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=`#oJ8Ln`LHz3Rwy$Uva=;l>Is z@x4w9M7V4n`)t$&n3SGy#XgbIQ&Byp5$h`PKmV%FZjbo39uA;Z7})SGMEqH%Z|U2z z{c*o?mhUg#Ib*r0<@0UF%1>r-IM->^->cqV@qYTBbC%n;S{-g$7#ebQ!K#Hk2Un#r zD4o{}xO$=3c>f16huT-yb|=hdiq~@x6znwY6VpF5T~i=g=5#6Fl6BFW*grfh`Mz#L j#jo}V*?OqW4fS6bDx1w7Pg&^s7$oHB>gTe~DWM4f`=4If literal 0 HcmV?d00001 diff --git a/Resources/Textures/Effects/speech.rsi/guardian2.png b/Resources/Textures/Effects/speech.rsi/guardian2.png new file mode 100644 index 0000000000000000000000000000000000000000..fb64f26e52cf160e1cff1a4f4883c7895e442ba2 GIT binary patch literal 235 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=n><|{Ln`LHy?T)IumO)lplM2% z+@>Z47Ot5Gl&hPZ7K&_f;+wZvefAaKTXSLp{_nmaW_M%L1{a`C7-%S8Ze`G0yej$o z@vnEDS-k!F`l!h{&(P_;wI{QMcG;}dmv`FnWalcq=&27Kzt?u>@8;|bMx z1n>QFd$nR89A?*0Qc5$c_`?yut6?VZ>ry`t+3!nK5}ucOhgQ`5b>6eh9%|bG`zj{$ V^?Hxx(t2e;LY}UEF6*2UngGtlSycc4 literal 0 HcmV?d00001 diff --git a/Resources/Textures/Effects/speech.rsi/holo0.png b/Resources/Textures/Effects/speech.rsi/holo0.png new file mode 100644 index 0000000000000000000000000000000000000000..002e0c835fed420f1fc148e40729d2e7ac64b4c0 GIT binary patch literal 506 zcmeAS@N?(olHy`uVBq!ia0vp^2Y|SNgAGW=wxne>ykGhKRoiAZ3_2-orCLCwq?UkGJ?hW=bGJQVnSa?NR%~j}uAcg(_Hq9M_@h3)e^$L1$o%ko zQpB^$e|xI=%e$_p$#^2@E4w_4MzF7YA-i!Y0 z^}Cqg{Qehe|MB~u50@9&m(_c{{AE`WIc@Fr@&mfRlE25d^!fi<{GAz_D>20cJkM2D zo4=d;FXHdv!l3qNbGARusJ*ZJ*YLOeSB2+$d@q+ohIqX+`Zx7o#b0OJjHCN!zwZSy z55GQQbAJBUbd%Em7Jc&N@1L*V`|bNb`yDY1noC7Kwy&4{d;a<7`**8%IxqQm{PmGt z>+WkF?a04a|IYr{UaPx|UYciJ?azL9tKakQvTVZr@9Tec?tk7sU+nM3-@#tjzaKok vDQE9K=DdsYyZg literal 0 HcmV?d00001 diff --git a/Resources/Textures/Effects/speech.rsi/holo2.png b/Resources/Textures/Effects/speech.rsi/holo2.png new file mode 100644 index 0000000000000000000000000000000000000000..636d3ea31aeae21931f8533cea3d5433d5e95c09 GIT binary patch literal 241 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=J3L(+Ln`LHy|$6}u!2DALviiH zci6t!OzC-~xTh=Htl^gLHIG&op*c$yS^S^h!r*vt?uAAcpgt%#@O4JyFSGAytD4OM zjJ>yhy=J>}yRfmhVoSg8?QJ`G;ojZnYxx-x3b5NnV<#S2Xv2`ouLQXxg?%)p~Wy@7@1b8aw&A{egHr dSsR$OGu4$O&AQLdIgtkv@pScbS?83{1OV>3W9k3^ literal 0 HcmV?d00001 diff --git a/Resources/Textures/Effects/speech.rsi/lawyer0.png b/Resources/Textures/Effects/speech.rsi/lawyer0.png new file mode 100644 index 0000000000000000000000000000000000000000..6b804ab7348725a064d701841565a1722ad760be GIT binary patch literal 579 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7xzrVB+$0aSW-L^Y*S`-faUBwg=Pi zIEueu?eg2AFK}Y}#lRxn^n}2a_Y)MIi#W;*W}I+Kuylyo`sBOsq7>%$;yxFzd{P3M zg#;S@hj^`A6MJ~pu5!e(!%K%N!3{I^~o_pX&3NWtm!E zqfQ$yx&7yJ!}BY%H(|=AY?ytVQR}o^9$l z9GsB(&3yL$y!vT9k8eD&x>5blLsOP@P4&a7eR0vd&t<=~t#^^x|HPtJ@Y0iS&t2aC zJfD?!_VU zUhc!3Flmjlx%Nv#iq1XVQu)dK?3CHu^3$KMf4uee-z;;%_H#G)@B7=)(eZwNy_?IC znm?aDefrjVHcj~V-+#~Y#jAY1fBGL^zkdDMC;R8}%gvK}e8)m53Fyh~i$ryw{(n(t z@_3T!f_2Th{{(lHaAp|Q-~Fev^6k~@>%OlBx+ciWo$+zopr0JJ?6NB{r; literal 0 HcmV?d00001 diff --git a/Resources/Textures/Effects/speech.rsi/lawyer1.png b/Resources/Textures/Effects/speech.rsi/lawyer1.png new file mode 100644 index 0000000000000000000000000000000000000000..7a8a817dd1fb6ae8bf6c137a2a0bcad6b65ce502 GIT binary patch literal 822 zcmeAS@N?(olHy`uVBq!ia0vp^2Y|SNgAGW=wxnepQy-<_afRrQaWL-O}@&dotHi509=Jere|zBQi3W z_lVDtoK?gEG!hf++5B#?{saE0tIz9xyW+xb9)7I2)Fe@&t*7HpnBDPD9V?q()?A(S zEYV@;`G#;)dRho=V>$s7;9 zX8AkEQtt8(_V`cv*NfQAmrmKUcdsq4&0d@1`;RBvq`0LWvv2=@uI}N#fB*Kb|L7~r z*Pgfiw(F7XZyg=$FYjFU`q}gQtG3=bxLmWwZBBcA$FIZZzx}RFNh`Vgy1VZy)Aui* zWa9gdpEz?PZ@YY9Xa2gEd|sN%*Xq@6`uept{_MBySH9QwJ+3%aSFTu{cCMc*Gz;W3 z{=)^+YJb|ubib*+AO79^!kU+Sv-srimmjdaKgV9oI%%!-eXx*yr0<)}*Y`iR2($cn zeb&1LFF)8@RsUK2ad&_Gg%Wn(_W34X^A*BP?mAw6$cP!DSVS`1%2RFXKR$o3zHIX! z10H8LmpbkF$(2V;6qkM0J#qEoqHR_Ycm9a?cbqtLCdg}RYRqc|sVIh7ZxUpG6>dJc zJM)?TaV4eIItn@rg1uz{UMCA>{0}~#UB6AHKl4ws{?F~38BFdSl=)Ye`!|&mjld`}(Q*FA6M5cHh-gjw^O^sp;)#?|88*uZ}ao zRMy4i{&&m1$C=me$IkdV({jB?sOHi+VY_~uJazqhZRt;CsrLyIZJTG;@02~ZsAu2% z*VX@KZ`s3eX4cz;d4E6i+W9v`tYN!)_7^L61K+jx<>%vSdml%nzwms`?a;z^?cBC~ ztM2`dyZDL8;PSzpX8V}-ui44^fJxS5-BZ?tWz48y_rUot|7(U`!G^9o*FZ9!u6{1- HoD!M>{q?7-*tR*#e*M4q<@EC{xoOP1)}5ZL@#=rw&FOQB&uz9{<(2(+|4){T z%3oh==kH#3`uO9I?{+H2J-%(=a?a9kd9aP0o!*8+=dTz2S6+1Y<@BIak1d|SKtPCQ)@a{Eulp8vZ3`-1{+pZ$J6 z*dlA~-k<-r+x@*)Xw0bmu1;Z9*FX2&m)|pA{rBsq+~5E4!I%53f64dy+P`~Yj}f?7 z1RRRPy!9V+pFUb!9lEeRzQR{8FVtIes_C=hTgNPO#iP%Q2j#N8yT`D$ZNdBBd+xtq z{Cd}HHU5uPwRIC)br)Raak>A!Qs#I}ivlo;>i6e(tg~c^z5dj5&(+v#^MGNW5VG_RA?=R!x;vQAl+_SYil3gR+w&RJy zqn|&2zH5l-zGf_-xpaQr7vC%%$LponIVI$;*!3mXUf-V|b>3mK;*5P?XIwYmFZXWO zx>u(cbd(uiDB)OkGW^OkWu@uD8&7qbUoK*s#qW6W!fQqgrB5b;tFwPtN1bmFh`Ceq z#{JG;tteB;9YCiCzO0X{3w|vrz?6a(rV;n*^UDOMPd8Kc2g!K4`njxgN@xNAF#C@2 literal 0 HcmV?d00001 diff --git a/Resources/Textures/Effects/speech.rsi/machine0.png b/Resources/Textures/Effects/speech.rsi/machine0.png new file mode 100644 index 0000000000000000000000000000000000000000..3d039ad4d95b60dfd84735be083b8e55bd6565d1 GIT binary patch literal 342 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7xzrV3hTAaSW-L^Y-q+-ev;<)(ba7 zXBD#EJf(4Hs-@_KbMq$1Omp=JWcswHoKc1E-!+*uKVe3oW@Hd^H~jlOzCZszuWOER zIQ3`yzr|BNz3=~Qv;VI_`so?}f4^Za&|{r2hsnc_am|&e_v!N=P8K>v;Uj5H^?UYN8ZuHnJdh=6Rsq)7Jd1QM~*%c?>JvzawtncSD^Z&Ja zo!Lg)OKuytPc__s_uxs*h9@EpMfF?@u2sFCe{;>}{yKjD-~)|n89VfLOg8%ePGU!l z$8|>ja>luDewuF$^6K8BeP#3W{*EUPo$C;`Jh)WHcyhJ#R_%p7vLI1US3j3^P6^hd*?@<^VSCa{ zp9_A|6pbQhG?e~wS|qxqhT-JiYRif8eWFJ@ff~SI({9~I`;`AzhA4(hPU{XjmG;QQ zw_UurH8FSd)^9T&N4>6yvwXgLeeSiW<8RpKuQgM6^j9op+s%fKKiBOaC3C&0V7ctu zxIVcc??e*QlI#Qed#7AVzAvx1cE6ns*!-D$7{XL!m|1)>=79K~u6{1-oD!M(|*VEO{Wt~$(6999KLZAQu literal 0 HcmV?d00001 diff --git a/Resources/Textures/Effects/speech.rsi/meta.json b/Resources/Textures/Effects/speech.rsi/meta.json new file mode 100644 index 0000000000..5a42d7ecfd --- /dev/null +++ b/Resources/Textures/Effects/speech.rsi/meta.json @@ -0,0 +1,345 @@ +{ + "version": 1, + "size": { + "x": 64, + "y": 64 + }, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/c6e3401f2e7e1e55c57060cdf956a98ef1fefc24", + "states": [ + { + "name": "alien0", + "delays": [ + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.4 + ] + ] + }, + { + "name": "alien1" + }, + { + "name": "alien2" + }, + { + "name": "alienroyal0", + + "delays": [ + [ + 0.2, + 0.3, + 0.3, + 0.3, + 0.3, + 0.5 + ] + ] + }, + { + "name": "alienroyal1", + }, + { + "name": "alienroyal2" + }, + { + "name": "blob0", + "delays": [ + [ + 0.2, + 0.4, + 0.4, + 0.4, + 0.4, + 0.4 + ] + ] + }, + { + "name": "blob1" + }, + { + "name": "blob2" + }, + { + "name": "clock0", + "delays": [ + [ + 0.2, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.3 + ] + ] + }, + { + "name": "clock1", + "delays": [ + [ + 0.6, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ] + ] + }, + { + "name": "clock2", + "delays": [ + [ + 0.6, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ] + ] + }, + { + "name": "default0", + "delays": [ + [ + 0.2, + 0.3, + 0.3, + 0.5 + ] + ] + }, + { + "name": "default1" + }, + { + "name": "default2" + }, + { + "name": "guardian0", + "delays": [ + [ + 0.4, + 0.4, + 0.4, + 0.6 + ] + ] + }, + { + "name": "guardian1" + }, + { + "name": "guardian2" + }, + { + "name": "holo0", + "delays": [ + [ + 0.2, + 0.1, + 0.1, + 0.1, + 0.1, + 0.2 + ] + ] + }, + { + "name": "holo1" + }, + { + "name": "holo2" + }, + { + "name": "lawyer0", + "delays": [ + [ + 0.3, + 0.3, + 0.3, + 0.4 + ] + ] + }, + { + "name": "lawyer1", + "delays": [ + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ] + ] + }, + { + "name": "lawyer2", + "delays": [ + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ] + ] + }, + { + "name": "machine0", + "delays": [ + [ + 0.4, + 0.4, + 0.4, + 0.4 + ] + ] + }, + { + "name": "machine1" + }, + { + "name": "machine2" + }, + { + "name": "robot0", + "delays": [ + [ + 0.4, + 0.4, + 0.4, + 0.4 + ] + ] + }, + { + "name": "robot1" + }, + { + "name": "robot2" + }, + { + "name": "slime0", + "delays": [ + [ + 0.1, + 0.1, + 0.2, + 0.1, + 0.1, + 0.1, + 0.1, + 0.5 + ] + ] + }, + { + "name": "slime1", + "delays": [ + [ + 0.1, + 0.3, + 0.1, + 0.1, + 0.1, + 0.6 + ] + ] + }, + { + "name": "slime2", + "delays": [ + [ + 0.1, + 0.3, + 0.1, + 0.1, + 0.1, + 0.6 + ] + ] + }, + { + "name": "swarmer0", + "delays": [ + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ] + ] + }, + { + "name": "swarmer1", + "delays": [ + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ] + ] + }, + { + "name": "swarmer2", + "delays": [ + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ] + ] + }, + { + "name": "syndibot0", + "delays": [ + [ + 0.2, + 0.2, + 0.2, + 0.2 + ] + ] + }, + { + "name": "syndibot1" + }, + { + "name": "syndibot2" + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/Effects/speech.rsi/robot0.png b/Resources/Textures/Effects/speech.rsi/robot0.png new file mode 100644 index 0000000000000000000000000000000000000000..557b641cea4a5f2cc65d834825c478d6331b4396 GIT binary patch literal 346 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7xzrU{v&UaSW-L^Y-q++`|SutO46j z7|l|cs~lXi#Al-A70xYw{%j%FO?gB*c|Pv#&ON|h+sOXu2nSFrLwJ*eyl@nz!fc{3^tlTQ7a z{xA9Fl$}d&&$C?H^YbmApC6;ja)wF2LmA%w+wtvu&b6QE``hJ%4>YP}?9$sY+35c} ti5)Q>*BSlG8DVzr(Y~@7!^#I&_A&anxEXApb!{%la!*%3mvv4FO#thNiYNd8 literal 0 HcmV?d00001 diff --git a/Resources/Textures/Effects/speech.rsi/robot1.png b/Resources/Textures/Effects/speech.rsi/robot1.png new file mode 100644 index 0000000000000000000000000000000000000000..66320382bd7816b0c062062597694b0f995325a2 GIT binary patch literal 210 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=Gd*1#Ln`LHy>^hd*?@<^VSAFK z|Ali0MS8AiTCw{~DE;Nc#K|S^z*WBf$f?8IbSwpddO*P8t=#0|{g40tUg5Itl8^q( zoxe6rDXK6E&zRQq^mA?R^EbC1?kPF<{5o#`$e;OL zK+Tb3QZm~Qr7(wVXWRZ|=FQ{#?47pe#oB<4H~q=D^S;EUc}Ezpg7}`UelF{r5}E-1 C!Bd$4 literal 0 HcmV?d00001 diff --git a/Resources/Textures/Effects/speech.rsi/robot2.png b/Resources/Textures/Effects/speech.rsi/robot2.png new file mode 100644 index 0000000000000000000000000000000000000000..1dd1755ff574c46aa4319b0a5ee183cc7e5d0da1 GIT binary patch literal 193 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=9iA?ZAr*7pUOUL^q{zb%V4`=K zvxIMXfy(j%zBvu0HZu!W{yY6&<-FZsJ|F?xJE{^%->$0SC z?bf^9k`L#w-uhf>yJpwE9lN{NxYTqPsW^?n?Yw?&hL< j`ya>Us9ztzrk#1hyyS-Pnu&GGY(acaS3j3^P6d{N8Pa@BX-n5d z?Vrid>MhyU#@6jG+xhx`+nQBc{)z{Q%EIp&SG&=d-onk?)pO2r~Uzu(GupB7z`uqi9pr!UT^O*hu9cIxtU03W= z_?16_uLX4fuVMr_dE3uy-ehJ3xwho2p#c_GBMZ%c zJDqvyluFBK#dd$wEN^$uv!C_&v$JNZpXObu&(ApI9l!h$lznw}_VoSUns=k>kIi#A zFMsvnXOS1zS1}$d++!2JI1=PujWAvVp2J^%N}V;TzhdRGF{>eS*15GCHD(`p@pEw{ zIMm}y{&GagJFj2#y#Lixi7o%0zPnzqT<+rM%L0FY=vUPtI|{`9vA4eNS}n70XfZ2@ N>*?y}vd$@?2>>fK5;_0? literal 0 HcmV?d00001 diff --git a/Resources/Textures/Effects/speech.rsi/slime1.png b/Resources/Textures/Effects/speech.rsi/slime1.png new file mode 100644 index 0000000000000000000000000000000000000000..87010aa307beacf62bb562b7beb61589750f9ae2 GIT binary patch literal 515 zcmeAS@N?(olHy`uVBq!ia0vp^2Y|SNgAGW=wxneBj3S@2S_fvkcrC*wVqM;4Dt3Z0s!%G4V|Eqh2Ra@;F=S*J42{Zv4_>t-VyYbqs zUGk?b=gSvc@At1?^Y8iF+1GB(3fd)ClP_B-WcBysFaN(s&GucZTDdjuSGwlX*B2)z zs{Bg+HouR}TeDa*^`4)T%CE;?z6N>G=U$Eo+E7kaK#$Wk9$KP7@yZLs$-tT^;uKqvsV!8Ue-t}+l zm_DB8!{%sAF$ayohD P4?rTGu6{1-oD!M<_W;yw literal 0 HcmV?d00001 diff --git a/Resources/Textures/Effects/speech.rsi/slime2.png b/Resources/Textures/Effects/speech.rsi/slime2.png new file mode 100644 index 0000000000000000000000000000000000000000..0cd03b9dd4052c792a8c74c4a8187e6cf4af7fb8 GIT binary patch literal 493 zcmeAS@N?(olHy`uVBq!ia0vp^2Y|SNgAGW=wxne-eRozrKe-2Wcy(8g^4tKdc=&;YEU?$+&Z4w|Vq z`&Q<%j&3n;* zv;N0Fkr&qwGtOA{^6^yhPuE}cU;JOdkoTWS`I7wN|9K2qYmX;J*fRpDxcH`RUyi@p z&*yNiGH2miwJo_UzmmVLpXPXbO8mCieP8XBv-ZEQcVB-x{`@8R-SZv(++f7&R186m zr@7S&rfkaTe>&&*^_X+-FUzm~|M#1-W~!&=-12kLRr@%5|5f~L-?q7T{_~$#z6EdHI|K<1_`@UY()LSrYuWuB5*?+O#oMG#=#|CTW1F7Wo z`i{}RlE22YD?GQ^cA-r8mKoDu!{6bj7v!GOzpZ!w*ZxKJW&hX3P2WFnsePF}1H%PF keH*YlV>k2gKpp)5u6kRBeiHBZW1B%Dp00i_>zopr0QZ&Dy8r+H literal 0 HcmV?d00001 diff --git a/Resources/Textures/Effects/speech.rsi/swarmer0.png b/Resources/Textures/Effects/speech.rsi/swarmer0.png new file mode 100644 index 0000000000000000000000000000000000000000..baa18dbe79a8853e91159b3b26ac882126293a74 GIT binary patch literal 749 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K54sfsm$=7f9rZF%uZSiz*45^s&_SVMS#|9#7 z3CB({%sY@fhxgH;6ACvKHpk56?V0Tyx~1`vnrp+C$O8vV4U=w(-HkcY@cuW0v%--_ zf17?POz`3anTrSPc%mg;>TGZI`Qf{N%ezS>|cM@u4T%k zKX)%I|L*k9wc(F`>z~zTpYLDMdk`LZU2nmr{zH3@ri5Pk9JXWotTkPpPk9e8Nu)Dg zSg?mNg5j|agOtf>xBOOl#Tfhgd)L-2T$b@Oefn~NlkYP3t&Mf|z4&Q*KjZh^kJi3# zcz;s=|3ZeJpKTi+Pnh0!EJ}I*pY}yh?N@&(bYb~)njeo3a7+0-Aj)r_+Rxapj%5hw zXJcs0WDvOF$-JZP-yYjNZp!uZ>~9r!O#8ot@y)zvw-0BVe0u)V*|xpZYxn-oZ+B*| zK6Sj|ckD&CatHqt^PkuMKeeA@*8iv4|5x4pV}A36#gnCrCtjO#`oEmb|IGK6KUdaE)3S!NxzIm~lHDkBsn+Q*L)J^q;HQvHSnaq*dMX<`?Xfb};{vxmjlZ z&mXtGDRs=7KOt=^H^Zxo3t5=^~)o^kOI8p-34lI7IJ_o4khkxD6j5VegKOgSAblgzT gOV z!--W4eg~xd8+1CJ8{BL7TF-O+&VdTPutS^$PY)fj70-C&B&D$}_4DOfKj*0%Zd=-2 zxz#wy+Y4kY9&kX_eCyt?d6%o69lrZ_E4#79-(R;=Zq>&yT-)tiHFMkipI_#^aDtGm#eqgw@jH-_gC?E z{%k(6)qmIA(wqJ`xBl=?=`XL>Z!3N;Zz`(;6nOVA_RH)3OZRs$ED4EcRlc;o{)OR} zg{6!hn*X=kPk(XU{)@!AwIAgiybK@he!+iZzeV1(^^f1atXsEBo;j@C)p#%SukL?U z|DP|c7uxdw?FIYu+xP$DdtmeF(&CACOup8$y|16W%tSK7rZ#)`zvs-o&;4KgDsW-> z^_3Z`w+IQ^?pXPDZTakL#`5ppt+$df>D`(KW`k$+9qeY%rfBQez(>o_L z{$eU9+c1&%s+pR(Wgp|`^$oT3@-B-bP0l+XkK DQX6}g literal 0 HcmV?d00001 diff --git a/Resources/Textures/Effects/speech.rsi/swarmer2.png b/Resources/Textures/Effects/speech.rsi/swarmer2.png new file mode 100644 index 0000000000000000000000000000000000000000..493cf3743d13f989afd2c72cae2ad3aed3012017 GIT binary patch literal 824 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K54sfsm$=7f9rZF%u{r7Zn45^s&_SRlsZATHd z3rCkRgg>|uk{}at<)aJxk@Y-D7v4vG}Ui)`o&E(^2Oux%;X1Z@a z_;=0R)v0N#L6+bHJDw~vz9swpq_zCJclEnxTs)~?@3rvfY_<*7?^aKD{dMm6_N4lj zRjZ!dm;HYAxWm87hCl0>e}<<%+wbe^W4=7~uZi^&owSnI&x(FM-?C==!B6#kXU=X8 zHJtbT*{9o|_e(9D$Fqw!;qL6W{odE37`_(ge~e}jS#g^|r(sJ1x5Ay<@%~f(Og{Hw z@6Xx)Y;|ux&R914a?u;9V=DDMek|%tFnmxMzUPbV2jibl^SLMN z-_3r3zw%|q8k0|_?~CdF?{Bl~>u{+12@Xem-o+-@z>{aS_-^6+J)a-G`?tMYa_*l` z%SC4G&t%yBcwbfIlJ_+=+TYw)tn#Y+>HKW|5;dR`jnWmbyVd)%Kf?eO(=dD)=(iT%v}JiXU%-&~NJp7lfG!yfIZ7kn?w zKb3#3uX#G3{p|l=Q|m+X{=JiSuz#|2@x)7iru(<#@oNJmw7B50~wir|#vw#$w0* z?Ags#4LQ4b7i?vC_59wW{tXQG*q?=g#M=59&+mQ|Sax7B&^n;+{wwY;F$tUX;^)KJ l3;UCmI_Ak^b$G-72l872HT@V&mBK&@JYD@<);T3K0RWHQTg?Cf literal 0 HcmV?d00001 diff --git a/Resources/Textures/Effects/speech.rsi/syndibot0.png b/Resources/Textures/Effects/speech.rsi/syndibot0.png new file mode 100644 index 0000000000000000000000000000000000000000..31160c37089db4bbdafdacf786b997354410adc9 GIT binary patch literal 346 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7xzrU{v&UaSW-L^Y-q++`|SutO46j zaLGCet7w-zTC>1!f#N0?f3}e8raU5@JRkRV=N@3MZDjv+gafD_89aD%Z`b##2mfzx z<||^-{2c$QKj_qa`)$|e)y=qQ^sHXKmf=_s!%5ACCn5(*O{^JY{QCY|~- z{a^CUDLa?mo@cqX=jU5KKR-s5|nxxu>h2%Q~loCIA3GiID&R literal 0 HcmV?d00001 diff --git a/Resources/Textures/Effects/speech.rsi/syndibot1.png b/Resources/Textures/Effects/speech.rsi/syndibot1.png new file mode 100644 index 0000000000000000000000000000000000000000..3cc5779af6f5c8ad743788554eeb9f60529ab70d GIT binary patch literal 210 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=Gd*1#Ln`LHy>^hd*?@<^VS5s5 z+Xt3ZR<(Cr299knglZK7Il1H=xXRZbIdyoOj-?<_4+uECmD~Jz!{gfW5T)?UXVcHb z)JAw&+RW58@jh7obI-Ek`EM)Yte#ivvHs3Eev5m)j+w%va;~gxw;DQr9k+kv&-^Z+ z=EyOrIol59Fo$es+s-r7^2k1Wr>%LhHelmTe=RoDzprZDtm%{CE1l%7=TLm*l*dHJ1XlfWc<#($~8b|L+S|TpaVu*JVlP z+O2oHB_GaVo%goZdeel!OKfYDat|o4Td&M5c~tlW)9O17^OHYVi|*nyxGV8zxtojb j?SCAXqkeq=n|9_2^UmMGYn;84&w}`#u6{1-oD!M