From ac42e7c7854dbe208c863871f0a38356fd7a3ed8 Mon Sep 17 00:00:00 2001 From: Ethan Moffat Date: Thu, 31 Mar 2022 00:57:17 -0700 Subject: [PATCH] Implement drunk action from using beer types items. Implement drunk text transformation --- EOBot/Program.cs | 2 + .../Character/CharacterRenderProperties.cs | 12 ++++ .../Character/ICharacterRenderProperties.cs | 3 + EOLib/Domain/Chat/ChatActions.cs | 13 ++-- EOLib/Domain/Chat/ChatProcessor.cs | 30 +++++++++ EOLib/Domain/Notifiers/IEmoteNotifier.cs | 4 ++ EOLib/PacketHandlers/Items/UseItemHandler.cs | 7 +-- EndlessClient/Controllers/ChatController.cs | 27 ++------ EndlessClient/Controllers/NumPadController.cs | 2 +- .../HUD/Controls/HudControlsFactory.cs | 4 +- .../Character/CharacterAnimationActions.cs | 22 +++++-- .../Rendering/Character/CharacterAnimator.cs | 61 +++++++++++++++++++ .../Rendering/OldCharacterRenderer.cs | 31 ---------- 13 files changed, 147 insertions(+), 71 deletions(-) diff --git a/EOBot/Program.cs b/EOBot/Program.cs index bacccca1c..6dc93d8a8 100644 --- a/EOBot/Program.cs +++ b/EOBot/Program.cs @@ -136,6 +136,8 @@ public void JunkItem(short id, int amountRemoved) var maxWeight = _characterProvider.MainCharacter.Stats[CharacterStat.MaxWeight]; ConsoleHelper.WriteMessage(ConsoleHelper.Type.JunkItem, $"{weight,3}/{maxWeight,3} - weight - {inventoryCount?.Amount ?? 0} in inventory"); } + + public void MakeDrunk() { } } [AutoMappedType] diff --git a/EOLib/Domain/Character/CharacterRenderProperties.cs b/EOLib/Domain/Character/CharacterRenderProperties.cs index 9892ea956..2bb09a99a 100644 --- a/EOLib/Domain/Character/CharacterRenderProperties.cs +++ b/EOLib/Domain/Character/CharacterRenderProperties.cs @@ -37,6 +37,7 @@ public class CharacterRenderProperties : ICharacterRenderProperties public bool IsHidden { get; private set; } public bool IsDead { get; private set; } + public bool IsDrunk { get; private set; } public bool IsRangedWeapon { get; private set; } @@ -232,6 +233,13 @@ public ICharacterRenderProperties WithAlive() return props; } + public ICharacterRenderProperties WithIsDrunk(bool drunk) + { + var props = MakeCopy(this); + props.IsDrunk = drunk; + return props; + } + public object Clone() { return MakeCopy(this); @@ -269,6 +277,8 @@ private static CharacterRenderProperties MakeCopy(ICharacterRenderProperties oth IsHidden = other.IsHidden, IsDead = other.IsDead, + IsDrunk = other.IsDrunk, + IsRangedWeapon = other.IsRangedWeapon }; } @@ -298,6 +308,7 @@ public override bool Equals(object obj) Emote == properties.Emote && IsHidden == properties.IsHidden && IsDead == properties.IsDead && + IsDrunk == properties.IsDrunk && IsRangedWeapon == properties.IsRangedWeapon; } @@ -326,6 +337,7 @@ public override int GetHashCode() hashCode = hashCode * -1521134295 + Emote.GetHashCode(); hashCode = hashCode * -1521134295 + IsHidden.GetHashCode(); hashCode = hashCode * -1521134295 + IsDead.GetHashCode(); + hashCode = hashCode * -1521134295 + IsDrunk.GetHashCode(); hashCode = hashCode * -1521134295 + IsRangedWeapon.GetHashCode(); return hashCode; } diff --git a/EOLib/Domain/Character/ICharacterRenderProperties.cs b/EOLib/Domain/Character/ICharacterRenderProperties.cs index 8f0f6df1e..8bf6f6c5d 100644 --- a/EOLib/Domain/Character/ICharacterRenderProperties.cs +++ b/EOLib/Domain/Character/ICharacterRenderProperties.cs @@ -33,6 +33,7 @@ public interface ICharacterRenderProperties : ICloneable bool IsHidden { get; } bool IsDead { get; } + bool IsDrunk { get; } bool IsRangedWeapon { get; } @@ -63,5 +64,7 @@ public interface ICharacterRenderProperties : ICloneable ICharacterRenderProperties WithIsHidden(bool hidden); ICharacterRenderProperties WithDead(); ICharacterRenderProperties WithAlive(); + + ICharacterRenderProperties WithIsDrunk(bool drunk); } } diff --git a/EOLib/Domain/Chat/ChatActions.cs b/EOLib/Domain/Chat/ChatActions.cs index e4ab67676..14179bbe9 100644 --- a/EOLib/Domain/Chat/ChatActions.cs +++ b/EOLib/Domain/Chat/ChatActions.cs @@ -36,14 +36,14 @@ public ChatActions(IChatRepository chatRepository, _chatProcessor = chatProcessor; } - public async Task SendChatToServer(string chat, string targetCharacter) + public string SendChatToServer(string chat, string targetCharacter) { var chatType = _chatTypeCalculator.CalculateChatType(chat); if (chatType == ChatType.Command) { if (HandleCommand(chat)) - return; + return chat; //treat unhandled command as local chat chatType = ChatType.Local; @@ -59,10 +59,15 @@ public async Task SendChatToServer(string chat, string targetCharacter) chat = _chatProcessor.RemoveFirstCharacterIfNeeded(chat, chatType, targetCharacter); + if (_characterProvider.MainCharacter.RenderProperties.IsDrunk) + chat = _chatProcessor.MakeDrunk(chat); + var chatPacket = _chatPacketBuilder.BuildChatPacket(chatType, chat, targetCharacter); - await _packetSendService.SendPacketAsync(chatPacket); + _packetSendService.SendPacket(chatPacket); AddChatForLocalDisplay(chatType, chat, targetCharacter); + + return chat; } /// @@ -126,6 +131,6 @@ private void AddChatForLocalDisplay(ChatType chatType, string chat, string targe public interface IChatActions { - Task SendChatToServer(string chat, string targetCharacter); + string SendChatToServer(string chat, string targetCharacter); } } \ No newline at end of file diff --git a/EOLib/Domain/Chat/ChatProcessor.cs b/EOLib/Domain/Chat/ChatProcessor.cs index ed3c07960..aecaf2354 100644 --- a/EOLib/Domain/Chat/ChatProcessor.cs +++ b/EOLib/Domain/Chat/ChatProcessor.cs @@ -1,4 +1,6 @@ using System; +using System.Linq; +using System.Text; using AutomaticTypeMapper; namespace EOLib.Domain.Chat @@ -6,6 +8,8 @@ namespace EOLib.Domain.Chat [AutoMappedType] public class ChatProcessor : IChatProcessor { + private readonly Random _random = new Random(); + public string RemoveFirstCharacterIfNeeded(string chat, ChatType chatType, string targetCharacter) { switch (chatType) @@ -33,10 +37,36 @@ public string RemoveFirstCharacterIfNeeded(string chat, ChatType chatType, strin throw new ArgumentOutOfRangeException(nameof(chatType)); } } + + public string MakeDrunk(string input) + { + // implementation from Phorophor::notepad (thanks Apollo) + // https://discord.com/channels/723989119503696013/785190349026492437/791376941822246953 + var ret = new StringBuilder(); + + foreach (var c in input) + { + var repeats = _random.Next(0, 8) < 6 ? 1 : 2; + ret.Append(c, repeats); + + if ((c == 'a' || c == 'e') && _random.NextDouble() / 1.0 < 0.555) + ret.Append('j'); + + if ((c == 'u' || c == 'o') && _random.NextDouble() / 1.0 < 0.444) + ret.Append('w'); + + if ((c == ' ') && _random.NextDouble() / 1.0 < 0.333) + ret.Append(" *hic*"); + } + + return ret.ToString(); + } } public interface IChatProcessor { string RemoveFirstCharacterIfNeeded(string input, ChatType chatType, string targetCharacter); + + string MakeDrunk(string input); } } diff --git a/EOLib/Domain/Notifiers/IEmoteNotifier.cs b/EOLib/Domain/Notifiers/IEmoteNotifier.cs index 74518b193..f29c022c0 100644 --- a/EOLib/Domain/Notifiers/IEmoteNotifier.cs +++ b/EOLib/Domain/Notifiers/IEmoteNotifier.cs @@ -6,11 +6,15 @@ namespace EOLib.Domain.Notifiers public interface IEmoteNotifier { void NotifyEmote(short playerId, Emote emote); + + void MakeMainPlayerDrunk(); } [AutoMappedType] public class NoOpEmoteNotifier : IEmoteNotifier { public void NotifyEmote(short playerId, Emote emote) { } + + public void MakeMainPlayerDrunk() { } } } diff --git a/EOLib/PacketHandlers/Items/UseItemHandler.cs b/EOLib/PacketHandlers/Items/UseItemHandler.cs index 8748829b2..9dd110cf4 100644 --- a/EOLib/PacketHandlers/Items/UseItemHandler.cs +++ b/EOLib/PacketHandlers/Items/UseItemHandler.cs @@ -86,10 +86,9 @@ public override bool HandlePacket(IPacket packet) renderProps = renderProps.WithHairColor(hairColor); break; case ItemType.Beer: - // todo: drunk - // old logic: - // OldWorld.Instance.ActiveCharacterRenderer.MakeDrunk(); - // m_game.Hud.SetStatusLabel(EOResourceID.STATUS_LABEL_TYPE_WARNING, EOResourceID.STATUS_LABEL_ITEM_USE_DRUNK); + renderProps = renderProps.WithIsDrunk(true); + foreach (var notifier in _emoteNotifiers) + notifier.MakeMainPlayerDrunk(); break; case ItemType.EffectPotion: var potionId = packet.ReadShort(); diff --git a/EndlessClient/Controllers/ChatController.cs b/EndlessClient/Controllers/ChatController.cs index 140ff5e70..8d7534bdf 100644 --- a/EndlessClient/Controllers/ChatController.cs +++ b/EndlessClient/Controllers/ChatController.cs @@ -18,45 +18,32 @@ public class ChatController : IChatController private readonly IChatTextBoxActions _chatTextBoxActions; private readonly IChatActions _chatActions; private readonly IPrivateMessageActions _privateMessageActions; - private readonly IGameStateActions _gameStateActions; - private readonly IErrorDialogDisplayAction _errorDisplayAction; private readonly IChatBubbleActions _chatBubbleActions; - private readonly ISafeNetworkOperationFactory _safeNetworkOperationFactory; private readonly IHudControlProvider _hudControlProvider; public ChatController(IChatTextBoxActions chatTextBoxActions, IChatActions chatActions, IPrivateMessageActions privateMessageActions, - IGameStateActions gameStateActions, - IErrorDialogDisplayAction errorDisplayAction, IChatBubbleActions chatBubbleActions, - ISafeNetworkOperationFactory safeNetworkOperationFactory, IHudControlProvider hudControlProvider) { _chatTextBoxActions = chatTextBoxActions; _chatActions = chatActions; _privateMessageActions = privateMessageActions; - _gameStateActions = gameStateActions; - _errorDisplayAction = errorDisplayAction; _chatBubbleActions = chatBubbleActions; - _safeNetworkOperationFactory = safeNetworkOperationFactory; _hudControlProvider = hudControlProvider; } - public async Task SendChatAndClearTextBox() + public void SendChatAndClearTextBox() { var localTypedText = ChatTextBox.Text; var targetCharacter = _privateMessageActions.GetTargetCharacter(localTypedText); - var sendChatOperation = _safeNetworkOperationFactory.CreateSafeAsyncOperation( - async () => await _chatActions.SendChatToServer(localTypedText, targetCharacter), - SetInitialStateAndShowError); - if (!await sendChatOperation.Invoke()) - return; + var updatedChat = _chatActions.SendChatToServer(localTypedText, targetCharacter); _chatTextBoxActions.ClearChatText(); - _chatBubbleActions.ShowChatBubbleForMainCharacter(localTypedText); + _chatBubbleActions.ShowChatBubbleForMainCharacter(updatedChat); } public void SelectChatTextBox() @@ -64,18 +51,12 @@ public void SelectChatTextBox() _chatTextBoxActions.FocusChatTextBox(); } - private void SetInitialStateAndShowError(NoDataSentException ex) - { - _gameStateActions.ChangeToState(GameStates.Initial); - _errorDisplayAction.ShowException(ex); - } - private ChatTextBox ChatTextBox => _hudControlProvider.GetComponent(HudControlIdentifier.ChatTextBox); } public interface IChatController { - Task SendChatAndClearTextBox(); + void SendChatAndClearTextBox(); void SelectChatTextBox(); } diff --git a/EndlessClient/Controllers/NumPadController.cs b/EndlessClient/Controllers/NumPadController.cs index 3b890f18a..467bf5f3d 100644 --- a/EndlessClient/Controllers/NumPadController.cs +++ b/EndlessClient/Controllers/NumPadController.cs @@ -28,7 +28,7 @@ public void Emote(Emote whichEmote) var mainRenderer = _characterRendererProvider.MainCharacterRenderer; mainRenderer.MatchSome(renderer => { - if (renderer.Character.RenderProperties.IsActing(CharacterActionState.Emote)) + if (!renderer.Character.RenderProperties.IsActing(CharacterActionState.Standing)) return; _characterActions.Emote(whichEmote); diff --git a/EndlessClient/HUD/Controls/HudControlsFactory.cs b/EndlessClient/HUD/Controls/HudControlsFactory.cs index ca01f980a..40dc94d08 100644 --- a/EndlessClient/HUD/Controls/HudControlsFactory.cs +++ b/EndlessClient/HUD/Controls/HudControlsFactory.cs @@ -349,8 +349,8 @@ private ChatTextBox CreateChatTextBox() Visible = true, DrawOrder = HUD_CONTROL_LAYER }; - chatTextBox.OnEnterPressed += async (o, e) => await _chatController.SendChatAndClearTextBox(); - chatTextBox.OnClicked += (o, e) => _chatController.SelectChatTextBox(); + chatTextBox.OnEnterPressed += (_, _) => _chatController.SendChatAndClearTextBox(); + chatTextBox.OnClicked += (_, _) => _chatController.SelectChatTextBox(); return chatTextBox; } diff --git a/EndlessClient/Rendering/Character/CharacterAnimationActions.cs b/EndlessClient/Rendering/Character/CharacterAnimationActions.cs index 167a35c58..55e26fa95 100644 --- a/EndlessClient/Rendering/Character/CharacterAnimationActions.cs +++ b/EndlessClient/Rendering/Character/CharacterAnimationActions.cs @@ -1,5 +1,6 @@ using AutomaticTypeMapper; using EndlessClient.ControlSets; +using EndlessClient.HUD; using EndlessClient.HUD.Controls; using EndlessClient.Rendering.Map; using EOLib; @@ -9,6 +10,7 @@ using EOLib.Domain.Notifiers; using EOLib.IO.Map; using EOLib.IO.Repositories; +using EOLib.Localization; using Optional; namespace EndlessClient.Rendering.Character @@ -23,6 +25,7 @@ public class CharacterAnimationActions : ICharacterAnimationActions, IOtherChara private readonly ICurrentMapProvider _currentMapProvider; private readonly ISpikeTrapActions _spikeTrapActions; private readonly IESFFileProvider _esfFileProvider; + private readonly IStatusLabelSetter _statusLabelSetter; public CharacterAnimationActions(IHudControlProvider hudControlProvider, ICharacterRepository characterRepository, @@ -30,7 +33,8 @@ public CharacterAnimationActions(IHudControlProvider hudControlProvider, ICharacterRendererProvider characterRendererProvider, ICurrentMapProvider currentMapProvider, ISpikeTrapActions spikeTrapActions, - IESFFileProvider esfFileProvider) + IESFFileProvider esfFileProvider, + IStatusLabelSetter statusLabelSetter) { _hudControlProvider = hudControlProvider; _characterRepository = characterRepository; @@ -39,6 +43,7 @@ public CharacterAnimationActions(IHudControlProvider hudControlProvider, _currentMapProvider = currentMapProvider; _spikeTrapActions = spikeTrapActions; _esfFileProvider = esfFileProvider; + _statusLabelSetter = statusLabelSetter; } public void Face(EODirection direction) @@ -176,6 +181,16 @@ public void NotifyEarthquake(byte strength) mapRenderer.StartEarthquake(strength); } + public void NotifyEmote(short playerId, Emote emote) + { + Animator.Emote(playerId, emote); + } + + public void MakeMainPlayerDrunk() + { + _statusLabelSetter.SetStatusLabel(EOResourceID.STATUS_LABEL_TYPE_WARNING, EOResourceID.STATUS_LABEL_ITEM_USE_DRUNK); + } + private void ShowWaterSplashiesIfNeeded(CharacterActionState action, int characterID) { var character = characterID == _characterRepository.MainCharacter.ID @@ -211,11 +226,6 @@ private void ShowWaterSplashiesIfNeeded(CharacterActionState action, int charact }); } - public void NotifyEmote(short playerId, Emote emote) - { - Animator.Emote(playerId, emote); - } - private ICharacterAnimator Animator => _hudControlProvider.GetComponent(HudControlIdentifier.CharacterAnimator); } diff --git a/EndlessClient/Rendering/Character/CharacterAnimator.cs b/EndlessClient/Rendering/Character/CharacterAnimator.cs index e7a424e4f..a646e8f8c 100644 --- a/EndlessClient/Rendering/Character/CharacterAnimator.cs +++ b/EndlessClient/Rendering/Character/CharacterAnimator.cs @@ -9,6 +9,7 @@ using Optional; using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; namespace EndlessClient.Rendering.Character @@ -33,6 +34,12 @@ public class CharacterAnimator : GameComponent, ICharacterAnimator private readonly Dictionary _otherPlayerStartSpellCastTimes; private readonly Dictionary _startEmoteTimes; + private readonly Random _random; + + private Option _drunkStart; + private Option _drunkTimeSinceLastEmote; + private int _drunkIntervalSeconds; + private Queue _walkPath; private Option _targetCoordinate; @@ -57,6 +64,9 @@ public CharacterAnimator(IEndlessGameProvider gameProvider, _otherPlayerStartAttackingTimes = new Dictionary(); _otherPlayerStartSpellCastTimes = new Dictionary(); _startEmoteTimes = new Dictionary(); + + _random = new Random(); + _walkPath = new Queue(); } @@ -172,6 +182,12 @@ public void StartOtherCharacterSpellCast(int characterID) public void Emote(int characterID, Emote whichEmote) { + if (_otherPlayerStartWalkingTimes.ContainsKey(characterID) || + _otherPlayerStartAttackingTimes.ContainsKey(characterID) || + _otherPlayerStartSpellCastTimes.ContainsKey(characterID) || + _startEmoteTimes.ContainsKey(characterID)) + return; + var startEmoteTime = new RenderFrameActionTime(characterID); if (characterID == _characterRepository.MainCharacter.ID) { @@ -425,6 +441,51 @@ private void AnimateCharacterSpells() private void AnimateCharacterEmotes() { + // todo: Special1 for FairySoda items is 10 or 100. Possible time parameter for how long effect lasts + // small fairysoda lasts ~ 0:30, large ~ 2:15 + _drunkStart.Match( + some: ds => + { + if ((DateTime.Now - ds).TotalSeconds > 30) + { + _drunkStart = Option.None(); + _drunkTimeSinceLastEmote = Option.None(); + _drunkIntervalSeconds = 0; + + _characterRepository.MainCharacter = _characterRepository.MainCharacter.WithRenderProperties( + _characterRepository.MainCharacter.RenderProperties.WithIsDrunk(false)); + } + else + { + _drunkTimeSinceLastEmote.MatchSome(dt => + { + if (dt.ElapsedMilliseconds > _drunkIntervalSeconds * 1000) + { + _drunkIntervalSeconds = _random.Next(4, 7); + _drunkTimeSinceLastEmote = Option.Some(Stopwatch.StartNew()); + + var rp = _characterRepository.MainCharacter.RenderProperties.WithEmote(EOLib.Domain.Character.Emote.Drunk); + _characterRepository.MainCharacter = _characterRepository.MainCharacter.WithRenderProperties(rp); + _startEmoteTimes[_characterRepository.MainCharacter.ID] = new RenderFrameActionTime(_characterRepository.MainCharacter.ID); + _characterActions.Emote(EOLib.Domain.Character.Emote.Drunk); + } + }); + } + }, + none: () => + { + if (_characterRepository.MainCharacter.RenderProperties.IsDrunk) + { + _drunkStart = Option.Some(DateTime.Now); + _drunkIntervalSeconds = _random.Next(2, 6); + _drunkTimeSinceLastEmote = Option.Some(Stopwatch.StartNew()); + + var rp = _characterRepository.MainCharacter.RenderProperties.WithEmote(EOLib.Domain.Character.Emote.Drunk); + _characterRepository.MainCharacter = _characterRepository.MainCharacter.WithRenderProperties(rp); + _startEmoteTimes[_characterRepository.MainCharacter.ID] = new RenderFrameActionTime(_characterRepository.MainCharacter.ID); + } + }); + var playersDoneEmoting = new HashSet(); foreach (var pair in _startEmoteTimes.Values) { diff --git a/EndlessClient/Rendering/OldCharacterRenderer.cs b/EndlessClient/Rendering/OldCharacterRenderer.cs index efb4aa29f..df0769ff7 100644 --- a/EndlessClient/Rendering/OldCharacterRenderer.cs +++ b/EndlessClient/Rendering/OldCharacterRenderer.cs @@ -93,9 +93,6 @@ public EODirection Facing private DateTime? m_deadTime; private DateTime m_lastActTime; - private DateTime? m_drunkTime; - private int m_drunkOffset; - private DateTime? _spellInvocationStartTime; private CharacterActionState State => Character.State; @@ -252,7 +249,6 @@ public override void Update(GameTime gameTime) { _adjustSP(gameTime); _checkAFKCharacter(); - _checkHandleDrunkCharacter(); } } @@ -378,27 +374,6 @@ private void _checkAFKCharacter() //} } - private void _checkHandleDrunkCharacter() - { - //if (m_drunkTime.HasValue && Character.IsDrunk) - //{ - // //note: these timer values (between 1-6 seconds and 30 seconds) are completely arbitrary - // if (!m_lastEmoteTime.HasValue || (DateTime.Now - m_lastEmoteTime.Value).TotalMilliseconds > m_drunkOffset) - // { - // m_lastEmoteTime = DateTime.Now; - // Character.Emote(Emote.Drunk); - // PlayerEmote(); - // m_drunkOffset = (new Random()).Next(1000, 6000); //between 1-6 seconds - // } - - // if ((DateTime.Now - m_drunkTime.Value).TotalMilliseconds >= 30000) - // { - // m_drunkTime = null; - // Character.IsDrunk = false; - // } - //} - } - private bool _getMouseOverActual() { var skinDrawLoc = _getSkinDrawLoc(); @@ -1079,12 +1054,6 @@ public void SetDamageCounterValue(int value, int pctHealth, bool isHeal = false) m_damageCounter.SetValue(value, pctHealth, isHeal); } - public void MakeDrunk() - { - m_drunkTime = DateTime.Now; - Character.IsDrunk = true; - } - #region Spell Casting //Workflow for spells (main player):