diff --git a/EOLib.Localization/EOResourceID.cs b/EOLib.Localization/EOResourceID.cs index 597ee6b1d..8cf1d06b7 100644 --- a/EOLib.Localization/EOResourceID.cs +++ b/EOLib.Localization/EOResourceID.cs @@ -193,7 +193,7 @@ public enum EOResourceID GUILD_REMOVE_A_MEMBER_FROM_GUILD = 203, GUILD_YOU_ARE_ABOUT_TO_JOIN_A_GUILD = 204, GUILD_JOINING_A_GUILD_IS_FREE = 205, - + // GUILD_RECRUITER_MUST_BE_PRESENT = 206, // not used anymore? GUILD_PLEASE_CONSIDER_CAREFULLY = 207, GUILD_CLICK_HERE_TO_JOIN_A_GUILD = 208, GUILD_YOU_ARE_ABOUT_TO_LEAVE_THE_GUILD = 209, @@ -207,7 +207,8 @@ public enum EOResourceID GUILD_GUILD_TAG = 217, GUILD_GUILD_NAME = 218, GUILD_WORD_DESCRIPTION = 219, - + // GUILD_RANKING = 220 // duplicate of 193 + // GUILD_HERE_YOU_CAN_JOIN = 221 // not used anymore? GUILD_PLEASE_WAIT_FOR_ALL_MEMBERS_TO_JOIN = 222, GUILD_DO_YOU_ACCEPT = 223, GUILD_RECRUITER = 224, @@ -218,11 +219,11 @@ public enum EOResourceID GUILD_GUILD_DESCRIPTION = 229, GUILD_RANKING_SYSTEM = 230, GUILD_LEADERS = 231, - + // GUILD_RECRUITERS = 232 // not used anymore? GUILD_ASSIGN_RANK_TO_MEMBER = 233, GUILD_CURRENT_DESCRIPTION = 234, GUILD_CLICK_HERE_TO_CHANGE_THE_DESCRIPTION = 235, - + GUILD_ENTER_YOUR_RANKINGS = 236, GUILD_DISBAND_DESCRIPTION_1 = 237, GUILD_DISBAND_DESCRIPTION_2 = 238, GUILD_DISBAND_DESCRIPTION_3 = 239, @@ -234,7 +235,11 @@ public enum EOResourceID GUILD_BANK_DESCRIPTION_1 = 245, GUILD_BANK_DESCRIPTION_2 = 246, GUILD_BANK_DESCRIPTION_3 = 247, - + GUILD_RANK_DESCRIPTION_1 = 248, + GUILD_RANK_DESCRIPTION_2 = 249, + GUILD_RANK_DESCRIPTION_3 = 250, + GUILD_RANK_ASSIGN_NAME = 251, + GUILD_RANK_ASSIGN_RANK = 252, SETTING_KEYBOARD_ENGLISH = 253, SETTING_KEYBOARD_DUTCH = 254, SETTING_KEYBOARD_SWEDISH = 255, diff --git a/EOLib/Domain/Character/Character.cs b/EOLib/Domain/Character/Character.cs index ec7ae9dcc..df81e530d 100644 --- a/EOLib/Domain/Character/Character.cs +++ b/EOLib/Domain/Character/Character.cs @@ -37,6 +37,8 @@ public sealed partial class Character : ISpellTargetable public string GuildRank { get; } + public int GuildRankID { get; } + public string GuildTag { get; } public bool InGuild => GuildTag != " "; diff --git a/EOLib/Domain/Interact/Guild/GuildActions.cs b/EOLib/Domain/Interact/Guild/GuildActions.cs index 2acd84539..39fcfc12e 100644 --- a/EOLib/Domain/Interact/Guild/GuildActions.cs +++ b/EOLib/Domain/Interact/Guild/GuildActions.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; using AutomaticTypeMapper; using EOLib.Domain.Character; using EOLib.Extensions; @@ -46,11 +47,11 @@ public void GetGuildDescription(string guildTag) public void SetGuildDescription(string description) { - _packetSendService.SendPacket(new GuildAgreeClientPacket() + _packetSendService.SendPacket(new GuildAgreeClientPacket { SessionId = _guildSessionRepository.SessionID, InfoType = GuildInfoType.Description, - InfoTypeData = new GuildAgreeClientPacket.InfoTypeDataDescription() + InfoTypeData = new GuildAgreeClientPacket.InfoTypeDataDescription { Description = description } @@ -76,6 +77,48 @@ public void BankDeposit(int depositAmount) }); } + public void GetGuildRanks(string guildTag) + { + _packetSendService.SendPacket(new GuildTakeClientPacket + { + SessionId = _guildSessionRepository.SessionID, + InfoType = GuildInfoType.Ranks, + GuildTag = guildTag + }); + + } + + public void SetGuildRanks(IReadOnlyList ranks) + { + var ranksList = ranks.ToList(); + while (ranksList.Count != 9) + { + ranksList.Add(string.Empty); + } + + _packetSendService.SendPacket(new GuildAgreeClientPacket + { + SessionId = _guildSessionRepository.SessionID, + InfoType = GuildInfoType.Ranks, + InfoTypeData = new GuildAgreeClientPacket.InfoTypeDataRanks + { + Ranks = ranksList + } + }); + } + + public void AssignRank(string playerName, int rank) + { + _guildSessionRepository.GuildPlayerModifyCandidate = playerName.Capitalize(); + + _packetSendService.SendPacket(new GuildRankClientPacket + { + SessionId = _guildSessionRepository.SessionID, + MemberName = playerName, + Rank = rank + }); + } + public void RequestToJoinGuild(string guildTag, string recruiterName) { _packetSendService.SendPacket(new GuildPlayerClientPacket { SessionId = _guildSessionRepository.SessionID, GuildTag = guildTag, RecruiterName = recruiterName }); @@ -127,7 +170,7 @@ public void CancelGuildCreate() public void KickMember(string member) { - _guildSessionRepository.RemoveCandidate = member.Capitalize(); + _guildSessionRepository.GuildPlayerModifyCandidate = member.Capitalize(); _packetSendService.SendPacket(new GuildKickClientPacket { @@ -140,6 +183,12 @@ public void DisbandGuild() { _packetSendService.SendPacket(new GuildJunkClientPacket { SessionId = _guildSessionRepository.SessionID }); } + + public void ClearLocalState() + { + _guildSessionRepository.GuildMembers.Clear(); + _guildSessionRepository.GuildInfo = Option.None(); + } } public interface IGuildActions @@ -156,6 +205,12 @@ public interface IGuildActions void BankDeposit(int depositAmount); + void GetGuildRanks(string guildTag); + + void SetGuildRanks(IReadOnlyList ranks); + + void AssignRank(string playerName, int rank); + void RequestToJoinGuild(string guildTag, string recruiterName); void LeaveGuild(); @@ -169,5 +224,7 @@ public interface IGuildActions void KickMember(string responseText); void DisbandGuild(); + + void ClearLocalState(); } } diff --git a/EOLib/Domain/Interact/Guild/GuildSessionRepository.cs b/EOLib/Domain/Interact/Guild/GuildSessionRepository.cs index a790d9fdd..ca995d477 100644 --- a/EOLib/Domain/Interact/Guild/GuildSessionRepository.cs +++ b/EOLib/Domain/Interact/Guild/GuildSessionRepository.cs @@ -19,7 +19,7 @@ public interface IGuildSessionProvider IReadOnlyList GuildMembers { get; } - string RemoveCandidate { get; } + string GuildPlayerModifyCandidate { get; } } public interface IGuildSessionRepository : IResettable @@ -36,7 +36,7 @@ public interface IGuildSessionRepository : IResettable List GuildMembers { get; set; } - string RemoveCandidate { get; set; } + string GuildPlayerModifyCandidate { get; set; } } [AutoMappedType(IsSingleton = true)] @@ -54,7 +54,7 @@ public class GuildSessionRepository : IGuildSessionRepository, IGuildSessionProv public List GuildMembers { get; set; } - public string RemoveCandidate { get; set; } + public string GuildPlayerModifyCandidate { get; set; } IReadOnlyList IGuildSessionProvider.GuildMembers => GuildMembers; @@ -71,7 +71,7 @@ public void ResetState() GuildBalance = 0; GuildInfo = Option.None(); GuildMembers = new List(); - RemoveCandidate = string.Empty; + GuildPlayerModifyCandidate = string.Empty; } } } diff --git a/EOLib/Domain/Login/LoginActions.cs b/EOLib/Domain/Login/LoginActions.cs index c62697c12..ca68c1d09 100644 --- a/EOLib/Domain/Login/LoginActions.cs +++ b/EOLib/Domain/Login/LoginActions.cs @@ -98,6 +98,7 @@ public async Task RequestCharacterLogin(Character.Character character) .WithName(data.Name.Capitalize()) .WithTitle(data.Title) .WithGuildName(data.GuildName.Capitalize()) + .WithGuildRankID(data.GuildRank) .WithGuildRank(data.GuildRankName.Capitalize()) .WithGuildTag(data.GuildTag.ToUpper()) .WithClassID(data.ClassId) diff --git a/EOLib/Domain/Notifiers/IGuildNotifier.cs b/EOLib/Domain/Notifiers/IGuildNotifier.cs index 8ee9dbe40..d39b8e4f8 100644 --- a/EOLib/Domain/Notifiers/IGuildNotifier.cs +++ b/EOLib/Domain/Notifiers/IGuildNotifier.cs @@ -1,4 +1,5 @@ -using AutomaticTypeMapper; +using System.Collections.Generic; +using AutomaticTypeMapper; using Moffat.EndlessOnline.SDK.Protocol.Net.Server; namespace EOLib.Domain.Notifiers @@ -16,6 +17,8 @@ public interface IGuildNotifier void NotifyNewGuildBankBalance(int balance); void NotifyAcceptedIntoGuild(); + + void NotifyRanks(IReadOnlyList ranks); } [AutoMappedType] @@ -27,5 +30,6 @@ public void NotifyGuildReply(GuildReply reply) { } public void NotifyConfirmCreateGuild() { } public void NotifyNewGuildBankBalance(int balance) { } public void NotifyAcceptedIntoGuild() { } + public void NotifyRanks(IReadOnlyList ranks) { } } } diff --git a/EOLib/PacketHandlers/Guild/GuildAcceptHandler.cs b/EOLib/PacketHandlers/Guild/GuildAcceptHandler.cs new file mode 100644 index 000000000..cd154e9e5 --- /dev/null +++ b/EOLib/PacketHandlers/Guild/GuildAcceptHandler.cs @@ -0,0 +1,32 @@ +using AutomaticTypeMapper; +using EOLib.Domain.Character; +using EOLib.Domain.Login; +using EOLib.Net.Handlers; +using Moffat.EndlessOnline.SDK.Protocol.Net; +using Moffat.EndlessOnline.SDK.Protocol.Net.Server; + +namespace EOLib.PacketHandlers.Guild +{ + [AutoMappedType] + public class GuildAcceptHandler : InGameOnlyPacketHandler + { + private readonly ICharacterRepository _characterRepository; + + public override PacketFamily Family => PacketFamily.Guild; + + public override PacketAction Action => PacketAction.Accept; + + public GuildAcceptHandler(IPlayerInfoProvider playerInfoProvider, + ICharacterRepository characterRepository) + : base(playerInfoProvider) + { + _characterRepository = characterRepository; + } + + public override bool HandlePacket(GuildAcceptServerPacket packet) + { + _characterRepository.MainCharacter = _characterRepository.MainCharacter.WithGuildRankID(packet.Rank); + return true; + } + } +} diff --git a/EOLib/PacketHandlers/Guild/GuildRankHandler.cs b/EOLib/PacketHandlers/Guild/GuildRankHandler.cs new file mode 100644 index 000000000..5276e0f53 --- /dev/null +++ b/EOLib/PacketHandlers/Guild/GuildRankHandler.cs @@ -0,0 +1,35 @@ +using System.Collections.Generic; +using AutomaticTypeMapper; +using EOLib.Domain.Login; +using EOLib.Domain.Notifiers; +using EOLib.Net.Handlers; +using Moffat.EndlessOnline.SDK.Protocol.Net; +using Moffat.EndlessOnline.SDK.Protocol.Net.Server; + +namespace EOLib.PacketHandlers.Guild +{ + [AutoMappedType] + public class GuildRankHandler : InGameOnlyPacketHandler + { + private readonly IEnumerable _guildNotifiers; + + public override PacketFamily Family => PacketFamily.Guild; + + public override PacketAction Action => PacketAction.Rank; + + public GuildRankHandler(IPlayerInfoProvider playerInfoProvider, + IEnumerable guildNotifiers) + : base(playerInfoProvider) + { + _guildNotifiers = guildNotifiers; + } + + public override bool HandlePacket(GuildRankServerPacket packet) + { + foreach (var notifier in _guildNotifiers) + notifier.NotifyRanks(packet.Ranks); + + return true; + } + } +} diff --git a/EOLib/PacketHandlers/Guild/GuildReplyHandler.cs b/EOLib/PacketHandlers/Guild/GuildReplyHandler.cs index 3a3a24f26..c44bb0b68 100644 --- a/EOLib/PacketHandlers/Guild/GuildReplyHandler.cs +++ b/EOLib/PacketHandlers/Guild/GuildReplyHandler.cs @@ -33,27 +33,26 @@ public override bool HandlePacket(GuildReplyServerPacket packet) { switch (packet.ReplyCode) { - case GuildReply.JoinRequest: - { - var data = (GuildReplyServerPacket.ReplyCodeDataJoinRequest)(packet.ReplyCodeData); - foreach (var notifier in _guildNotifiers) - notifier.NotifyRequestToJoinGuild(data.PlayerId, data.Name); - break; - } - case GuildReply.Updated: - case GuildReply.NotFound: + case GuildReply.Busy: + case GuildReply.NotApproved: + case GuildReply.AlreadyMember: + case GuildReply.NoCandidates: + case GuildReply.Exists: case GuildReply.RecruiterOffline: case GuildReply.RecruiterNotHere: case GuildReply.RecruiterWrongGuild: case GuildReply.NotRecruiter: - case GuildReply.NotApproved: - case GuildReply.Exists: - case GuildReply.NoCandidates: - case GuildReply.Busy: + case GuildReply.NotPresent: + case GuildReply.AccountLow: + case GuildReply.Accepted: + case GuildReply.NotFound: + case GuildReply.Updated: + case GuildReply.RanksUpdated: case GuildReply.RemoveLeader: case GuildReply.RemoveNotMember: case GuildReply.Removed: - case GuildReply.Accepted: + case GuildReply.RankingLeader: + case GuildReply.RankingNotMember: { foreach (var notifier in _guildNotifiers) notifier.NotifyGuildReply(packet.ReplyCode); @@ -67,30 +66,38 @@ public override bool HandlePacket(GuildReplyServerPacket packet) }); break; } - case GuildReply.CreateAdd: + case GuildReply.CreateAddConfirm: { _guildSessionRepository.CreationSession.MatchSome(creationSession => { - var data = (GuildReplyServerPacket.ReplyCodeDataCreateAdd)packet.ReplyCodeData; + var data = (GuildReplyServerPacket.ReplyCodeDataCreateAddConfirm)packet.ReplyCodeData; var updatedMemberList = new HashSet(creationSession.Members) { data.Name }; _guildSessionRepository.CreationSession = Option.Some(creationSession.WithMembers(updatedMemberList)); + + foreach (var notifier in _guildNotifiers) + notifier.NotifyConfirmCreateGuild(); }); - break; } - case GuildReply.CreateAddConfirm: + case GuildReply.CreateAdd: { _guildSessionRepository.CreationSession.MatchSome(creationSession => { - var data = (GuildReplyServerPacket.ReplyCodeDataCreateAddConfirm)packet.ReplyCodeData; + var data = (GuildReplyServerPacket.ReplyCodeDataCreateAdd)packet.ReplyCodeData; var updatedMemberList = new HashSet(creationSession.Members) { data.Name }; _guildSessionRepository.CreationSession = Option.Some(creationSession.WithMembers(updatedMemberList)); - - foreach (var notifier in _guildNotifiers) - notifier.NotifyConfirmCreateGuild(); }); + break; } + case GuildReply.JoinRequest: + { + var data = (GuildReplyServerPacket.ReplyCodeDataJoinRequest)(packet.ReplyCodeData); + foreach (var notifier in _guildNotifiers) + notifier.NotifyRequestToJoinGuild(data.PlayerId, data.Name); + break; + } + } return true; diff --git a/EndlessClient/Dialogs/GuildDialog.cs b/EndlessClient/Dialogs/GuildDialog.cs index 4ec42b3ae..fa5692371 100644 --- a/EndlessClient/Dialogs/GuildDialog.cs +++ b/EndlessClient/Dialogs/GuildDialog.cs @@ -45,7 +45,6 @@ private enum GuildDialogState // Management List Items Modify, - ManageRankings, AssignRank, RemoveMember, Disband, @@ -81,8 +80,6 @@ private class State public static State Modify => new(GuildDialogState.Modify); - public static State ManageRankings => new(GuildDialogState.ManageRankings); - public static State AssignRank => new(GuildDialogState.AssignRank); public static State RemoveMember => new(GuildDialogState.RemoveMember); @@ -106,6 +103,7 @@ private State(GuildDialogState dialogState) case GuildDialogState.WaitingForMembers: case GuildDialogState.RemoveMember: case GuildDialogState.BankAccount: + case GuildDialogState.AssignRank: ListItemStyle = ListDialogItem.ListItemStyle.Small; break; } @@ -206,7 +204,7 @@ public GuildDialog(INativeGraphicsManager nativeGraphicsManager, // Management state transitions { State.Modify, SetupModifyState }, - // TODO: ranking states + { State.AssignRank, SetupAssignRankState }, { State.RemoveMember, SetupRemoveMemberState }, { State.Disband, SetupDisbandState }, }; @@ -255,7 +253,7 @@ protected override void OnUnconditionalUpdateControl(GameTime gameTime) _cachedMembers = _guildSessionProvider.GuildMembers.ToHashSet(); AddTextAsKeyValueListItems( - _cachedMembers.Select(x => ($"{x.Rank} {x.Name}", x.RankName.Capitalize())).ToArray() + _cachedMembers.Select(x => ($"{x.Rank} {x.Name.Capitalize()}", x.RankName.Capitalize())).ToArray() ); } break; @@ -329,7 +327,7 @@ void CacheAndSetGuildInfo(GuildInfo guildInfo) string.Join("\n", guildInfo.Ranks.Select((x, n) => $"{n + 1} {x.Capitalize()}")), " ", _localizedStringFinder.GetString(EOResourceID.GUILD_LEADERS), - string.Join("\n", guildInfo.Staff.Select(x => $"{x.Name}{(x.Rank == 0 ? " (founder)" : string.Empty)}")), + string.Join("\n", guildInfo.Staff.Select(x => $"{x.Name.Capitalize()}{(x.Rank == 0 ? " (founder)" : string.Empty)}")), " " ); } @@ -339,6 +337,11 @@ void CacheAndSetGuildInfo(GuildInfo guildInfo) private void GoBack() { + _guildActions.ClearLocalState(); + + _cachedMembers.Clear(); + _cachedGuildInfo = Option.None(); + _modifyGuildDescriptionListItem = Option.None(); _guildBalanceListItem = Option.None(); _lastGuildBalance = 0; @@ -543,8 +546,8 @@ private void SetupManagementState() SubText = _localizedStringFinder.GetString(EOResourceID.GUILD_CHANGE_YOUR_GUILD_DETAILS), OffsetY = 45, }; - modifyGuildItem.LeftClick += (_, _) => SetStateIfInGuild(State.Modify); - modifyGuildItem.RightClick += (_, _) => SetStateIfInGuild(State.Modify); + modifyGuildItem.LeftClick += (_, _) => SetStateIfLeaderRank(State.Modify); + modifyGuildItem.RightClick += (_, _) => SetStateIfLeaderRank(State.Modify); var manageRankingItem = new ListDialogItem(this, ListDialogItem.ListItemStyle.Large, 0) { @@ -555,8 +558,8 @@ private void SetupManagementState() SubText = _localizedStringFinder.GetString(EOResourceID.GUILD_MANAGE_MEMBER_RANKINGS), OffsetY = 45, }; - manageRankingItem.LeftClick += (_, _) => SetStateIfInGuild(State.ManageRankings); - manageRankingItem.RightClick += (_, _) => SetStateIfInGuild(State.ManageRankings); + manageRankingItem.LeftClick += (_, _) => ShowManageRankingsDialog(); + manageRankingItem.RightClick += (_, _) => ShowManageRankingsDialog(); var assignRankItem = new ListDialogItem(this, ListDialogItem.ListItemStyle.Large, 0) { @@ -567,8 +570,8 @@ private void SetupManagementState() SubText = _localizedStringFinder.GetString(EOResourceID.GUILD_ASSIGN_RANK_TO_MEMBER), OffsetY = 45, }; - assignRankItem.LeftClick += (_, _) => SetStateIfInGuild(State.AssignRank); - assignRankItem.RightClick += (_, _) => SetStateIfInGuild(State.AssignRank); + assignRankItem.LeftClick += (_, _) => SetStateIfLeaderRank(State.AssignRank); + assignRankItem.RightClick += (_, _) => SetStateIfLeaderRank(State.AssignRank); var removeMemberItem = new ListDialogItem(this, ListDialogItem.ListItemStyle.Large, 0) { @@ -579,8 +582,8 @@ private void SetupManagementState() SubText = _localizedStringFinder.GetString(EOResourceID.GUILD_REMOVE_A_MEMBER_FROM_GUILD), OffsetY = 45, }; - removeMemberItem.LeftClick += (_, _) => SetStateIfInGuild(State.RemoveMember); - removeMemberItem.RightClick += (_, _) => SetStateIfInGuild(State.RemoveMember); + removeMemberItem.LeftClick += (_, _) => SetStateIfLeaderRank(State.RemoveMember); + removeMemberItem.RightClick += (_, _) => SetStateIfLeaderRank(State.RemoveMember); var disbandItem = new ListDialogItem(this, ListDialogItem.ListItemStyle.Large, 0) { @@ -591,8 +594,8 @@ private void SetupManagementState() SubText = _localizedStringFinder.GetString(EOResourceID.GUILD_DISBAND_YOUR_GUILD), OffsetY = 45, }; - disbandItem.LeftClick += (_, _) => SetStateIfInGuild(State.Disband); - disbandItem.RightClick += (_, _) => SetStateIfInGuild(State.Disband); + disbandItem.LeftClick += (_, _) => SetStateIfLeaderRank(State.Disband); + disbandItem.RightClick += (_, _) => SetStateIfLeaderRank(State.Disband); SetItemList(new List { modifyGuildItem, manageRankingItem, assignRankItem, removeMemberItem, disbandItem }); } @@ -732,6 +735,74 @@ void ShowChangeDescriptionMessageBox() } } + private void ShowManageRankingsDialog() + { + if (!_characterProvider.MainCharacter.InGuild) + { + var dlg = _messageBoxFactory.CreateMessageBox(DialogResourceID.GUILD_NOT_IN_GUILD); + dlg.ShowDialog(); + return; + } + + if (_characterProvider.MainCharacter.GuildRankID >= 2) + { + var dlg = _messageBoxFactory.CreateMessageBox(DialogResourceID.GUILD_RANK_TOO_LOW); + dlg.ShowDialog(); + return; + } + + // guild ranks dialog pops up on response from server (see GuildEventSubscriber) + _guildActions.GetGuildRanks(_characterProvider.MainCharacter.GuildTag); + } + + private void SetupAssignRankState() + { + AddTextAsListItems( + _contentProvider.Fonts[Constants.FontSize08pt5], + insertLineBreaks: true, + new List { ShowAssignRankInputBox }, + _localizedStringFinder.GetString(EOResourceID.GUILD_RANKING), + _localizedStringFinder.GetString(EOResourceID.GUILD_RANK_DESCRIPTION_1), + _localizedStringFinder.GetString(EOResourceID.GUILD_RANK_DESCRIPTION_2), + _localizedStringFinder.GetString(EOResourceID.GUILD_RANK_DESCRIPTION_3) + ); + + void ShowAssignRankInputBox() + { + var dlg = _textMultiInputDialogFactory.Create( + _localizedStringFinder.GetString(EOResourceID.GUILD_RANKING), + _localizedStringFinder.GetString(EOResourceID.GUILD_ASSIGN_RANK_TO_MEMBER), + TextMultiInputDialog.DialogSize.Two, + new TextMultiInputDialog.InputInfo(_localizedStringFinder.GetString(EOResourceID.GUILD_RANK_ASSIGN_NAME)), + new TextMultiInputDialog.InputInfo( + _localizedStringFinder.GetString(EOResourceID.GUILD_RANK_ASSIGN_RANK), + MaxChars: 1, + InputRestriction: TextMultiInputDialog.InputInfo.InputRestrict.Numeric + ) + ); + + dlg.DialogClosing += (_, e) => + { + if (e.Result == XNADialogResult.OK) + { + // The only input validation the official client does is that none of the fields are empty + + if (dlg.Responses.Any(string.IsNullOrWhiteSpace)) + { + var errorDlg = _messageBoxFactory.CreateMessageBox(DialogResourceID.ACCOUNT_CREATE_FIELDS_STILL_EMPTY); + errorDlg.ShowDialog(); + + e.Cancel = true; + return; + } + + _guildActions.AssignRank(dlg.Responses[0], int.Parse(dlg.Responses[1])); + } + }; + dlg.ShowDialog(); + } + } + private void SetupRemoveMemberState() { AddTextAsListItems( @@ -806,7 +877,7 @@ private void ShowJoinGuildMessageBox() _localizedStringFinder.GetString(DialogResourceID.GUILD_JOIN_GUILD), _localizedStringFinder.GetString(DialogResourceID.GUILD_JOIN_GUILD + 1), TextMultiInputDialog.DialogSize.Two, - new TextMultiInputDialog.InputInfo(_localizedStringFinder.GetString(EOResourceID.GUILD_GUILD_TAG), MaxChars: 3, UpperCase: true), + new TextMultiInputDialog.InputInfo(_localizedStringFinder.GetString(EOResourceID.GUILD_GUILD_TAG), MaxChars: 3, InputRestriction: TextMultiInputDialog.InputInfo.InputRestrict.Uppercase), new TextMultiInputDialog.InputInfo(_localizedStringFinder.GetString(EOResourceID.GUILD_RECRUITER), MaxChars: 12) ); @@ -882,7 +953,7 @@ private void ShowRegisterGuildMessageBox() _localizedStringFinder.GetString(EOResourceID.GUILD_REGISTER_GUILD), _localizedStringFinder.GetString(EOResourceID.GUILD_ENTER_YOUR_GUILD_DETAILS), TextMultiInputDialog.DialogSize.Three, - new TextMultiInputDialog.InputInfo(_localizedStringFinder.GetString(EOResourceID.GUILD_GUILD_TAG), MaxChars: 3, UpperCase: true), + new TextMultiInputDialog.InputInfo(_localizedStringFinder.GetString(EOResourceID.GUILD_GUILD_TAG), MaxChars: 3, InputRestriction: TextMultiInputDialog.InputInfo.InputRestrict.Uppercase), new TextMultiInputDialog.InputInfo(_localizedStringFinder.GetString(EOResourceID.GUILD_GUILD_NAME), MaxChars: 24), new TextMultiInputDialog.InputInfo(_localizedStringFinder.GetString(EOResourceID.GUILD_WORD_DESCRIPTION), MaxChars: 240) ); @@ -961,6 +1032,25 @@ private void SetStateIfInGuild(State state) SetState(state); } + private void SetStateIfLeaderRank(State state) + { + if (!_characterProvider.MainCharacter.InGuild) + { + var dlg = _messageBoxFactory.CreateMessageBox(DialogResourceID.GUILD_NOT_IN_GUILD); + dlg.ShowDialog(); + return; + } + + if (_characterProvider.MainCharacter.GuildRankID >= 2) + { + var dlg = _messageBoxFactory.CreateMessageBox(DialogResourceID.GUILD_RANK_TOO_LOW); + dlg.ShowDialog(); + return; + } + + SetState(state); + } + private void SetStateIfNotInGuild(State state) { if (_characterProvider.MainCharacter.InGuild) diff --git a/EndlessClient/Dialogs/TextMultiInputDialog.cs b/EndlessClient/Dialogs/TextMultiInputDialog.cs index ac0120088..1e4ddbdf0 100644 --- a/EndlessClient/Dialogs/TextMultiInputDialog.cs +++ b/EndlessClient/Dialogs/TextMultiInputDialog.cs @@ -10,6 +10,7 @@ using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using XNAControls; +using static System.Net.Mime.MediaTypeNames; namespace EndlessClient.Dialogs { @@ -25,7 +26,18 @@ public enum DialogSize NineWithScroll } - public record InputInfo(string Label, int MaxChars = 12, bool UpperCase = false); + public record InputInfo(string Label, + string DefaultValue = "", + int MaxChars = 12, + InputInfo.InputRestrict InputRestriction = InputInfo.InputRestrict.None) + { + public enum InputRestrict + { + None = 0, + Uppercase = 1, + Numeric = 2 + } + } // can't use base class functionality because otherwise the bottom part of the dialog is drawn over the buttons private readonly Texture2D _background; @@ -158,7 +170,7 @@ public TextMultiInputDialog(INativeGraphicsManager nativeGraphicsManager, lblPrompt.Initialize(); lblPrompt.SetParentControl(this); - int yCoord = 69; + int yCoord = 69; // nice for (int i = 0; i < inputInfo.Length; i++) { _inputLabels[i] = new XNALabel(Constants.FontSize10) @@ -176,12 +188,16 @@ public TextMultiInputDialog(INativeGraphicsManager nativeGraphicsManager, LeftPadding = 4, TextColor = ColorConstants.LightBeigeText, MaxWidth = 160, - TabOrder = i, + TabOrder = i }; _inputBoxes[i].SetParentControl(this); + // Initialize must be called here because it sets up the label for text rendering; otherwise, the call to set the default value fails + _inputBoxes[i].Initialize(); + _inputBoxes[i].Text = inputInfo[i].DefaultValue; - if (inputInfo[i].UpperCase) + if (inputInfo[i].InputRestriction != InputInfo.InputRestrict.None) { + var inputRestriction = inputInfo[i].InputRestriction; _inputBoxes[i].OnTextChanged += (sender, _) => { if (_suppressTextChangedEvent) return; @@ -189,13 +205,27 @@ public TextMultiInputDialog(INativeGraphicsManager nativeGraphicsManager, if (sender is XNATextBox inputBox) { _suppressTextChangedEvent = true; - inputBox.Text = inputBox.Text.ToUpper(); + switch (inputRestriction) + { + case InputInfo.InputRestrict.Uppercase: + inputBox.Text = inputBox.Text.ToUpper(); + break; + case InputInfo.InputRestrict.Numeric: + if (inputBox.Text.Length > 0) + { + if (!char.IsNumber(inputBox.Text[inputBox.Text.Length - 1])) + { + inputBox.Text = inputBox.Text.Remove(inputBox.Text.Length - 1); + } + } + break; + } _suppressTextChangedEvent = false; } }; } - yCoord += 24; + yCoord += 23; } var ok = new XNAButton(eoDialogButtonService.SmallButtonSheet, @@ -221,8 +251,6 @@ public override void Initialize() { foreach (var label in _inputLabels) label.Initialize(); - foreach (var box in _inputBoxes) - box.Initialize(); _inputBoxes[0].Selected = true; @@ -268,8 +296,8 @@ protected override void OnUpdateControl(GameTime gameTime) currLabel.Visible = true; int relativeIndex = i - _scrollBar.ScrollOffset; - currBox.DrawPosition = new Vector2(currBox.DrawPosition.X, 69 + relativeIndex * 24); - currLabel.DrawPosition = new Vector2(currLabel.DrawPosition.X, 69 + relativeIndex * 24); + currBox.DrawPosition = new Vector2(currBox.DrawPosition.X, 69 + relativeIndex * 23); + currLabel.DrawPosition = new Vector2(currLabel.DrawPosition.X, 69 + relativeIndex * 23); } else diff --git a/EndlessClient/Subscribers/GuildEventSubscriber.cs b/EndlessClient/Subscribers/GuildEventSubscriber.cs index ddad0d2de..b440f1ec5 100644 --- a/EndlessClient/Subscribers/GuildEventSubscriber.cs +++ b/EndlessClient/Subscribers/GuildEventSubscriber.cs @@ -1,9 +1,12 @@ -using AutomaticTypeMapper; +using System.Collections.Generic; +using System.Linq; +using AutomaticTypeMapper; using EndlessClient.Audio; using EndlessClient.Dialogs; using EndlessClient.Dialogs.Factories; using EOLib.Domain.Interact.Guild; using EOLib.Domain.Notifiers; +using EOLib.Extensions; using EOLib.IO.Repositories; using EOLib.Localization; using EOLib.Net.Communication; @@ -17,25 +20,31 @@ namespace EndlessClient.Subscribers public class GuildEventSubscriber : IGuildNotifier { private readonly IEOMessageBoxFactory _messageBoxFactory; + private readonly ITextMultiInputDialogFactory _textMultiInputDialogFactory; private readonly IGuildActions _guildActions; private readonly IPacketSendService _packetSendService; private readonly ISfxPlayer _sfxPlayer; private readonly IGuildSessionProvider _guildSessionProvider; private readonly IEIFFileProvider _itemFileProvider; + private readonly ILocalizedStringFinder _localizedStringFinder; public GuildEventSubscriber(IEOMessageBoxFactory messageBoxFactory, + ITextMultiInputDialogFactory textMultiInputDialogFactory, IGuildActions guildActions, IPacketSendService packetSendService, ISfxPlayer sfxPlayer, IGuildSessionProvider guildSessionProvider, - IEIFFileProvider itemFileProvider) + IEIFFileProvider itemFileProvider, + ILocalizedStringFinder localizedStringFinder) { _messageBoxFactory = messageBoxFactory; + _textMultiInputDialogFactory = textMultiInputDialogFactory; _guildActions = guildActions; _packetSendService = packetSendService; _sfxPlayer = sfxPlayer; _guildSessionProvider = guildSessionProvider; _itemFileProvider = itemFileProvider; + _localizedStringFinder = localizedStringFinder; } public void NotifyGuildCreationRequest(int creatorPlayerID, string guildIdentity) @@ -96,28 +105,34 @@ public void NotifyGuildReply(GuildReply reply) { var dialogMessage = reply switch { - GuildReply.Updated => DialogResourceID.GUILD_DETAILS_UPDATED, - GuildReply.NotFound => DialogResourceID.GUILD_DOES_NOT_EXIST, + GuildReply.Busy => DialogResourceID.GUILD_MASTER_IS_BUSY, + GuildReply.NotApproved => DialogResourceID.GUILD_CREATE_NAME_NOT_APPROVED, + GuildReply.AlreadyMember => DialogResourceID.GUILD_ALREADY_A_MEMBER, + GuildReply.NoCandidates => DialogResourceID.GUILD_CREATE_NO_CANDIDATES, + GuildReply.Exists => DialogResourceID.GUILD_TAG_OR_NAME_ALREADY_EXISTS, GuildReply.RecruiterOffline => DialogResourceID.GUILD_RECRUITER_NOT_FOUND, GuildReply.RecruiterNotHere => DialogResourceID.GUILD_RECRUITER_NOT_HERE, GuildReply.RecruiterWrongGuild => DialogResourceID.GUILD_RECRUITER_NOT_MEMBER, GuildReply.NotRecruiter => DialogResourceID.GUILD_RECRUITER_RANK_TOO_LOW, - GuildReply.Busy => DialogResourceID.GUILD_MASTER_IS_BUSY, - GuildReply.NotApproved => DialogResourceID.GUILD_CREATE_NAME_NOT_APPROVED, - GuildReply.Exists => DialogResourceID.GUILD_TAG_OR_NAME_ALREADY_EXISTS, - GuildReply.NoCandidates => DialogResourceID.GUILD_CREATE_NO_CANDIDATES, - GuildReply.RemoveLeader => DialogResourceID.GUILD_REMOVE_PLAYER_IS_LEADER, - GuildReply.RemoveNotMember => DialogResourceID.GUILD_REMOVE_PLAYER_NOT_MEMBER, - GuildReply.Removed => DialogResourceID.GUILD_REMOVE_SUCCESS, + GuildReply.NotPresent => DialogResourceID.GUILD_RECRUITER_NOT_HERE, + GuildReply.AccountLow => DialogResourceID.GUILD_BANK_ACCOUNT_LOW, GuildReply.Accepted => DialogResourceID.GUILD_MEMBER_HAS_BEEN_ACCEPTED, + GuildReply.NotFound => DialogResourceID.GUILD_DOES_NOT_EXIST, + GuildReply.Updated => DialogResourceID.GUILD_DETAILS_UPDATED, + GuildReply.RanksUpdated => DialogResourceID.GUILD_DETAILS_UPDATED, + GuildReply.RemoveLeader or + GuildReply.RankingLeader => DialogResourceID.GUILD_REMOVE_PLAYER_IS_LEADER, + GuildReply.RemoveNotMember or + GuildReply.RankingNotMember => DialogResourceID.GUILD_REMOVE_PLAYER_NOT_MEMBER, + GuildReply.Removed => DialogResourceID.GUILD_REMOVE_SUCCESS, _ => default }; var prependData = reply switch { - GuildReply.RemoveLeader or - GuildReply.RemoveNotMember or - GuildReply.Removed => $"{_guildSessionProvider.RemoveCandidate} ", + GuildReply.RemoveLeader or GuildReply.RankingLeader or + GuildReply.RemoveNotMember or GuildReply.RankingNotMember or + GuildReply.Removed => $"{_guildSessionProvider.GuildPlayerModifyCandidate} ", _ => string.Empty }; @@ -166,5 +181,28 @@ public void NotifyAcceptedIntoGuild() var dlg = _messageBoxFactory.CreateMessageBox(DialogResourceID.GUILD_YOU_HAVE_BEEN_ACCEPTED); dlg.ShowDialog(); } + + public void NotifyRanks(IReadOnlyList ranks) + { + var inputs = ranks.Select( + (rankString, i) => new TextMultiInputDialog.InputInfo( + Label: $"{_localizedStringFinder.GetString(EOResourceID.GUILD_RANKING)} {i + 1}", + DefaultValue: rankString.Capitalize(), + MaxChars: 16 + ) + ); + + var dialog = _textMultiInputDialogFactory.Create( + _localizedStringFinder.GetString(EOResourceID.GUILD_RANKING), + _localizedStringFinder.GetString(EOResourceID.GUILD_ENTER_YOUR_RANKINGS), + TextMultiInputDialog.DialogSize.NineWithScroll, + inputs.ToArray()); + dialog.DialogClosing += (_, e) => + { + if (e.Result == XNADialogResult.OK) + _guildActions.SetGuildRanks(dialog.Responses); + }; + dialog.ShowDialog(); + } } }