diff --git a/EOLib.Localization/EOResourceID.cs b/EOLib.Localization/EOResourceID.cs index 72e00358..9687731c 100644 --- a/EOLib.Localization/EOResourceID.cs +++ b/EOLib.Localization/EOResourceID.cs @@ -161,6 +161,11 @@ public enum EOResourceID SETTING_KEYBOARD_SWEDISH = 255, SETTING_KEYBOARD_AZERTY = 256, + BOARD_TOWN_BOARD = 271, + BOARD_TOWN_BOARD_NOW_VIEWED = 272, + BOARD_LOADING_MESSAGE = 273, + BOARD_POSTING_NEW_MESSAGE = 274, + JAIL_WARNING_CANNOT_DROP_ITEMS = 275, JAIL_WARNING_CANNOT_TRADE = 276, JAIL_WARNING_CANNOT_USE_GLOBAL = 277, diff --git a/EOLib/Domain/Login/LoginActions.cs b/EOLib/Domain/Login/LoginActions.cs index e2126e35..3af287c6 100644 --- a/EOLib/Domain/Login/LoginActions.cs +++ b/EOLib/Domain/Login/LoginActions.cs @@ -113,6 +113,8 @@ public async Task RequestCharacterLogin(Character.Character character) .WithStats(data.CharacterStats); _playerInfoRepository.IsFirstTimePlayer = data.FirstTimePlayer; + _playerInfoRepository.PlayerHasAdminCharacter = _characterSelectorRepository.Characters.Any(x => x.AdminLevel > 0); + _currentMapStateRepository.CurrentMapID = data.MapID; _currentMapStateRepository.JailMapID = data.JailMap; diff --git a/EOLib/Domain/Login/PlayerInfoRepository.cs b/EOLib/Domain/Login/PlayerInfoRepository.cs index 4e7ce55a..4b1fc547 100644 --- a/EOLib/Domain/Login/PlayerInfoRepository.cs +++ b/EOLib/Domain/Login/PlayerInfoRepository.cs @@ -13,6 +13,8 @@ public interface IPlayerInfoRepository bool IsFirstTimePlayer { get; set; } bool PlayerIsInGame { get; set; } + + bool PlayerHasAdminCharacter { get; set; } } public interface IPlayerInfoProvider @@ -26,6 +28,8 @@ public interface IPlayerInfoProvider bool IsFirstTimePlayer { get; } bool PlayerIsInGame { get; } + + bool PlayerHasAdminCharacter { get; } } [AutoMappedType(IsSingleton = true)] @@ -41,6 +45,8 @@ public sealed class PlayerInfoRepository : IPlayerInfoRepository, IPlayerInfoPro public bool PlayerIsInGame { get; set; } + public bool PlayerHasAdminCharacter { get; set; } + public void ResetState() { LoggedInAccountName = ""; @@ -48,6 +54,7 @@ public void ResetState() PlayerID = 0; IsFirstTimePlayer = false; PlayerIsInGame = false; + PlayerHasAdminCharacter = false; } } } diff --git a/EndlessClient/Dialogs/Actions/InGameDialogActions.cs b/EndlessClient/Dialogs/Actions/InGameDialogActions.cs index 45b87a3b..e2a85f01 100644 --- a/EndlessClient/Dialogs/Actions/InGameDialogActions.cs +++ b/EndlessClient/Dialogs/Actions/InGameDialogActions.cs @@ -1,14 +1,17 @@ using AutomaticTypeMapper; using EndlessClient.Audio; using EndlessClient.Dialogs.Factories; +using EndlessClient.HUD; using EOLib.Domain.Character; using EOLib.Domain.Interact.Quest; using EOLib.Domain.Interact.Shop; using EOLib.Domain.Interact.Skill; +using EOLib.Localization; using Optional; using System; using System.Collections.Generic; using System.Linq; +using XNAControls; namespace EndlessClient.Dialogs.Actions { @@ -32,6 +35,7 @@ public class InGameDialogActions : IInGameDialogActions private readonly ITradeDialogFactory _tradeDialogFactory; private readonly IBoardDialogFactory _boardDialogFactory; private readonly ISfxPlayer _sfxPlayer; + private readonly IStatusLabelSetter _statusLabelSetter; private readonly IShopDialogFactory _shopDialogFactory; private readonly IQuestDialogFactory _questDialogFactory; @@ -53,7 +57,8 @@ public class InGameDialogActions : IInGameDialogActions IScrollingListDialogFactory scrollingListDialogFactory, ITradeDialogFactory tradeDialogFactory, IBoardDialogFactory boardDialogFactory, - ISfxPlayer sfxPlayer) + ISfxPlayer sfxPlayer, + IStatusLabelSetter statusLabelSetter) { _friendIgnoreListDialogFactory = friendIgnoreListDialogFactory; _paperdollDialogFactory = paperdollDialogFactory; @@ -72,6 +77,7 @@ public class InGameDialogActions : IInGameDialogActions _tradeDialogFactory = tradeDialogFactory; _boardDialogFactory = boardDialogFactory; _sfxPlayer = sfxPlayer; + _statusLabelSetter = statusLabelSetter; _shopDialogFactory = shopDialogFactory; _questDialogFactory = questDialogFactory; } @@ -314,27 +320,31 @@ public void ShowBoardDialog() dlg.DialogClosed += (_, _) => _activeDialogRepository.BoardDialog = Option.None(); _activeDialogRepository.BoardDialog = Option.Some(dlg); - UseDefaultDialogSounds(dlg); - dlg.Show(); + + UseDefaultDialogSounds(dlg); }); + + // the vanilla client shows the status label any time the server sends the BOARD_OPEN packet + _statusLabelSetter.SetStatusLabel(EOResourceID.STATUS_LABEL_TYPE_ACTION, EOResourceID.BOARD_TOWN_BOARD_NOW_VIEWED); } private void UseDefaultDialogSounds(ScrollingListDialog dialog) { UseDefaultDialogSounds((BaseEODialog)dialog); - EventHandler handler = (_, _) => _sfxPlayer.PlaySfx(SoundEffectID.DialogButtonClick); - dialog.AddAction += handler; - dialog.BackAction += handler; - dialog.NextAction += handler; - dialog.HistoryAction += handler; - dialog.ProgressAction += handler; + foreach (var button in dialog.ChildControls.OfType()) + button.OnClick += Handler; + + void Handler(object sender, EventArgs e) => _sfxPlayer.PlaySfx(SoundEffectID.DialogButtonClick); } private void UseDefaultDialogSounds(BaseEODialog dialog) { dialog.DialogClosing += (_, _) => _sfxPlayer.PlaySfx(SoundEffectID.DialogButtonClick); + + foreach (var textbox in dialog.ChildControls.OfType()) + textbox.OnGotFocus += (_, _) => _sfxPlayer.PlaySfx(SoundEffectID.TextBoxFocus); } private void UseQuestDialogSounds(QuestDialog dialog) diff --git a/EndlessClient/Dialogs/BoardDialog.cs b/EndlessClient/Dialogs/BoardDialog.cs index c95b65e7..f24e405f 100644 --- a/EndlessClient/Dialogs/BoardDialog.cs +++ b/EndlessClient/Dialogs/BoardDialog.cs @@ -1,14 +1,315 @@ -using EndlessClient.Dialogs.Services; +using EndlessClient.Content; +using EndlessClient.ControlSets; +using EndlessClient.Dialogs.Factories; +using EndlessClient.Dialogs.Services; +using EndlessClient.HUD.Controls; +using EndlessClient.UIControls; +using EOLib; +using EOLib.Domain.Character; +using EOLib.Domain.Interact.Board; +using EOLib.Domain.Login; using EOLib.Graphics; +using EOLib.Localization; +using Microsoft.Xna.Framework; +using MonoGame.Extended.Input.InputListeners; +using Optional; +using Optional.Collections; +using System; +using System.Collections.Generic; +using System.Linq; +using XNAControls; namespace EndlessClient.Dialogs { public class BoardDialog : ScrollingListDialog { + private enum BoardDialogState + { + ViewList, + ViewPost, + CreatePost, + } + + private readonly ILocalizedStringFinder _localizedStringFinder; + private readonly IEOMessageBoxFactory _eoMessageBoxFactory; + private readonly IBoardActions _boardActions; + private readonly IBoardRepository _boardRepository; + private readonly IPlayerInfoProvider _playerInfoProvider; + private readonly ICharacterProvider _characterProvider; + private readonly IHudControlProvider _hudControlProvider; + + private readonly XNATextBox _subject, _message; + + private BoardDialogState _state; + private HashSet _cachedPostInfo; + public BoardDialog(INativeGraphicsManager nativeGraphicsManager, - IEODialogButtonService dialogButtonService) - : base(nativeGraphicsManager, dialogButtonService, ScrollingListDialogSize.MediumWithHeader) + IEODialogButtonService dialogButtonService, + ILocalizedStringFinder localizedStringFinder, + IEOMessageBoxFactory eoMessageBoxFactory, + IBoardActions boardActions, + IBoardRepository boardRepository, + IPlayerInfoProvider playerInfoProvider, + ICharacterProvider characterProvider, + IContentProvider contentProvider, + IHudControlProvider hudControlProvider) + : base(nativeGraphicsManager, dialogButtonService, ScrollingListDialogSize.Medium) + { + _localizedStringFinder = localizedStringFinder; + _eoMessageBoxFactory = eoMessageBoxFactory; + _boardActions = boardActions; + _boardRepository = boardRepository; + _playerInfoProvider = playerInfoProvider; + _characterProvider = characterProvider; + _hudControlProvider = hudControlProvider; + ListItemType = ListDialogItem.ListItemStyle.Small; + Title = _localizedStringFinder.GetString(EOResourceID.BOARD_TOWN_BOARD); + _state = BoardDialogState.ViewList; + _cachedPostInfo = new HashSet(); + + _subject = new XNATextBox(new Rectangle(150, 44, 315, 19), Constants.FontSize08, caretTexture: contentProvider.Textures[ContentProvider.Cursor]) + { + TextAlignment = LabelAlignment.MiddleLeft, + TextColor = ColorConstants.LightGrayText, + Visible = false, + MaxWidth = 310 + }; + _subject.SetScrollWheelHandler(_scrollBar); + _subject.SetParentControl(this); + + _message = new XNATextBox(new Rectangle(18, 80, 430, 168), Constants.FontSize08, caretTexture: contentProvider.Textures[ContentProvider.Cursor]) + { + TextAlignment = LabelAlignment.TopLeft, + TextColor = ColorConstants.LightGrayText, + Visible = false, + MaxWidth = 412, + + Multiline = true, + ScrollHandler = _scrollBar, + RowSpacing = 16, + }; + _message.SetScrollWheelHandler(_scrollBar); + _message.SetParentControl(this); + + _add.OnClick += AddButton_Click; + _delete.OnClick += DeleteButton_Click; + } + + public override void Initialize() + { + _subject.Initialize(); + _message.Initialize(); + + base.Initialize(); + } + + protected override void OnUpdateControl(GameTime gameTime) { + switch (_state) + { + case BoardDialogState.ViewList: + if (!_cachedPostInfo.SetEquals(_boardRepository.Posts)) + { + ClearItemList(); + + _cachedPostInfo = new HashSet(_boardRepository.Posts); + + var index = 0; + foreach (var post in _cachedPostInfo) + { + var childItem = new ListDialogItem(this, ListDialogItem.ListItemStyle.Small, index++) + { + PrimaryText = char.ToUpper(post.Author[0]) + post.Author[1..], + SubText = post.Subject, + Data = post.PostId, + Visible = true, + UnderlineLinks = false, + ShowSubtext = true, + OffsetX = 2, + OffsetY = 48, + }; + + childItem.DrawArea = new Rectangle(childItem.DrawArea.Location, new Point(427, 16)); + childItem.SetPrimaryClickAction(ChildItem_Click); + childItem.SetScrollWheelHandler(this); + + AddItemToList(childItem, sortList: false); + } + + _scrollBar.ScrollToTop(); + } + + break; + case BoardDialogState.ViewPost: + if (_boardRepository.ActivePostMessage.Map(msg => msg != _message.Text).ValueOr(false)) + { + _boardRepository.ActivePostMessage.MatchSome(msg => _message.Text = msg); + _scrollBar.ScrollToTop(); + } + break; + } + + base.OnUpdateControl(gameTime); + } + + private void SetState(BoardDialogState state, int postId = -1) + { + // todo: + // 1. backspace on scrolled newline is broken + // 2. need 10 rows per message for parity (increase space between rows) + + if (state == _state) + return; + + _state = state; + + _titleText.DrawArea = _state == BoardDialogState.ViewList + ? GetTitleDrawArea(DialogSize) + : GetTitleDrawArea(DialogSize).WithPosition(new Vector2(150, _titleText.DrawArea.Y)); + + BackgroundTextureSource = _state == BoardDialogState.ViewList + ? GetBackgroundSourceRectangle(BackgroundTexture, DialogSize) + : GetBackgroundSourceRectangle(BackgroundTexture, DialogSize).Value.WithPosition(new Vector2(0, BackgroundTexture.Height / 2)); + + _scrollBar.LinesToRender = _state == BoardDialogState.ViewList + ? 12 + : 10; + + switch (_state) + { + case BoardDialogState.ViewList: + _hudControlProvider.GetComponent(HudControlIdentifier.ChatTextBox).Selected = true; + + Buttons = ScrollingListDialogButtons.AddCancel; + Title = _localizedStringFinder.GetString(EOResourceID.BOARD_TOWN_BOARD); + _subject.Visible = _message.Visible = false; + _subject.Selected = _message.Selected = false; + + _scrollBar.DrawArea = new Rectangle( + _scrollBar.DrawArea.X, 44, + _scrollBar.DrawArea.Width, GetScrollBarHeight(DialogSize)); + + break; + + case BoardDialogState.CreatePost: + Buttons = ScrollingListDialogButtons.OkCancel; + Title = _localizedStringFinder.GetString(EOResourceID.BOARD_POSTING_NEW_MESSAGE); + + _subject.Text = _message.Text = string.Empty; + _subject.Visible = _message.Visible = true; + _subject.Enabled = _message.Enabled = true; + + _subject.TabOrder = 0; + _message.TabOrder = 1; + + _subject.Selected = true; + + _scrollBar.DrawArea = new Rectangle( + _scrollBar.DrawArea.X, 74, + _scrollBar.DrawArea.Width, GetScrollBarHeight(DialogSize) - 30); + + ClearItemList(); + _cachedPostInfo.Clear(); + break; + + case BoardDialogState.ViewPost: + var author = _boardRepository.ActivePost.Map(x => x.Author).ValueOr(string.Empty); + var matchesAuthor = author.IndexOf(_characterProvider.MainCharacter.Name, StringComparison.OrdinalIgnoreCase) >= 0; + if (_playerInfoProvider.PlayerHasAdminCharacter || matchesAuthor) + Buttons = ScrollingListDialogButtons.DeleteCancel; + else + Buttons = ScrollingListDialogButtons.OffsetCancel; + + _boardRepository.Posts.SingleOrNone(x => x.PostId == postId) + .MatchSome(post => + { + Title = post.Author; + _subject.Text = post.Subject; + }); + + _subject.Visible = true; + _message.Text = _localizedStringFinder.GetString(EOResourceID.BOARD_LOADING_MESSAGE); + _message.Visible = true; + + _subject.Enabled = _message.Enabled = false; + + _scrollBar.DrawArea = new Rectangle( + _scrollBar.DrawArea.X, 74, + _scrollBar.DrawArea.Width, GetScrollBarHeight(DialogSize) - 30); + + ClearItemList(); + _cachedPostInfo.Clear(); + break; + } + } + + private void AddButton_Click(object sender, EventArgs e) + { + var numPostsByThisPlayer = _boardRepository.Posts.Count(x => string.Equals(x.Author, _characterProvider.MainCharacter.Name, StringComparison.OrdinalIgnoreCase)); + if (numPostsByThisPlayer > 2) + { + var dlg = _eoMessageBoxFactory.CreateMessageBox(DialogResourceID.BOARD_ERROR_TOO_MANY_MESSAGES); + dlg.ShowDialog(); + } + else + { + SetState(BoardDialogState.CreatePost); + } + } + + private void DeleteButton_Click(object sender, MouseEventArgs e) + { + _boardRepository.ActivePost.MatchSome(x => _boardActions.DeletePost(x.PostId)); + SetState(BoardDialogState.ViewList); + } + + protected override void CloseButton_Click(object sender, MouseEventArgs e) + { + if (sender == _cancel && _state == BoardDialogState.ViewList) + { + Close(XNADialogResult.Cancel); + } + else + { + if (sender == _ok && _state == BoardDialogState.CreatePost) + { + if (string.IsNullOrEmpty(_message.Text)) + { + var dlg = _eoMessageBoxFactory.CreateMessageBox(DialogResourceID.BOARD_ERROR_NO_MESSAGE); + dlg.ShowDialog(); + return; + } + else if (string.IsNullOrEmpty(_subject.Text)) + { + var dlg = _eoMessageBoxFactory.CreateMessageBox(DialogResourceID.BOARD_ERROR_NO_SUBJECT); + dlg.ShowDialog(); + return; + } + + _boardActions.AddPost(_subject.Text, _message.Text); + } + + _boardRepository.ActivePost = Option.None(); + + SetState(BoardDialogState.ViewList); + } + } + + private void ChildItem_Click(object sender, MouseEventArgs e) + { + var postId = sender is ListDialogItem itemSender + ? (int)itemSender.Data + : sender is IXNAHyperLink linkSender + ? (int)((ListDialogItem)linkSender.ImmediateParent).Data + : -1; + + if (postId >= 0) + { + _boardRepository.ActivePost = _boardRepository.Posts.SingleOrNone(x => x.PostId == postId); + + SetState(BoardDialogState.ViewPost, postId); + _boardActions.ViewPost(postId); + } } } } diff --git a/EndlessClient/Dialogs/ChestDialog.cs b/EndlessClient/Dialogs/ChestDialog.cs index eceee83a..0c047a0c 100644 --- a/EndlessClient/Dialogs/ChestDialog.cs +++ b/EndlessClient/Dialogs/ChestDialog.cs @@ -28,10 +28,8 @@ public class ChestDialog : ScrollingListDialog private readonly IInventorySpaceValidator _inventorySpaceValidator; private readonly IMapItemGraphicProvider _mapItemGraphicProvider; private readonly IChestDataProvider _chestDataProvider; - private readonly IHudControlProvider _hudControlProvider; private readonly IEIFFileProvider _eifFileProvider; private readonly ICharacterProvider _characterProvider; - private readonly InventoryPanel _inventoryPanel; private HashSet _cachedItems; @@ -44,7 +42,6 @@ public class ChestDialog : ScrollingListDialog IInventorySpaceValidator inventorySpaceValidator, IMapItemGraphicProvider mapItemGraphicProvider, IChestDataProvider chestDataProvider, - IHudControlProvider hudControlProvider, IEIFFileProvider eifFileProvider, ICharacterProvider characterProvider) : base(nativeGraphicsManager, dialogButtonService, dialogSize: ScrollingListDialogSize.LargeNoScroll) @@ -56,14 +53,12 @@ public class ChestDialog : ScrollingListDialog _inventorySpaceValidator = inventorySpaceValidator; _mapItemGraphicProvider = mapItemGraphicProvider; _chestDataProvider = chestDataProvider; - _hudControlProvider = hudControlProvider; _eifFileProvider = eifFileProvider; _characterProvider = characterProvider; ListItemType = ListDialogItem.ListItemStyle.Large; Buttons = ScrollingListDialogButtons.Cancel; - _inventoryPanel = _hudControlProvider.GetComponent(HudControlIdentifier.InventoryPanel); _cachedItems = new HashSet(); _statusLabelSetter.SetStatusLabel(EOResourceID.STATUS_LABEL_TYPE_ACTION, diff --git a/EndlessClient/Dialogs/Factories/BoardDialogFactory.cs b/EndlessClient/Dialogs/Factories/BoardDialogFactory.cs index 1bc1953c..ebfd4628 100644 --- a/EndlessClient/Dialogs/Factories/BoardDialogFactory.cs +++ b/EndlessClient/Dialogs/Factories/BoardDialogFactory.cs @@ -1,6 +1,12 @@ using AutomaticTypeMapper; +using EndlessClient.Content; +using EndlessClient.ControlSets; using EndlessClient.Dialogs.Services; +using EOLib.Domain.Character; +using EOLib.Domain.Interact.Board; +using EOLib.Domain.Login; using EOLib.Graphics; +using EOLib.Localization; namespace EndlessClient.Dialogs.Factories { @@ -9,17 +15,50 @@ public class BoardDialogFactory : IBoardDialogFactory { private readonly INativeGraphicsManager _nativeGraphicsManager; private readonly IEODialogButtonService _eoDialogButtonService; + private readonly ILocalizedStringFinder _localizedStringFinder; + private readonly IEOMessageBoxFactory _eoMessageBoxFactory; + private readonly IBoardActions _boardActions; + private readonly IBoardRepository _boardRepository; + private readonly IPlayerInfoProvider _playerInfoProvider; + private readonly ICharacterProvider _characterProvider; + private readonly IContentProvider _contentProvider; + private readonly IHudControlProvider _hudControlProvider; public BoardDialogFactory(INativeGraphicsManager nativeGraphicsManager, - IEODialogButtonService eoDialogButtonService) + IEODialogButtonService eoDialogButtonService, + ILocalizedStringFinder localizedStringFinder, + IEOMessageBoxFactory eoMessageBoxFactory, + IBoardActions boardActions, + IBoardRepository boardRepository, + IPlayerInfoProvider playerInfoProvider, + ICharacterProvider characterProvider, + IContentProvider contentProvider, + IHudControlProvider hudControlProvider) { _nativeGraphicsManager = nativeGraphicsManager; _eoDialogButtonService = eoDialogButtonService; + _localizedStringFinder = localizedStringFinder; + _eoMessageBoxFactory = eoMessageBoxFactory; + _boardActions = boardActions; + _boardRepository = boardRepository; + _playerInfoProvider = playerInfoProvider; + _characterProvider = characterProvider; + _contentProvider = contentProvider; + _hudControlProvider = hudControlProvider; } public BoardDialog Create() { - return new BoardDialog(_nativeGraphicsManager, _eoDialogButtonService); + return new BoardDialog(_nativeGraphicsManager, + _eoDialogButtonService, + _localizedStringFinder, + _eoMessageBoxFactory, + _boardActions, + _boardRepository, + _playerInfoProvider, + _characterProvider, + _contentProvider, + _hudControlProvider); } } diff --git a/EndlessClient/Dialogs/Factories/ChestDialogFactory.cs b/EndlessClient/Dialogs/Factories/ChestDialogFactory.cs index 3c39106d..731a80f8 100644 --- a/EndlessClient/Dialogs/Factories/ChestDialogFactory.cs +++ b/EndlessClient/Dialogs/Factories/ChestDialogFactory.cs @@ -24,7 +24,6 @@ public class ChestDialogFactory : IChestDialogFactory private readonly IInventorySpaceValidator _inventorySpaceValidator; private readonly IMapItemGraphicProvider _mapItemGraphicProvider; private readonly IChestDataProvider _chestDataProvider; - private readonly IHudControlProvider _hudControlProvider; private readonly IEIFFileProvider _eifFileProvider; private readonly ICharacterProvider _characterProvider; @@ -37,7 +36,6 @@ public class ChestDialogFactory : IChestDialogFactory IInventorySpaceValidator inventorySpaceValidator, IMapItemGraphicProvider mapItemGraphicProvider, IChestDataProvider chestDataProvider, - IHudControlProvider hudControlProvider, IEIFFileProvider eifFileProvider, ICharacterProvider characterProvider) { @@ -50,7 +48,6 @@ public class ChestDialogFactory : IChestDialogFactory _inventorySpaceValidator = inventorySpaceValidator; _mapItemGraphicProvider = mapItemGraphicProvider; _chestDataProvider = chestDataProvider; - _hudControlProvider = hudControlProvider; _eifFileProvider = eifFileProvider; _characterProvider = characterProvider; } @@ -66,7 +63,6 @@ public ChestDialog Create() _inventorySpaceValidator, _mapItemGraphicProvider, _chestDataProvider, - _hudControlProvider, _eifFileProvider, _characterProvider); } diff --git a/EndlessClient/Dialogs/ListDialogItem.cs b/EndlessClient/Dialogs/ListDialogItem.cs index 5cfb8937..779c8d3e 100644 --- a/EndlessClient/Dialogs/ListDialogItem.cs +++ b/EndlessClient/Dialogs/ListDialogItem.cs @@ -96,6 +96,14 @@ public string SubText public bool ShowIconBackGround { get; set; } + public bool ShowSubtext + { + get => _subText.Visible; + set => ((XNAControl)_subText).Visible = value; + } + + public bool UnderlineLinks { get; set; } = true; + public object Data { get; set; } public event EventHandler RightClick; @@ -133,7 +141,7 @@ public ListDialogItem(BaseEODialog parent, ListItemStyle style, int listIndex = AutoSize = true, BackColor = _primaryText.BackColor, ForeColor = _primaryText.ForeColor, - DrawPosition = new Vector2(56, 20), + DrawPosition = Style == ListItemStyle.Large ? new Vector2(56, 20) : new Vector2(100, 0), Text = " ", Visible = Style == ListItemStyle.Large }; @@ -166,7 +174,7 @@ public void SetPrimaryClickAction(EventHandler onClickAction) ForeColor = oldText.ForeColor, MouseOverColor = oldText.ForeColor, Text = oldText.Text, - Underline = true + Underline = UnderlineLinks }; ((XNAHyperLink)_primaryText).OnClick += onClickAction; @@ -195,7 +203,7 @@ public void SetSubtextClickAction(EventHandler onClickAction) ForeColor = oldText.ForeColor, MouseOverColor = oldText.ForeColor, Text = oldText.Text, - Underline = true + Underline = UnderlineLinks }; ((XNAHyperLink)_subText).OnClick += onClickAction; diff --git a/EndlessClient/Dialogs/QuestStatusDialog.cs b/EndlessClient/Dialogs/QuestStatusDialog.cs index 4e314503..2f2b1103 100644 --- a/EndlessClient/Dialogs/QuestStatusDialog.cs +++ b/EndlessClient/Dialogs/QuestStatusDialog.cs @@ -63,13 +63,13 @@ private void ShowHistory() if (_cachedHistory.Count == 0) { - AddItemToList( - new QuestStatusListDialogItem(this, QuestPage.History) - { - QuestName = _localizedStringFinder.GetString(EOResourceID.QUEST_DID_NOT_FINISH_ANY), - ShowIcons = false - }, - sortList: false); + var nextItem = new QuestStatusListDialogItem(this, QuestPage.History) + { + QuestName = _localizedStringFinder.GetString(EOResourceID.QUEST_DID_NOT_FINISH_ANY), + ShowIcons = false + }; + nextItem.SetScrollWheelHandler(this); + AddItemToList(nextItem, sortList: false); } foreach (var questName in _cachedHistory) @@ -79,6 +79,7 @@ private void ShowHistory() QuestName = questName, QuestProgress = _localizedStringFinder.GetString(EOResourceID.QUEST_COMPLETED), }; + nextItem.SetScrollWheelHandler(this); AddItemToList(nextItem, sortList: false); } @@ -93,13 +94,13 @@ private void ShowProgress() if (_cachedProgress.Count == 0) { - AddItemToList( - new QuestStatusListDialogItem(this, QuestPage.Progress) - { - QuestName = _localizedStringFinder.GetString(EOResourceID.QUEST_DID_NOT_START_ANY), - ShowIcons = false, - }, - sortList: false); + var nextItem = new QuestStatusListDialogItem(this, QuestPage.Progress) + { + QuestName = _localizedStringFinder.GetString(EOResourceID.QUEST_DID_NOT_START_ANY), + ShowIcons = false, + }; + nextItem.SetScrollWheelHandler(this); + AddItemToList(nextItem, sortList: false); } foreach (var quest in _cachedProgress) @@ -111,6 +112,7 @@ private void ShowProgress() Icon = (QuestStatusListDialogItem.QuestStatusIcon)quest.IconIndex, QuestProgress = quest.Target > 0 ? $"{quest.Progress} / {quest.Target}" : "n / a" }; + nextItem.SetScrollWheelHandler(this); AddItemToList(nextItem, sortList: false); } diff --git a/EndlessClient/Dialogs/ScrollingListDialog.cs b/EndlessClient/Dialogs/ScrollingListDialog.cs index 2d8eb984..e947e170 100644 --- a/EndlessClient/Dialogs/ScrollingListDialog.cs +++ b/EndlessClient/Dialogs/ScrollingListDialog.cs @@ -4,6 +4,7 @@ using EOLib.Graphics; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; +using MonoGame.Extended.Input.InputListeners; using System; using System.Collections.Generic; using System.Linq; @@ -14,32 +15,39 @@ namespace EndlessClient.Dialogs [Flags] public enum ScrollingListDialogButtons { - None = 0x00, - Add = 0x01, - Cancel = 0x02, - Back = 0x04, - Next = 0x08, - Ok = 0x10, - History = 0x20, - Progress = 0x40, - DualButtons = 0x80, - - AddCancel = DualButtons | Add | Cancel, - BackCancel = DualButtons | Back | Cancel, - BackOk = DualButtons | Back | Ok, - CancelOk = DualButtons | Cancel | Ok, - BackNext = DualButtons | Back | Next, - CancelNext = DualButtons | Cancel | Next, - HistoryOk = DualButtons | History | Ok, - ProgressOk = DualButtons | Progress | Ok, + None = 0x00, + Add = 0x01, + Cancel = 0x02, + Back = 0x04, + Next = 0x08, + Ok = 0x10, + History = 0x20, + Progress = 0x40, + Delete = 0x80, + DualButtons = 0x800, + // indicates a configuration in which a pairing of DualButtons is already defined, but the order is reversed + Alternate = 0x1000, + + AddCancel = DualButtons | Add | Cancel, + BackCancel = DualButtons | Back | Cancel, + BackOk = DualButtons | Back | Ok, + CancelOk = DualButtons | Cancel | Ok, + OkCancel = DualButtons | Cancel | Ok | Alternate, + BackNext = DualButtons | Back | Next, + CancelNext = DualButtons | Cancel | Next, + HistoryOk = DualButtons | History | Ok, + ProgressOk = DualButtons | Progress | Ok, + DeleteCancel = DualButtons | Delete | Cancel, + + // There is only one button, but we want it to show on the right side as if it were a dual button setting + OffsetCancel = DualButtons | Alternate | Cancel, } public enum ScrollingListDialogSize { Large, // standard dialog with large list items (locker, shop, friend/ignore list) LargeNoScroll, // standard dialog with large list items / no scrollbar (chest) - Medium, // quest progress/history dialog - MediumWithHeader, // board dialog + Medium, // quest progress/history dialog, board dialog Small, // npc quest dialog SmallNoScroll, // bank account dialog } @@ -53,16 +61,13 @@ public class ScrollingListDialog : BaseEODialog private ListDialogItem.ListItemStyle _listItemType; protected readonly XNAButton _add, _back, _cancel; - protected readonly XNAButton _next, _ok; + protected readonly XNAButton _next, _ok, _delete; protected readonly XNAButton _history, _progress; protected readonly Vector2 _button1Position, _button2Position, _buttonCenterPosition; private ScrollingListDialogButtons _buttons; - // cancel button debounce - private bool _otherClicked; - public IReadOnlyList NamesList => _listItems.Select(item => item.PrimaryText).ToList(); public string Title @@ -82,7 +87,6 @@ public int ItemsToShow { case ScrollingListDialogSize.Large: - case ScrollingListDialogSize.MediumWithHeader: case ScrollingListDialogSize.Medium: return 12; case ScrollingListDialogSize.Small: return 6; @@ -117,25 +121,30 @@ public ScrollingListDialogButtons Buttons _cancel.Visible = Buttons.HasFlag(ScrollingListDialogButtons.Cancel); _history.Visible = Buttons.HasFlag(ScrollingListDialogButtons.History); _progress.Visible = Buttons.HasFlag(ScrollingListDialogButtons.Progress); + _delete.Visible = Buttons.HasFlag(ScrollingListDialogButtons.Delete); if (Buttons.HasFlag(ScrollingListDialogButtons.DualButtons)) { if (Buttons == ScrollingListDialogButtons.BackCancel || - Buttons == ScrollingListDialogButtons.AddCancel) + Buttons == ScrollingListDialogButtons.AddCancel || + Buttons == ScrollingListDialogButtons.DeleteCancel) { _add.DrawPosition = _button1Position; _back.DrawPosition = _button1Position; + _delete.DrawPosition = _button1Position; _cancel.DrawPosition = _button2Position; } else { + var alternate = Buttons.HasFlag(ScrollingListDialogButtons.Alternate); + _back.DrawPosition = _button1Position; - _cancel.DrawPosition = _button1Position; + _cancel.DrawPosition = alternate ? _button2Position : _button1Position; _history.DrawPosition = _button1Position; _progress.DrawPosition = _button1Position; _next.DrawPosition = _button2Position; - _ok.DrawPosition = _button2Position; + _ok.DrawPosition = alternate ? _button1Position : _button2Position; } } else @@ -180,8 +189,7 @@ public ScrollingListDialogButtons Buttons }; _titleText.SetParentControl(this); - var isMediumDialog = DialogSize == ScrollingListDialogSize.Medium || DialogSize == ScrollingListDialogSize.MediumWithHeader; - _scrollBar = new ScrollBar(new Vector2(isMediumDialog ? 449 : 252, 44), new Vector2(16, GetScrollBarHeight(DialogSize)), ScrollBarColors.LightOnMed, GraphicsManager) + _scrollBar = new ScrollBar(new Vector2(DialogSize == ScrollingListDialogSize.Medium ? 449 : 252, 44), new Vector2(16, GetScrollBarHeight(DialogSize)), ScrollBarColors.LightOnMed, GraphicsManager) { Visible = DialogSize != ScrollingListDialogSize.LargeNoScroll && DialogSize != ScrollingListDialogSize.SmallNoScroll, }; @@ -200,7 +208,6 @@ public ScrollingListDialogButtons Buttons }; _add.SetParentControl(this); _add.OnClick += (o, e) => AddAction?.Invoke(o, e); - AddAction += (_, _) => _otherClicked = true; _back = new XNAButton(dialogButtonService.SmallButtonSheet, Vector2.Zero, dialogButtonService.GetSmallDialogButtonOutSource(SmallButton.Back), @@ -211,7 +218,6 @@ public ScrollingListDialogButtons Buttons }; _back.SetParentControl(this); _back.OnClick += (o, e) => BackAction?.Invoke(o, e); - BackAction += (_, _) => _otherClicked = true; _next = new XNAButton(dialogButtonService.SmallButtonSheet, Vector2.Zero, dialogButtonService.GetSmallDialogButtonOutSource(SmallButton.Next), @@ -222,7 +228,6 @@ public ScrollingListDialogButtons Buttons }; _next.SetParentControl(this); _next.OnClick += (o, e) => NextAction?.Invoke(o, e); - NextAction += (_, _) => _otherClicked = true; _history = new XNAButton(dialogButtonService.SmallButtonSheet, Vector2.Zero, dialogButtonService.GetSmallDialogButtonOutSource(SmallButton.History), @@ -233,7 +238,6 @@ public ScrollingListDialogButtons Buttons }; _history.SetParentControl(this); _history.OnClick += (o, e) => HistoryAction?.Invoke(o, e); - HistoryAction += (_, _) => _otherClicked = true; _progress = new XNAButton(dialogButtonService.SmallButtonSheet, Vector2.Zero, dialogButtonService.GetSmallDialogButtonOutSource(SmallButton.Progress), @@ -244,7 +248,16 @@ public ScrollingListDialogButtons Buttons }; _progress.SetParentControl(this); _progress.OnClick += (o, e) => ProgressAction?.Invoke(o, e); - ProgressAction += (_, _) => _otherClicked = true; + + _delete = new XNAButton(dialogButtonService.SmallButtonSheet, Vector2.Zero, + dialogButtonService.GetSmallDialogButtonOutSource(SmallButton.Delete), + dialogButtonService.GetSmallDialogButtonOverSource(SmallButton.Delete)) + { + Visible = false, + UpdateOrder = 1, + }; + _delete.SetParentControl(this); + _delete.OnClick += (o, e) => ProgressAction?.Invoke(o, e); _ok = new XNAButton(dialogButtonService.SmallButtonSheet, Vector2.Zero, dialogButtonService.GetSmallDialogButtonOutSource(SmallButton.Ok), @@ -254,7 +267,7 @@ public ScrollingListDialogButtons Buttons UpdateOrder = 2, }; _ok.SetParentControl(this); - _ok.OnClick += (_, _) => { if (!_otherClicked) { Close(XNADialogResult.OK); } }; + _ok.OnClick += CloseButton_Click; _cancel = new XNAButton(dialogButtonService.SmallButtonSheet, Vector2.Zero, dialogButtonService.GetSmallDialogButtonOutSource(SmallButton.Cancel), @@ -264,7 +277,7 @@ public ScrollingListDialogButtons Buttons UpdateOrder = 2, }; _cancel.SetParentControl(this); - _cancel.OnClick += (_, _) => { if (!_otherClicked) { Close(XNADialogResult.Cancel); } }; + _cancel.OnClick += CloseButton_Click; _button1Position = GetButton1Position(DrawArea, _ok.DrawArea, DialogSize); _button2Position = GetButton2Position(DrawArea, _ok.DrawArea, DialogSize); @@ -278,6 +291,14 @@ public ScrollingListDialogButtons Buttons DrawPosition = new Vector2(DrawPosition.X, 15); } + protected virtual void CloseButton_Click(object sender, MouseEventArgs e) + { + if (sender == _ok) + Close(XNADialogResult.OK); + else if (sender == _cancel) + Close(XNADialogResult.Cancel); + } + public void SetItemList(List itemList) { if (!itemList.All(x => x.Style == ListItemType)) @@ -395,18 +416,15 @@ protected override void OnUpdateControl(GameTime gameTime) } base.OnUpdateControl(gameTime); - - _otherClicked = false; } - private static Rectangle GetTitleDrawArea(ScrollingListDialogSize size) + protected static Rectangle GetTitleDrawArea(ScrollingListDialogSize size) { switch(size) { case ScrollingListDialogSize.Large: case ScrollingListDialogSize.LargeNoScroll: return new Rectangle(16, 13, 253, 19); case ScrollingListDialogSize.Medium: - case ScrollingListDialogSize.MediumWithHeader: return new Rectangle(18, 14, 452, 19); case ScrollingListDialogSize.Small: return new Rectangle(16, 16, 255, 18); case ScrollingListDialogSize.SmallNoScroll: return new Rectangle(129, 20, 121, 16); @@ -414,14 +432,13 @@ private static Rectangle GetTitleDrawArea(ScrollingListDialogSize size) } } - private static int GetScrollBarHeight(ScrollingListDialogSize size) + protected static int GetScrollBarHeight(ScrollingListDialogSize size) { switch (size) { case ScrollingListDialogSize.Large: case ScrollingListDialogSize.LargeNoScroll: case ScrollingListDialogSize.Medium: - case ScrollingListDialogSize.MediumWithHeader: return 199; case ScrollingListDialogSize.Small: case ScrollingListDialogSize.SmallNoScroll: return 99; @@ -436,7 +453,6 @@ private static int GetBackgroundTexture(ScrollingListDialogSize size) case ScrollingListDialogSize.Large: return 52; case ScrollingListDialogSize.LargeNoScroll: return 51; case ScrollingListDialogSize.Medium: - case ScrollingListDialogSize.MediumWithHeader: return 59; case ScrollingListDialogSize.Small: return 67; case ScrollingListDialogSize.SmallNoScroll: return 53; @@ -444,14 +460,13 @@ private static int GetBackgroundTexture(ScrollingListDialogSize size) } } - private static Rectangle? GetBackgroundSourceRectangle(Texture2D backgroundTexture, ScrollingListDialogSize size) + protected static Rectangle? GetBackgroundSourceRectangle(Texture2D backgroundTexture, ScrollingListDialogSize size) { switch (size) { case ScrollingListDialogSize.Large: case ScrollingListDialogSize.LargeNoScroll: return null; case ScrollingListDialogSize.Medium: - case ScrollingListDialogSize.MediumWithHeader: return new Rectangle(0, 0, backgroundTexture.Width, backgroundTexture.Height / 2); case ScrollingListDialogSize.Small: case ScrollingListDialogSize.SmallNoScroll: return null; @@ -470,7 +485,6 @@ private static Vector2 GetButton1Position(Rectangle dialogArea, Rectangle button case ScrollingListDialogSize.SmallNoScroll: return new Vector2((int)Math.Floor((dialogArea.Width - buttonArea.Width) / 2.0) - 48, yCoord); // buttons are offset from center on these dialogs case ScrollingListDialogSize.Medium: - case ScrollingListDialogSize.MediumWithHeader: return new Vector2(288, yCoord); case ScrollingListDialogSize.Small: return new Vector2(89, yCoord); default: throw new NotImplementedException(); @@ -488,7 +502,6 @@ private static Vector2 GetButton2Position(Rectangle dialogArea, Rectangle button case ScrollingListDialogSize.SmallNoScroll: return new Vector2((int)Math.Floor((dialogArea.Width - buttonArea.Width) / 2.0) + 48, yCoord); // buttons are offset from center on these dialogs case ScrollingListDialogSize.Medium: - case ScrollingListDialogSize.MediumWithHeader: return new Vector2(380, yCoord); case ScrollingListDialogSize.Small: return new Vector2(183, yCoord); default: throw new NotImplementedException(); diff --git a/EndlessClient/EndlessClient.csproj b/EndlessClient/EndlessClient.csproj index 93242377..2756e346 100644 --- a/EndlessClient/EndlessClient.csproj +++ b/EndlessClient/EndlessClient.csproj @@ -94,6 +94,6 @@ - + diff --git a/EndlessClient/UIControls/ScrollBar.cs b/EndlessClient/UIControls/ScrollBar.cs index 9a16d387..be4fd268 100644 --- a/EndlessClient/UIControls/ScrollBar.cs +++ b/EndlessClient/UIControls/ScrollBar.cs @@ -1,9 +1,8 @@ -using System; -using EOLib.Graphics; +using EOLib.Graphics; using Microsoft.Xna.Framework; using MonoGame.Extended.Input.InputListeners; +using System; using XNAControls; -using XNAControls.Input; namespace EndlessClient.UIControls { @@ -15,7 +14,7 @@ public enum ScrollBarColors DarkOnDark //very bottom set } - public class ScrollBar : XNAControl + public class ScrollBar : XNAControl, IScrollHandler { private Rectangle scrollArea; //area valid for scrolling: always 16 from top and 16 from bottom public int ScrollOffset { get; private set; } @@ -25,12 +24,25 @@ public class ScrollBar : XNAControl private int _totalHeight; + public override Rectangle DrawArea + { + get => base.DrawArea; + set + { + base.DrawArea = value; + + scrollArea = new Rectangle(0, 15, 0, value.Height - 15); + + if (_downButton != null) + _downButton.DrawPosition = new Vector2(0, value.Height - 15); + } + } + public ScrollBar(Vector2 locationRelativeToParent, Vector2 size, ScrollBarColors palette, INativeGraphicsManager nativeGraphicsManager) { - scrollArea = new Rectangle(0, 15, 0, (int)size.Y - 15); DrawPosition = locationRelativeToParent; SetSize((int)size.X, (int)size.Y); ScrollOffset = 0;