diff --git a/EOBot/Interpreter/BuiltInIdentifierConfigurator.cs b/EOBot/Interpreter/BuiltInIdentifierConfigurator.cs index 6cac73f90..0466680e5 100644 --- a/EOBot/Interpreter/BuiltInIdentifierConfigurator.cs +++ b/EOBot/Interpreter/BuiltInIdentifierConfigurator.cs @@ -6,6 +6,7 @@ using EOLib.Domain.Login; using EOLib.Domain.Map; using EOLib.Domain.NPC; +using EOLib.Domain.Party; using EOLib.Domain.Protocol; using EOLib.IO.Repositories; using EOLib.Net.Communication; @@ -45,6 +46,8 @@ public void SetupBuiltInFunctions() _state.SymbolTable[PredefinedIdentifiers.SETENV_FUNC] = Readonly(new VoidFunction(PredefinedIdentifiers.SETENV_FUNC, (varName, varValue) => Environment.SetEnvironmentVariable(varName, varValue, EnvironmentVariableTarget.User))); _state.SymbolTable[PredefinedIdentifiers.GETENV_FUNC] = Readonly(new Function(PredefinedIdentifiers.GETENV_FUNC, varName => Environment.GetEnvironmentVariable(varName, EnvironmentVariableTarget.User))); _state.SymbolTable[PredefinedIdentifiers.ERROR_FUNC] = Readonly(new VoidFunction(PredefinedIdentifiers.ERROR_FUNC, message => throw new BotScriptErrorException(message))); + _state.SymbolTable[PredefinedIdentifiers.LOWER_FUNC] = Readonly(new Function(PredefinedIdentifiers.LOWER_FUNC, s => s.ToLower())); + _state.SymbolTable[PredefinedIdentifiers.UPPER_FUNC] = Readonly(new Function(PredefinedIdentifiers.UPPER_FUNC, s => s.ToUpper())); BotDependencySetup(); _state.SymbolTable[PredefinedIdentifiers.CONNECT_FUNC] = Readonly(new AsyncVoidFunction(PredefinedIdentifiers.CONNECT_FUNC, ConnectAsync)); @@ -56,7 +59,9 @@ public void SetupBuiltInFunctions() _state.SymbolTable[PredefinedIdentifiers.CREATE_CHARACTER_FUNC] = Readonly(new AsyncFunction(PredefinedIdentifiers.CREATE_CHARACTER_FUNC, CreateCharacterAsync)); _state.SymbolTable[PredefinedIdentifiers.DELETE_CHARACTER_FUNC] = Readonly(new AsyncFunction(PredefinedIdentifiers.DELETE_CHARACTER_FUNC, DeleteCharacterAsync)); _state.SymbolTable[PredefinedIdentifiers.LOGIN_CHARACTER_FUNC] = Readonly(new AsyncVoidFunction(PredefinedIdentifiers.LOGIN_CHARACTER_FUNC, LoginToCharacterAsync)); + _state.SymbolTable[PredefinedIdentifiers.JOIN_PARTY] = Readonly(new VoidFunction(PredefinedIdentifiers.JOIN_PARTY, JoinParty)); } + public void SetupBuiltInVariables() { _state.SymbolTable[PredefinedIdentifiers.HOST] = Readonly(new StringVariable(_parsedArgs.Host)); @@ -175,6 +180,12 @@ private Task LoginToCharacterAsync(string charName) return _botHelper.LoginToCharacterAsync(charName); } + private void JoinParty(int characterId) + { + var c = DependencyMaster.TypeRegistry[_botIndex]; + c.Resolve().RequestParty(PartyRequestType.Join, (short)characterId); + } + private (bool, IIdentifiable) SetupAccountObject() { var playerInfoProv = DependencyMaster.TypeRegistry[_botIndex].Resolve(); @@ -201,6 +212,7 @@ private Task LoginToCharacterAsync(string charName) var pubProvider = DependencyMaster.TypeRegistry[_botIndex].Resolve(); var charObj = new RuntimeEvaluatedMemberObjectVariable(); + charObj.SymbolTable["id"] = (true, () => new IntVariable(cp.MainCharacter.ID)); charObj.SymbolTable[PredefinedIdentifiers.NAME] = (true, () => new StringVariable(cp.MainCharacter.Name)); charObj.SymbolTable["map"] = (true, () => new IntVariable(cp.MainCharacter.MapID)); charObj.SymbolTable["x"] = (true, () => new IntVariable(cp.MainCharacter.RenderProperties.MapX)); @@ -261,6 +273,7 @@ private Task LoginToCharacterAsync(string charName) private IVariable GetMapStateCharacter(Character c) { var charObj = new ObjectVariable(); + charObj.SymbolTable["id"] = Readonly(new IntVariable(c.ID)); charObj.SymbolTable[PredefinedIdentifiers.NAME] = Readonly(new StringVariable(c.Name)); charObj.SymbolTable["map"] = Readonly(new IntVariable(c.MapID)); charObj.SymbolTable["x"] = Readonly(new IntVariable(c.RenderProperties.MapX)); diff --git a/EOBot/Interpreter/Variables/PredefinedIdentifiers.cs b/EOBot/Interpreter/Variables/PredefinedIdentifiers.cs index bd779f3e4..4ed218cf1 100644 --- a/EOBot/Interpreter/Variables/PredefinedIdentifiers.cs +++ b/EOBot/Interpreter/Variables/PredefinedIdentifiers.cs @@ -31,6 +31,8 @@ public static class PredefinedIdentifiers public const string SETENV_FUNC = "setenv"; public const string GETENV_FUNC = "getenv"; public const string ERROR_FUNC = "error"; + public const string LOWER_FUNC = "lower"; + public const string UPPER_FUNC = "upper"; // game functions public const string CONNECT_FUNC = "Connect"; @@ -42,5 +44,7 @@ public static class PredefinedIdentifiers public const string CREATE_CHARACTER_FUNC = "CreateCharacter"; public const string LOGIN_CHARACTER_FUNC = "LoginCharacter"; public const string DELETE_CHARACTER_FUNC = "DeleteCharacter"; + + public const string JOIN_PARTY = "JoinParty"; } } diff --git a/EOBot/Program.cs b/EOBot/Program.cs index 778a0c7cb..94dc03c7d 100644 --- a/EOBot/Program.cs +++ b/EOBot/Program.cs @@ -6,9 +6,11 @@ using EOLib.Domain.Map; using EOLib.Domain.Notifiers; using EOLib.Domain.NPC; +using EOLib.Domain.Spells; using EOLib.IO.Repositories; using Optional; using System; +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -160,6 +162,7 @@ public void NotifyStartSpellCast(short playerId, short spellId) { } public void NotifyTargetOtherSpellCast(short sourcePlayerID, short targetPlayerID, short spellId, int recoveredHP, byte targetPercentHealth) { } public void StartOtherCharacterAttackAnimation(int characterID, int noteIndex) { } public void StartOtherCharacterWalkAnimation(int characterID, byte destinationX, byte destinationY, EODirection direction) { } + public void NotifyGroupSpellCast(short playerId, short spellId, short spellHp, List spellTargets) { } } static async Task Main(string[] args) diff --git a/EOLib/Domain/Character/CharacterActions.cs b/EOLib/Domain/Character/CharacterActions.cs index 5f292bf27..f73b4d00c 100644 --- a/EOLib/Domain/Character/CharacterActions.cs +++ b/EOLib/Domain/Character/CharacterActions.cs @@ -126,7 +126,6 @@ public void CastSpell(int spellId, ISpellTargetable target) if (data.Target == IO.SpellTarget.Group) { - // todo: implement packet handling for group target spells builder = builder .AddShort((short)spellId) .AddThree(DateTime.Now.ToEOTimeStamp()); diff --git a/EOLib/Domain/Chat/ChatActions.cs b/EOLib/Domain/Chat/ChatActions.cs index aea3e1ab6..145aff7d4 100644 --- a/EOLib/Domain/Chat/ChatActions.cs +++ b/EOLib/Domain/Chat/ChatActions.cs @@ -1,5 +1,6 @@ using AutomaticTypeMapper; using EOLib.Domain.Character; +using EOLib.Domain.Party; using EOLib.Net; using EOLib.Net.Builders; using EOLib.Net.Communication; @@ -15,6 +16,7 @@ public enum ChatResult HideSpeechBubble, Command, AdminAnnounce, + HideAll, } [AutoMappedType] @@ -22,6 +24,7 @@ public class ChatActions : IChatActions { private readonly IChatRepository _chatRepository; private readonly ICharacterProvider _characterProvider; + private readonly IPartyDataProvider _partyDataProvider; private readonly IChatTypeCalculator _chatTypeCalculator; private readonly IChatPacketBuilder _chatPacketBuilder; private readonly IPacketSendService _packetSendService; @@ -30,6 +33,7 @@ public class ChatActions : IChatActions public ChatActions(IChatRepository chatRepository, ICharacterProvider characterProvider, + IPartyDataProvider partyDataProvider, IChatTypeCalculator chatTypeCalculator, IChatPacketBuilder chatPacketBuilder, IPacketSendService packetSendService, @@ -38,6 +42,7 @@ public ChatActions(IChatRepository chatRepository, { _chatRepository = chatRepository; _characterProvider = characterProvider; + _partyDataProvider = partyDataProvider; _chatTypeCalculator = chatTypeCalculator; _chatPacketBuilder = chatPacketBuilder; _packetSendService = packetSendService; @@ -47,8 +52,6 @@ public ChatActions(IChatRepository chatRepository, public (ChatResult, string) SendChatToServer(string chat, string targetCharacter) { - // todo: if not in a group, don't do group chat - var chatType = _chatTypeCalculator.CalculateChatType(chat); if (chatType == ChatType.Command) @@ -66,6 +69,10 @@ public ChatActions(IChatRepository chatRepository, else if (string.IsNullOrEmpty(_chatRepository.PMTarget2)) _chatRepository.PMTarget2 = targetCharacter; } + else if (chatType == ChatType.Party && !_partyDataProvider.Members.Any()) + { + return (ChatResult.HideAll, String.Empty); + } chat = _chatProcessor.RemoveFirstCharacterIfNeeded(chat, chatType, targetCharacter); var (ok, filtered) = _chatProcessor.FilterCurses(chat); diff --git a/EOLib/Domain/Chat/ChatLoggerProvider.cs b/EOLib/Domain/Chat/ChatLoggerProvider.cs index 93fdbbc54..c6cc93bc2 100644 --- a/EOLib/Domain/Chat/ChatLoggerProvider.cs +++ b/EOLib/Domain/Chat/ChatLoggerProvider.cs @@ -1,4 +1,5 @@ using AutomaticTypeMapper; +using EOLib.Config; using EOLib.Logger; using System; @@ -14,9 +15,12 @@ public class ChatLoggerProvider : IChatLoggerProvider { public ILogger ChatLogger { get; } - public ChatLoggerProvider(ILoggerFactory loggerFactory) + public ChatLoggerProvider(IConfigurationProvider configurationProvider, ILoggerFactory loggerFactory) { - ChatLogger = loggerFactory.CreateLogger(Constants.ChatLogFile); + if (configurationProvider.LogChatToFile) + ChatLogger = loggerFactory.CreateLogger(Constants.ChatLogFile); + else + ChatLogger = loggerFactory.CreateLogger(); } public void Dispose() diff --git a/EOLib/Domain/Notifiers/IOtherCharacterAnimationNotifier.cs b/EOLib/Domain/Notifiers/IOtherCharacterAnimationNotifier.cs index 827343f5e..73f8b0493 100644 --- a/EOLib/Domain/Notifiers/IOtherCharacterAnimationNotifier.cs +++ b/EOLib/Domain/Notifiers/IOtherCharacterAnimationNotifier.cs @@ -1,4 +1,6 @@ using AutomaticTypeMapper; +using EOLib.Domain.Spells; +using System.Collections.Generic; namespace EOLib.Domain.Notifiers { @@ -13,6 +15,8 @@ public interface IOtherCharacterAnimationNotifier void NotifySelfSpellCast(short playerId, short spellId, int spellHp, byte percentHealth); void NotifyTargetOtherSpellCast(short sourcePlayerID, short targetPlayerID, short spellId, int recoveredHP, byte targetPercentHealth); + + void NotifyGroupSpellCast(short playerId, short spellId, short spellHp, List spellTargets); } [AutoMappedType] @@ -27,5 +31,7 @@ public void NotifyStartSpellCast(short playerId, short spellId) { } public void NotifySelfSpellCast(short playerId, short spellId, int spellHp, byte percentHealth) { } public void NotifyTargetOtherSpellCast(short sourcePlayerID, short targetPlayerID, short spellId, int recoveredHP, byte targetPercentHealth) { } + + public void NotifyGroupSpellCast(short playerId, short spellId, short spellHp, List spellTargets) { } } } diff --git a/EOLib/Domain/Notifiers/IPartyEventNotifier.cs b/EOLib/Domain/Notifiers/IPartyEventNotifier.cs new file mode 100644 index 000000000..cdde80116 --- /dev/null +++ b/EOLib/Domain/Notifiers/IPartyEventNotifier.cs @@ -0,0 +1,28 @@ +using AutomaticTypeMapper; +using EOLib.Domain.Party; + +namespace EOLib.Domain.Notifiers +{ + public interface IPartyEventNotifier + { + void NotifyPartyRequest(PartyRequestType type, short playerId, string name); + + void NotifyPartyJoined(); + + void NotifyPartyMemberAdd(string name); + + void NotifyPartyMemberRemove(string name); + } + + [AutoMappedType] + public class NoOpPartyEventNotifier : IPartyEventNotifier + { + public void NotifyPartyRequest(PartyRequestType type, short playerId, string name) { } + + public void NotifyPartyJoined() { } + + public void NotifyPartyMemberAdd(string name) { } + + public void NotifyPartyMemberRemove(string name) { } + } +} diff --git a/EOLib/Domain/Party/PartyActions.cs b/EOLib/Domain/Party/PartyActions.cs new file mode 100644 index 000000000..e8744e4e5 --- /dev/null +++ b/EOLib/Domain/Party/PartyActions.cs @@ -0,0 +1,65 @@ +using AutomaticTypeMapper; +using EOLib.Net; +using EOLib.Net.Communication; + +namespace EOLib.Domain.Party +{ + [AutoMappedType] + public class PartyActions : IPartyActions + { + private readonly IPacketSendService _packetSendService; + + public PartyActions(IPacketSendService packetSendService) + { + _packetSendService = packetSendService; + } + + public void RequestParty(PartyRequestType type, short targetCharacterId) + { + var packet = new PacketBuilder(PacketFamily.Party, PacketAction.Request) + .AddChar((byte)type) + .AddShort(targetCharacterId) + .Build(); + + _packetSendService.SendPacket(packet); + } + + public void AcceptParty(PartyRequestType type, short targetCharacterId) + { + var packet = new PacketBuilder(PacketFamily.Party, PacketAction.Accept) + .AddChar((byte)type) + .AddShort(targetCharacterId) + .Build(); + + _packetSendService.SendPacket(packet); + } + + public void ListParty() + { + var packet = new PacketBuilder(PacketFamily.Party, PacketAction.Take) + .Build(); + + _packetSendService.SendPacket(packet); + } + + public void RemovePartyMember(short targetCharacterId) + { + var packet = new PacketBuilder(PacketFamily.Party, PacketAction.Remove) + .AddShort(targetCharacterId) + .Build(); + + _packetSendService.SendPacket(packet); + } + } + + public interface IPartyActions + { + void RequestParty(PartyRequestType type, short targetCharacterId); + + void AcceptParty(PartyRequestType type, short targetCharacterId); + + void ListParty(); + + void RemovePartyMember(short targetCharacterId); + } +} diff --git a/EOLib/Domain/Party/PartyMember.cs b/EOLib/Domain/Party/PartyMember.cs new file mode 100644 index 000000000..3cf1effa6 --- /dev/null +++ b/EOLib/Domain/Party/PartyMember.cs @@ -0,0 +1,18 @@ +using Amadevus.RecordGenerator; + +namespace EOLib.Domain.Party +{ + [Record(Features.ObjectEquals | Features.Withers | Features.Builder | Features.Constructor | Features.ToString)] + public sealed partial class PartyMember + { + public short CharacterID { get; } + + public bool IsLeader { get; } + + public byte Level { get; } + + public byte PercentHealth { get; } + + public string Name { get; } + } +} diff --git a/EOLib/Domain/Party/PartyRepository.cs b/EOLib/Domain/Party/PartyRepository.cs new file mode 100644 index 000000000..6f39e3702 --- /dev/null +++ b/EOLib/Domain/Party/PartyRepository.cs @@ -0,0 +1,28 @@ +using AutomaticTypeMapper; +using System.Collections.Generic; + +namespace EOLib.Domain.Party +{ + public interface IPartyDataRepository + { + List Members { get; } + } + + public interface IPartyDataProvider + { + IReadOnlyList Members { get; } + } + + [AutoMappedType(IsSingleton = true)] + public class PartyDataRepository : IPartyDataRepository, IPartyDataProvider + { + public List Members { get; set; } + + IReadOnlyList IPartyDataProvider.Members => Members; + + public PartyDataRepository() + { + Members = new List(); + } + } +} diff --git a/EOLib/Domain/Party/PartyRequestType.cs b/EOLib/Domain/Party/PartyRequestType.cs new file mode 100644 index 000000000..3bdca2e5c --- /dev/null +++ b/EOLib/Domain/Party/PartyRequestType.cs @@ -0,0 +1,8 @@ +namespace EOLib.Domain.Party +{ + public enum PartyRequestType + { + Join, + Invite + } +} diff --git a/EOLib/Domain/Spells/GroupSpellTarget.cs b/EOLib/Domain/Spells/GroupSpellTarget.cs new file mode 100644 index 000000000..135da119d --- /dev/null +++ b/EOLib/Domain/Spells/GroupSpellTarget.cs @@ -0,0 +1,14 @@ +using Amadevus.RecordGenerator; + +namespace EOLib.Domain.Spells +{ + [Record] + public sealed partial class GroupSpellTarget + { + public short TargetId { get; } + + public byte PercentHealth { get; } + + public short TargetHp { get; } + } +} diff --git a/EOLib/Net/API/PacketAPI.cs b/EOLib/Net/API/PacketAPI.cs index 03880045b..84835e35c 100644 --- a/EOLib/Net/API/PacketAPI.cs +++ b/EOLib/Net/API/PacketAPI.cs @@ -20,8 +20,6 @@ public PacketAPI(EOClient client) m_client = client; //each of these sets up members of the partial PacketAPI class relevant to a particular packet family - _createPartyMembers(); - _createSpellMembers(); _createTradeMembers(); } diff --git a/EOLib/Net/API/Party.cs b/EOLib/Net/API/Party.cs deleted file mode 100644 index fa0a0064c..000000000 --- a/EOLib/Net/API/Party.cs +++ /dev/null @@ -1,194 +0,0 @@ -using System; -using System.Collections.Generic; -using EOLib.Net.Handlers; - -namespace EOLib.Net.API -{ - public enum PartyRequestType - { - Join, - Invite - } - - public struct PartyMember - { - private readonly bool m_isFullData; - /// - /// Determines if full data is present. When false, only ID and PercentHealth are valid. - /// - public bool IsFullData => m_isFullData; - - private readonly short m_id; - private readonly bool m_isLeader; - private readonly byte m_level; - private byte m_pctHealth; - private readonly string m_name; - - public short ID => m_id; - public bool IsLeader => m_isLeader; - public byte Level => m_level; - public byte PercentHealth => m_pctHealth; - public string Name => m_name; - - internal PartyMember(OldPacket pkt, bool isFullData) - { - m_isFullData = isFullData; - m_id = pkt.GetShort(); - if (!m_isFullData) - { - //if it isn't full data, only the id and health are being provide as part of an AGREE packet - m_pctHealth = pkt.GetChar(); - m_isLeader = false; - m_level = 0; - m_name = ""; - return; - } - - m_isLeader = pkt.GetChar() != 0; - m_level = pkt.GetChar(); - m_pctHealth = pkt.GetChar(); - m_name = pkt.GetBreakString(); - m_name = char.ToUpper(m_name[0]) + m_name.Substring(1); - } - - public void SetPercentHealth(byte percentHealth) - { - m_pctHealth = percentHealth; - } - } - - partial class PacketAPI - { - public delegate void PartyRequestEvent(PartyRequestType type, short playerID, string name); - - public event PartyRequestEvent OnPartyRequest; - public event Action> OnPartyDataRefresh; - public event Action OnPartyMemberJoin; - public event Action OnPartyMemberLeave; - public event Action OnPartyClose; - - private void _createPartyMembers() - { - m_client.AddPacketHandler(new FamilyActionPair(PacketFamily.Party, PacketAction.Request), _handlePartyRequest, true); - m_client.AddPacketHandler(new FamilyActionPair(PacketFamily.Party, PacketAction.Create), _handlePartyCreateList, true); - m_client.AddPacketHandler(new FamilyActionPair(PacketFamily.Party, PacketAction.List), _handlePartyCreateList, true); - m_client.AddPacketHandler(new FamilyActionPair(PacketFamily.Party, PacketAction.Agree), _handlePartyAgree, true); - m_client.AddPacketHandler(new FamilyActionPair(PacketFamily.Party, PacketAction.Add), _handlePartyAdd, true); - m_client.AddPacketHandler(new FamilyActionPair(PacketFamily.Party, PacketAction.Remove), _handlePartyRemove, true); - m_client.AddPacketHandler(new FamilyActionPair(PacketFamily.Party, PacketAction.Close), _handlePartyClose, true); - } - - /// - /// Send a party request to another player - /// - /// Either Join or Invite - /// ID of the other character - /// True on successful send operation, false otherwise - public bool PartyRequest(PartyRequestType type, short otherCharID) - { - if (!m_client.ConnectedAndInitialized || !Initialized) - return false; - - OldPacket pkt = new OldPacket(PacketFamily.Party, PacketAction.Request); - pkt.AddChar((byte)type); - pkt.AddShort(otherCharID); - - return m_client.SendPacket(pkt); - } - - /// - /// Accept another character's party request - /// - /// Join to join another player's party, invite to have them join yours - /// ID of the other character - /// True on successful send operation, false otherwise - public bool PartyAcceptRequest(PartyRequestType type, short otherCharID) - { - if (!m_client.ConnectedAndInitialized || !Initialized) - return false; - - OldPacket pkt = new OldPacket(PacketFamily.Party, PacketAction.Accept); - pkt.AddChar((byte)type); - pkt.AddShort(otherCharID); - - return m_client.SendPacket(pkt); - } - - /// - /// Remove a player from a party - /// - /// ID of the other character - /// - public bool PartyRemovePlayer(short otherCharID) - { - if (!m_client.ConnectedAndInitialized || !Initialized) - return false; - - OldPacket pkt = new OldPacket(PacketFamily.Party, PacketAction.Remove); - pkt.AddShort(otherCharID); - - return m_client.SendPacket(pkt); - } - - public bool PartyListMembers() - { - if (!m_client.ConnectedAndInitialized || !Initialized) - return false; - - return m_client.SendPacket(new OldPacket(PacketFamily.Party, PacketAction.Take)); - } - - //handles a request to join/invite from another player - private void _handlePartyRequest(OldPacket pkt) - { - if (OnPartyRequest == null) return; - PartyRequestType type = (PartyRequestType) pkt.GetChar(); - short playerID = pkt.GetShort(); - string name = pkt.GetEndString(); - name = char.ToUpper(name[0]) + name.Substring(1); - OnPartyRequest(type, playerID, name); - } - - //handles party_create and party_list packets - //party_create should do some creation logic - party_list should only update the members info - private void _handlePartyCreateList(OldPacket pkt) - { - if (OnPartyDataRefresh == null) return; - List members = new List(); - while (pkt.ReadPos != pkt.Length) - members.Add(new PartyMember(pkt, true)); - OnPartyDataRefresh(members); - } - - //handles an HP update for party members - private void _handlePartyAgree(OldPacket pkt) - { - if (OnPartyDataRefresh == null) return; - List members = new List(); - while (pkt.ReadPos != pkt.Length) - members.Add(new PartyMember(pkt, false)); - OnPartyDataRefresh(members); - } - - private void _handlePartyAdd(OldPacket pkt) - { - if(OnPartyMemberJoin != null) - OnPartyMemberJoin(new PartyMember(pkt, true)); - } - - //handles removing a player from a party (not this player) - private void _handlePartyRemove(OldPacket pkt) - { - if (OnPartyMemberLeave != null) - OnPartyMemberLeave(pkt.GetShort()); //id of character leaving - } - - //closes a party on this end - private void _handlePartyClose(OldPacket pkt) - { - //1 byte - 255 - if (OnPartyClose != null) - OnPartyClose(); - } - } -} diff --git a/EOLib/Net/API/Spell.cs b/EOLib/Net/API/Spell.cs deleted file mode 100644 index 61c6eedd9..000000000 --- a/EOLib/Net/API/Spell.cs +++ /dev/null @@ -1,76 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using EOLib.Net.Handlers; - -namespace EOLib.Net.API -{ - public struct GroupSpellTarget - { - private readonly short _partyMemberID, _partyMemberPercentHealth, _partyMemberHP; - - public short MemberID => _partyMemberID; - public short MemberPercentHealth => _partyMemberPercentHealth; - public short MemberHP => _partyMemberHP; - - internal GroupSpellTarget(OldPacket pkt) - { - _partyMemberID = pkt.GetShort(); - _partyMemberPercentHealth = pkt.GetChar(); - _partyMemberHP = pkt.GetShort(); - } - } - - #region event delegates - - public delegate void CastSpellGroupEvent(short spellID, short fromPlayerID, short fromPlayerTP, short spellHPGain, List spellTargets); - - #endregion - - partial class PacketAPI - { - #region public events - - public event CastSpellGroupEvent OnCastSpellTargetGroup; - - #endregion - - #region initialization - - private void _createSpellMembers() - { - //note: see CAST_REPLY handler in _handleNPCReply for NPCs taking damage from a spell. handler is almost identical - // see CAST_ACCPT handler for leveling up off NPC kill via spell - // see CAST_SPEC handler for regular NPC death via spell - //other note: Spell attacks for PK are not supported yet - m_client.AddPacketHandler(new FamilyActionPair(PacketFamily.Spell, PacketAction.TargetGroup), _handleSpellTargetGroup, true); - } - - #endregion - - #region handler methods - - private void _handleSpellTargetGroup(OldPacket pkt) - { - if (OnCastSpellTargetGroup == null) return; - - short spellID = pkt.GetShort(); - short fromPlayerID = pkt.GetShort(); - short fromPlayerTP = pkt.GetShort(); - short spellHealthGain = pkt.GetShort(); - - var spellTargets = new List(); - while (pkt.ReadPos != pkt.Length) - { - //malformed packet - eoserv puts 5 '255' bytes between party members - if (pkt.GetBytes(5).Any(x => x != 255)) return; - - spellTargets.Add(new GroupSpellTarget(pkt)); - } - - OnCastSpellTargetGroup(spellID, fromPlayerID, fromPlayerTP, spellHealthGain, spellTargets); - } - - #endregion - } -} diff --git a/EOLib/PacketHandlers/Chat/GroupChatHandler.cs b/EOLib/PacketHandlers/Chat/GroupChatHandler.cs index cb9158646..89e3857ae 100644 --- a/EOLib/PacketHandlers/Chat/GroupChatHandler.cs +++ b/EOLib/PacketHandlers/Chat/GroupChatHandler.cs @@ -34,6 +34,9 @@ protected override void DoTalk(IPacket packet, Character character) { var message = packet.ReadBreakString(); + var localChatData = new ChatData(ChatTab.Local, character.Name, message, ChatIcon.PlayerPartyDark, ChatColor.PM); + _chatRepository.AllChat[ChatTab.Local].Add(localChatData); + var chatData = new ChatData(ChatTab.Group, character.Name, message, ChatIcon.PlayerPartyDark); _chatRepository.AllChat[ChatTab.Group].Add(chatData); diff --git a/EOLib/PacketHandlers/Init/OnlinePlayerListHandler.cs b/EOLib/PacketHandlers/Init/OnlinePlayerListHandler.cs index 8e70a4236..495e9330a 100644 --- a/EOLib/PacketHandlers/Init/OnlinePlayerListHandler.cs +++ b/EOLib/PacketHandlers/Init/OnlinePlayerListHandler.cs @@ -39,7 +39,7 @@ protected override OnlinePlayerInfo GetNextRecord(IPacket packet) else title = char.ToUpper(title[0]) + title.Substring(1); - var className = _classFileProvider.ECFFile.Length >= clsId + var className = _classFileProvider.ECFFile.Length >= clsId && clsId != 0 ? _classFileProvider.ECFFile[clsId].Name : "-"; diff --git a/EOLib/PacketHandlers/Party/PartyAddHandler.cs b/EOLib/PacketHandlers/Party/PartyAddHandler.cs new file mode 100644 index 000000000..e707694d7 --- /dev/null +++ b/EOLib/PacketHandlers/Party/PartyAddHandler.cs @@ -0,0 +1,55 @@ +using AutomaticTypeMapper; +using EOLib.Domain.Login; +using EOLib.Domain.Notifiers; +using EOLib.Domain.Party; +using EOLib.Net; +using EOLib.Net.Handlers; +using System.Collections.Generic; + +namespace EOLib.PacketHandlers.Party +{ + /// + /// Handles new member joining party + /// + [AutoMappedType] + public class PartyAddHandler : InGameOnlyPacketHandler + { + private readonly IPartyDataRepository _partyDataRepository; + private readonly IEnumerable _partyEventNotifiers; + + public override PacketFamily Family => PacketFamily.Party; + + public override PacketAction Action => PacketAction.Add; + + public PartyAddHandler(IPlayerInfoProvider playerInfoProvider, + IPartyDataRepository partyDataRepository, + IEnumerable partyEventNotifiers) + : base(playerInfoProvider) + { + _partyDataRepository = partyDataRepository; + _partyEventNotifiers = partyEventNotifiers; + } + + public override bool HandlePacket(IPacket packet) + { + var partyMember = new PartyMember.Builder + { + CharacterID = packet.ReadShort(), + IsLeader = packet.ReadChar() != 0, + Level = packet.ReadChar(), + PercentHealth = packet.ReadChar(), + Name = packet.ReadBreakString(), + }; + partyMember.Name = char.ToUpper(partyMember.Name[0]) + partyMember.Name.Substring(1); + + _partyDataRepository.Members.Add(partyMember.ToImmutable()); + + foreach (var notifier in _partyEventNotifiers) + { + notifier.NotifyPartyMemberAdd(partyMember.Name); + } + + return true; + } + } +} diff --git a/EOLib/PacketHandlers/Party/PartyAgreeHandler.cs b/EOLib/PacketHandlers/Party/PartyAgreeHandler.cs new file mode 100644 index 000000000..dbf6e04b7 --- /dev/null +++ b/EOLib/PacketHandlers/Party/PartyAgreeHandler.cs @@ -0,0 +1,53 @@ +using AutomaticTypeMapper; +using EOLib.Domain.Login; +using EOLib.Domain.Map; +using EOLib.Domain.Party; +using EOLib.Net; +using EOLib.Net.Handlers; +using Optional.Collections; + +namespace EOLib.PacketHandlers.Party +{ + /// + /// Handles HP update for party members + /// + [AutoMappedType] + public class PartyAgreeHandler : InGameOnlyPacketHandler + { + private readonly IPartyDataRepository _partyDataRepository; + private readonly ICurrentMapStateProvider _currentMapStateProvider; + + public override PacketFamily Family => PacketFamily.Party; + + public override PacketAction Action => PacketAction.Agree; + + public PartyAgreeHandler(IPlayerInfoProvider playerInfoProvider, + IPartyDataRepository partyDataRepository, + ICurrentMapStateProvider currentMapStateProvider) + : base(playerInfoProvider) + { + _partyDataRepository = partyDataRepository; + _currentMapStateProvider = currentMapStateProvider; + } + + public override bool HandlePacket(IPacket packet) + { + while (packet.ReadPosition < packet.Length) + { + var playerId = packet.ReadShort(); + var percentHealth = packet.ReadChar(); + + _partyDataRepository.Members.SingleOrNone(x => x.CharacterID == playerId) + .Match( + some: x => + { + _partyDataRepository.Members.Remove(x); + _partyDataRepository.Members.Add(x.WithPercentHealth(percentHealth)); + }, + none: () => _currentMapStateProvider.UnknownPlayerIDs.Add(playerId)); + } + + return true; + } + } +} diff --git a/EOLib/PacketHandlers/Party/PartyCloseHandler.cs b/EOLib/PacketHandlers/Party/PartyCloseHandler.cs new file mode 100644 index 000000000..549e16d35 --- /dev/null +++ b/EOLib/PacketHandlers/Party/PartyCloseHandler.cs @@ -0,0 +1,34 @@ +using AutomaticTypeMapper; +using EOLib.Domain.Login; +using EOLib.Domain.Party; +using EOLib.Net; +using EOLib.Net.Handlers; + +namespace EOLib.PacketHandlers.Party +{ + /// + /// Handles leaving (or being removed from) a party + /// + [AutoMappedType] + public class PartyCloseHandler : InGameOnlyPacketHandler + { + private readonly IPartyDataRepository _partyDataRepository; + + public override PacketFamily Family => PacketFamily.Party; + + public override PacketAction Action => PacketAction.Close; + + public PartyCloseHandler(IPlayerInfoProvider playerInfoProvider, + IPartyDataRepository partyDataRepository) + : base(playerInfoProvider) + { + _partyDataRepository = partyDataRepository; + } + + public override bool HandlePacket(IPacket packet) + { + _partyDataRepository.Members.Clear(); + return packet.ReadByte() == 255; + } + } +} diff --git a/EOLib/PacketHandlers/Party/PartyCreateHandler.cs b/EOLib/PacketHandlers/Party/PartyCreateHandler.cs new file mode 100644 index 000000000..f4fa1b3d5 --- /dev/null +++ b/EOLib/PacketHandlers/Party/PartyCreateHandler.cs @@ -0,0 +1,62 @@ +using AutomaticTypeMapper; +using EOLib.Domain.Login; +using EOLib.Domain.Notifiers; +using EOLib.Domain.Party; +using EOLib.Net; +using EOLib.Net.Handlers; +using System.Collections.Generic; + +namespace EOLib.PacketHandlers.Party +{ + /// + /// Handles HP update for party members + /// + [AutoMappedType] + public class PartyCreateHandler : InGameOnlyPacketHandler + { + private readonly IPartyDataRepository _partyDataRepository; + private readonly IEnumerable _partyEventNotifiers; + + public override PacketFamily Family => PacketFamily.Party; + + public override PacketAction Action => PacketAction.Create; + + public PartyCreateHandler(IPlayerInfoProvider playerInfoProvider, + IPartyDataRepository partyDataRepository, + IEnumerable partyEventNotifiers) + : base(playerInfoProvider) + { + _partyDataRepository = partyDataRepository; + _partyEventNotifiers = partyEventNotifiers; + } + + public override bool HandlePacket(IPacket packet) + { + _partyDataRepository.Members.Clear(); + + while (packet.ReadPosition < packet.Length) + { + var partyMember = new PartyMember.Builder + { + CharacterID = packet.ReadShort(), + IsLeader = packet.ReadChar() != 0, + Level = packet.ReadChar(), + PercentHealth = packet.ReadChar(), + Name = packet.ReadBreakString(), + }; + partyMember.Name = char.ToUpper(partyMember.Name[0]) + partyMember.Name.Substring(1); + + _partyDataRepository.Members.Add(partyMember.ToImmutable()); + } + + if (Action == PacketAction.Create) + { + // don't notify a party was joined for LIST packet handling + foreach (var notifier in _partyEventNotifiers) + notifier.NotifyPartyJoined(); + } + + return true; + } + } +} diff --git a/EOLib/PacketHandlers/Party/PartyListHandler.cs b/EOLib/PacketHandlers/Party/PartyListHandler.cs new file mode 100644 index 000000000..9ce0e7783 --- /dev/null +++ b/EOLib/PacketHandlers/Party/PartyListHandler.cs @@ -0,0 +1,23 @@ +using AutomaticTypeMapper; +using EOLib.Domain.Login; +using EOLib.Domain.Notifiers; +using EOLib.Domain.Party; +using EOLib.Net; +using System.Collections.Generic; + +namespace EOLib.PacketHandlers.Party +{ + /// + /// Handles data update for party members + /// + [AutoMappedType] + public class PartyListHandler : PartyCreateHandler + { + public override PacketAction Action => PacketAction.List; + + public PartyListHandler(IPlayerInfoProvider playerInfoProvider, + IPartyDataRepository partyDataRepository, + IEnumerable partyEventNotifiers) + : base(playerInfoProvider, partyDataRepository, partyEventNotifiers) { } + } +} diff --git a/EOLib/PacketHandlers/Party/PartyRemoveHandler.cs b/EOLib/PacketHandlers/Party/PartyRemoveHandler.cs new file mode 100644 index 000000000..1864c0db4 --- /dev/null +++ b/EOLib/PacketHandlers/Party/PartyRemoveHandler.cs @@ -0,0 +1,49 @@ +using AutomaticTypeMapper; +using EOLib.Domain.Login; +using EOLib.Domain.Notifiers; +using EOLib.Domain.Party; +using EOLib.Net; +using EOLib.Net.Handlers; +using Optional.Collections; +using System.Collections.Generic; + +namespace EOLib.PacketHandlers.Party +{ + /// + /// Handles removing a member from the party + /// + [AutoMappedType] + public class PartyRemoveHandler : InGameOnlyPacketHandler + { + private readonly IPartyDataRepository _partyDataRepository; + private readonly IEnumerable _partyEventNotifiers; + + public override PacketFamily Family => PacketFamily.Party; + + public override PacketAction Action => PacketAction.Remove; + + public PartyRemoveHandler(IPlayerInfoProvider playerInfoProvider, + IPartyDataRepository partyDataRepository, + IEnumerable partyEventNotifiers) + : base(playerInfoProvider) + { + _partyDataRepository = partyDataRepository; + _partyEventNotifiers = partyEventNotifiers; + } + + public override bool HandlePacket(IPacket packet) + { + var memberToRemove = packet.ReadShort(); + + _partyDataRepository.Members.SingleOrNone(x => x.CharacterID == memberToRemove) + .MatchSome(x => + { + _partyDataRepository.Members.Remove(x); + foreach (var notifier in _partyEventNotifiers) + notifier.NotifyPartyMemberRemove(x.Name); + }); + + return true; + } + } +} diff --git a/EOLib/PacketHandlers/Party/PartyRequestHandler.cs b/EOLib/PacketHandlers/Party/PartyRequestHandler.cs new file mode 100644 index 000000000..a49f12332 --- /dev/null +++ b/EOLib/PacketHandlers/Party/PartyRequestHandler.cs @@ -0,0 +1,42 @@ +using AutomaticTypeMapper; +using EOLib.Domain.Login; +using EOLib.Domain.Notifiers; +using EOLib.Domain.Party; +using EOLib.Net; +using EOLib.Net.Handlers; +using System.Collections.Generic; + +namespace EOLib.PacketHandlers.Party +{ + /// + /// Handles invite/join request from another player + /// + [AutoMappedType] + public class PartyRequestHandler : InGameOnlyPacketHandler + { + private readonly IEnumerable _partyEventNotifiers; + + public override PacketFamily Family => PacketFamily.Party; + + public override PacketAction Action => PacketAction.Request; + + public PartyRequestHandler(IPlayerInfoProvider playerInfoProvider, + IEnumerable partyEventNotifiers) + : base(playerInfoProvider) + { + _partyEventNotifiers = partyEventNotifiers; + } + + public override bool HandlePacket(IPacket packet) + { + var type = (PartyRequestType)packet.ReadChar(); + var playerId = packet.ReadShort(); + var name = packet.ReadEndString(); + + foreach (var notifier in _partyEventNotifiers) + notifier.NotifyPartyRequest(type, playerId, name); + + return true; + } + } +} diff --git a/EOLib/PacketHandlers/TargetGroupSpellHandler.cs b/EOLib/PacketHandlers/TargetGroupSpellHandler.cs new file mode 100644 index 000000000..8026eb5f6 --- /dev/null +++ b/EOLib/PacketHandlers/TargetGroupSpellHandler.cs @@ -0,0 +1,90 @@ +using AutomaticTypeMapper; +using EOLib.Domain.Character; +using EOLib.Domain.Login; +using EOLib.Domain.Notifiers; +using EOLib.Domain.Party; +using EOLib.Domain.Spells; +using EOLib.Net; +using EOLib.Net.Handlers; +using Optional.Collections; +using System.Collections.Generic; +using System.Linq; + +namespace EOLib.PacketHandlers +{ + [AutoMappedType] + public class TargetGroupSpellHandler : InGameOnlyPacketHandler + { + private readonly ICharacterRepository _characterRepository; + private readonly IPartyDataRepository _partyDataRepository; + private readonly IEnumerable _notifiers; + + public override PacketFamily Family => PacketFamily.Spell; + + public override PacketAction Action => PacketAction.TargetGroup; + + public TargetGroupSpellHandler(IPlayerInfoProvider playerInfoProvider, + ICharacterRepository characterRepository, + IPartyDataRepository partyDataRepository, + IEnumerable notifiers) + : base(playerInfoProvider) + { + _characterRepository = characterRepository; + _partyDataRepository = partyDataRepository; + _notifiers = notifiers; + } + + public override bool HandlePacket(IPacket packet) + { + var spellId = packet.ReadShort(); + var sourcePlayerId = packet.ReadShort(); + var fromPlayerTp = packet.ReadShort(); + var spellHealthGain = packet.ReadShort(); + + if (sourcePlayerId == _characterRepository.MainCharacter.ID) + { + var stats = _characterRepository.MainCharacter.Stats.WithNewStat(CharacterStat.TP, fromPlayerTp); + _characterRepository.MainCharacter = _characterRepository.MainCharacter.WithStats(stats); + } + + var spellTargets = new List(); + while (packet.ReadPosition != packet.Length) + { + // eoserv puts 5 '255' bytes between party members + // unknown what data structures are supposed to be represented between these break bytes + if (packet.ReadBytes(5).Any(x => x != 255)) return false; + + var targetId = packet.ReadShort(); + var targetPercentHealth = packet.ReadChar(); + var targetHp = packet.ReadShort(); + + spellTargets.Add(new GroupSpellTarget.Builder + { + TargetId = targetId, + PercentHealth = targetPercentHealth, + TargetHp = targetHp, + }.ToImmutable()); + + _partyDataRepository.Members.SingleOrNone(x => x.CharacterID == targetId) + .MatchSome(x => + { + _partyDataRepository.Members.Remove(x); + _partyDataRepository.Members.Add(x.WithPercentHealth(targetPercentHealth)); + }); + + if (targetId == _characterRepository.MainCharacter.ID) + { + var stats = _characterRepository.MainCharacter.Stats.WithNewStat(CharacterStat.HP, targetHp); + _characterRepository.MainCharacter = _characterRepository.MainCharacter.WithStats(stats); + } + } + + foreach (var notifier in _notifiers) + { + notifier.NotifyGroupSpellCast(sourcePlayerId, spellId, spellHealthGain, spellTargets); + } + + return true; + } + } +} diff --git a/EndlessClient/Audio/SoundEffectID.cs b/EndlessClient/Audio/SoundEffectID.cs index 435e0d299..5615fb6a9 100644 --- a/EndlessClient/Audio/SoundEffectID.cs +++ b/EndlessClient/Audio/SoundEffectID.cs @@ -25,7 +25,7 @@ public enum SoundEffectID HudStatusBarClick = 12, AdminAnnounceReceived, MeleeWeaponAttack, - UnknownClickSound2, + MemberLeftParty, TradeAccepted = 16, JoinParty = TradeAccepted, GroupChatReceived, diff --git a/EndlessClient/Content/ContentProvider.cs b/EndlessClient/Content/ContentProvider.cs index 4b80e7e5a..753c288ef 100644 --- a/EndlessClient/Content/ContentProvider.cs +++ b/EndlessClient/Content/ContentProvider.cs @@ -55,6 +55,11 @@ public class ContentProvider : IContentProvider public const string ChatRR = @"ChatBubble\RR"; public const string ChatNUB = @"ChatBubble\NUB"; + public const string HPOutline = @"Party\hp-outline"; + public const string HPRed = @"Party\hp-red"; + public const string HPYellow = @"Party\hp-yellow"; + public const string HPGreen = @"Party\hp-green"; + public IReadOnlyDictionary Textures => _textures; public IReadOnlyDictionary Fonts => _fonts; @@ -109,6 +114,11 @@ private void RefreshTextures() _textures[ChatRM] = _content.Load(ChatRM); _textures[ChatRR] = _content.Load(ChatRR); _textures[ChatNUB] = _content.Load(ChatNUB); + + _textures[HPOutline] = _content.Load(HPOutline); + _textures[HPRed] = _content.Load(HPRed); + _textures[HPYellow] = _content.Load(HPYellow); + _textures[HPGreen] = _content.Load(HPGreen); } private void RefreshFonts() diff --git a/EndlessClient/Controllers/ChatController.cs b/EndlessClient/Controllers/ChatController.cs index 1c4573033..915ad320d 100644 --- a/EndlessClient/Controllers/ChatController.cs +++ b/EndlessClient/Controllers/ChatController.cs @@ -69,6 +69,7 @@ public void SendChatAndClearTextBox() break; case ChatResult.AdminAnnounce: _sfxPlayer.PlaySfx(SoundEffectID.AdminAnnounceReceived); break; case ChatResult.HideSpeechBubble: break; // no-op + case ChatResult.HideAll: break; // no-op } } diff --git a/EndlessClient/Dialogs/Actions/PartyDialogActions.cs b/EndlessClient/Dialogs/Actions/PartyDialogActions.cs new file mode 100644 index 000000000..6aefd2b5d --- /dev/null +++ b/EndlessClient/Dialogs/Actions/PartyDialogActions.cs @@ -0,0 +1,85 @@ +using AutomaticTypeMapper; +using EndlessClient.Audio; +using EndlessClient.Dialogs.Factories; +using EndlessClient.HUD; +using EOLib.Config; +using EOLib.Domain.Chat; +using EOLib.Domain.Notifiers; +using EOLib.Domain.Party; +using EOLib.Localization; +using XNAControls; + +namespace EndlessClient.Dialogs.Actions +{ + [AutoMappedType] + public class PartyDialogActions : IPartyEventNotifier + { + private readonly IPartyActions _partyActions; + private readonly ISfxPlayer _sfxPlayer; + private readonly IEOMessageBoxFactory _messageBoxFactory; + private readonly IStatusLabelSetter _statusLabelSetter; + private readonly ILocalizedStringFinder _localizedStringFinder; + private readonly IChatRepository _chatRepository; + private readonly IConfigurationProvider _configurationProvider; + + public PartyDialogActions(IPartyActions partyActions, + ISfxPlayer sfxPlayer, + IEOMessageBoxFactory messageBoxFactory, + IStatusLabelSetter statusLabelSetter, + ILocalizedStringFinder localizedStringFinder, + IChatRepository chatRepository, + IConfigurationProvider configurationProvider) + { + _partyActions = partyActions; + _sfxPlayer = sfxPlayer; + _messageBoxFactory = messageBoxFactory; + _statusLabelSetter = statusLabelSetter; + _localizedStringFinder = localizedStringFinder; + _chatRepository = chatRepository; + _configurationProvider = configurationProvider; + } + + public void NotifyPartyRequest(PartyRequestType type, short playerId, string name) + { + if (!_configurationProvider.Interaction) + return; + + var dlg = _messageBoxFactory.CreateMessageBox( + char.ToUpper(name[0]) + name[1..], + type == PartyRequestType.Join + ? DialogResourceID.PARTY_GROUP_REQUEST_TO_JOIN + : DialogResourceID.PARTY_GROUP_SEND_INVITATION, + EODialogButtons.OkCancel); + dlg.DialogClosing += (_, e) => + { + if (e.Result == XNADialogResult.OK) + { + _partyActions.AcceptParty(type, playerId); + } + }; + dlg.ShowDialog(); + } + + public void NotifyPartyJoined() + { + _sfxPlayer.PlaySfx(SoundEffectID.JoinParty); + + _statusLabelSetter.SetStatusLabel(EOResourceID.STATUS_LABEL_TYPE_INFORMATION, EOResourceID.STATUS_LABEL_PARTY_YOU_JOINED); + _chatRepository.AllChat[ChatTab.System].Add(new ChatData(ChatTab.System, string.Empty, _localizedStringFinder.GetString(EOResourceID.STATUS_LABEL_PARTY_YOU_JOINED), ChatIcon.PlayerParty, ChatColor.PM)); + } + + public void NotifyPartyMemberAdd(string name) + { + _statusLabelSetter.SetStatusLabel(EOResourceID.STATUS_LABEL_TYPE_INFORMATION, name, EOResourceID.STATUS_LABEL_PARTY_JOINED_YOUR); + _chatRepository.AllChat[ChatTab.System].Add(new ChatData(ChatTab.System, string.Empty, _localizedStringFinder.GetString(EOResourceID.STATUS_LABEL_PARTY_JOINED_YOUR), ChatIcon.PlayerParty, ChatColor.PM)); + } + + public void NotifyPartyMemberRemove(string name) + { + _sfxPlayer.PlaySfx(SoundEffectID.MemberLeftParty); + + _statusLabelSetter.SetStatusLabel(EOResourceID.STATUS_LABEL_TYPE_INFORMATION, name, EOResourceID.STATUS_LABEL_PARTY_LEFT_YOUR); + _chatRepository.AllChat[ChatTab.System].Add(new ChatData(ChatTab.System, string.Empty, _localizedStringFinder.GetString(EOResourceID.STATUS_LABEL_PARTY_LEFT_YOUR), ChatIcon.PlayerPartyDark, ChatColor.PM)); + } + } +} diff --git a/EndlessClient/Dialogs/Old/TradeDialog.cs b/EndlessClient/Dialogs/Old/TradeDialog.cs index db5a66693..685c89c99 100644 --- a/EndlessClient/Dialogs/Old/TradeDialog.cs +++ b/EndlessClient/Dialogs/Old/TradeDialog.cs @@ -116,7 +116,7 @@ public TradeDialog(PacketAPI apiHandle) { if (!m_api.TradeClose()) ((EOGame)Game).DoShowLostConnectionDialogAndReturnToMainMenu(); - ((EOGame)Game).Hud.SetStatusLabel(EOResourceID.STATUS_LABEL_TYPE_ACTION, EOResourceID.STATUS_LABEL_TRADE_ABORTED); + //((EOGame)Game).Hud.SetStatusLabel(EOResourceID.STATUS_LABEL_TYPE_ACTION, EOResourceID.STATUS_LABEL_TRADE_ABORTED); } localTimer.Dispose(); @@ -161,7 +161,7 @@ public void SetPlayerItems(short playerID, List items) m_rightAgrees = false; m_rightPlayerStatus.Text = OldWorld.GetString(EOResourceID.DIALOG_TRADE_WORD_TRADING); EOMessageBox.Show(DialogResourceID.TRADE_ABORTED_OFFER_CHANGED, EODialogButtons.Ok, EOMessageBoxStyle.SmallDialogSmallHeader); - ((EOGame)Game).Hud.SetStatusLabel(EOResourceID.STATUS_LABEL_TYPE_WARNING, EOResourceID.STATUS_LABEL_TRADE_OTHER_PLAYER_CHANGED_OFFER); + //((EOGame)Game).Hud.SetStatusLabel(EOResourceID.STATUS_LABEL_TYPE_WARNING, EOResourceID.STATUS_LABEL_TRADE_OTHER_PLAYER_CHANGED_OFFER); } } else if (playerID == m_rightPlayerID) @@ -184,7 +184,7 @@ public void SetPlayerItems(short playerID, List items) m_leftAgrees = false; m_leftPlayerStatus.Text = OldWorld.GetString(EOResourceID.DIALOG_TRADE_WORD_TRADING); EOMessageBox.Show(DialogResourceID.TRADE_ABORTED_OFFER_CHANGED, EODialogButtons.Ok, EOMessageBoxStyle.SmallDialogSmallHeader); - ((EOGame)Game).Hud.SetStatusLabel(EOResourceID.STATUS_LABEL_TYPE_WARNING, EOResourceID.STATUS_LABEL_TRADE_OTHER_PLAYER_CHANGED_OFFER); + //((EOGame)Game).Hud.SetStatusLabel(EOResourceID.STATUS_LABEL_TYPE_WARNING, EOResourceID.STATUS_LABEL_TRADE_OTHER_PLAYER_CHANGED_OFFER); } } else @@ -237,12 +237,12 @@ public void SetPlayerAgree(bool isMain, bool agrees) short playerID = isMain ? (short)m_main.ID : (m_leftPlayerID == m_main.ID ? m_rightPlayerID : m_leftPlayerID); if (playerID == m_leftPlayerID) { - if (agrees && !m_leftAgrees) - ((EOGame)Game).Hud.SetStatusLabel(EOResourceID.STATUS_LABEL_TYPE_ACTION, - isMain ? EOResourceID.STATUS_LABEL_TRADE_YOU_ACCEPT : EOResourceID.STATUS_LABEL_TRADE_OTHER_ACCEPT); - else if (!agrees && m_leftAgrees) - ((EOGame)Game).Hud.SetStatusLabel(EOResourceID.STATUS_LABEL_TYPE_ACTION, - isMain ? EOResourceID.STATUS_LABEL_TRADE_YOU_CANCEL : EOResourceID.STATUS_LABEL_TRADE_OTHER_CANCEL); + //if (agrees && !m_leftAgrees) + // ((EOGame)Game).Hud.SetStatusLabel(EOResourceID.STATUS_LABEL_TYPE_ACTION, + // isMain ? EOResourceID.STATUS_LABEL_TRADE_YOU_ACCEPT : EOResourceID.STATUS_LABEL_TRADE_OTHER_ACCEPT); + //else if (!agrees && m_leftAgrees) + // ((EOGame)Game).Hud.SetStatusLabel(EOResourceID.STATUS_LABEL_TYPE_ACTION, + // isMain ? EOResourceID.STATUS_LABEL_TRADE_YOU_CANCEL : EOResourceID.STATUS_LABEL_TRADE_OTHER_CANCEL); m_leftAgrees = agrees; m_leftPlayerStatus.Text = @@ -250,12 +250,12 @@ public void SetPlayerAgree(bool isMain, bool agrees) } else if (playerID == m_rightPlayerID) { - if (agrees && !m_rightAgrees) - ((EOGame)Game).Hud.SetStatusLabel(EOResourceID.STATUS_LABEL_TYPE_ACTION, - isMain ? EOResourceID.STATUS_LABEL_TRADE_YOU_ACCEPT : EOResourceID.STATUS_LABEL_TRADE_OTHER_ACCEPT); - else if (!agrees && m_rightAgrees) - ((EOGame)Game).Hud.SetStatusLabel(EOResourceID.STATUS_LABEL_TYPE_ACTION, - isMain ? EOResourceID.STATUS_LABEL_TRADE_YOU_CANCEL : EOResourceID.STATUS_LABEL_TRADE_OTHER_CANCEL); + //if (agrees && !m_rightAgrees) + // ((EOGame)Game).Hud.SetStatusLabel(EOResourceID.STATUS_LABEL_TYPE_ACTION, + // isMain ? EOResourceID.STATUS_LABEL_TRADE_YOU_ACCEPT : EOResourceID.STATUS_LABEL_TRADE_OTHER_ACCEPT); + //else if (!agrees && m_rightAgrees) + // ((EOGame)Game).Hud.SetStatusLabel(EOResourceID.STATUS_LABEL_TYPE_ACTION, + // isMain ? EOResourceID.STATUS_LABEL_TRADE_YOU_CANCEL : EOResourceID.STATUS_LABEL_TRADE_OTHER_CANCEL); m_rightAgrees = agrees; m_rightPlayerStatus.Text = @@ -317,7 +317,7 @@ private void _buttonOkClicked(object sender, EventArgs e) EOMessageBox.Show(OldWorld.GetString(EOResourceID.DIALOG_TRADE_BOTH_PLAYERS_OFFER_ONE_ITEM), OldWorld.GetString(EOResourceID.STATUS_LABEL_TYPE_WARNING), EODialogButtons.Ok, EOMessageBoxStyle.SmallDialogSmallHeader); - ((EOGame)Game).Hud.SetStatusLabel(EOResourceID.STATUS_LABEL_TYPE_WARNING, EOResourceID.DIALOG_TRADE_BOTH_PLAYERS_OFFER_ONE_ITEM); + //((EOGame)Game).Hud.SetStatusLabel(EOResourceID.STATUS_LABEL_TYPE_WARNING, EOResourceID.DIALOG_TRADE_BOTH_PLAYERS_OFFER_ONE_ITEM); return; } diff --git a/EndlessClient/HUD/Chat/ChatBubbleActions.cs b/EndlessClient/HUD/Chat/ChatBubbleActions.cs index bb543e54c..c446f1c20 100644 --- a/EndlessClient/HUD/Chat/ChatBubbleActions.cs +++ b/EndlessClient/HUD/Chat/ChatBubbleActions.cs @@ -3,7 +3,9 @@ using EndlessClient.Rendering.Chat; using EndlessClient.Rendering.NPC; using EOLib.Domain.Chat; +using EOLib.Domain.Party; using Optional; +using System.Linq; namespace EndlessClient.HUD.Chat { @@ -14,30 +16,35 @@ public class ChatBubbleActions : IChatBubbleActions private readonly IChatTypeCalculator _chatTypeCalculator; private readonly ICharacterRendererProvider _characterRendererProvider; private readonly INPCRendererProvider _npcRendererProvider; + private readonly IPartyDataProvider _partyDataProvider; private readonly IChatBubbleFactory _chatBubbleFactory; public ChatBubbleActions(IChatProcessor chatProcessor, IChatTypeCalculator chatTypeCalculator, ICharacterRendererProvider characterRendererProvider, INPCRendererProvider npcRendererProvider, + IPartyDataProvider partyDataProvider, IChatBubbleFactory chatBubbleFactory) { _chatProcessor = chatProcessor; _chatTypeCalculator = chatTypeCalculator; _characterRendererProvider = characterRendererProvider; _npcRendererProvider = npcRendererProvider; + _partyDataProvider = partyDataProvider; _chatBubbleFactory = chatBubbleFactory; } public void ShowChatBubbleForMainCharacter(string input) { - //todo: don't show chat bubble if group chat and character is not in a group (party) _characterRendererProvider.MainCharacterRenderer.MatchSome(r => { _chatTypeCalculator.CalculateChatType(input) .SomeWhen(x => x == ChatType.Local || x == ChatType.Party || x == ChatType.Announce) .MatchSome(chatType => { + if (!_partyDataProvider.Members.Any() && chatType == ChatType.Party) + return; + var text = _chatProcessor.RemoveFirstCharacterIfNeeded(input, chatType, string.Empty); r.ShowChatBubble(text, chatType == ChatType.Party); }); diff --git a/EndlessClient/HUD/Controls/HUD.cs b/EndlessClient/HUD/Controls/HUD.cs deleted file mode 100644 index 258665a25..000000000 --- a/EndlessClient/HUD/Controls/HUD.cs +++ /dev/null @@ -1,121 +0,0 @@ -using EndlessClient.HUD.Panels.Old; -using EndlessClient.Old; -using EndlessClient.UIControls; -using EOLib.Localization; -using EOLib.Net.API; -using Microsoft.Xna.Framework; -using System; -using System.Collections.Generic; - -namespace EndlessClient.HUD.Controls -{ - /// - /// Note that this is NOT an XNAControl - it is just a DrawableGameComponent - /// - public class HUD : DrawableGameComponent - { - private readonly PacketAPI m_packetAPI; - - private const int HUD_CONTROL_DRAW_ORDER = 101; - - private readonly OldEOPartyPanel m_party; - - private ChatTextBox chatTextBox; - - public DateTime SessionStartTime { get; private set; } - - public HUD(Game g, PacketAPI api) : base(g) - { - if(!api.Initialized) - throw new ArgumentException("Need to initialize connection before the in-game stuff will work"); - m_packetAPI = api; - - DrawOrder = 100; - - CreateChatTextbox(); - - //m_party = new OldEOPartyPanel(pnlParty); - - //no need to make this a member variable - //it does not have any resources to dispose and it is automatically disposed by the framework - // ReSharper disable once UnusedVariable - //OldEOSettingsPanel settings = new OldEOSettingsPanel(pnlSettings); - } - - #region Constructor Helpers - - private void CreateChatTextbox() - { - chatTextBox.OnTextChanged += (s, e) => - { - if (chatTextBox.Text.Length == 1 && chatTextBox.Text[0] == '~' && - OldWorld.Instance.MainPlayer.ActiveCharacter.CurrentMap == OldWorld.Instance.JailMap) - { - chatTextBox.Text = ""; - SetStatusLabel(EOResourceID.STATUS_LABEL_TYPE_WARNING, EOResourceID.JAIL_WARNING_CANNOT_USE_GLOBAL); - } - }; - } - - #endregion - - public override void Initialize() - { - OldWorld.Instance.ActiveMapRenderer.Visible = true; - if (!Game.Components.Contains(OldWorld.Instance.ActiveMapRenderer)) - Game.Components.Add(OldWorld.Instance.ActiveMapRenderer); - OldWorld.Instance.ActiveCharacterRenderer.Visible = true; - - //the draw orders are adjusted for child items in the constructor. - //calling SetParent will break this. - - SessionStartTime = DateTime.Now; - - base.Initialize(); - } - - #region Helper Methods - - #endregion - - #region Public Interface for classes outside HUD - - public void SetChatText(string text) - { - chatTextBox.Text = text; - } - - public void SetStatusLabel(EOResourceID type, EOResourceID message, string extra = "") - { - } - - public void SetStatusLabel(EOResourceID type, string extra, EOResourceID message) - { - //SetStatusLabelText(string.Format("[ {0} ] {1} {2}", typeText, extra, messageText)); - } - - public void SetStatusLabel(EOResourceID type, string detail) - { - //SetStatusLabelText(string.Format("[ {0} ] {1}", typeText, detail)); - } - - public void SetPartyData(List party) { m_party.SetData(party); } - public void AddPartyMember(PartyMember member) { m_party.AddMember(member); } - public void RemovePartyMember(short memberID) { m_party.RemoveMember(memberID); } - public void CloseParty() { m_party.CloseParty(); } - public bool MainPlayerIsInParty() { return m_party.PlayerIsMember((short)OldWorld.Instance.MainPlayer.ActiveCharacter.ID); } - public bool PlayerIsPartyMember(short playerID) { return m_party.PlayerIsMember(playerID); } - - #endregion - - protected override void Dispose(bool disposing) - { - if (disposing) - { - m_packetAPI.Dispose(); - } - - base.Dispose(disposing); - } - } -} diff --git a/EndlessClient/HUD/IStatusLabelSetter.cs b/EndlessClient/HUD/IStatusLabelSetter.cs index 424789e7b..8f6b3aa59 100644 --- a/EndlessClient/HUD/IStatusLabelSetter.cs +++ b/EndlessClient/HUD/IStatusLabelSetter.cs @@ -5,9 +5,9 @@ namespace EndlessClient.HUD { public interface IStatusLabelSetter : IStatusLabelNotifier { - void SetStatusLabel(EOResourceID type, EOResourceID text, string appended = ""); + void SetStatusLabel(EOResourceID type, EOResourceID text, string appended = "", bool showChatError = false); - void SetStatusLabel(EOResourceID type, string prepended, EOResourceID text); + void SetStatusLabel(EOResourceID type, string prepended, EOResourceID text, bool showChatError = false); void SetStatusLabel(EOResourceID type, string text); diff --git a/EndlessClient/HUD/Panels/HudPanelFactory.cs b/EndlessClient/HUD/Panels/HudPanelFactory.cs index dfa57ae53..34c034811 100644 --- a/EndlessClient/HUD/Panels/HudPanelFactory.cs +++ b/EndlessClient/HUD/Panels/HudPanelFactory.cs @@ -16,6 +16,7 @@ using EOLib.Domain.Item; using EOLib.Domain.Login; using EOLib.Domain.Online; +using EOLib.Domain.Party; using EOLib.Graphics; using EOLib.IO.Repositories; using EOLib.Localization; @@ -54,6 +55,8 @@ public class HudPanelFactory : IHudPanelFactory private readonly ILocalizedStringFinder _localizedStringFinder; private readonly IAudioActions _audioActions; private readonly ISfxPlayer _sfxPlayer; + private readonly IPartyActions _partyActions; + private readonly IPartyDataProvider _partyDataProvider; public HudPanelFactory(INativeGraphicsManager nativeGraphicsManager, IInventoryController inventoryController, @@ -81,7 +84,9 @@ public HudPanelFactory(INativeGraphicsManager nativeGraphicsManager, IOnlinePlayerProvider onlinePlayerProvider, ILocalizedStringFinder localizedStringFinder, IAudioActions audioActions, - ISfxPlayer sfxPlayer) + ISfxPlayer sfxPlayer, + IPartyActions partyActions, + IPartyDataProvider partyDataProvider) { _nativeGraphicsManager = nativeGraphicsManager; _inventoryController = inventoryController; @@ -110,6 +115,8 @@ public HudPanelFactory(INativeGraphicsManager nativeGraphicsManager, _localizedStringFinder = localizedStringFinder; _audioActions = audioActions; _sfxPlayer = sfxPlayer; + _partyActions = partyActions; + _partyDataProvider = partyDataProvider; } public NewsPanel CreateNewsPanel() @@ -184,12 +191,12 @@ public StatsPanel CreateStatsPanel() public OnlineListPanel CreateOnlineListPanel() { var chatFont = _contentProvider.Fonts[Constants.FontSize08]; - return new OnlineListPanel(_nativeGraphicsManager, _hudControlProvider, _onlinePlayerProvider, _friendIgnoreListService, _sfxPlayer, chatFont) { DrawOrder = HUD_CONTROL_LAYER }; + return new OnlineListPanel(_nativeGraphicsManager, _hudControlProvider, _onlinePlayerProvider, _partyDataProvider, _friendIgnoreListService, _sfxPlayer, chatFont) { DrawOrder = HUD_CONTROL_LAYER }; } public PartyPanel CreatePartyPanel() { - return new PartyPanel(_nativeGraphicsManager) { DrawOrder = HUD_CONTROL_LAYER }; + return new PartyPanel(_nativeGraphicsManager, _partyActions, _contentProvider, _partyDataProvider, _characterProvider) { DrawOrder = HUD_CONTROL_LAYER }; } public SettingsPanel CreateSettingsPanel() diff --git a/EndlessClient/HUD/Panels/Old/OldPartyPanel.cs b/EndlessClient/HUD/Panels/Old/OldPartyPanel.cs deleted file mode 100644 index c3524a417..000000000 --- a/EndlessClient/HUD/Panels/Old/OldPartyPanel.cs +++ /dev/null @@ -1,250 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using EndlessClient.Old; -using EndlessClient.UIControls; -using EOLib; -using EOLib.Domain.Chat; -using EOLib.Graphics; -using EOLib.Localization; -using EOLib.Net.API; -using Microsoft.Xna.Framework; -using Microsoft.Xna.Framework.Graphics; -using XNAControls.Old; - -namespace EndlessClient.HUD.Panels.Old -{ - public class OldEOPartyPanel : XNAControl - { - private readonly OldScrollBar m_scrollBar; - private readonly XNALabel m_numMembers; - private List m_members; - private readonly List m_buttons; - private bool m_mainIsLeader; - private readonly Texture2D m_removeTexture; - private readonly Texture2D[] m_healthBar; - - private const int DRAW_OFFSET_Y = 20, - DRAW_ICON_X = 5, - DRAW_NAME_X = 23, - DRAW_LEVEL_X = 138, - DRAW_HP_X = 205, - DRAW_HEALTHBAR_X = 228, - DRAW_REMOVE_X = 337; - - private const int HP_OUTLINE = 0, HP_RED = 1, HP_YELLOW = 2, HP_GREEN = 3; - - public OldEOPartyPanel(XNAPanel parent) - : base(null, null, parent) - { - _setSize(parent.BackgroundImage.Width, parent.BackgroundImage.Height); - - m_removeTexture = ((EOGame)Game).GFXManager.TextureFromResource(GFXTypes.PostLoginUI, 43); - m_buttons = new List(); - - //will tint this different colors for health bar and fill a rectangle - m_healthBar = new Texture2D[4]; - m_healthBar[HP_OUTLINE] = Game.Content.Load("Party\\hp-outline"); - m_healthBar[HP_RED] = Game.Content.Load("Party\\hp-red"); - m_healthBar[HP_YELLOW] = Game.Content.Load("Party\\hp-yellow"); - m_healthBar[HP_GREEN] = Game.Content.Load("Party\\hp-green"); - - m_numMembers = new XNALabel(new Rectangle(455, 2, 27, 14), Constants.FontSize08pt5) - { - AutoSize = false, - ForeColor = ColorConstants.LightGrayText, - TextAlign = LabelAlignment.MiddleRight - }; - m_numMembers.SetParent(this); - - m_scrollBar = new OldScrollBar(this, new Vector2(467, 20), new Vector2(16, 97), ScrollBarColors.LightOnMed) - { - LinesToRender = 7, - Visible = true - }; - m_scrollBar.SetParent(this); - OldWorld.IgnoreDialogs(m_scrollBar); - } - - public void SetData(List memberList) - { - if (memberList.TrueForAll(_member => _member.IsFullData)) - { - if(m_members == null || m_members.Count == 0) - { - ((EOGame)Game).Hud.SetStatusLabel(EOResourceID.STATUS_LABEL_TYPE_INFORMATION, EOResourceID.STATUS_LABEL_PARTY_YOU_JOINED); - //((EOGame)Game).Hud.AddChat(ChatTab.System, "", OldWorld.GetString(EOResourceID.STATUS_LABEL_PARTY_YOU_JOINED), ChatIcon.PlayerParty, ChatColor.PM); - } - - Visible = true; - m_numMembers.Text = $"{memberList.Count}"; - m_members = memberList; - - m_mainIsLeader = m_members.FindIndex(_member => _member.IsLeader && _member.ID == OldWorld.Instance.MainPlayer.ActiveCharacter.ID) >= 0; - m_scrollBar.UpdateDimensions(memberList.Count); - - m_buttons.Clear(); - - foreach (PartyMember member in m_members) - { - _addRemoveButtonForMember(member); - } - } - else - { - //update HP only -// ReSharper disable once ForCanBeConvertedToForeach - for (int i = 0; i < memberList.Count; ++i) - { - int ndx = m_members.FindIndex(_member => _member.ID == memberList[i].ID); - PartyMember member = m_members[ndx]; - member.SetPercentHealth(memberList[i].PercentHealth); - m_members[ndx] = member; - } - } - } - - private void _addRemoveButtonForMember(PartyMember member) - { - int delta = m_removeTexture.Height / 3; - bool enabled = m_mainIsLeader || member.ID == OldWorld.Instance.MainPlayer.ActiveCharacter.ID; - XNAButton nextButton = new XNAButton(m_removeTexture, - new Vector2(DrawAreaWithOffset.X + DRAW_REMOVE_X, DRAW_OFFSET_Y), - enabled ? new Rectangle(0, 0, m_removeTexture.Width, delta) : new Rectangle(0, delta, m_removeTexture.Width, delta), - enabled ? new Rectangle(0, delta * 2, m_removeTexture.Width, delta) : new Rectangle(0, delta, m_removeTexture.Width, delta)); - if (enabled) - { - PartyMember localMember = member; - nextButton.OnClick += (sender, args) => RemoveMember(localMember.ID); - } - nextButton.SetParent(this); - m_buttons.Add(nextButton); - } - - public void AddMember(PartyMember member) - { - m_members.Add(member); - - m_numMembers.Text = $"{m_members.Count}"; - m_scrollBar.UpdateDimensions(m_members.Count); - - _addRemoveButtonForMember(member); - - ((EOGame)Game).Hud.SetStatusLabel(EOResourceID.STATUS_LABEL_TYPE_INFORMATION, member.Name, EOResourceID.STATUS_LABEL_PARTY_JOINED_YOUR); - //((EOGame)Game).Hud.AddChat(ChatTab.System, "", member.Name + " " + OldWorld.GetString(EOResourceID.STATUS_LABEL_PARTY_JOINED_YOUR), ChatIcon.PlayerParty, ChatColor.PM); - } - - public void RemoveMember(short memberID) - { - int memberIndex = m_members.FindIndex(_member => _member.ID == memberID); - if (memberIndex < 0 || memberIndex >= m_members.Count) - return; - - if (!((EOGame) Game).API.PartyRemovePlayer(m_members[memberIndex].ID)) - ((EOGame) Game).DoShowLostConnectionDialogAndReturnToMainMenu(); - - string name = m_members[memberIndex].Name; - - m_members.RemoveAt(memberIndex); - m_buttons[memberIndex].SetParent(null); - m_buttons[memberIndex].Close(); - m_buttons.RemoveAt(memberIndex); - - m_numMembers.Text = "" + m_members.Count; - m_scrollBar.UpdateDimensions(m_members.Count); - if (m_members.Count <= m_scrollBar.LinesToRender) - m_scrollBar.ScrollToTop(); - - ((EOGame)Game).Hud.SetStatusLabel(EOResourceID.STATUS_LABEL_TYPE_INFORMATION, name, EOResourceID.STATUS_LABEL_PARTY_LEFT_YOUR); - //((EOGame)Game).Hud.AddChat(ChatTab.System, "", name + " " + OldWorld.GetString(EOResourceID.STATUS_LABEL_PARTY_LEFT_YOUR), ChatIcon.PlayerPartyDark, ChatColor.PM); - } - - public void CloseParty() - { - m_members.Clear(); - Visible = false; - m_numMembers.Text = "0"; - m_scrollBar.UpdateDimensions(0); - } - - public bool PlayerIsMember(short ID) - { - return m_members != null && m_members.FindIndex(_member => _member.ID == ID) >= 0; - } - - public override void Update(GameTime gameTime) - { - if (!Visible && m_buttons.Any(_btn => _btn.Visible)) - { - m_buttons.ForEach(_btn => _btn.Visible = false); - return; - } - - for (int i = 0; i < m_buttons.Count; ++i) - { - if (i < m_scrollBar.ScrollOffset || i >= m_scrollBar.ScrollOffset + m_scrollBar.LinesToRender) - m_buttons[i].Visible = false; - else - m_buttons[i].Visible = true; - } - base.Update(gameTime); - } - - public override void Draw(GameTime gameTime) - { - if (!Visible) - { - base.Draw(gameTime); - return; - } - - if(m_members != null) - { - SpriteBatch.Begin(); - for (int i = m_scrollBar.ScrollOffset; i < m_scrollBar.LinesToRender + m_scrollBar.ScrollOffset && i < m_members.Count; ++i) - { - PartyMember member = m_members[i]; - int yCoord = DRAW_OFFSET_Y + DrawAreaWithOffset.Y + (i - m_scrollBar.ScrollOffset) * 13; - m_buttons[i].DrawLocation = new Vector2(DRAW_REMOVE_X, yCoord - DrawAreaWithOffset.Y + 1); - //SpriteBatch.Draw(OldChatTab.GetChatIcon(member.IsLeader ? ChatIcon.Star : ChatIcon.Player), new Vector2(DrawAreaWithOffset.X + DRAW_ICON_X, yCoord), Color.White); - SpriteBatch.DrawString(((EOGame) Game).DBGFont, member.Name, new Vector2(DrawAreaWithOffset.X + DRAW_NAME_X, yCoord), Color.Black); - SpriteBatch.DrawString(((EOGame) Game).DBGFont, "" + member.Level, new Vector2(DrawAreaWithOffset.X + DRAW_LEVEL_X, yCoord), Color.Black); - SpriteBatch.DrawString(((EOGame) Game).DBGFont, "HP", new Vector2(DrawAreaWithOffset.X + DRAW_HP_X, yCoord), Color.Black); - _drawHealthBar(member.PercentHealth, yCoord); - } - SpriteBatch.End(); - } - - base.Draw(gameTime); - } - - private void _drawHealthBar(int percentHealth, int yCoord) - { - yCoord += 1; //slightly offset from the rest of the row - Rectangle barSrcRect = new Rectangle(0, 0, (int)Math.Round(m_healthBar[HP_RED].Width * (percentHealth / 100.0)), m_healthBar[1].Height); - SpriteBatch.Draw(m_healthBar[HP_OUTLINE], new Vector2(DrawAreaWithOffset.X + DRAW_HEALTHBAR_X, yCoord), Color.White); - - int color = percentHealth > 50 ? HP_GREEN : percentHealth > 25 ? HP_YELLOW : HP_RED; - SpriteBatch.Draw(m_healthBar[color], new Vector2(DrawAreaWithOffset.X + DRAW_HEALTHBAR_X, yCoord), barSrcRect, Color.White); - } - - protected override void OnVisibleChanged(object sender, EventArgs args) - { - if (Visible && m_members.Count > 0 && !((EOGame) Game).API.PartyListMembers()) - ((EOGame) Game).DoShowLostConnectionDialogAndReturnToMainMenu(); - - base.OnVisibleChanged(sender, args); - } - - protected override void Dispose(bool disposing) - { - if (disposing) - { - foreach (Texture2D t in m_healthBar) - t.Dispose(); - } - - base.Dispose(disposing); - } - } -} diff --git a/EndlessClient/HUD/Panels/OnlineListPanel.cs b/EndlessClient/HUD/Panels/OnlineListPanel.cs index 15a9512f0..b36de7300 100644 --- a/EndlessClient/HUD/Panels/OnlineListPanel.cs +++ b/EndlessClient/HUD/Panels/OnlineListPanel.cs @@ -5,6 +5,7 @@ using EndlessClient.UIControls; using EOLib; using EOLib.Domain.Online; +using EOLib.Domain.Party; using EOLib.Extensions; using EOLib.Graphics; using Microsoft.Xna.Framework; @@ -35,6 +36,7 @@ private enum Filter private readonly INativeGraphicsManager _nativeGraphicsManager; private readonly IHudControlProvider _hudControlProvider; private readonly IOnlinePlayerProvider _onlinePlayerProvider; + private readonly IPartyDataProvider _partyDataProvider; private readonly IFriendIgnoreListService _friendIgnoreListService; private readonly ISfxPlayer _sfxPlayer; @@ -57,6 +59,7 @@ private enum Filter public OnlineListPanel(INativeGraphicsManager nativeGraphicsManager, IHudControlProvider hudControlProvider, IOnlinePlayerProvider onlinePlayerProvider, + IPartyDataProvider partyDataProvider, IFriendIgnoreListService friendIgnoreListService, ISfxPlayer sfxPlayer, SpriteFont chatFont) @@ -64,6 +67,7 @@ public OnlineListPanel(INativeGraphicsManager nativeGraphicsManager, _nativeGraphicsManager = nativeGraphicsManager; _hudControlProvider = hudControlProvider; _onlinePlayerProvider = onlinePlayerProvider; + _partyDataProvider = partyDataProvider; _friendIgnoreListService = friendIgnoreListService; _sfxPlayer = sfxPlayer; _chatFont = chatFont; @@ -146,11 +150,7 @@ protected override void OnUpdateControl(GameTime gameTime) PreviousMouseState.LeftButton == ButtonState.Pressed) { _sfxPlayer.PlaySfx(SoundEffectID.DialogButtonClick); - _filter = (Filter)(((int)_filter + 1) % (int)Filter.Max); - if (_filter == Filter.Party) // todo: show this when guild/party is supported - _filter = (Filter)(((int)_filter + 1) % (int)Filter.Max); - _scrollBar.ScrollToTop(); ApplyFilter(); @@ -208,8 +208,7 @@ private void ApplyFilter() { case Filter.Friends: _filteredList = _onlineList.Where(x => _friendList.Contains(x.Name, StringComparer.InvariantCultureIgnoreCase)).ToList(); break; case Filter.Admins: _filteredList = _onlineList.Where(IsAdminIcon).ToList(); break; - // todo: implement for party/guild - case Filter.Party: _filteredList.Clear(); break; + case Filter.Party: _filteredList = _onlineList.Where(x => _partyDataProvider.Members.Any(y => string.Equals(y.Name, x.Name, StringComparison.InvariantCultureIgnoreCase))).ToList(); break; case Filter.All: default: _filteredList = new List(_onlineList); break; } diff --git a/EndlessClient/HUD/Panels/PartyPanel.cs b/EndlessClient/HUD/Panels/PartyPanel.cs index e0ba3af9a..75bf2306e 100644 --- a/EndlessClient/HUD/Panels/PartyPanel.cs +++ b/EndlessClient/HUD/Panels/PartyPanel.cs @@ -1,5 +1,14 @@ -using EOLib.Graphics; +using EndlessClient.Content; +using EndlessClient.HUD.Party; +using EndlessClient.UIControls; +using EOLib; +using EOLib.Domain.Character; +using EOLib.Domain.Party; +using EOLib.Graphics; using Microsoft.Xna.Framework; +using Optional.Collections; +using System.Collections.Generic; +using System.Linq; using XNAControls; namespace EndlessClient.HUD.Panels @@ -7,13 +16,143 @@ namespace EndlessClient.HUD.Panels public class PartyPanel : XNAPanel, IHudPanel { private readonly INativeGraphicsManager _nativeGraphicsManager; + private readonly IPartyActions _partyActions; + private readonly IContentProvider _contentProvider; + private readonly IPartyDataProvider _partyDataProvider; + private readonly ICharacterProvider _characterProvider; - public PartyPanel(INativeGraphicsManager nativeGraphicsManager) + private readonly ScrollBar _scrollBar; + private readonly IXNALabel _numMembers; + private readonly List _children; + + private HashSet _cachedParty; + private int _cachedScrollOffset; + + public PartyPanel(INativeGraphicsManager nativeGraphicsManager, + IPartyActions partyActions, + IContentProvider contentProvider, + IPartyDataProvider partyDataProvider, + ICharacterProvider characterProvider) { _nativeGraphicsManager = nativeGraphicsManager; + _partyActions = partyActions; + _contentProvider = contentProvider; + _partyDataProvider = partyDataProvider; + _characterProvider = characterProvider; + _scrollBar = new ScrollBar(new Vector2(467, 20), new Vector2(16, 97), ScrollBarColors.LightOnMed, _nativeGraphicsManager) + { + LinesToRender = 7, + Visible = true, + }; + _scrollBar.SetParentControl(this); + + _numMembers = new XNALabel(Constants.FontSize09) + { + AutoSize = false, + DrawArea = new Rectangle(455, 2, 27, 14), + ForeColor = ColorConstants.LightGrayText, + TextAlign = LabelAlignment.MiddleRight + }; + _numMembers.SetParentControl(this); + + _children = new List(); + + _cachedParty = new HashSet(); BackgroundImage = _nativeGraphicsManager.TextureFromResource(GFXTypes.PostLoginUI, 42); DrawArea = new Rectangle(102, 330, BackgroundImage.Width, BackgroundImage.Height); } + + public override void Initialize() + { + _scrollBar.Initialize(); + _numMembers.Initialize(); + + base.Initialize(); + } + + protected override void OnUpdateControl(GameTime gameTime) + { + if (!_cachedParty.SetEquals(_partyDataProvider.Members)) + { + var added = _partyDataProvider.Members.Where(x => !_cachedParty.Any(y => y.CharacterID == x.CharacterID)).ToList(); + var removed = _cachedParty.Where(x => !_partyDataProvider.Members.Any(y => y.CharacterID == x.CharacterID)).ToList(); + var updated = _cachedParty.Where(x => _partyDataProvider.Members.Any(y => y.CharacterID == x.CharacterID && !y.Equals(x))).ToList(); + _cachedParty = _partyDataProvider.Members.ToHashSet(); + + var mainCharacterIsLeader = _cachedParty.Any(x => x.IsLeader && x.CharacterID == _characterProvider.MainCharacter.ID); + + foreach (var member in added) + { + var next = new PartyPanelMember( + _nativeGraphicsManager, + _contentProvider, + mainCharacterIsLeader || member.CharacterID == _characterProvider.MainCharacter.ID) + { + PartyMember = member, + }; + next.RemoveAction += (_, _) => _partyActions.RemovePartyMember(member.CharacterID); + next.SetParentControl(this); + next.Initialize(); + + _children.Add(next); + } + + foreach (var member in removed) + { + _children.SingleOrNone(x => x.PartyMember.CharacterID == member.CharacterID) + .MatchSome(x => + { + x.SetControlUnparented(); + x.Dispose(); + _children.Remove(x); + }); + } + + foreach (var member in updated) + { + _children.SingleOrNone(x => x.PartyMember.CharacterID == member.CharacterID) + .MatchSome(x => x.PartyMember = member); + } + + _numMembers.Text = $"{_cachedParty.Count}"; + _scrollBar.UpdateDimensions(_cachedParty.Count); + + UpdateChildMemberVisibility(); + } + + if (_cachedScrollOffset != _scrollBar.ScrollOffset) + { + _cachedScrollOffset = _scrollBar.ScrollOffset; + UpdateChildMemberVisibility(); + } + + base.OnUpdateControl(gameTime); + } + + protected override void OnVisibleChanged(object sender, System.EventArgs args) + { + if (Visible) + { + // request party list when viewing the party panel + _partyActions.ListParty(); + } + + base.OnVisibleChanged(sender, args); + } + + private void UpdateChildMemberVisibility() + { + foreach (var child in _children) + child.Visible = false; + + for (int i = 0; i < _scrollBar.LinesToRender; i++) + { + if (_scrollBar.ScrollOffset + i >= _children.Count) + break; + _children[_scrollBar.ScrollOffset + i].DisplayIndex = i; + _children[_scrollBar.ScrollOffset + i].Visible = true; + } + } } } \ No newline at end of file diff --git a/EndlessClient/HUD/Panels/SettingsPanel.cs b/EndlessClient/HUD/Panels/SettingsPanel.cs index f210def48..ecb5f34aa 100644 --- a/EndlessClient/HUD/Panels/SettingsPanel.cs +++ b/EndlessClient/HUD/Panels/SettingsPanel.cs @@ -232,7 +232,7 @@ private void SettingChange(WhichSetting setting) _configurationRepository.LogChatToFile = !_configurationRepository.LogChatToFile; break; case WhichSetting.Interaction: - // todo: block party/trade requests when this is true + // todo: block trade requests when this is true _configurationRepository.Interaction = !_configurationRepository.Interaction; break; } diff --git a/EndlessClient/HUD/Party/PartyPanelMember.cs b/EndlessClient/HUD/Party/PartyPanelMember.cs new file mode 100644 index 000000000..be8e952be --- /dev/null +++ b/EndlessClient/HUD/Party/PartyPanelMember.cs @@ -0,0 +1,138 @@ +using EndlessClient.Content; +using EOLib; +using EOLib.Domain.Chat; +using EOLib.Domain.Party; +using EOLib.Extensions; +using EOLib.Graphics; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using System; +using XNAControls; + +namespace EndlessClient.HUD.Party +{ + public class PartyPanelMember : XNAControl + { + private readonly IContentProvider _contentProvider; + private readonly Texture2D _chatIconsTexture; + + private readonly IXNAButton _removeButton; + private readonly IXNALabel _nameLabel, _levelLabel, _hpLabel; + + private ChatIcon _playerIcon; + private Rectangle _iconSource; + + private int _displayIndex; + public int DisplayIndex + { + get => _displayIndex; + set + { + _displayIndex = value; + _removeButton.DrawPosition = new Vector2(_removeButton.DrawPosition.X, 20 + _displayIndex * 13); + _nameLabel.DrawPosition = new Vector2(_nameLabel.DrawPosition.X, 20 + _displayIndex * 13); + _levelLabel.DrawPosition = new Vector2(_levelLabel.DrawPosition.X, 20 + _displayIndex * 13); + _hpLabel.DrawPosition = new Vector2(_hpLabel.DrawPosition.X, 20 + _displayIndex * 13); + } + } + + private PartyMember _partyMember; + public PartyMember PartyMember + { + get => _partyMember; + set + { + _partyMember = value; + _nameLabel.Text = _partyMember.Name; + _levelLabel.Text = $"{_partyMember.Level}"; + + _playerIcon = _partyMember.IsLeader ? ChatIcon.Star : ChatIcon.Player; + var (X, Y, Width, Height) = _playerIcon.GetChatIconRectangleBounds().ValueOr((0, 0, 0, 0)); + _iconSource = new Rectangle(X, Y, Width, Height); + } + } + + public event EventHandler RemoveAction; + + public PartyPanelMember(INativeGraphicsManager nativeGraphicsManager, + IContentProvider contentProvider, + bool isRemovable) + { + _contentProvider = contentProvider; + _chatIconsTexture = nativeGraphicsManager.TextureFromResource(GFXTypes.PostLoginUI, 32, true); + + var removeTexture = nativeGraphicsManager.TextureFromResource(GFXTypes.PostLoginUI, 43); + var delta = removeTexture.Height / 3; + _removeButton = new XNAButton(removeTexture, + new Vector2(337, 20), + isRemovable + ? new Rectangle(0, 0, removeTexture.Width, delta) + : new Rectangle(0, delta, removeTexture.Width, delta), + isRemovable + ? new Rectangle(0, delta * 2, removeTexture.Width, delta) + : new Rectangle(0, delta, removeTexture.Width, delta)); + + if (isRemovable) + _removeButton.OnClick += (o, e) => RemoveAction?.Invoke(o, e); + _removeButton.SetParentControl(this); + + _nameLabel = new XNALabel(Constants.FontSize08) + { + DrawPosition = new Vector2(23, 20), + AutoSize = true, + ForeColor = Color.Black + }; + _nameLabel.SetParentControl(this); + + _levelLabel = new XNALabel(Constants.FontSize08) + { + DrawPosition = new Vector2(138, 20), + AutoSize = true, + ForeColor = Color.Black + }; + _levelLabel.SetParentControl(this); + + _hpLabel = new XNALabel(Constants.FontSize08) + { + DrawPosition = new Vector2(205, 20), + AutoSize = true, + ForeColor= Color.Black, + Text = "HP" + }; + _hpLabel.SetParentControl(this); + } + + public override void Initialize() + { + _removeButton.Initialize(); + _nameLabel.Initialize(); + _levelLabel.Initialize(); + _hpLabel.Initialize(); + + base.Initialize(); + } + + protected override void OnDrawControl(GameTime gameTime) + { + var drawYCoordinate = 21 + DisplayIndex * 13; + var iconDrawPosition = DrawPositionWithParentOffset + new Vector2(5, drawYCoordinate); + var healthBarDrawPosition = DrawPositionWithParentOffset + new Vector2(228, drawYCoordinate); + + var barTexture = _partyMember.PercentHealth > 50 + ? _contentProvider.Textures[ContentProvider.HPGreen] + : _partyMember.PercentHealth > 25 + ? _contentProvider.Textures[ContentProvider.HPYellow] + : _contentProvider.Textures[ContentProvider.HPRed]; + + var barSource = new Rectangle(0, 0, (int)Math.Round(barTexture.Width * (_partyMember.PercentHealth / 100.0)), barTexture.Height); + + _spriteBatch.Begin(); + _spriteBatch.Draw(_chatIconsTexture, iconDrawPosition, _iconSource, Color.White); + _spriteBatch.Draw(_contentProvider.Textures[ContentProvider.HPOutline], healthBarDrawPosition, Color.White); + _spriteBatch.Draw(barTexture, healthBarDrawPosition, barSource, Color.White); + _spriteBatch.End(); + + base.OnDrawControl(gameTime); + } + } +} diff --git a/EndlessClient/HUD/Spells/SpellSelectActions.cs b/EndlessClient/HUD/Spells/SpellSelectActions.cs index 28bf09044..0c01ea633 100644 --- a/EndlessClient/HUD/Spells/SpellSelectActions.cs +++ b/EndlessClient/HUD/Spells/SpellSelectActions.cs @@ -1,7 +1,9 @@ using AutomaticTypeMapper; +using EOLib.Domain.Party; using EOLib.IO.Repositories; using EOLib.Localization; using Optional; +using System.Linq; namespace EndlessClient.HUD.Spells { @@ -10,14 +12,17 @@ public class SpellSelectActions : ISpellSelectActions { private readonly IStatusLabelSetter _statusLabelSetter; private readonly ISpellSlotDataRepository _spellSlotDataRepository; + private readonly IPartyDataProvider _partyDataProvider; private readonly IESFFileProvider _esfFileProvider; public SpellSelectActions(IStatusLabelSetter statusLabelSetter, ISpellSlotDataRepository spellSlotDataRepository, + IPartyDataProvider partyDataProvider, IESFFileProvider esfFileProvider) { _statusLabelSetter = statusLabelSetter; _spellSlotDataRepository = spellSlotDataRepository; + _partyDataProvider = partyDataProvider; _esfFileProvider = esfFileProvider; } @@ -28,7 +33,7 @@ public void SelectSpellBySlot(int slot) { var spellData = _esfFileProvider.ESFFile[si.ID]; - if (spellData.Target == EOLib.IO.SpellTarget.Group /*&& not in party*/) // todo: parties + if (spellData.Target == EOLib.IO.SpellTarget.Group && !_partyDataProvider.Members.Any()) { _statusLabelSetter.SetStatusLabel(EOResourceID.STATUS_LABEL_TYPE_WARNING, EOResourceID.SPELL_ONLY_WORKS_ON_GROUP); } diff --git a/EndlessClient/HUD/StatusLabelSetter.cs b/EndlessClient/HUD/StatusLabelSetter.cs index c36de056a..46501d04f 100644 --- a/EndlessClient/HUD/StatusLabelSetter.cs +++ b/EndlessClient/HUD/StatusLabelSetter.cs @@ -1,5 +1,6 @@ using System; using AutomaticTypeMapper; +using EOLib.Domain.Chat; using EOLib.Localization; namespace EndlessClient.HUD @@ -8,30 +9,35 @@ namespace EndlessClient.HUD public class StatusLabelSetter : IStatusLabelSetter { private readonly IStatusLabelTextRepository _statusLabelTextRepository; + private readonly IChatRepository _chatRepository; private readonly ILocalizedStringFinder _localizedStringFinder; public StatusLabelSetter(IStatusLabelTextRepository statusLabelTextRepository, - ILocalizedStringFinder localizedStringFinder) + IChatRepository chatRepository, + ILocalizedStringFinder localizedStringFinder) { _statusLabelTextRepository = statusLabelTextRepository; + _chatRepository = chatRepository; _localizedStringFinder = localizedStringFinder; } - public void SetStatusLabel(EOResourceID type, EOResourceID text, string appended = "") + public void SetStatusLabel(EOResourceID type, EOResourceID text, string appended = "", bool showChatError = false) { CheckStatusLabelType(type); SetStatusLabelText(_localizedStringFinder.GetString(type), _localizedStringFinder.GetString(text), - appended); + appended, + showChatError); } - public void SetStatusLabel(EOResourceID type, string prepended, EOResourceID text) + public void SetStatusLabel(EOResourceID type, string prepended, EOResourceID text, bool showChatError = false) { CheckStatusLabelType(type); SetStatusLabelText(_localizedStringFinder.GetString(type), prepended, - _localizedStringFinder.GetString(text)); + _localizedStringFinder.GetString(text), + showChatError); } public void SetStatusLabel(EOResourceID type, string text) @@ -51,10 +57,16 @@ public void ShowWarning(string message) SetStatusLabel(EOResourceID.STATUS_LABEL_TYPE_WARNING, message); } - private void SetStatusLabelText(string type, string text, string extra = "") + private void SetStatusLabelText(string type, string text, string extra = "", bool showChatError = false) { _statusLabelTextRepository.StatusText = $"[ {type} ] {text}{extra}"; _statusLabelTextRepository.SetTime = DateTime.Now; + + if (showChatError) + { + var chatData = new ChatData(ChatTab.System, string.Empty, $"{text}{extra}", ChatIcon.Error, ChatColor.Error); + _chatRepository.AllChat[ChatTab.System].Add(chatData); + } } private void CheckStatusLabelType(EOResourceID type) diff --git a/EndlessClient/Old/GameUI.cs b/EndlessClient/Old/GameUI.cs index d58dbb4de..45e529615 100644 --- a/EndlessClient/Old/GameUI.cs +++ b/EndlessClient/Old/GameUI.cs @@ -24,8 +24,6 @@ public partial class EOGame private XNAButton _backButton; private bool _backButtonPressed; //workaround so the lost connection dialog doesn't show from the client disconnect event - public HUD.Controls.HUD Hud { get; private set; } - private void InitializeControls(bool reinit = false) { Texture2D back = GFXManager.TextureFromResource(GFXTypes.PreLoginUI, 24, true); diff --git a/EndlessClient/Old/PacketAPICallbackManager.cs b/EndlessClient/Old/PacketAPICallbackManager.cs index 3e01549f4..5c0aeba04 100644 --- a/EndlessClient/Old/PacketAPICallbackManager.cs +++ b/EndlessClient/Old/PacketAPICallbackManager.cs @@ -22,13 +22,6 @@ public PacketAPICallbackManager(PacketAPI apiObj, EOGame game) public void AssignCallbacks() { - //party - m_packetAPI.OnPartyClose += _partyClose; - m_packetAPI.OnPartyDataRefresh += _partyDataRefresh; - m_packetAPI.OnPartyRequest += _partyRequest; - m_packetAPI.OnPartyMemberJoin += _partyMemberJoin; - m_packetAPI.OnPartyMemberLeave += _partyMemberLeave; - //trade m_packetAPI.OnTradeRequested += _tradeRequested; m_packetAPI.OnTradeOpen += _tradeOpen; @@ -37,47 +30,6 @@ public void AssignCallbacks() m_packetAPI.OnTradeYouAgree += _tradeSetLocalPlayerAgree; m_packetAPI.OnTradeOfferUpdate += _tradeOfferUpdate; m_packetAPI.OnTradeCompleted += _tradeCompleted; - - //spell casting - m_packetAPI.OnCastSpellTargetGroup += _playerCastGroupSpell; - } - - private void _partyClose() - { - m_game.Hud.CloseParty(); - } - - private void _partyDataRefresh(List list) - { - m_game.Hud.SetPartyData(list); - } - - private void _partyRequest(PartyRequestType type, short id, string name) - { - if (!OldWorld.Instance.Interaction) - return; - - EOMessageBox.Show(name + " ", - type == PartyRequestType.Join ? DialogResourceID.PARTY_GROUP_REQUEST_TO_JOIN : DialogResourceID.PARTY_GROUP_SEND_INVITATION, - EODialogButtons.OkCancel, EOMessageBoxStyle.SmallDialogSmallHeader, - (o, e) => - { - if (e.Result == XNADialogResult.OK) - { - if (!m_packetAPI.PartyAcceptRequest(type, id)) - m_game.DoShowLostConnectionDialogAndReturnToMainMenu(); - } - }); - } - - private void _partyMemberJoin(PartyMember member) - { - m_game.Hud.AddPartyMember(member); - } - - private void _partyMemberLeave(short id) - { - m_game.Hud.RemovePartyMember(id); } private void _tradeRequested(short playerID, string name) @@ -106,15 +58,15 @@ private void _tradeOpen(short p1, string p1name, short p2, string p2name) else throw new ArgumentException("Invalid player ID for this trade session!", nameof(p1)); - m_game.Hud.SetStatusLabel(EOResourceID.STATUS_LABEL_TYPE_ACTION, EOResourceID.STATUS_LABEL_TRADE_YOU_ARE_TRADING_WITH, - otherName + " " + OldWorld.GetString(EOResourceID.STATUS_LABEL_DRAG_AND_DROP_ITEMS)); + //m_game.Hud.SetStatusLabel(EOResourceID.STATUS_LABEL_TYPE_ACTION, EOResourceID.STATUS_LABEL_TRADE_YOU_ARE_TRADING_WITH, + // otherName + " " + OldWorld.GetString(EOResourceID.STATUS_LABEL_DRAG_AND_DROP_ITEMS)); } private void _tradeCancel(short otherPlayerID) { if (TradeDialog.Instance == null) return; TradeDialog.Instance.Close(XNADialogResult.NO_BUTTON_PRESSED); - m_game.Hud.SetStatusLabel(EOResourceID.STATUS_LABEL_TYPE_ACTION, EOResourceID.STATUS_LABEL_TRADE_ABORTED); + //m_game.Hud.SetStatusLabel(EOResourceID.STATUS_LABEL_TYPE_ACTION, EOResourceID.STATUS_LABEL_TRADE_ABORTED); } private void _tradeRemotePlayerAgree(short otherPlayerID, bool agree) @@ -141,16 +93,5 @@ private void _tradeCompleted(short id1, List items1, short id2, L if (TradeDialog.Instance == null) return; TradeDialog.Instance.CompleteTrade(id1, items1, id2, items2); } - - private void _playerCastGroupSpell(short spellID, short fromPlayerID, short fromPlayerTP, short spellHPgain, List spellTargets) - { - OldWorld.Instance.ActiveMapRenderer.PlayerCastSpellGroup(fromPlayerID, spellID, spellHPgain, spellTargets); - - if (fromPlayerID == OldWorld.Instance.MainPlayer.ActiveCharacter.ID) - { - OldWorld.Instance.MainPlayer.ActiveCharacter.Stats.TP = fromPlayerTP; - //m_game.Hud.RefreshStats(); - } - } } } diff --git a/EndlessClient/Rendering/Character/CharacterAnimationActions.cs b/EndlessClient/Rendering/Character/CharacterAnimationActions.cs index 3dfe9a378..0e504063f 100644 --- a/EndlessClient/Rendering/Character/CharacterAnimationActions.cs +++ b/EndlessClient/Rendering/Character/CharacterAnimationActions.cs @@ -18,6 +18,7 @@ using Optional; using Optional.Collections; using System; +using System.Collections.Generic; using System.Linq; namespace EndlessClient.Rendering.Character @@ -228,6 +229,42 @@ public void NotifyTargetOtherSpellCast(short sourcePlayerID, short targetPlayerI } } + public void NotifyGroupSpellCast(short playerId, short spellId, short spellHp, List spellTargets) + { + if (playerId == _characterRepository.MainCharacter.ID) + { + _characterRendererProvider.MainCharacterRenderer.MatchSome(cr => cr.ShoutSpellCast()); + } + else if (_characterRendererProvider.CharacterRenderers.ContainsKey(playerId)) + { + Animator.StartOtherCharacterSpellCast(playerId); + _characterRendererProvider.CharacterRenderers[playerId].ShoutSpellCast(); + } + else + { + _currentMapStateProvider.UnknownPlayerIDs.Add(playerId); + } + + var spellData = _pubFileProvider.ESFFile[spellId]; + + foreach (var target in spellTargets) + { + if (target.TargetId == _characterRepository.MainCharacter.ID) + { + _characterRendererProvider.MainCharacterRenderer.MatchSome(cr => + { + cr.ShowSpellAnimation(spellData.Graphic); + cr.ShowDamageCounter(spellHp, target.PercentHealth, isHeal: true); + }); + } + else if (_characterRendererProvider.CharacterRenderers.ContainsKey(target.TargetId)) + { + _characterRendererProvider.CharacterRenderers[target.TargetId].ShowSpellAnimation(spellData.Graphic); + _characterRendererProvider.CharacterRenderers[target.TargetId].ShowDamageCounter(spellHp, target.PercentHealth, isHeal: true); + } + } + } + public void NotifyMapEffect(MapEffect effect, byte strength = 0) { switch (effect) diff --git a/EndlessClient/Rendering/Character/CharacterRenderer.cs b/EndlessClient/Rendering/Character/CharacterRenderer.cs index a400e7948..69a345ff9 100644 --- a/EndlessClient/Rendering/Character/CharacterRenderer.cs +++ b/EndlessClient/Rendering/Character/CharacterRenderer.cs @@ -461,9 +461,9 @@ public void ShowPotionAnimation(int potionId) _effectRenderer.PlayEffect(EffectType.Potion, potionId); } - public void ShowSpellAnimation(int spellId) + public void ShowSpellAnimation(int spellGraphic) { - _effectRenderer.PlayEffect(EffectType.Spell, spellId); + _effectRenderer.PlayEffect(EffectType.Spell, spellGraphic); } #endregion diff --git a/EndlessClient/Rendering/ContextMenuRenderer.cs b/EndlessClient/Rendering/ContextMenuRenderer.cs index 4ad9c3614..fea00f08f 100644 --- a/EndlessClient/Rendering/ContextMenuRenderer.cs +++ b/EndlessClient/Rendering/ContextMenuRenderer.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using EndlessClient.ControlSets; using EndlessClient.Dialogs.Actions; using EndlessClient.HUD; @@ -9,7 +10,9 @@ using EndlessClient.Services; using EndlessClient.UIControls; using EOLib; +using EOLib.Domain.Chat; using EOLib.Domain.Interact; +using EOLib.Domain.Party; using EOLib.Graphics; using EOLib.Localization; using Microsoft.Xna.Framework; @@ -39,35 +42,43 @@ private enum MenuAction private readonly Rectangle _outSource, _overSource; private readonly Dictionary _menuActions; private Option _overRect; + private readonly IInGameDialogActions _inGameDialogActions; private readonly IPaperdollActions _paperdollActions; + private readonly IPartyActions _partyActions; private readonly IStatusLabelSetter _statusLabelSetter; private readonly IFriendIgnoreListService _friendIgnoreListService; private readonly IHudControlProvider _hudControlProvider; private readonly IContextMenuRepository _contextMenuRepository; private readonly IUserInputRepository _userInputRepository; + private readonly IPartyDataProvider _partyDataProvider; private readonly ICharacterRenderer _characterRenderer; - //private DateTime? m_lastPartyRequestedTime, m_lastTradeRequestedTime; + //private DateTime? m_lastTradeRequestedTime; + private static DateTime? _lastPartyRequestTime; public ContextMenuRenderer(INativeGraphicsManager nativeGraphicsManager, IInGameDialogActions inGameDialogActions, IPaperdollActions paperdollActions, + IPartyActions partyActions, IStatusLabelSetter statusLabelSetter, IFriendIgnoreListService friendIgnoreListService, IHudControlProvider hudControlProvider, IContextMenuRepository contextMenuRepository, IUserInputRepository userInputRepository, + IPartyDataProvider partyDataProvider, ICharacterRenderer characterRenderer) { _menuActions = new Dictionary(); _inGameDialogActions = inGameDialogActions; _paperdollActions = paperdollActions; + _partyActions = partyActions; _statusLabelSetter = statusLabelSetter; _friendIgnoreListService = friendIgnoreListService; _hudControlProvider = hudControlProvider; _contextMenuRepository = contextMenuRepository; _userInputRepository = userInputRepository; + _partyDataProvider = partyDataProvider; _characterRenderer = characterRenderer; //first, load up the images. split in half: the right half is the 'over' text @@ -212,8 +223,8 @@ private Action GetActionFromMenuAction(MenuAction menuAction) { case MenuAction.Paperdoll: return ShowPaperdollAction; case MenuAction.Book: return () => { };//return _eventShowBook; - case MenuAction.Join: return () => { };//return _eventJoinParty; - case MenuAction.Invite: return () => { }; //return _eventInviteToParty; + case MenuAction.Join: return JoinParty; + case MenuAction.Invite: return InviteToParty; case MenuAction.Trade: return () => { }; //return _eventTrade; case MenuAction.Whisper: return PrivateMessage; case MenuAction.Friend: return AddFriend; @@ -233,45 +244,43 @@ private void ShowPaperdollAction() // EOMessageBox.Show("TODO: Show quest info", "TODO ITEM", EODialogButtons.Ok, EOMessageBoxStyle.SmallDialogSmallHeader); //} - //private void _eventJoinParty(object arg1, EventArgs arg2) - //{ - // if (((EOGame) Game).Hud.PlayerIsPartyMember((short)m_rend.Character.ID)) - // { - // ((EOGame) Game).Hud.SetStatusLabel(EOResourceID.STATUS_LABEL_TYPE_WARNING, m_rend.Character.Name, EOResourceID.STATUS_LABEL_PARTY_IS_ALREADY_MEMBER); - // return; - // } + private void JoinParty() + { + if (_partyDataProvider.Members.Any(x => x.CharacterID == _characterRenderer.Character.ID)) + { + _statusLabelSetter.SetStatusLabel(EOResourceID.STATUS_LABEL_TYPE_WARNING, _characterRenderer.Character.Name + " ", EOResourceID.STATUS_LABEL_PARTY_IS_ALREADY_MEMBER, showChatError: true); + return; + } - // if (m_lastPartyRequestedTime != null && (DateTime.Now - m_lastPartyRequestedTime.Value).TotalSeconds < Constants.PartyRequestTimeoutSeconds) - // { - // ((EOGame) Game).Hud.SetStatusLabel(EOResourceID.STATUS_LABEL_TYPE_WARNING, EOResourceID.STATUS_LABEL_PARTY_RECENTLY_REQUESTED); - // return; - // } + if (_lastPartyRequestTime != null && (DateTime.Now - _lastPartyRequestTime.Value).TotalSeconds < Constants.PartyRequestTimeoutSeconds) + { + _statusLabelSetter.SetStatusLabel(EOResourceID.STATUS_LABEL_TYPE_WARNING, EOResourceID.STATUS_LABEL_PARTY_RECENTLY_REQUESTED, showChatError: true); + return; + } - // if (!m_api.PartyRequest(PartyRequestType.Join, (short) m_rend.Character.ID)) - // EOGame.Instance.DoShowLostConnectionDialogAndReturnToMainMenu(); - // m_lastPartyRequestedTime = DateTime.Now; - // ((EOGame) Game).Hud.SetStatusLabel(EOResourceID.STATUS_LABEL_TYPE_ACTION, EOResourceID.STATUS_LABEL_PARTY_REQUESTED_TO_JOIN); - //} + _lastPartyRequestTime = DateTime.Now; + _partyActions.RequestParty(PartyRequestType.Join, (short)_characterRenderer.Character.ID); + _statusLabelSetter.SetStatusLabel(EOResourceID.STATUS_LABEL_TYPE_ACTION, EOResourceID.STATUS_LABEL_PARTY_REQUESTED_TO_JOIN); + } - //private void _eventInviteToParty(object arg1, EventArgs arg2) - //{ - // if (((EOGame)Game).Hud.PlayerIsPartyMember((short)m_rend.Character.ID)) - // { - // ((EOGame)Game).Hud.SetStatusLabel(EOResourceID.STATUS_LABEL_TYPE_WARNING, m_rend.Character.Name, EOResourceID.STATUS_LABEL_PARTY_IS_ALREADY_MEMBER); - // return; - // } + private void InviteToParty() + { + if (_partyDataProvider.Members.Any(x => x.CharacterID == _characterRenderer.Character.ID)) + { + _statusLabelSetter.SetStatusLabel(EOResourceID.STATUS_LABEL_TYPE_WARNING, _characterRenderer.Character.Name + " ", EOResourceID.STATUS_LABEL_PARTY_IS_ALREADY_MEMBER, showChatError: true); + return; + } - // if (m_lastPartyRequestedTime != null && (DateTime.Now - m_lastPartyRequestedTime.Value).TotalSeconds < Constants.PartyRequestTimeoutSeconds) - // { - // ((EOGame)Game).Hud.SetStatusLabel(EOResourceID.STATUS_LABEL_TYPE_WARNING, EOResourceID.STATUS_LABEL_PARTY_RECENTLY_REQUESTED); - // return; - // } + if (_lastPartyRequestTime != null && (DateTime.Now - _lastPartyRequestTime.Value).TotalSeconds < Constants.PartyRequestTimeoutSeconds) + { + _statusLabelSetter.SetStatusLabel(EOResourceID.STATUS_LABEL_TYPE_WARNING, EOResourceID.STATUS_LABEL_PARTY_RECENTLY_REQUESTED, showChatError: true); + return; + } - // if (!m_api.PartyRequest(PartyRequestType.Invite, (short)m_rend.Character.ID)) - // EOGame.Instance.DoShowLostConnectionDialogAndReturnToMainMenu(); - // m_lastPartyRequestedTime = DateTime.Now; - // ((EOGame)Game).Hud.SetStatusLabel(EOResourceID.STATUS_LABEL_TYPE_ACTION, m_rend.Character.Name, EOResourceID.STATUS_LABEL_PARTY_IS_INVITED); - //} + _lastPartyRequestTime = DateTime.Now; + _partyActions.RequestParty(PartyRequestType.Invite, (short)_characterRenderer.Character.ID); + _statusLabelSetter.SetStatusLabel(EOResourceID.STATUS_LABEL_TYPE_ACTION, _characterRenderer.Character.Name, EOResourceID.STATUS_LABEL_PARTY_IS_INVITED); + } //private void _eventTrade(object arg1, EventArgs arg2) //{ diff --git a/EndlessClient/Rendering/Effects/IEffectTarget.cs b/EndlessClient/Rendering/Effects/IEffectTarget.cs index a9b9cfa5e..b767bfb0a 100644 --- a/EndlessClient/Rendering/Effects/IEffectTarget.cs +++ b/EndlessClient/Rendering/Effects/IEffectTarget.cs @@ -16,6 +16,6 @@ public interface IEffectTarget void ShowPotionAnimation(int potionId); - void ShowSpellAnimation(int spellId); + void ShowSpellAnimation(int spellGraphic); } } diff --git a/EndlessClient/Rendering/Factories/ContextMenuRendererFactory.cs b/EndlessClient/Rendering/Factories/ContextMenuRendererFactory.cs index 6331b25c2..71af096e9 100644 --- a/EndlessClient/Rendering/Factories/ContextMenuRendererFactory.cs +++ b/EndlessClient/Rendering/Factories/ContextMenuRendererFactory.cs @@ -6,6 +6,7 @@ using EndlessClient.Rendering.Character; using EndlessClient.Services; using EOLib.Domain.Interact; +using EOLib.Domain.Party; using EOLib.Graphics; namespace EndlessClient.Rendering.Factories @@ -16,29 +17,35 @@ public class ContextMenuRendererFactory : IContextMenuRendererFactory private readonly INativeGraphicsManager _nativeGraphicsManager; private readonly IInGameDialogActions _inGameDialogActions; private readonly IPaperdollActions _paperdollActions; + private readonly IPartyActions _partyActions; private readonly IStatusLabelSetter _statusLabelSetter; private readonly IFriendIgnoreListService _friendIgnoreListService; private readonly IHudControlProvider _hudControlProvider; private readonly IContextMenuRepository _contextMenuRepository; private readonly IUserInputRepository _userInputRepository; + private readonly IPartyDataProvider _partyDataProvider; public ContextMenuRendererFactory(INativeGraphicsManager nativeGraphicsManager, IInGameDialogActions inGameDialogActions, IPaperdollActions paperdollActions, + IPartyActions partyActions, IStatusLabelSetter statusLabelSetter, IFriendIgnoreListService friendIgnoreListService, IHudControlProvider hudControlProvider, IContextMenuRepository contextMenuRepository, - IUserInputRepository userInputRepository) + IUserInputRepository userInputRepository, + IPartyDataProvider partyDataProvider) { _nativeGraphicsManager = nativeGraphicsManager; _inGameDialogActions = inGameDialogActions; _paperdollActions = paperdollActions; + _partyActions = partyActions; _statusLabelSetter = statusLabelSetter; _friendIgnoreListService = friendIgnoreListService; _hudControlProvider = hudControlProvider; _contextMenuRepository = contextMenuRepository; _userInputRepository = userInputRepository; + _partyDataProvider = partyDataProvider; } public IContextMenuRenderer CreateContextMenuRenderer(ICharacterRenderer characterRenderer) @@ -46,11 +53,13 @@ public IContextMenuRenderer CreateContextMenuRenderer(ICharacterRenderer charact return new ContextMenuRenderer(_nativeGraphicsManager, _inGameDialogActions, _paperdollActions, + _partyActions, _statusLabelSetter, _friendIgnoreListService, _hudControlProvider, _contextMenuRepository, _userInputRepository, + _partyDataProvider, characterRenderer); } } diff --git a/EndlessClient/Rendering/NPC/NPCRenderer.cs b/EndlessClient/Rendering/NPC/NPCRenderer.cs index 56e6f4878..3bd035144 100644 --- a/EndlessClient/Rendering/NPC/NPCRenderer.cs +++ b/EndlessClient/Rendering/NPC/NPCRenderer.cs @@ -216,9 +216,9 @@ public void ShowWarpLeave() { } public void ShowPotionAnimation(int potionId) { } - public void ShowSpellAnimation(int spellId) + public void ShowSpellAnimation(int spellGraphic) { - _effectRenderer.PlayEffect(EffectType.Spell, spellId); + _effectRenderer.PlayEffect(EffectType.Spell, spellGraphic); } #endregion diff --git a/EndlessClient/Rendering/OldMapRenderer.cs b/EndlessClient/Rendering/OldMapRenderer.cs index bdcee06a3..5f9e2cee5 100644 --- a/EndlessClient/Rendering/OldMapRenderer.cs +++ b/EndlessClient/Rendering/OldMapRenderer.cs @@ -136,44 +136,44 @@ private void RemoveOtherPlayer(short id, WarpAnimation anim = WarpAnimation.None } } - public void PlayerCastSpellGroup(short fromPlayerID, short spellID, short spellHPgain, List spellTargets) - { - lock (_characterListLock) - { - bool fromIsMain = false; - var fromRenderer = _characterRenderers.Find(x => x.Character.ID == fromPlayerID); - if (fromRenderer == null && fromPlayerID == OldWorld.Instance.MainPlayer.ActiveCharacter.ID) - { - fromIsMain = true; - fromRenderer = OldWorld.Instance.ActiveCharacterRenderer; - } - - if (fromRenderer != null && !fromIsMain) - { - fromRenderer.StopShouting(false); - fromRenderer.StartCastingSpell(); - } - - foreach (var target in spellTargets) - { - bool targetIsMain = false; - var targetRenderer = _characterRenderers.Find(x => x.Character.ID == target.MemberID); - if (targetRenderer == null && target.MemberID == OldWorld.Instance.MainPlayer.ActiveCharacter.ID) - { - targetIsMain = true; - targetRenderer = OldWorld.Instance.ActiveCharacterRenderer; - } - - if (targetRenderer == null) continue; - - if (targetIsMain) - targetRenderer.Character.Stats.HP = target.MemberHP; - targetRenderer.SetDamageCounterValue(spellHPgain, target.MemberPercentHealth, true); - - _renderSpellOnPlayer(spellID, targetRenderer); - } - } - } + //public void PlayerCastSpellGroup(short fromPlayerID, short spellID, short spellHPgain, List spellTargets) + //{ + // lock (_characterListLock) + // { + // bool fromIsMain = false; + // var fromRenderer = _characterRenderers.Find(x => x.Character.ID == fromPlayerID); + // if (fromRenderer == null && fromPlayerID == OldWorld.Instance.MainPlayer.ActiveCharacter.ID) + // { + // fromIsMain = true; + // fromRenderer = OldWorld.Instance.ActiveCharacterRenderer; + // } + + // if (fromRenderer != null && !fromIsMain) + // { + // fromRenderer.StopShouting(false); + // fromRenderer.StartCastingSpell(); + // } + + // foreach (var target in spellTargets) + // { + // bool targetIsMain = false; + // var targetRenderer = _characterRenderers.Find(x => x.Character.ID == target.MemberID); + // if (targetRenderer == null && target.MemberID == OldWorld.Instance.MainPlayer.ActiveCharacter.ID) + // { + // targetIsMain = true; + // targetRenderer = OldWorld.Instance.ActiveCharacterRenderer; + // } + + // if (targetRenderer == null) continue; + + // if (targetIsMain) + // targetRenderer.Character.Stats.HP = target.MemberHP; + // targetRenderer.SetDamageCounterValue(spellHPgain, target.MemberPercentHealth, true); + + // _renderSpellOnPlayer(spellID, targetRenderer); + // } + // } + //} public void UpdateOtherPlayers() { diff --git a/EndlessClient/UIControls/OldScrollBar.cs b/EndlessClient/UIControls/OldScrollBar.cs index b9daeae19..a1e11a06a 100644 --- a/EndlessClient/UIControls/OldScrollBar.cs +++ b/EndlessClient/UIControls/OldScrollBar.cs @@ -1,5 +1,4 @@ using System; -using EndlessClient.HUD.Panels.Old; using EndlessClient.Old; using EOLib.Graphics; using Microsoft.Xna.Framework;