diff --git a/dev/Configuration/UserInterfaceSettings.cs b/dev/Configuration/UserInterfaceSettings.cs index 3eca3ab9..38b5090b 100644 --- a/dev/Configuration/UserInterfaceSettings.cs +++ b/dev/Configuration/UserInterfaceSettings.cs @@ -30,12 +30,13 @@ public class UserInterfaceSettings : ASettingsSection private bool m_AlwaysRun; private bool m_MenuBarDisabled; - private int m_SpeechColor; - private int m_EmoteColor; - private int m_PartyMsgColor; - private int m_GuildMsgColor; + private int m_SpeechColor = 4 * Utility.RandomValue(0, 99) * 5; + private int m_EmoteColor = 646; + private int m_PartyMsgPrivateColor = 58; + private int m_PartyMsgColor = 68; + private int m_GuildMsgColor = 70; private bool m_IgnoreGuildMsg; - private int m_AllianceMsgColor; + private int m_AllianceMsgColor = 487; private bool m_IgnoreAllianceMsg; private bool m_CrimeQuery; @@ -122,6 +123,12 @@ public int EmoteColor set { SetProperty(ref m_EmoteColor, Clamp(value, 0, HueData.HueCount - 1)); } } + public int PartyPrivateMsgColor + { + get { return m_PartyMsgPrivateColor; } + set { SetProperty(ref m_PartyMsgPrivateColor, Clamp(value, 0, HueData.HueCount - 1)); } + } + public int PartyMsgColor { get { return m_PartyMsgColor; } diff --git a/dev/Core/UI/UserInterfaceService.cs b/dev/Core/UI/UserInterfaceService.cs index 0218a4e7..581e9f41 100644 --- a/dev/Core/UI/UserInterfaceService.cs +++ b/dev/Core/UI/UserInterfaceService.cs @@ -230,7 +230,7 @@ public void Update(double totalMS, double frameMS) for (int i = 0; i < m_Controls.Count; i++) { AControl c = m_Controls[i]; - if (!c.IsInitialized) + if (!c.IsInitialized && !c.IsDisposed) c.Initialize(); c.Update(totalMS, frameMS); } @@ -640,7 +640,7 @@ private void ClipMouse(ref Point position) position.X = -8; if (position.Y < -8) position.Y = -8; - if (position.Y >= Width + 8) + if (position.X >= Width + 8) position.X = Width + 8; if (position.Y >= Height + 8) position.Y = Height + 8; diff --git a/dev/Ultima/Data/ChatMode.cs b/dev/Ultima/Data/ChatMode.cs index 980ea431..90a2e239 100644 --- a/dev/Ultima/Data/ChatMode.cs +++ b/dev/Ultima/Data/ChatMode.cs @@ -4,6 +4,7 @@ public enum ChatMode { Whisper, Emote, Party, + PartyPrivate, Guild, Alliance } diff --git a/dev/Ultima/Data/MessageTypes.cs b/dev/Ultima/Data/MessageTypes.cs index 1e97dc77..52a77ed2 100644 --- a/dev/Ultima/Data/MessageTypes.cs +++ b/dev/Ultima/Data/MessageTypes.cs @@ -9,8 +9,11 @@ * ***************************************************************************/ +using System; + namespace UltimaXNA.Ultima.Data { + [Flags] public enum MessageTypes { Normal = 0x00, @@ -23,11 +26,13 @@ public enum MessageTypes Whisper = 0x08, Yell = 0x09, Spell = 0x0A, - Guild = 0x0D, Alliance = 0x0E, Command = 0x0F, - - EncodedTriggers = 0xC0 + /// + /// This is used for display only. This is not in the UO protocol. Do not send msgs of this type to the server. + /// + PartyDisplayOnly = 0x10, + EncodedTriggers = 0xC0 // 0x40 + 0x80 } } diff --git a/dev/Ultima/Network/Client/AsciiSpeechPacket.cs b/dev/Ultima/Network/Client/AsciiSpeechPacket.cs index 181df574..e189f473 100644 --- a/dev/Ultima/Network/Client/AsciiSpeechPacket.cs +++ b/dev/Ultima/Network/Client/AsciiSpeechPacket.cs @@ -26,7 +26,7 @@ public AsciiSpeechPacket(MessageTypes type, int font, int hue, string lang, stri int triggerCount; int[] triggers; SpeechData.GetSpeechTriggers(text, lang, out triggerCount, out triggers); if (triggerCount > 0) - type = (MessageTypes)(type | MessageTypes.EncodedTriggers); + type = type | MessageTypes.EncodedTriggers; Stream.Write((byte)type); Stream.Write((short)hue); diff --git a/dev/Ultima/Network/Client/Extensions/GuildLocationQueryPacket.cs b/dev/Ultima/Network/Client/Extensions/GuildLocationQueryPacket.cs new file mode 100644 index 00000000..da6b456f --- /dev/null +++ b/dev/Ultima/Network/Client/Extensions/GuildLocationQueryPacket.cs @@ -0,0 +1,22 @@ +/*************************************************************************** + * GuildLocationQueryPacket.cs + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ +using UltimaXNA.Core.Network.Packets; + +namespace UltimaXNA.Ultima.Network.Client.Extensions { + /// + /// MapUO Protocol: Requests the position of all guild members. + /// + public class GuildLocationQueryPacket : SendPacket { + public GuildLocationQueryPacket() + : base(0xF0, "Query Guild Member Locations") { + Stream.Write((byte)0); + } + } +} \ No newline at end of file diff --git a/dev/Ultima/Network/Client/Extensions/PartyLocationQueryPacket.cs b/dev/Ultima/Network/Client/Extensions/PartyLocationQueryPacket.cs new file mode 100644 index 00000000..c2f2b2d6 --- /dev/null +++ b/dev/Ultima/Network/Client/Extensions/PartyLocationQueryPacket.cs @@ -0,0 +1,22 @@ +/*************************************************************************** + * PartyLocationQueryPacket.cs + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ +using UltimaXNA.Core.Network.Packets; + +namespace UltimaXNA.Ultima.Network.Client.Extensions { + /// + /// MapUO Protocol: Requests the position of all party members. + /// + public class PartyLocationQueryPacket : SendPacket { + public PartyLocationQueryPacket() + : base(0xF0, "Query Party Member Locations") { + Stream.Write((byte)0); + } + } +} \ No newline at end of file diff --git a/dev/Ultima/Network/Client/Partying/PartyAcceptPacket.cs b/dev/Ultima/Network/Client/Partying/PartyAcceptPacket.cs new file mode 100644 index 00000000..cfd38b95 --- /dev/null +++ b/dev/Ultima/Network/Client/Partying/PartyAcceptPacket.cs @@ -0,0 +1,21 @@ +/*************************************************************************** + * PartyAcceptPacket.cs + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ +using UltimaXNA.Core.Network.Packets; + +namespace UltimaXNA.Ultima.Network.Client.Partying { + public class PartyAcceptPacket : SendPacket { + public PartyAcceptPacket(Serial invitingPartyLeader) + : base(0xbf, "Party Join Accept") { + Stream.Write((short)6); + Stream.Write((byte)8); + Stream.Write(invitingPartyLeader); + } + } +} \ No newline at end of file diff --git a/dev/Ultima/Network/Client/Partying/PartyDeclinePacket.cs b/dev/Ultima/Network/Client/Partying/PartyDeclinePacket.cs new file mode 100644 index 00000000..69987a4e --- /dev/null +++ b/dev/Ultima/Network/Client/Partying/PartyDeclinePacket.cs @@ -0,0 +1,21 @@ +/*************************************************************************** + * PartyDeclinePacket.cs + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ +using UltimaXNA.Core.Network.Packets; + +namespace UltimaXNA.Ultima.Network.Client.Partying { + public class PartyDeclinePacket : SendPacket { + public PartyDeclinePacket(Serial invitingPartyLeader) + : base(0xbf, "Party Join Decline") { + Stream.Write((short)6); + Stream.Write((byte)9); + Stream.Write(invitingPartyLeader); + } + } +} \ No newline at end of file diff --git a/dev/Ultima/Network/Client/Partying/PartyLeavePacket.cs b/dev/Ultima/Network/Client/Partying/PartyLeavePacket.cs new file mode 100644 index 00000000..8de41c75 --- /dev/null +++ b/dev/Ultima/Network/Client/Partying/PartyLeavePacket.cs @@ -0,0 +1,22 @@ +/*************************************************************************** + * PartyLeavePacket.cs + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ +using UltimaXNA.Core.Network.Packets; +using UltimaXNA.Ultima.World; + +namespace UltimaXNA.Ultima.Network.Client.Partying { + public class PartyLeavePacket : SendPacket { + public PartyLeavePacket() + : base(0xbf, "Leave Party") { + Stream.Write((short)6); + Stream.Write((byte)2); + Stream.Write(WorldModel.PlayerSerial); + } + } +} \ No newline at end of file diff --git a/dev/Ultima/Network/Client/Partying/PartyLootEnablePacket.cs b/dev/Ultima/Network/Client/Partying/PartyLootEnablePacket.cs new file mode 100644 index 00000000..590f351a --- /dev/null +++ b/dev/Ultima/Network/Client/Partying/PartyLootEnablePacket.cs @@ -0,0 +1,21 @@ +/*************************************************************************** + * PartyCanLootPacket.cs + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ +using UltimaXNA.Core.Network.Packets; + +namespace UltimaXNA.Ultima.Network.Client.Partying { + class PartyCanLootPacket : SendPacket { + public PartyCanLootPacket(bool isLootable) + : base(0xbf, "Party Can Loot") { + Stream.Write((short)6); + Stream.Write((byte)6); + Stream.Write(isLootable); + } + } +} \ No newline at end of file diff --git a/dev/Ultima/Network/Client/Partying/PartyPrivateMessagePacket.cs b/dev/Ultima/Network/Client/Partying/PartyPrivateMessagePacket.cs new file mode 100644 index 00000000..3c9225b1 --- /dev/null +++ b/dev/Ultima/Network/Client/Partying/PartyPrivateMessagePacket.cs @@ -0,0 +1,22 @@ +/*************************************************************************** + * PartyPrivateMessagePacket.cs + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ +using UltimaXNA.Core.Network.Packets; + +namespace UltimaXNA.Ultima.Network.Client.Partying { + public class PartyPrivateMessagePacket : SendPacket { + public PartyPrivateMessagePacket(Serial memberSerial, string msg) + : base(0xbf, "Private Party Message") { + Stream.Write((short)6); + Stream.Write((byte)3); + Stream.Write(memberSerial); + Stream.WriteBigUniNull(msg); + } + } +} \ No newline at end of file diff --git a/dev/Ultima/Network/Client/Partying/PartyPublicMessagePacket.cs b/dev/Ultima/Network/Client/Partying/PartyPublicMessagePacket.cs new file mode 100644 index 00000000..93af4fc3 --- /dev/null +++ b/dev/Ultima/Network/Client/Partying/PartyPublicMessagePacket.cs @@ -0,0 +1,21 @@ +/*************************************************************************** + * PartyPublicMessagePacket.cs + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ +using UltimaXNA.Core.Network.Packets; + +namespace UltimaXNA.Ultima.Network.Client.Partying { + public class PartyPublicMessagePacket : SendPacket { + public PartyPublicMessagePacket(string msg) + : base(0xbf, "Public Party Message") { + Stream.Write((short)6); + Stream.Write((byte)4); + Stream.WriteBigUniNull(msg); + } + } +} \ No newline at end of file diff --git a/dev/Ultima/Network/Client/Partying/PartyRemoveMemberPacket.cs b/dev/Ultima/Network/Client/Partying/PartyRemoveMemberPacket.cs new file mode 100644 index 00000000..70a40166 --- /dev/null +++ b/dev/Ultima/Network/Client/Partying/PartyRemoveMemberPacket.cs @@ -0,0 +1,21 @@ +/*************************************************************************** + * PartyRemoveMemberPacket.cs + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ +using UltimaXNA.Core.Network.Packets; + +namespace UltimaXNA.Ultima.Network.Client.Partying { + public class PartyRemoveMemberPacket : SendPacket { + public PartyRemoveMemberPacket(Serial serial) + : base(0xbf, "Remove Party Member") { + Stream.Write((short)6); + Stream.Write((byte)2); + Stream.Write(serial); + } + } +} \ No newline at end of file diff --git a/dev/Ultima/Network/Client/Partying/PartyRequestAddTargetPacket.cs b/dev/Ultima/Network/Client/Partying/PartyRequestAddTargetPacket.cs new file mode 100644 index 00000000..330794ee --- /dev/null +++ b/dev/Ultima/Network/Client/Partying/PartyRequestAddTargetPacket.cs @@ -0,0 +1,21 @@ +/*************************************************************************** + * PartyRequestAddTargetPacket.cs + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ +using UltimaXNA.Core.Network.Packets; + +namespace UltimaXNA.Ultima.Network.Client.Partying { + class PartyRequestAddTargetPacket : SendPacket { + public PartyRequestAddTargetPacket() + : base(0xbf, "Add Party Member") { + Stream.Write((short)6); + Stream.Write((byte)1); + Stream.Write(0); + } + } +} \ No newline at end of file diff --git a/dev/Ultima/Network/Client/Partying/PartyRequestRemoveTargetPacket.cs b/dev/Ultima/Network/Client/Partying/PartyRequestRemoveTargetPacket.cs new file mode 100644 index 00000000..6b0e7a8c --- /dev/null +++ b/dev/Ultima/Network/Client/Partying/PartyRequestRemoveTargetPacket.cs @@ -0,0 +1,21 @@ +/*************************************************************************** + * PartyRequestRemoveTargetPacket.cs + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ +using UltimaXNA.Core.Network.Packets; + +namespace UltimaXNA.Ultima.Network.Client.Partying { + public class PartyRequestRemoveTargetPacket : SendPacket { + public PartyRequestRemoveTargetPacket() + : base(0xbf, "Remove Party Member") { + Stream.Write((short)6); + Stream.Write((byte)2); + Stream.Write(0); + } + } +} \ No newline at end of file diff --git a/dev/Ultima/Network/Server/CustomHousePacket.cs b/dev/Ultima/Network/Server/CustomHousePacket.cs index d3eab9dc..b13c3689 100644 --- a/dev/Ultima/Network/Server/CustomHousePacket.cs +++ b/dev/Ultima/Network/Server/CustomHousePacket.cs @@ -1,13 +1,6 @@ /*************************************************************************** * CustomHousePacket.cs * - * begin : February 24, 2010 - * email : poplicola@ultimaxna.com - * - ***************************************************************************/ - -/*************************************************************************** - * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or @@ -17,48 +10,41 @@ #region usings using UltimaXNA.Core.Network; using UltimaXNA.Core.Network.Packets; -using UltimaXNA.Ultima.World; +using UltimaXNA.Ultima.World.Data; #endregion -namespace UltimaXNA.Ultima.Network.Server -{ - public class CustomHousePacket : RecvPacket - { - readonly Serial m_houseSerial; - public Serial HouseSerial { get { return m_houseSerial; } } +namespace UltimaXNA.Ultima.Network.Server { + public class CustomHousePacket : RecvPacket { + readonly Serial m_HouseSerial; + public Serial HouseSerial => m_HouseSerial; - readonly int m_revisionHash; - public int RevisionHash { get { return m_revisionHash; } } + readonly int m_RevisionHash; + public int RevisionHash => m_RevisionHash; - readonly int m_numPlanes; - public int PlaneCount { get { return m_numPlanes; } } + readonly int m_NumPlanes; + public int PlaneCount => m_NumPlanes; - CustomHousePlane[] m_planes; - public CustomHousePlane[] Planes { get { return m_planes; } } + CustomHousePlane[] m_Planes; + public CustomHousePlane[] Planes => m_Planes; public CustomHousePacket(PacketReader reader) - : base(0xD8, "Custom House Packet") - { + : base(0xD8, "Custom House Packet") { byte CompressionType = reader.ReadByte(); - if (CompressionType != 3) - { - m_houseSerial = Serial.Null; + if (CompressionType != 3) { + m_HouseSerial = Serial.Null; return; } reader.ReadByte(); // unknown, always 0? - m_houseSerial = reader.ReadInt32(); - m_revisionHash = reader.ReadInt32(); - + m_HouseSerial = reader.ReadInt32(); + m_RevisionHash = reader.ReadInt32(); // this is for compression type 3 only int bufferLength = reader.ReadInt16(); int trueBufferLength = reader.ReadInt16(); - m_numPlanes = reader.ReadByte(); + m_NumPlanes = reader.ReadByte(); // end compression type 3 - - m_planes = new CustomHousePlane[m_numPlanes]; - for (int i = 0; i < m_numPlanes; i++) - { - m_planes[i] = new CustomHousePlane(reader); + m_Planes = new CustomHousePlane[m_NumPlanes]; + for (int i = 0; i < m_NumPlanes; i++) { + m_Planes[i] = new CustomHousePlane(reader); } } } diff --git a/dev/Ultima/Network/Server/GeneralInfo/CloseGumpInfo.cs b/dev/Ultima/Network/Server/GeneralInfo/CloseGumpInfo.cs new file mode 100644 index 00000000..cc93b4da --- /dev/null +++ b/dev/Ultima/Network/Server/GeneralInfo/CloseGumpInfo.cs @@ -0,0 +1,16 @@ +using UltimaXNA.Core.Network; + +namespace UltimaXNA.Ultima.Network.Server.GeneralInfo { + /// + /// Subcommand 0x04: Close a generic gump. + /// + class CloseGumpInfo : IGeneralInfo { + public readonly int GumpTypeID; + public readonly int GumpButtonID; + + public CloseGumpInfo(PacketReader reader) { + GumpTypeID = reader.ReadInt32(); + GumpButtonID = reader.ReadInt32(); + } + } +} diff --git a/dev/Ultima/Network/Server/GeneralInfo/ContextMenuInfo.cs b/dev/Ultima/Network/Server/GeneralInfo/ContextMenuInfo.cs new file mode 100644 index 00000000..ae0fe5ee --- /dev/null +++ b/dev/Ultima/Network/Server/GeneralInfo/ContextMenuInfo.cs @@ -0,0 +1,28 @@ +using UltimaXNA.Core.Network; +using UltimaXNA.Ultima.Data; + +namespace UltimaXNA.Ultima.Network.Server.GeneralInfo { + /// + /// Subcommand 0x14: A context menu. + /// + class ContextMenuInfo : IGeneralInfo { + public readonly ContextMenuData Menu; + + public ContextMenuInfo(PacketReader reader) { + reader.ReadByte(); // unknown, always 0x00 + int subcommand = reader.ReadByte(); // 0x01 for 2D, 0x02 for KR + Menu = new ContextMenuData(reader.ReadInt32()); + int contextMenuChoiceCount = reader.ReadByte(); + for (int i = 0; i < contextMenuChoiceCount; i++) { + int iUniqueID = reader.ReadUInt16(); + int iClilocID = reader.ReadUInt16() + 3000000; + int iFlags = reader.ReadUInt16(); // 0x00=enabled, 0x01=disabled, 0x02=arrow, 0x20 = color + int iColor = 0; + if ((iFlags & 0x20) == 0x20) { + iColor = reader.ReadUInt16(); + } + Menu.AddItem(iUniqueID, iClilocID, iFlags, iColor); + } + } + } +} diff --git a/dev/Ultima/Network/Server/GeneralInfo/ExtendedStatsInfo.cs b/dev/Ultima/Network/Server/GeneralInfo/ExtendedStatsInfo.cs new file mode 100644 index 00000000..3b2baf7c --- /dev/null +++ b/dev/Ultima/Network/Server/GeneralInfo/ExtendedStatsInfo.cs @@ -0,0 +1,45 @@ +using UltimaXNA.Core.Diagnostics.Tracing; +using UltimaXNA.Core.Network; +using UltimaXNA.Ultima.Data; + +namespace UltimaXNA.Ultima.Network.Server.GeneralInfo { + /// + /// Subcommand 0x19: the serial of the mobile which the extended stats must be applied to. + /// + class ExtendedStatsInfo :IGeneralInfo { + + public readonly Serial Serial; + public readonly StatLocks Locks; + + public ExtendedStatsInfo(PacketReader reader) { + int clientFlag = reader.ReadByte(); // (0x2 for 2D client, 0x5 for KR client) + Serial = reader.ReadInt32(); + byte unknown0 = reader.ReadByte(); // (always 0) + byte lockFlags = reader.ReadByte(); + // Lock flags = bitflags 00SSDDII + // 00 = up + // 01 = down + // 10 = locked + // FF = update mobile status animation ( KR only ) + if (lockFlags != 0xFF) { + int strengthLock = (lockFlags >> 4) & 0x03; + int dexterityLock = (lockFlags >> 2) & 0x03; + int inteligenceLock = (lockFlags) & 0x03; + Locks = new StatLocks(strengthLock, dexterityLock, inteligenceLock); + } + if (clientFlag == 5) { + Tracer.Warn("ClientFlags == 5 in GeneralInfoPacket ExtendedStats 0x19. This is not a KR client."); + // If(Lock flags = 0xFF) //Update mobile status animation + // BYTE[1] Status // Unveryfied if lock flags == FF the locks will be handled here + // BYTE[1] unknown (0x00) + // BYTE[1] Animation + // BYTE[1] unknown (0x00) + // BYTE[1] Frame + // else + // BYTE[1] unknown (0x00) + // BYTE[4] unknown (0x00000000) + // endif + } + } + } +} diff --git a/dev/Ultima/Network/Server/GeneralInfo/HouseRevisionInfo.cs b/dev/Ultima/Network/Server/GeneralInfo/HouseRevisionInfo.cs new file mode 100644 index 00000000..18706643 --- /dev/null +++ b/dev/Ultima/Network/Server/GeneralInfo/HouseRevisionInfo.cs @@ -0,0 +1,17 @@ +using UltimaXNA.Core.Network; +using UltimaXNA.Ultima.Data; + +namespace UltimaXNA.Ultima.Network.Server.GeneralInfo { + /// + /// Subcommand 0x1D: The revision hash of a custom house. + /// + class HouseRevisionInfo : IGeneralInfo { + public readonly HouseRevisionState Revision; + + public HouseRevisionInfo(PacketReader reader) { + Serial s = reader.ReadInt32(); + int hash = reader.ReadInt32(); + Revision = new HouseRevisionState(s, hash); + } + } +} diff --git a/dev/Ultima/Network/Server/GeneralInfo/IGeneralInfo.cs b/dev/Ultima/Network/Server/GeneralInfo/IGeneralInfo.cs new file mode 100644 index 00000000..3e95cda5 --- /dev/null +++ b/dev/Ultima/Network/Server/GeneralInfo/IGeneralInfo.cs @@ -0,0 +1,13 @@ +/*************************************************************************** + * IGeneralInfo.cs + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +namespace UltimaXNA.Ultima.Network.Server.GeneralInfo { + public interface IGeneralInfo { } +} diff --git a/dev/Ultima/Data/MapDiffInfo.cs b/dev/Ultima/Network/Server/GeneralInfo/MapDiffInfo.cs similarity index 52% rename from dev/Ultima/Data/MapDiffInfo.cs rename to dev/Ultima/Network/Server/GeneralInfo/MapDiffInfo.cs index a041c1d5..0b00d685 100644 --- a/dev/Ultima/Data/MapDiffInfo.cs +++ b/dev/Ultima/Network/Server/GeneralInfo/MapDiffInfo.cs @@ -1,20 +1,20 @@ using UltimaXNA.Core.Network; -namespace UltimaXNA.Ultima.Data -{ - public class MapDiffInfo - { +namespace UltimaXNA.Ultima.Network.Server.GeneralInfo { + /// + /// Subcommand 0x18: The count of map diffs that were received. + /// As of 6.0.0.0, this is only used to inform the client of the number of active maps. + /// + public class MapDiffInfo : IGeneralInfo { public readonly int MapCount; public readonly int[] MapPatches; public readonly int[] StaticPatches; - public MapDiffInfo(PacketReader reader) - { + public MapDiffInfo(PacketReader reader) { MapCount = reader.ReadInt32(); MapPatches = new int[MapCount]; StaticPatches = new int[MapCount]; - for (int i = 0; i < MapCount; i++) - { + for (int i = 0; i < MapCount; i++) { StaticPatches[i] = reader.ReadInt32(); MapPatches[i] = reader.ReadInt32(); } diff --git a/dev/Ultima/Network/Server/GeneralInfo/MapIndexInfo.cs b/dev/Ultima/Network/Server/GeneralInfo/MapIndexInfo.cs new file mode 100644 index 00000000..6b42c2bb --- /dev/null +++ b/dev/Ultima/Network/Server/GeneralInfo/MapIndexInfo.cs @@ -0,0 +1,13 @@ +using UltimaXNA.Core.Network; + +namespace UltimaXNA.Ultima.Network.Server.GeneralInfo { + /// + /// Subcommand 0x08: The index of the map the player is located within. + /// + class MapIndexInfo : IGeneralInfo { + public readonly byte MapID; + public MapIndexInfo(PacketReader reader) { + MapID = reader.ReadByte(); + } + } +} diff --git a/dev/Ultima/Network/Server/GeneralInfo/PartyInfo.cs b/dev/Ultima/Network/Server/GeneralInfo/PartyInfo.cs new file mode 100644 index 00000000..7f1af742 --- /dev/null +++ b/dev/Ultima/Network/Server/GeneralInfo/PartyInfo.cs @@ -0,0 +1,43 @@ +using UltimaXNA.Core.Diagnostics.Tracing; +using UltimaXNA.Core.Network; + +namespace UltimaXNA.Ultima.Network.Server.GeneralInfo +{ + /// + /// Subcommand 0x06: Party info. + /// + class PartyInfo : IGeneralInfo { + public const byte CommandPartyList = 0x01; + public const byte CommandRemoveMember = 0x02; + public const byte CommandPrivateMessage = 0x03; + public const byte CommandPublicMessage = 0x04; + public const byte CommandInvitation = 0x07; + + public readonly byte SubsubCommand; + public readonly IGeneralInfo Info; + + public PartyInfo(PacketReader reader) { + SubsubCommand = reader.ReadByte(); + switch (SubsubCommand) { + case CommandPartyList: + Info = new PartyMemberListInfo(reader); + break; + case CommandRemoveMember: + Info = new PartyRemoveMemberInfo(reader); + break; + case CommandPrivateMessage: + Info = new PartyMessageInfo(reader, true); + break; + case CommandPublicMessage: + Info = new PartyMessageInfo(reader, false); + break; + case CommandInvitation://PARTY INVITE PROGRESS + Info = new PartyInvitationInfo(reader); + break; + default: + Tracer.Warn($"Unhandled Subsubcommand {SubsubCommand:X2} in PartyInfo."); + break; + } + } + } +} diff --git a/dev/Ultima/Network/Server/GeneralInfo/PartyInvitationInfo.cs b/dev/Ultima/Network/Server/GeneralInfo/PartyInvitationInfo.cs new file mode 100644 index 00000000..f8818e64 --- /dev/null +++ b/dev/Ultima/Network/Server/GeneralInfo/PartyInvitationInfo.cs @@ -0,0 +1,24 @@ +/*************************************************************************** + * PartyInvitationInfo.cs + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using UltimaXNA.Core.Network; + +namespace UltimaXNA.Ultima.Network.Server.GeneralInfo { + /// + /// Subcommand 0x06 / 0x07: Invitation to joint a party. + /// + public class PartyInvitationInfo : IGeneralInfo { + public readonly int PartyLeaderSerial; + + public PartyInvitationInfo(PacketReader reader) { + PartyLeaderSerial = reader.ReadInt32(); + } + } +} diff --git a/dev/Ultima/Network/Server/GeneralInfo/PartyMemberListInfo.cs b/dev/Ultima/Network/Server/GeneralInfo/PartyMemberListInfo.cs new file mode 100644 index 00000000..8cbe9e1f --- /dev/null +++ b/dev/Ultima/Network/Server/GeneralInfo/PartyMemberListInfo.cs @@ -0,0 +1,29 @@ +/*************************************************************************** + * PartyMemberListInfo.cs + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using UltimaXNA.Core.Network; + +namespace UltimaXNA.Ultima.Network.Server.GeneralInfo { + /// + /// Subcommand 0x06 / 0x01: Party menber list. + /// + public class PartyMemberListInfo : IGeneralInfo { + public readonly int Count; + public readonly int[] Serials; + + public PartyMemberListInfo(PacketReader reader) { + Count = reader.ReadByte(); + Serials = new int[Count]; + for (int i = 0; i < Count; i++) { + Serials[i] = reader.ReadInt32(); + } + } + } +} diff --git a/dev/Ultima/Network/Server/GeneralInfo/PartyMessageInfo.cs b/dev/Ultima/Network/Server/GeneralInfo/PartyMessageInfo.cs new file mode 100644 index 00000000..a4a39044 --- /dev/null +++ b/dev/Ultima/Network/Server/GeneralInfo/PartyMessageInfo.cs @@ -0,0 +1,28 @@ +/*************************************************************************** + * PartyMessageInfo.cs + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using UltimaXNA.Core.Network; + +namespace UltimaXNA.Ultima.Network.Server.GeneralInfo { + /// + /// Subcommand 0x06 / 0x03 and 0x06 / 0x04: Party message. + /// + public class PartyMessageInfo : IGeneralInfo { + public readonly bool IsPrivate; + public readonly Serial Source; + public readonly string Message; + + public PartyMessageInfo(PacketReader reader, bool isPrivate) { + IsPrivate = isPrivate; + Source = (Serial)reader.ReadInt32(); + Message = reader.ReadUnicodeString(); + } + } +} diff --git a/dev/Ultima/Network/Server/GeneralInfo/PartyRemoveMemberInfo.cs b/dev/Ultima/Network/Server/GeneralInfo/PartyRemoveMemberInfo.cs new file mode 100644 index 00000000..6c0d6acd --- /dev/null +++ b/dev/Ultima/Network/Server/GeneralInfo/PartyRemoveMemberInfo.cs @@ -0,0 +1,31 @@ +/*************************************************************************** + * PartyRemoveMemberInfo.cs + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using UltimaXNA.Core.Network; + +namespace UltimaXNA.Ultima.Network.Server.GeneralInfo { + /// + /// Subcommand 0x06 / 0x03 and 0x06 / 0x02: Remove party member. + /// + public class PartyRemoveMemberInfo : IGeneralInfo { + public readonly int Count; + public readonly int RemovedMember; + public readonly int[] Serials; + + public PartyRemoveMemberInfo(PacketReader reader) { + Count = reader.ReadByte(); + RemovedMember = reader.ReadInt32(); + Serials = new int[Count]; + for (int i = 0; i < Count; i++) { + Serials[i] = reader.ReadInt32(); + } + } + } +} diff --git a/dev/Ultima/Network/Server/GeneralInfo/SpellBookContentsInfo.cs b/dev/Ultima/Network/Server/GeneralInfo/SpellBookContentsInfo.cs new file mode 100644 index 00000000..90ee0058 --- /dev/null +++ b/dev/Ultima/Network/Server/GeneralInfo/SpellBookContentsInfo.cs @@ -0,0 +1,20 @@ +using UltimaXNA.Core.Network; +using UltimaXNA.Ultima.Data; + +namespace UltimaXNA.Ultima.Network.Server.GeneralInfo { + /// + /// Subcommand 0x1B: the contents of a spellbook. + /// + class SpellBookContentsInfo : IGeneralInfo { + public readonly SpellbookData Spellbook; + + public SpellBookContentsInfo(PacketReader reader) { + ushort unknown = reader.ReadUInt16(); // always 1 + Serial serial = reader.ReadInt32(); + ushort itemID = reader.ReadUInt16(); + ushort spellbookType = reader.ReadUInt16(); // 1==regular, 101=necro, 201=paladin, 401=bushido, 501=ninjitsu, 601=spellweaving + ulong spellBitfields = reader.ReadUInt32() + (((ulong)reader.ReadUInt32()) << 32); // first bit of first byte = spell #1, second bit of first byte = spell #2, first bit of second byte = spell #8, etc + Spellbook = new SpellbookData(serial, itemID, spellbookType, spellBitfields); + } + } +} diff --git a/dev/Ultima/Network/Server/GeneralInfoPacket.cs b/dev/Ultima/Network/Server/GeneralInfoPacket.cs index 9762a4cc..e30b4c2f 100644 --- a/dev/Ultima/Network/Server/GeneralInfoPacket.cs +++ b/dev/Ultima/Network/Server/GeneralInfoPacket.cs @@ -11,202 +11,59 @@ using UltimaXNA.Core.Diagnostics.Tracing; using UltimaXNA.Core.Network; using UltimaXNA.Core.Network.Packets; -using UltimaXNA.Ultima.Data; +using UltimaXNA.Ultima.Network.Server.GeneralInfo; #endregion -namespace UltimaXNA.Ultima.Network.Server -{ - public class GeneralInfoPacket : RecvPacket - { - public readonly short Subcommand; +namespace UltimaXNA.Ultima.Network.Server { + public class GeneralInfoPacket : RecvPacket { + public const int CloseGump = 0x04; + public const int Party = 0x06; + public const int SetMap = 0x08; + public const int ContextMenu = 0x14; + public const int MapDiff = 0x18; + public const int ExtendedStats = 0x19; + public const int SpellBookContents = 0x1B; + public const int HouseRevision = 0x1D; + public const int AOSAbilityIconConfirm = 0x21; - /// - /// Subcommand 0x04: Close a generic gump. - /// - public int CloseGumpTypeID - { - get; - private set; - } - public int CloseGumpButtonID - { - get; - private set; - } - - /// - /// Subcommand 0x08: The index of the map the player is located within. - /// - public byte MapID - { - get; - private set; - } - - /// - /// Subcommand 0x14: A context menu. - /// - public ContextMenuData ContextMenu - { - get; - private set; - } - - /// - /// Subcommand 0x18: The count of map diffs that were received. - /// - public MapDiffInfo MapDiffs - { - get; - private set; - } - - /// - /// Subcommand 0x1B: the contents of a spellbook. - /// - public SpellbookData Spellbook - { - get; - private set; - } - - /// - /// Subcommand 0x19: the serial of the mobile which the extended stats must be applied to. - /// - public Serial ExtendedStatsSerial; - public StatLocks ExtendedStatsLocks - { - get; - private set; - } - - /// - /// Subcommand 0x1D: The revision hash of a custom house. - /// - public HouseRevisionState HouseRevisionState - { - get; - private set; - } + public readonly short Subcommand; + public readonly IGeneralInfo Info; public GeneralInfoPacket(PacketReader reader) - : base(0xBF, "General Information") - { + : base(0xBF, "General Information") { Subcommand = reader.ReadInt16(); - - switch (Subcommand) - { - case 0x04: // Close generic gump - CloseGumpTypeID = reader.ReadInt32(); - CloseGumpButtonID = reader.ReadInt32(); + switch (Subcommand) { + case CloseGump: + Info = new CloseGumpInfo(reader); break; - case 0x06: - // party system, not implemented. + case Party: + Info = new PartyInfo(reader); break; - case 0x8: // Set cursor color / set map - MapID = reader.ReadByte(); + case SetMap: + Info = new MapIndexInfo(reader); break; - case 0x14: // return context menu - receiveContextMenu(reader); + case ContextMenu: + Info = new ContextMenuInfo(reader); break; - case 0x18: // Number of maps - receiveMapDiffManifest(reader); + case MapDiff: + Info = new MapDiffInfo(reader); break; - case 0x1D: // House revision - receiveHouseRevisionState(reader); + case ExtendedStats: + Info = new ExtendedStatsInfo(reader); break; - case 0x19: // Extended stats - receiveExtendedStats(reader); + case SpellBookContents: + Info = new SpellBookContentsInfo(reader); break; - case 0x1B: - receiveSpellBookContents(reader); + case HouseRevision: + Info = new HouseRevisionInfo(reader); break; - case 0x21: // (AOS) Ability icon confirm. + case AOSAbilityIconConfirm: // (AOS) Ability icon confirm. // no data, just (bf 00 05 00 21) break; default: - // do nothing. This unhandled subcommand will raise an error in UltimaClient.cs. + Tracer.Warn($"Unhandled Subcommand {Subcommand:X2} in GeneralInfoPacket."); break; } } - - private void receiveSpellBookContents(PacketReader reader) - { - ushort unknown = reader.ReadUInt16(); // always 1 - Serial serial = (Serial)reader.ReadInt32(); - ushort itemID = reader.ReadUInt16(); - ushort spellbookType = reader.ReadUInt16(); // 1==regular, 101=necro, 201=paladin, 401=bushido, 501=ninjitsu, 601=spellweaving - ulong spellBitfields = reader.ReadUInt32() + (((ulong)reader.ReadUInt32()) << 32); // first bit of first byte = spell #1, second bit of first byte = spell #2, first bit of second byte = spell #8, etc - - Spellbook = new SpellbookData(serial, itemID, spellbookType, spellBitfields); - } - - void receiveExtendedStats(PacketReader reader) - { - int clientFlag = reader.ReadByte(); // (0x2 for 2D client, 0x5 for KR client) - ExtendedStatsSerial = (Serial)reader.ReadInt32(); - byte unknown0 = reader.ReadByte(); // (always 0) - byte lockFlags = reader.ReadByte(); - // Lock flags = 00SSDDII ( in binary ) - // 00 = up - // 01 = down - // 10 = locked - // FF = update mobile status animation ( KR only ) - if (lockFlags != 0xFF) - { - int strengthLock = (lockFlags >> 4) & 0x03; - int dexterityLock = (lockFlags >> 2) & 0x03; - int inteligenceLock = (lockFlags) & 0x03; - ExtendedStatsLocks = new StatLocks(strengthLock, dexterityLock, inteligenceLock); - } - - if (clientFlag == 5) - { - Tracer.Warn("ClientFlags == 5 in GeneralInfoPacket ExtendedStats 0x19. This is not a KR client."); - // If(Lock flags = 0xFF) //Update mobile status animation - // BYTE[1] Status // Unveryfied if lock flags == FF the locks will be handled here - // BYTE[1] unknown (0x00) - // BYTE[1] Animation - // BYTE[1] unknown (0x00) - // BYTE[1] Frame - // else - // BYTE[1] unknown (0x00) - // BYTE[4] unknown (0x00000000) - // endif - } - } - - void receiveHouseRevisionState(PacketReader reader) - { - Serial s = reader.ReadInt32(); - int hash = reader.ReadInt32(); - HouseRevisionState = new HouseRevisionState(s, hash); - } - - void receiveMapDiffManifest(PacketReader reader) - { - MapDiffs = new MapDiffInfo(reader); - } - - void receiveContextMenu(PacketReader reader) - { - reader.ReadByte(); // unknown, always 0x00 - int subcommand = reader.ReadByte(); // 0x01 for 2D, 0x02 for KR - ContextMenu = new ContextMenuData(reader.ReadInt32()); - int contextMenuChoiceCount = reader.ReadByte(); - - for (int i = 0; i < contextMenuChoiceCount; i++) - { - int iUniqueID = reader.ReadUInt16(); - int iClilocID = reader.ReadUInt16() + 3000000; - int iFlags = reader.ReadUInt16(); // 0x00=enabled, 0x01=disabled, 0x02=arrow, 0x20 = color - int iColor = 0; - if ((iFlags & 0x20) == 0x20) - { - iColor = reader.ReadUInt16(); - } - ContextMenu.AddItem(iUniqueID, iClilocID, iFlags, iColor); - } - } } } diff --git a/dev/Ultima/Player/Partying/PartyMember.cs b/dev/Ultima/Player/Partying/PartyMember.cs new file mode 100644 index 00000000..dadd52be --- /dev/null +++ b/dev/Ultima/Player/Partying/PartyMember.cs @@ -0,0 +1,31 @@ +using UltimaXNA.Ultima.World; +using UltimaXNA.Ultima.World.Entities.Mobiles; + +namespace UltimaXNA.Ultima.Player.Partying +{ + public class PartyMember + { + public readonly Serial Serial; + string m_CachedName; + public Mobile Mobile => WorldModel.Entities.GetObject(Serial, false); + + public string Name + { + get + { + Mobile mobile = Mobile; + if (Mobile != null) + { + m_CachedName = Mobile.Name; + } + return m_CachedName; + } + } + + public PartyMember(Serial serial) + { + Serial = serial; + m_CachedName = Name; + } + } +} diff --git a/dev/Ultima/Player/Partying/PartySystem.cs b/dev/Ultima/Player/Partying/PartySystem.cs new file mode 100644 index 00000000..aeb538c1 --- /dev/null +++ b/dev/Ultima/Player/Partying/PartySystem.cs @@ -0,0 +1,251 @@ +/*************************************************************************** + * PartySystem.cs + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using System; +using System.Collections.Generic; +using System.Linq; +using UltimaXNA.Core.Network; +using UltimaXNA.Core.UI; +using UltimaXNA.Ultima.Network.Client; +using UltimaXNA.Ultima.Network.Client.Partying; +using UltimaXNA.Ultima.Network.Server.GeneralInfo; +using UltimaXNA.Ultima.UI; +using UltimaXNA.Ultima.UI.WorldGumps; +using UltimaXNA.Ultima.World; + +namespace UltimaXNA.Ultima.Player.Partying +{ + public class PartySystem + { + Serial m_LeaderSerial; + Serial m_InvitingPartyLeader; + List m_PartyMembers = new List(); + bool m_AllowPartyLoot; + + public Serial LeaderSerial => m_LeaderSerial; + public List Members => m_PartyMembers; + public bool InParty => m_PartyMembers.Count > 1; + public bool PlayerIsLeader => InParty && PlayerState.Partying.LeaderSerial == WorldModel.PlayerSerial; + + public bool AllowPartyLoot + { + get + { + return m_AllowPartyLoot; + } + set + { + m_AllowPartyLoot = value; + INetworkClient network = ServiceRegistry.GetService(); + network.Send(new PartyCanLootPacket(m_AllowPartyLoot)); + } + } + + public void ReceivePartyMemberList(PartyMemberListInfo info) + { + bool wasInParty = InParty; + m_PartyMembers.Clear(); + for (int i = 0; i < info.Count; i++) + AddMember(info.Serials[i]); + RefreshPartyGumps(); + if (InParty && !wasInParty) + { + AllowPartyLoot = m_AllowPartyLoot; + } + } + + public void ReceiveRemovePartyMember(PartyRemoveMemberInfo info) + { + m_PartyMembers.Clear(); + for (int i = 0; i < info.Count; i++) + AddMember(info.Serials[i]); + RefreshPartyGumps(); + } + + public void ReceiveInvitation(PartyInvitationInfo info) + { + m_InvitingPartyLeader = info.PartyLeaderSerial; + } + + public void AddMember(Serial serial) + { + int index = m_PartyMembers.FindIndex(p => p.Serial == serial); + if (index != -1) + { + m_PartyMembers.RemoveAt(index); + } + m_PartyMembers.Add(new PartyMember(serial)); + INetworkClient network = ServiceRegistry.GetService(); + network.Send(new MobileQueryPacket(MobileQueryPacket.StatusType.BasicStatus, serial)); + } + + public PartyMember GetMember(int index) + { + if (index >= 0 && index < m_PartyMembers.Count) + return m_PartyMembers[index]; + return null; + } + + public PartyMember GetMember(Serial serial) + { + return m_PartyMembers.Find(p => p.Serial == serial); + } + + public void LeaveParty() + { + INetworkClient network = ServiceRegistry.GetService(); + network.Send(new PartyLeavePacket()); + m_PartyMembers.Clear(); + m_LeaderSerial = Serial.Null; + UserInterfaceService ui = ServiceRegistry.GetService(); + ui.RemoveControl(); + } + + public void DoPartyCommand(string text) + { + // I do this a little differently than the legacy client. With legacy, if you type "/add this other player, + // please ?" the client will detect the first word is "add" and request an add player target. Instead, I + // interpret this as a message, and send the message "add this other player, please?" as a party message. + INetworkClient network = ServiceRegistry.GetService(); + WorldModel world = ServiceRegistry.GetService(); + string command = text.ToLower(); + bool commandHandled = false; + switch (command) + { + case "help": + ShowPartyHelp(); + commandHandled = true; + break; + case "add": + RequestAddPartyMemberTarget(); + commandHandled = true; + break; + case "rem": + case "remove": + if (InParty && PlayerIsLeader) + { + world.Interaction.ChatMessage("Who would you like to remove from your party?", 3, 10, false); + network.Send(new PartyRequestRemoveTargetPacket()); + } + commandHandled = true; + break; + case "accept": + if (!InParty && m_InvitingPartyLeader.IsValid) + { + network.Send(new PartyAcceptPacket(m_InvitingPartyLeader)); + m_LeaderSerial = m_InvitingPartyLeader; + m_InvitingPartyLeader = Serial.Null; + } + commandHandled = true; + break; + case "decline": + if (!InParty && m_InvitingPartyLeader.IsValid) + { + network.Send(new PartyDeclinePacket(m_InvitingPartyLeader)); + m_InvitingPartyLeader = Serial.Null; + } + commandHandled = true; + break; + case "quit": + LeaveParty(); + commandHandled = true; + break; + } + if (!commandHandled) + { + network.Send(new PartyPublicMessagePacket(text)); + } + } + + internal void BeginPrivateMessage(int serial) + { + PartyMember member = GetMember((Serial)serial); + if (member != null) + { + ChatControl chat = ServiceRegistry.GetService(); + chat.SetModeToPartyPrivate(member.Name, member.Serial); + } + } + + public void SendPartyPrivateMessage(Serial serial, string text) + { + WorldModel world = ServiceRegistry.GetService(); + PartyMember recipient = GetMember(serial); + if (recipient != null) + { + INetworkClient network = ServiceRegistry.GetService(); + network.Send(new PartyPrivateMessagePacket(serial, text)); + world.Interaction.ChatMessage($"To {recipient.Name}: {text}", 3, Settings.UserInterface.PartyPrivateMsgColor, false); + } + else + { + world.Interaction.ChatMessage("They are no longer in your party.", 3, Settings.UserInterface.PartyPrivateMsgColor, false); + } + } + + internal void RequestAddPartyMemberTarget() + { + INetworkClient network = ServiceRegistry.GetService(); + WorldModel world = ServiceRegistry.GetService(); + if (!InParty) + { + m_LeaderSerial = WorldModel.PlayerSerial; + network.Send(new PartyRequestAddTargetPacket()); + } + else if (InParty && PlayerIsLeader) + { + network.Send(new PartyRequestAddTargetPacket()); + } + else if (InParty && !PlayerIsLeader) + { + world.Interaction.ChatMessage("You may only add members to the party if you are the leader.", 3, 10, false); + } + } + + public void RefreshPartyGumps() + { + UserInterfaceService ui = ServiceRegistry.GetService(); + ui.RemoveControl(); + for (int i = 0; i < Members.Count; i++) + { + ui.AddControl(new PartyHealthTrackerGump(Members[i]), 5, 40 + (48 * i)); + } + Gump gump; + if ((gump = ui.GetControl()) != null) + { + int x = gump.X; + int y = gump.Y; + ui.RemoveControl(); + ui.AddControl(new PartyGump(), x, y); + } + } + + public void RemoveMember(Serial serial) + { + INetworkClient network = ServiceRegistry.GetService(); + network.Send(new PartyRemoveMemberPacket(serial)); + int index = m_PartyMembers.FindIndex(p => p.Serial == serial); + if (index != -1) + { + m_PartyMembers.RemoveAt(index); + } + } + + public void ShowPartyHelp() + { + WorldModel m_world = ServiceRegistry.GetService(); + m_world.Interaction.ChatMessage("/add - add a new member or create a party", 3, 51, true); + m_world.Interaction.ChatMessage("/rem - kick a member from your party", 3, 51, true); + m_world.Interaction.ChatMessage("/accept - join a party", 3, 51, true); + m_world.Interaction.ChatMessage("/decline - decline a party invitation", 3, 51, true); + m_world.Interaction.ChatMessage("/quit - leave your current party", 3, 51, true); + } + } +} \ No newline at end of file diff --git a/dev/Ultima/Player/PlayerState.cs b/dev/Ultima/Player/PlayerState.cs index 743e2ba0..817ed58b 100644 --- a/dev/Ultima/Player/PlayerState.cs +++ b/dev/Ultima/Player/PlayerState.cs @@ -1,36 +1,26 @@ - -namespace UltimaXNA.Ultima.Player -{ - class PlayerState - { - private static readonly PlayerState m_Instance; +using UltimaXNA.Ultima.Player.Partying; - private JournalData m_Journal; - private SkillData m_Skills; - private StatLockData m_StatLocks; +namespace UltimaXNA.Ultima.Player { + class PlayerState { + static readonly PlayerState m_Instance; - static PlayerState() - { + JournalData m_Journal; + SkillData m_Skills; + StatLockData m_StatLocks; + PartySystem m_Partying; + + static PlayerState() { m_Instance = new PlayerState(); m_Instance.m_Journal = new JournalData(); m_Instance.m_Skills = new SkillData(); m_Instance.m_StatLocks = new StatLockData(); + m_Instance.m_Partying = new PartySystem(); } - public static JournalData Journaling - { - get { return m_Instance.m_Journal; } - } - - public static SkillData Skills - { - get { return m_Instance.m_Skills; } - } - - public static StatLockData StatLocks - { - get { return m_Instance.m_StatLocks; } - } + public static JournalData Journaling => m_Instance.m_Journal; + public static SkillData Skills => m_Instance.m_Skills; + public static StatLockData StatLocks => m_Instance.m_StatLocks; + public static PartySystem Partying => m_Instance.m_Partying; } } diff --git a/dev/Ultima/Resources/TileMatrixDataPatch.cs b/dev/Ultima/Resources/TileMatrixDataPatch.cs index a745a69e..1c55472f 100644 --- a/dev/Ultima/Resources/TileMatrixDataPatch.cs +++ b/dev/Ultima/Resources/TileMatrixDataPatch.cs @@ -16,6 +16,7 @@ using UltimaXNA.Core.Windows; using UltimaXNA.Ultima.Data; using UltimaXNA.Ultima.IO; +using UltimaXNA.Ultima.Network.Server.GeneralInfo; #endregion namespace UltimaXNA.Ultima.Resources diff --git a/dev/Ultima/UI/LoginGumps/LoginGump.cs b/dev/Ultima/UI/LoginGumps/LoginGump.cs index 29897142..721a1e1a 100644 --- a/dev/Ultima/UI/LoginGumps/LoginGump.cs +++ b/dev/Ultima/UI/LoginGumps/LoginGump.cs @@ -73,10 +73,10 @@ public LoginGump(Action onLogin) // flag graphic AddControl(new GumpPic(this, 0, 0, 0x15A0, 0)); // buttons on the left side - int y = 450; AddControl(new ButtonResizable(this, 10, 450, 100, 23, "CREDITS", OnClickCredits)); + int y = 420; foreach (Tuple button in m_Buttons) { - AddControl(new ButtonResizable(this, 10, 420, 100, 23, button.Item1, button.Item2)); + AddControl(new ButtonResizable(this, 10, y, 100, 23, button.Item1, button.Item2)); y -= 30; } IsUncloseableWithRMB = true; diff --git a/dev/Ultima/UI/WorldGumps/ChatControl.cs b/dev/Ultima/UI/WorldGumps/ChatControl.cs index 07233ef5..147d52a2 100644 --- a/dev/Ultima/UI/WorldGumps/ChatControl.cs +++ b/dev/Ultima/UI/WorldGumps/ChatControl.cs @@ -20,6 +20,9 @@ using UltimaXNA.Ultima.UI.Controls; using UltimaXNA.Ultima.World; using UltimaXNA.Ultima.Data; +using UltimaXNA.Ultima.Player; +using UltimaXNA.Ultima.Network.Client; +using UltimaXNA.Core.Network; #endregion namespace UltimaXNA.Ultima.UI.WorldGumps @@ -31,11 +34,11 @@ class ChatControl : AControl private TextEntry m_TextEntry; private List m_TextEntries; private List> m_MessageHistory; - private InputManager m_Input; private WorldModel m_World; - private int m_MessageHistoryIndex = -1; + private Serial m_PrivateMessageSerial = Serial.Null; + private string m_PrivateMessageName; private ChatMode m_Mode = ChatMode.Default; private ChatMode Mode @@ -47,7 +50,7 @@ private ChatMode Mode switch (value) { case ChatMode.Default: - m_TextEntry.LeadingHtmlTag = string.Format("", + m_TextEntry.LeadingHtmlTag = string.Format("", Utility.GetColorFromUshort(Resources.HueData.GetHue(Settings.UserInterface.SpeechColor, 1))); m_TextEntry.LeadingText = string.Empty; m_TextEntry.Text = string.Empty; @@ -58,25 +61,31 @@ private ChatMode Mode m_TextEntry.LeadingText = "Whisper: "; m_TextEntry.Text = string.Empty; break; - case ChatMode.Emote: // emote + case ChatMode.Emote: m_TextEntry.LeadingHtmlTag = string.Format("", Utility.GetColorFromUshort(Resources.HueData.GetHue(Settings.UserInterface.EmoteColor, 1))); m_TextEntry.LeadingText = "Emote: "; m_TextEntry.Text = string.Empty; break; - case ChatMode.Party: // party + case ChatMode.Party: m_TextEntry.LeadingHtmlTag = string.Format("", Utility.GetColorFromUshort(Resources.HueData.GetHue(Settings.UserInterface.PartyMsgColor, 1))); m_TextEntry.LeadingText = "Party: "; m_TextEntry.Text = string.Empty; break; - case ChatMode.Guild: // guild + case ChatMode.PartyPrivate: + m_TextEntry.LeadingHtmlTag = string.Format("", + Utility.GetColorFromUshort(Resources.HueData.GetHue(Settings.UserInterface.PartyPrivateMsgColor, 1))); + m_TextEntry.LeadingText = $"To {m_PrivateMessageName}: "; + m_TextEntry.Text = string.Empty; + break; + case ChatMode.Guild: m_TextEntry.LeadingHtmlTag = string.Format("", Utility.GetColorFromUshort(Resources.HueData.GetHue(Settings.UserInterface.GuildMsgColor, 1))); m_TextEntry.LeadingText = "Guild: "; m_TextEntry.Text = string.Empty; break; - case ChatMode.Alliance: // alliance + case ChatMode.Alliance: m_TextEntry.LeadingHtmlTag = string.Format("", Utility.GetColorFromUshort(Resources.HueData.GetHue(Settings.UserInterface.AllianceMsgColor, 1))); m_TextEntry.LeadingText = "Alliance: "; @@ -101,6 +110,13 @@ public ChatControl(AControl parent, int x, int y, int width, int height) IsUncloseableWithRMB = true; } + public void SetModeToPartyPrivate(string name, Serial serial) + { + m_PrivateMessageName = name; + m_PrivateMessageSerial = serial; + Mode = ChatMode.PartyPrivate; + } + public override void Update(double totalMS, double frameMS) { if (m_TextEntry == null) @@ -154,26 +170,25 @@ public override void Update(double totalMS, double frameMS) Mode = ChatMode.Default; } - // if in default, only switch mode if there is a single command char (;, :, etc) followed by any other char. - // in not in default, only switch mode if the single command char is the only char entered. - if ((Mode == ChatMode.Default && m_TextEntry.Text.Length == 2) || + // only switch mode if the single command char is the only char entered. + if ((Mode == ChatMode.Default && m_TextEntry.Text.Length == 1) || (Mode != ChatMode.Default && m_TextEntry.Text.Length == 1)) { switch (m_TextEntry.Text[0]) { - case ':': // emote + case ':': Mode = ChatMode.Emote; break; - case ';': // whisper + case ';': Mode = ChatMode.Whisper; break; - case '/': // party + case '/': Mode = ChatMode.Party; break; - case '\\': // guild + case '\\': Mode = ChatMode.Guild; break; - case '|': // alliance + case '|': Mode = ChatMode.Alliance; break; } @@ -195,74 +210,110 @@ public override void Draw(SpriteBatchUI spriteBatch, Point position) public override void OnKeyboardReturn(int textID, string text) { + // local variables + ChatMode sentMode = Mode; + MessageTypes speechType = MessageTypes.Normal; + int hue = 0; + // save this message and reset chat for next entry m_TextEntry.Text = string.Empty; m_MessageHistory.Add(new Tuple(Mode, text)); m_MessageHistoryIndex = m_MessageHistory.Count; - m_World.Interaction.SendSpeech(text, Mode); Mode = ChatMode.Default; + // send the message and display it locally. + switch (sentMode) + { + case ChatMode.Default: + speechType = MessageTypes.Normal; + hue = Settings.UserInterface.SpeechColor; + break; + case ChatMode.Whisper: + speechType = MessageTypes.Whisper; + hue = Settings.UserInterface.SpeechColor; + break; + case ChatMode.Emote: + speechType = MessageTypes.Emote; + hue = Settings.UserInterface.EmoteColor; + break; + case ChatMode.Party: + PlayerState.Partying.DoPartyCommand(text); + return; + case ChatMode.PartyPrivate: + PlayerState.Partying.SendPartyPrivateMessage(m_PrivateMessageSerial, text); + return; + case ChatMode.Guild: + speechType = MessageTypes.Guild; + hue = Settings.UserInterface.GuildMsgColor; + break; + case ChatMode.Alliance: + speechType = MessageTypes.Alliance; + hue = Settings.UserInterface.AllianceMsgColor; + break; + } + INetworkClient network = ServiceRegistry.GetService(); + network.Send(new AsciiSpeechPacket(speechType, 0, hue + 2, "ENU", text)); } public void AddLine(string text, int font, int hue, bool asUnicode) { - m_TextEntries.Add(new ChatLineTimed(string.Format("{0}", - text, asUnicode ? "uni" : "ascii", font, Utility.GetColorFromUshort(Resources.HueData.GetHue(hue, -1))), + m_TextEntries.Add(new ChatLineTimed(string.Format("{0}", + text, asUnicode ? "uni" : "ascii", font, Utility.GetColorFromUshort(Resources.HueData.GetHue(hue, -1))), Width)); } - } - class ChatLineTimed - { - string m_text; - public string Text { get { return m_text; } } - float m_createdTime = float.MinValue; - bool m_isExpired; - public bool IsExpired { get { return m_isExpired; } } - float m_alpha; - public float Alpha { get { return m_alpha; } } - private int m_width = 0; + class ChatLineTimed + { + string m_text; + public string Text { get { return m_text; } } + float m_createdTime = float.MinValue; + bool m_isExpired; + public bool IsExpired { get { return m_isExpired; } } + float m_alpha; + public float Alpha { get { return m_alpha; } } + private int m_width = 0; - const float Time_Display = 10000.0f; - const float Time_Fadeout = 4000.0f; + const float Time_Display = 10000.0f; + const float Time_Fadeout = 4000.0f; - private RenderedText m_Texture; - public int TextHeight { get { return m_Texture.Height; } } + private RenderedText m_Texture; + public int TextHeight { get { return m_Texture.Height; } } - public ChatLineTimed(string text, int width) - { - m_text = text; - m_isExpired = false; - m_alpha = 1.0f; - m_width = width; + public ChatLineTimed(string text, int width) + { + m_text = text; + m_isExpired = false; + m_alpha = 1.0f; + m_width = width; - m_Texture = new RenderedText(m_text, m_width); - } + m_Texture = new RenderedText(m_text, m_width); + } - public void Update(double totalMS, double frameMS) - { - if (m_createdTime == float.MinValue) - m_createdTime = (float)totalMS; - float time = (float)totalMS - m_createdTime; - if (time > Time_Display) - m_isExpired = true; - else if (time > (Time_Display - Time_Fadeout)) + public void Update(double totalMS, double frameMS) { - m_alpha = 1.0f - ((time) - (Time_Display - Time_Fadeout)) / Time_Fadeout; + if (m_createdTime == float.MinValue) + m_createdTime = (float)totalMS; + float time = (float)totalMS - m_createdTime; + if (time > Time_Display) + m_isExpired = true; + else if (time > (Time_Display - Time_Fadeout)) + { + m_alpha = 1.0f - ((time) - (Time_Display - Time_Fadeout)) / Time_Fadeout; + } } - } - public void Draw(SpriteBatchUI sb, Point position) - { - m_Texture.Draw(sb, position, Utility.GetHueVector(0, false, (m_alpha < 1.0f), true)); - } + public void Draw(SpriteBatchUI sb, Point position) + { + m_Texture.Draw(sb, position, Utility.GetHueVector(0, false, (m_alpha < 1.0f), true)); + } - public void Dispose() - { - m_Texture = null; - } + public void Dispose() + { + m_Texture = null; + } - public override string ToString() - { - return m_text; + public override string ToString() + { + return m_text; + } } } } diff --git a/dev/Ultima/UI/WorldGumps/PaperDollGump.cs b/dev/Ultima/UI/WorldGumps/PaperDollGump.cs index f2385263..017dada1 100644 --- a/dev/Ultima/UI/WorldGumps/PaperDollGump.cs +++ b/dev/Ultima/UI/WorldGumps/PaperDollGump.cs @@ -10,6 +10,7 @@ ***************************************************************************/ #region usings + using Microsoft.Xna.Framework; using System.Collections.Generic; using UltimaXNA.Core.Graphics; @@ -21,7 +22,8 @@ using UltimaXNA.Ultima.UI.Controls; using UltimaXNA.Ultima.World; using UltimaXNA.Ultima.World.Entities.Mobiles; -#endregion + +#endregion usings namespace UltimaXNA.Ultima.UI.WorldGumps { @@ -135,6 +137,10 @@ private void BuildGump() // AddControl(new GumpPic(this, 158, 200, 0x2B34, 0)); // LastControl.MouseDoubleClickEvent += SpecialMoves_MouseDoubleClickEvent; + // PARTY MANIFEST CALLER + AddControl(new GumpPic(this, 44, 195, 2002, 0)); + LastControl.MouseDoubleClickEvent += PartyManifest_MouseDoubleClickEvent; + // equipment slots for hat/earrings/neck/ring/bracelet AddControl(new EquipmentSlot(this, 2, 76, Mobile, EquipLayer.Helm)); AddControl(new EquipmentSlot(this, 2, 76 + 22 * 1, Mobile, EquipLayer.Earrings)); @@ -154,7 +160,6 @@ private void BuildGump() } // name and title - AddControl(new HtmlGumpling(this, 34, 259, 180, 42, 0, 0, string.Format("{0}", Title))); AddControl(new HtmlGumpling(this, 35, 260, 180, 42, 0, 0, string.Format("{0}", Title))); } @@ -164,7 +169,16 @@ public override void Dispose() m_VirtueMenuButton.MouseDoubleClickEvent -= VirtueMenu_MouseDoubleClickEvent; base.Dispose(); } - + private void PartyManifest_MouseDoubleClickEvent(AControl control, int x, int y, MouseButton button) + { + if (button == MouseButton.Left) + { + if (UserInterface.GetControl() == null) + UserInterface.AddControl(new PartyGump(), 200, 40); + else + UserInterface.RemoveControl(); + } + } private void SpecialMoves_MouseDoubleClickEvent(AControl control, int x, int y, MouseButton button) { if (button == MouseButton.Left) diff --git a/dev/Ultima/UI/WorldGumps/PartyGump.cs b/dev/Ultima/UI/WorldGumps/PartyGump.cs new file mode 100644 index 00000000..e9d5d7dd --- /dev/null +++ b/dev/Ultima/UI/WorldGumps/PartyGump.cs @@ -0,0 +1,124 @@ +/*************************************************************************** + * PartyGump.cs + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ +using UltimaXNA.Ultima.Player; +using UltimaXNA.Ultima.UI.Controls; +using UltimaXNA.Ultima.World; + +namespace UltimaXNA.Ultima.UI.WorldGumps +{ + public class PartyGump : Gump { + const int ButtonIndexLoot = 0; + const int ButtonIndexLeave = 1; + const int ButtonIndexAdd = 2; + const int ButtonIndexKick = 100; + const int ButtonIndexTell = 200; + + public PartyGump() + : base(0, 0) { + IsMoveable = true; + AddControl(new ResizePic(this, 0, 0, 2600, 350, PlayerState.Partying.InParty ? 500 : 425)); + AddControl(new TextLabelAscii(this, 105, 15, 2, 902, "Party Manifest")); + AddControl(new TextLabelAscii(this, 34, 51, 1, 902, "Kick")); + AddControl(new TextLabelAscii(this, 84, 51, 1, 902, "Tell")); + AddControl(new TextLabelAscii(this, 160, 45, 2, 902, "Member Name")); + int lineY = 0; + bool playerIsLeader = PlayerState.Partying.LeaderSerial == WorldModel.PlayerSerial; + for (int i = 0; i < 10; i++) + { + if (i < PlayerState.Partying.Members.Count) + { + bool memberIsPlayer = PlayerState.Partying.GetMember(i).Serial == WorldModel.PlayerSerial; + if (!memberIsPlayer) + { + if (playerIsLeader) + { + AddControl(new Button(this, 35, 70 + lineY, 4017, 4018, ButtonTypes.Activate, PlayerState.Partying.Members[i].Serial, ButtonIndexKick + i));// KICK BUTTON + } + AddControl(new Button(this, 85, 70 + lineY, 4029, 4030, ButtonTypes.Activate, PlayerState.Partying.Members[i].Serial, ButtonIndexTell + i));// tell BUTTON + } + AddControl(new ResizePic(this, 130, 70 + lineY, 3000, 195, 25)); + AddControl(new HtmlGumpling(this, 130, 72 + lineY, 195, 20, 0, 0, $"
{PlayerState.Partying.Members[i].Name}")); + } + else + { + AddControl(new ResizePic(this, 130, 70 + lineY, 3000, 195, 25)); + } + lineY += 30; + } + if (PlayerState.Partying.InParty) + { + int lootGumpUp = PlayerState.Partying.AllowPartyLoot ? 4008 : 4002; + string txtLootStatus = PlayerState.Partying.AllowPartyLoot ? "Party CAN loot me" : "Party CANNOT loot me"; + AddControl(new TextLabelAscii(this, 100, 75 + lineY, 2, 902, txtLootStatus)); + AddControl(new Button(this, 65, 75 + lineY, lootGumpUp, lootGumpUp + 2, ButtonTypes.Activate, ButtonIndexLoot, 0)); + (LastControl as Button).GumpOverID = lootGumpUp + 1; + lineY += 25; + string txtLeave = (playerIsLeader) ? "Disband the party" : "Leave the party"; + AddControl(new TextLabelAscii(this, 100, 75 + lineY, 2, 902, txtLeave)); + AddControl(new Button(this, 65, 75 + lineY, 4017, 4019, ButtonTypes.Activate, 0, ButtonIndexLeave)); + (LastControl as Button).GumpOverID = 4018; + lineY += 25; + AddControl(new TextLabelAscii(this, 100, 75 + lineY, 2, 902, "Add new member")); + AddControl(new Button(this, 65, 75 + lineY, 4005, 4007, ButtonTypes.Activate, 0, ButtonIndexAdd)); + (LastControl as Button).GumpOverID = 4006; + /* msx752 proposed feature addition: Mark an enemy. + lineY += 25; + AddControl(new TextLabelAscii(this, 100, 75 + lineY, 2, 902, "Mark an enemy")); + if (playerIsLeader) + { + AddControl(new Button(this, 65, 75 + lineY, 4005, 4006, ButtonTypes.Activate, 0, ButtonIndexSetTarget)); + (LastControl as Button).GumpOverID = 4007; + }*/ + } + else + { + AddControl(new TextLabelAscii(this, 100, 75 + lineY, 2, 902, "Create a party")); + AddControl(new Button(this, 65, 75 + lineY, 4005, 4007, ButtonTypes.Activate, 0, ButtonIndexAdd)); + (LastControl as Button).GumpOverID = 4006; + } + } + + public override void OnButtonClick(int buttonID) { + if (buttonID == -1) { + return; + } + bool playerInParty = PlayerState.Partying.Members.Count > 1; + bool playerIsLeader = PlayerState.Partying.LeaderSerial == WorldModel.PlayerSerial; + if (buttonID >= ButtonIndexTell) { + int serial = PlayerState.Partying.GetMember(buttonID - ButtonIndexTell).Serial; + PlayerState.Partying.BeginPrivateMessage(serial); + } + else if (buttonID >= ButtonIndexKick) { + int serial = PlayerState.Partying.GetMember(buttonID - ButtonIndexKick).Serial; + PlayerState.Partying.RemoveMember(serial); + } + else if (buttonID == ButtonIndexLoot && playerInParty) { + if (PlayerState.Partying.AllowPartyLoot) { + PlayerState.Partying.AllowPartyLoot = false; + } + else { + PlayerState.Partying.AllowPartyLoot = true; + } + PlayerState.Partying.RefreshPartyGumps(); + } + else if (buttonID == ButtonIndexLeave) { + if (playerInParty) + { + PlayerState.Partying.LeaveParty(); + } + } + else if (buttonID == ButtonIndexAdd) { + if (!playerInParty || playerIsLeader) { + PlayerState.Partying.RequestAddPartyMemberTarget(); + } + } + } + } +} \ No newline at end of file diff --git a/dev/Ultima/UI/WorldGumps/PartyHealthTrackerGump.cs b/dev/Ultima/UI/WorldGumps/PartyHealthTrackerGump.cs new file mode 100644 index 00000000..be9c1ac6 --- /dev/null +++ b/dev/Ultima/UI/WorldGumps/PartyHealthTrackerGump.cs @@ -0,0 +1,101 @@ +/*************************************************************************** + * PartyHealthTrackerGump.cs + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using UltimaXNA.Core.Input; +using UltimaXNA.Core.UI; +using UltimaXNA.Ultima.Player; +using UltimaXNA.Ultima.Player.Partying; +using UltimaXNA.Ultima.UI.Controls; +using UltimaXNA.Ultima.World; +using UltimaXNA.Ultima.World.Entities.Mobiles; + +namespace UltimaXNA.Ultima.UI.WorldGumps { + class PartyHealthTrackerGump : Gump + { + Serial m_Serial; + + Button btnPrivateMsg; + GumpPic[] m_BarBGs; + GumpPicWithWidth[] m_Bars; + TextLabel m_Name; + + public PartyHealthTrackerGump(PartyMember member) + : base(member.Serial, 0) + { + while (UserInterface.GetControl() != null) + { + UserInterface.GetControl(member.Serial).Dispose(); + } + IsMoveable = false; + IsUncloseableWithRMB = true; + m_Serial = member.Serial; + //AddControl(m_Background = new ResizePic(this, 0, 0, 3000, 131, 48));//I need opacity %1 background + + AddControl(m_Name = new TextLabel(this, 1, 0, 1, member.Name)); + //m_Background.MouseDoubleClickEvent += Background_MouseDoubleClickEvent; //maybe private message calling? + m_BarBGs = new GumpPic[3]; + int sameX = 15; + int sameY = 3; + if (WorldModel.Entities.GetPlayerEntity().Serial != member.Serial)//you can't send a message to self + { + AddControl(btnPrivateMsg = new Button(this, 0, 20, 11401, 11402, ButtonTypes.Activate, member.Serial, 0));//private party message / use bandage ?? + } + AddControl(m_BarBGs[0] = new GumpPic(this, sameX, 15 + sameY, 9750, 0)); + AddControl(m_BarBGs[1] = new GumpPic(this, sameX, 24 + sameY, 9750, 0)); + AddControl(m_BarBGs[2] = new GumpPic(this, sameX, 33 + sameY, 9750, 0)); + m_Bars = new GumpPicWithWidth[3]; + AddControl(m_Bars[0] = new GumpPicWithWidth(this, sameX, 15 + sameY, 40, 0, 1f));//I couldn't find correct visual + AddControl(m_Bars[1] = new GumpPicWithWidth(this, sameX, 24 + sameY, 9751, 0, 1f));//I couldn't find correct visual + AddControl(m_Bars[2] = new GumpPicWithWidth(this, sameX, 33 + sameY, 41, 0, 1f));//I couldn't find correct visual + + // bars should not handle mouse input, pass it to the background gump. + for (int i = 0; i < m_BarBGs.Length; i++)//??? + { + m_Bars[i].HandlesMouseInput = false; + } + } + + public override void OnButtonClick(int buttonID) + { + if (buttonID == 0)//private message + { + PlayerState.Partying.BeginPrivateMessage(btnPrivateMsg.ButtonParameter); + } + } + + public override void Update(double totalMS, double frameMS) + { + PartyMember member = PlayerState.Partying.GetMember(m_Serial); + if (member == null) + { + Dispose(); + return; + } + m_Name.Text = member.Name; + Mobile mobile = member.Mobile; + if (mobile == null) + { + m_Bars[0].PercentWidthDrawn = m_Bars[1].PercentWidthDrawn = m_Bars[2].PercentWidthDrawn = 0f; + } + else + { + m_Bars[0].PercentWidthDrawn = ((float)mobile.Health.Current / mobile.Health.Max); + m_Bars[1].PercentWidthDrawn = ((float)mobile.Mana.Current / mobile.Mana.Max); + m_Bars[2].PercentWidthDrawn = ((float)mobile.Stamina.Current / mobile.Stamina.Max); + // I couldn't find correct visual + //if (Mobile.Flags.IsBlessed) + // m_Bars[0].GumpID = 0x0809; + //else if (Mobile.Flags.IsPoisoned) + // m_Bars[0].GumpID = 0x0808; + } + base.Update(totalMS, frameMS); + } + } +} \ No newline at end of file diff --git a/dev/Ultima/UI/WorldGumps/VendorBuyGump.cs b/dev/Ultima/UI/WorldGumps/VendorBuyGump.cs index 66effeca..81217b1d 100644 --- a/dev/Ultima/UI/WorldGumps/VendorBuyGump.cs +++ b/dev/Ultima/UI/WorldGumps/VendorBuyGump.cs @@ -45,6 +45,7 @@ class VendorBuyGump : Gump public VendorBuyGump(AEntity vendorBackpack, VendorBuyListPacket packet) : base(0, 0) { + // sanity checking: don't show buy gumps for empty containers. if (!(vendorBackpack is Container) || ((vendorBackpack as Container).Contents.Count <= 0) || (packet.Items.Count <= 0)) { @@ -67,11 +68,13 @@ public VendorBuyGump(AEntity vendorBackpack, VendorBuyListPacket packet) AddControl(m_OKButton = new Button(this, 220, 333, 0x907, 0x908, ButtonTypes.Activate, 0, 0)); m_OKButton.GumpOverID = 0x909; m_OKButton.MouseClickEvent += okButton_MouseClickEvent; + } public override void Dispose() { - m_OKButton.MouseClickEvent -= okButton_MouseClickEvent; + if (m_OKButton != null) + m_OKButton.MouseClickEvent -= okButton_MouseClickEvent; base.Dispose(); } @@ -172,7 +175,7 @@ private void BuildShopContents(AEntity vendorBackpack, VendorBuyListPacket packe m_ScrollBar.Value = 0; } - private const string c_Format = + private const string c_Format = "
" + "{0}
{1}gp, {3} available.
"; diff --git a/dev/Ultima/World/CustomHousing.cs b/dev/Ultima/World/Data/CustomHouse.cs similarity index 54% rename from dev/Ultima/World/CustomHousing.cs rename to dev/Ultima/World/Data/CustomHouse.cs index 1dae0eef..8f6f6088 100644 --- a/dev/Ultima/World/CustomHousing.cs +++ b/dev/Ultima/World/Data/CustomHouse.cs @@ -1,5 +1,5 @@ /*************************************************************************** - * CustomHousing.cs + * CustomHouse.cs * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -9,77 +9,27 @@ ***************************************************************************/ using System.Collections.Generic; -using UltimaXNA.Core.Network; -using UltimaXNA.Core.Network.Compression; using UltimaXNA.Ultima.Resources; -namespace UltimaXNA.Ultima.World -{ - class CustomHousing - { - static Dictionary m_customHouses = new Dictionary(); - - public static bool IsHashCurrent(Serial serial, int hash) - { - if (m_customHouses.ContainsKey(serial)) - { - CustomHouse h = m_customHouses[serial]; - if (h.Hash == hash) - return true; - else - return false; - } - else - { - return false; - } - - } - - public static CustomHouse GetCustomHouseData(Serial serial) - { - return m_customHouses[serial]; - } - - public static void UpdateCustomHouseData(Serial serial, int hash, int planecount, CustomHousePlane[] planes) - { - CustomHouse house; - if (m_customHouses.ContainsKey(serial)) - { - house = m_customHouses[serial]; - - } - else - { - house = new CustomHouse(serial); - m_customHouses.Add(serial, house); - } - house.Update(hash, planecount, planes); - } - } - - class CustomHouse - { +namespace UltimaXNA.Ultima.World.Data { + class CustomHouse { public Serial Serial; public int Hash; int m_planeCount; CustomHousePlane[] m_planes; - public CustomHouse(Serial serial) - { + public CustomHouse(Serial serial) { Serial = serial; } - public void Update(int hash, int planecount, CustomHousePlane[] planes) - { + public void Update(int hash, int planecount, CustomHousePlane[] planes) { Hash = hash; m_planeCount = planecount; m_planes = planes; } - public StaticTile[] GetStatics(int width, int height) - { + public StaticTile[] GetStatics(int width, int height) { List statics = new List(); // Custom Houses are sent in 'planes' of four different types. We determine which type we're looking at the index and the size. @@ -89,18 +39,15 @@ public StaticTile[] GetStatics(int width, int height) int numTilesInLastPlane = 0; int zIndex = 0; - for (int plane = 0; plane < m_planeCount; plane++) - { + for (int plane = 0; plane < m_planeCount; plane++) { int numTiles = m_planes[plane].ItemData.Length >> 1; if ((plane == m_planeCount - 1) && (numTiles != sizeFloor) && - (numTiles != sizeWalls)) - { + (numTiles != sizeWalls)) { numTiles = m_planes[plane].ItemData.Length / 5; int index = 0; - for (int j = 0; j < numTiles; j++) - { + for (int j = 0; j < numTiles; j++) { StaticTile s = new StaticTile(); s.ID = (short)((m_planes[plane].ItemData[index++] << 8) + m_planes[plane].ItemData[index++]); int x = (sbyte)m_planes[plane].ItemData[index++]; @@ -112,21 +59,18 @@ public StaticTile[] GetStatics(int width, int height) statics.Add(s); } } - else - { + else { int iWidth = width, iHeight = height; int iX = 0, iY = 0; int x = 0, y = 0, z = 0; - if (plane == 0) - { + if (plane == 0) { zIndex = 0; iWidth += 1; iHeight += 1; } - else if (numTiles == sizeFloor) - { + else if (numTiles == sizeFloor) { if (numTilesInLastPlane != sizeFloor) zIndex = 1; else @@ -136,8 +80,7 @@ public StaticTile[] GetStatics(int width, int height) iX = 1; iY = 1; } - else if (numTiles == sizeWalls) - { + else if (numTiles == sizeWalls) { if (numTilesInLastPlane != sizeWalls) zIndex = 1; else @@ -146,27 +89,35 @@ public StaticTile[] GetStatics(int width, int height) - switch (zIndex) - { - case 0: z = 0; break; - case 1: z = 7; break; - case 2: z = 27; break; - case 3: z = 47; break; - case 4: z = 67; break; - default: continue; + switch (zIndex) { + case 0: + z = 0; + break; + case 1: + z = 7; + break; + case 2: + z = 27; + break; + case 3: + z = 47; + break; + case 4: + z = 67; + break; + default: + continue; } int index = 0; - for (int j = 0; j < numTiles; j++) - { + for (int j = 0; j < numTiles; j++) { StaticTile s = new StaticTile(); s.ID = (short)((m_planes[plane].ItemData[index++] << 8) + m_planes[plane].ItemData[index++]); s.X = (byte)(x + iX); s.Y = (byte)(y + iY); s.Z = (sbyte)z; y++; - if (y >= iHeight) - { + if (y >= iHeight) { y = 0; x++; } @@ -178,24 +129,4 @@ public StaticTile[] GetStatics(int width, int height) return statics.ToArray(); } } - - public class CustomHousePlane - { - public readonly int Index; - public readonly bool IsFloor; - public readonly byte[] ItemData; - - public CustomHousePlane(PacketReader reader) - { - byte[] data = reader.ReadBytes(4); - Index = data[0]; - int uncompressedsize = data[1] + ((data[3] & 0xF0) << 4); - int compressedLength = data[2] + ((data[3] & 0xF) << 8); - ItemData = new byte[uncompressedsize]; - ZlibCompression.Unpack(ItemData, ref uncompressedsize, reader.ReadBytes(compressedLength), compressedLength); - - IsFloor = ((Index & 0x20) == 0x20); - Index &= 0x1F; - } - } } diff --git a/dev/Ultima/World/Data/CustomHousePlane.cs b/dev/Ultima/World/Data/CustomHousePlane.cs new file mode 100644 index 00000000..0623c314 --- /dev/null +++ b/dev/Ultima/World/Data/CustomHousePlane.cs @@ -0,0 +1,21 @@ +using UltimaXNA.Core.Network; +using UltimaXNA.Core.Network.Compression; + +namespace UltimaXNA.Ultima.World.Data { + public class CustomHousePlane { + public readonly int Index; + public readonly bool IsFloor; + public readonly byte[] ItemData; + + public CustomHousePlane(PacketReader reader) { + byte[] data = reader.ReadBytes(4); + Index = data[0]; + int uncompressedsize = data[1] + ((data[3] & 0xF0) << 4); + int compressedLength = data[2] + ((data[3] & 0xF) << 8); + ItemData = new byte[uncompressedsize]; + ZlibCompression.Unpack(ItemData, ref uncompressedsize, reader.ReadBytes(compressedLength), compressedLength); + IsFloor = ((Index & 0x20) == 0x20); + Index &= 0x1F; + } + } +} diff --git a/dev/Ultima/World/Data/CustomHousing.cs b/dev/Ultima/World/Data/CustomHousing.cs new file mode 100644 index 00000000..1cbd0977 --- /dev/null +++ b/dev/Ultima/World/Data/CustomHousing.cs @@ -0,0 +1,39 @@ +/*************************************************************************** + * CustomHousing.cs + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using System.Collections.Generic; + +namespace UltimaXNA.Ultima.World.Data { + class CustomHousing { + static Dictionary m_CustomHouses = new Dictionary(); + + public static bool IsHashCurrent(Serial serial, int hash) { + if (m_CustomHouses.ContainsKey(serial)) { + CustomHouse h = m_CustomHouses[serial]; + return (h.Hash == hash); + } + return false; + } + + public static CustomHouse GetCustomHouseData(Serial serial) => m_CustomHouses[serial]; + + public static void UpdateCustomHouseData(Serial serial, int hash, int planecount, CustomHousePlane[] planes) { + CustomHouse house; + if (m_CustomHouses.ContainsKey(serial)) { + house = m_CustomHouses[serial]; + } + else { + house = new CustomHouse(serial); + m_CustomHouses.Add(serial, house); + } + house.Update(hash, planecount, planes); + } + } +} \ No newline at end of file diff --git a/dev/Ultima/World/Entities/Multis/Multi.cs b/dev/Ultima/World/Entities/Multis/Multi.cs index 8a1d6a23..1b6c2c61 100644 --- a/dev/Ultima/World/Entities/Multis/Multi.cs +++ b/dev/Ultima/World/Entities/Multis/Multi.cs @@ -14,6 +14,7 @@ using UltimaXNA.Ultima.Resources; using UltimaXNA.Ultima.World.Entities.Items; using UltimaXNA.Ultima.World.Maps; +using UltimaXNA.Ultima.World.Data; #endregion namespace UltimaXNA.Ultima.World.Entities.Multis diff --git a/dev/Ultima/World/Input/WorldInput.cs b/dev/Ultima/World/Input/WorldInput.cs index 1529efa5..01b6462c 100644 --- a/dev/Ultima/World/Input/WorldInput.cs +++ b/dev/Ultima/World/Input/WorldInput.cs @@ -21,6 +21,7 @@ using UltimaXNA.Ultima.Data; using UltimaXNA.Ultima.Input; using UltimaXNA.Ultima.Network.Client; +using UltimaXNA.Ultima.Player; using UltimaXNA.Ultima.UI.Controls; using UltimaXNA.Ultima.UI.WorldGumps; using UltimaXNA.Ultima.World.Entities; @@ -440,6 +441,8 @@ private void onInteractButton(InputEventMouse e, AEntity overEntity, Vector2 ove } else if (overEntity is Mobile) { + if (PlayerState.Partying.GetMember(overEntity.Serial) != null)//is he in your party// number of 0x11 packet dont have information about stamina/mana k(IMPORTANT!!!) + return; // request basic stats - gives us the name rename flag m_Network.Send(new MobileQueryPacket(MobileQueryPacket.StatusType.BasicStatus, overEntity.Serial)); // drag off a status gump for this mobile. diff --git a/dev/Ultima/World/WorldClient.cs b/dev/Ultima/World/WorldClient.cs index 09c2766c..014ccdec 100644 --- a/dev/Ultima/World/WorldClient.cs +++ b/dev/Ultima/World/WorldClient.cs @@ -23,10 +23,13 @@ using UltimaXNA.Ultima.Data; using UltimaXNA.Ultima.Network.Client; using UltimaXNA.Ultima.Network.Server; +using UltimaXNA.Ultima.Network.Server.GeneralInfo; using UltimaXNA.Ultima.Player; +using UltimaXNA.Ultima.Player.Partying; using UltimaXNA.Ultima.Resources; using UltimaXNA.Ultima.UI; using UltimaXNA.Ultima.UI.WorldGumps; +using UltimaXNA.Ultima.World.Data; using UltimaXNA.Ultima.World.Entities; using UltimaXNA.Ultima.World.Entities.Items; using UltimaXNA.Ultima.World.Entities.Items.Containers; @@ -39,16 +42,15 @@ namespace UltimaXNA.Ultima.World { class WorldClient : IDisposable { - private Timer m_KeepAliveTimer; - private INetworkClient m_Network; - private UserInterfaceService m_UserInterface; - private WorldModel m_World; - private List> m_RegisteredHandlers; - + Timer m_KeepAliveTimer; + INetworkClient m_Network; + UserInterfaceService m_UserInterface; + WorldModel m_World; + List> m_RegisteredHandlers; + public WorldClient(WorldModel world) { m_World = world; - m_RegisteredHandlers = new List>(); m_Network = ServiceRegistry.GetService(); m_UserInterface = ServiceRegistry.GetService(); @@ -66,7 +68,6 @@ public void Initialize() Register(0x22, "Move Acknowledged", 3, new TypedPacketReceiveHandler(ReceiveMoveAck)); Register(0x23, "Drag Effect", 26, new TypedPacketReceiveHandler(ReceiveDragItem)); Register(0x24, "Open Container", 7, new TypedPacketReceiveHandler(ReceiveContainer)); - if (ClientVersion.HasExtendedAddItemPacket(Settings.UltimaOnline.PatchVersion)) { Register(0x25, "Container Content Update", 20, new TypedPacketReceiveHandler(ReceiveAddSingleItemToContainer)); @@ -75,7 +76,6 @@ public void Initialize() { Register(0x25, "Container Content Update", 21, new TypedPacketReceiveHandler(ReceiveAddSingleItemToContainer)); } - Register(0x27, "Lift Rejection", 2, new TypedPacketReceiveHandler(ReceiveRejectMoveItemRequest)); Register(0x2C, "Resurect menu", 2, new TypedPacketReceiveHandler(ReceiveResurrectionMenu)); Register(0x2D, "Mob Attributes", 17, new TypedPacketReceiveHandler(ReceiveMobileAttributes)); @@ -129,7 +129,6 @@ public void Initialize() Register(0xD8, "Send Custom House", -1, new TypedPacketReceiveHandler(ReceiveSendCustomHouse)); Register(0xDC, "SE Introduced Revision", 9, new TypedPacketReceiveHandler(ReceiveToolTipRevision)); Register(0xDD, "Compressed Gump", -1, new TypedPacketReceiveHandler(ReceiveCompressedGump)); - /* Deprecated (not used by RunUO) and/or not implmented * Left them here incase we need to implement in the future network.Register(0x17, "Health Bar Status Update", 12, OnHealthBarStatusUpdate); @@ -172,7 +171,7 @@ public void Register(int id, string name, int length, TypedPacketReceiveHandl m_RegisteredHandlers.Add(new Tuple(id, onReceive)); m_Network.Register(id, name, length, onReceive); } - + public void SendWorldLoginPackets() { GetMySkills(); @@ -199,13 +198,13 @@ public void StartKeepAlivePackets() TimeSpan.FromSeconds(4)); } - private void StopKeepAlivePackets() + void StopKeepAlivePackets() { if (m_KeepAliveTimer != null) m_KeepAliveTimer.Dispose(); } - private void SendKeepAlivePacket() + void SendKeepAlivePacket() { m_Network.Send(new UOSEKeepAlivePacket()); } @@ -250,19 +249,19 @@ public void GetMyBasicStatus() m_Network.Send(new MobileQueryPacket(MobileQueryPacket.StatusType.BasicStatus, WorldModel.PlayerSerial)); } - private void ReceiveTargetCursor(IRecvPacket packet) + void ReceiveTargetCursor(IRecvPacket packet) { TargetCursorPacket p = (TargetCursorPacket)packet; m_World.Cursor.SetTargeting((WorldCursor.TargetType)p.CommandType, p.CursorID); } - private void ReceiveTargetCursorMulti(IRecvPacket packet) + void ReceiveTargetCursorMulti(IRecvPacket packet) { TargetCursorMultiPacket p = (TargetCursorMultiPacket)packet; m_World.Cursor.SetTargetingMulti(p.DeedSerial, p.MultiModel); } - private void InternalOnEntity_SendMoveRequestPacket(MoveRequestPacket packet) + void InternalOnEntity_SendMoveRequestPacket(MoveRequestPacket packet) { m_Network.Send(packet); } @@ -271,17 +270,17 @@ private void InternalOnEntity_SendMoveRequestPacket(MoveRequestPacket packet) // Effect handling // ====================================================================== - private void ReceiveGraphicEffect(IRecvPacket packet) + void ReceiveGraphicEffect(IRecvPacket packet) { WorldModel.Effects.Add((GraphicEffectPacket)packet); } - private void ReceiveHuedEffect(IRecvPacket packet) + void ReceiveHuedEffect(IRecvPacket packet) { WorldModel.Effects.Add((GraphicEffectHuedPacket)packet); } - private void ReceiveOnParticleEffect(IRecvPacket packet) + void ReceiveOnParticleEffect(IRecvPacket packet) { WorldModel.Effects.Add((GraphicEffectExtendedPacket)packet); } @@ -290,7 +289,7 @@ private void ReceiveOnParticleEffect(IRecvPacket packet) // Entity handling // ====================================================================== - private void ReceiveAddMultipleItemsToContainer(IRecvPacket packet) + void ReceiveAddMultipleItemsToContainer(IRecvPacket packet) { ContainerContentPacket p = (ContainerContentPacket)packet; if (p.Items.Length == 0) @@ -319,7 +318,7 @@ private void ReceiveAddMultipleItemsToContainer(IRecvPacket packet) } } - private void ReceiveAddSingleItemToContainer(IRecvPacket packet) + void ReceiveAddSingleItemToContainer(IRecvPacket packet) { AddSingleItemToContainerPacket p = (AddSingleItemToContainerPacket)packet; @@ -343,7 +342,7 @@ private void ReceiveAddSingleItemToContainer(IRecvPacket packet) } } - private Item add_Item(Serial serial, int itemID, int nHue, Serial parentSerial, int amount) + Item add_Item(Serial serial, int itemID, int nHue, Serial parentSerial, int amount) { Item item; if (itemID == 0x2006) @@ -376,7 +375,7 @@ private Item add_Item(Serial serial, int itemID, int nHue, Serial parentSerial, return item; } - private void ReceiveContainer(IRecvPacket packet) + void ReceiveContainer(IRecvPacket packet) { OpenContainerPacket p = (OpenContainerPacket)packet; @@ -409,7 +408,7 @@ private void ReceiveContainer(IRecvPacket packet) } } - private void ReceiveWorldItem(IRecvPacket packet) + void ReceiveWorldItem(IRecvPacket packet) { ObjectInfoPacket p = (ObjectInfoPacket)packet; @@ -430,7 +429,7 @@ private void ReceiveWorldItem(IRecvPacket packet) } } - private void ReceiveWornItem(IRecvPacket packet) + void ReceiveWornItem(IRecvPacket packet) { WornItemPacket p = (WornItemPacket)packet; Item item = add_Item(p.Serial, p.ItemId, p.Hue, p.ParentSerial, 0); @@ -439,13 +438,13 @@ private void ReceiveWornItem(IRecvPacket packet) m_Network.Send(new QueryPropertiesPacket(item.Serial)); } - private void ReceiveDeleteObject(IRecvPacket packet) + void ReceiveDeleteObject(IRecvPacket packet) { RemoveEntityPacket p = (RemoveEntityPacket)packet; WorldModel.Entities.RemoveEntity(p.Serial); } - private void ReceiveMobileIncoming(IRecvPacket packet) + void ReceiveMobileIncoming(IRecvPacket packet) { MobileIncomingPacket p = (MobileIncomingPacket)packet; Mobile mobile = WorldModel.Entities.GetObject(p.Serial, true); @@ -473,7 +472,7 @@ private void ReceiveMobileIncoming(IRecvPacket packet) m_Network.Send(new SingleClickPacket(p.Serial)); // look at the object so we receive its stats. } - private void ReceiveDeathAnimation(IRecvPacket packet) + void ReceiveDeathAnimation(IRecvPacket packet) { DeathAnimationPacket p = (DeathAnimationPacket)packet; Mobile m = WorldModel.Entities.GetObject(p.PlayerSerial, false); @@ -490,7 +489,7 @@ private void ReceiveDeathAnimation(IRecvPacket packet) } } - private void ReceiveDragItem(IRecvPacket packet) + void ReceiveDragItem(IRecvPacket packet) { DragEffectPacket p = (DragEffectPacket)packet; // This is sent by the server to display an item being dragged from one place to another. @@ -510,7 +509,7 @@ private void ReceiveDragItem(IRecvPacket packet) announce_UnhandledPacket(packet); } - private void ReceiveMobileAttributes(IRecvPacket packet) + void ReceiveMobileAttributes(IRecvPacket packet) { MobileAttributesPacket p = (MobileAttributesPacket)packet; Mobile mobile = WorldModel.Entities.GetObject(p.Serial, false); @@ -527,7 +526,7 @@ private void ReceiveMobileAttributes(IRecvPacket packet) mobile.Stamina.Max = p.MaxStamina; } - private void ReceiveMobileAnimation(IRecvPacket packet) + void ReceiveMobileAnimation(IRecvPacket packet) { MobileAnimationPacket p = (MobileAnimationPacket)packet; Mobile mobile = WorldModel.Entities.GetObject(p.Serial, false); @@ -537,7 +536,7 @@ private void ReceiveMobileAnimation(IRecvPacket packet) mobile.Animate(p.Action, p.FrameCount, p.RepeatCount, p.Reverse, p.Repeat, p.Delay); } - private void ReceiveMobileMoving(IRecvPacket packet) + void ReceiveMobileMoving(IRecvPacket packet) { MobileMovingPacket p = (MobileMovingPacket)packet; Mobile mobile = WorldModel.Entities.GetObject(p.Serial, true); @@ -561,7 +560,7 @@ private void ReceiveMobileMoving(IRecvPacket packet) } } - private void ReceiveMobileUpdate(IRecvPacket packet) + void ReceiveMobileUpdate(IRecvPacket packet) { MobileUpdatePacket p = (MobileUpdatePacket)packet; Mobile mobile = WorldModel.Entities.GetObject(p.Serial, true); @@ -580,7 +579,7 @@ private void ReceiveMobileUpdate(IRecvPacket packet) } } - private void ReceiveMoveAck(IRecvPacket packet) + void ReceiveMoveAck(IRecvPacket packet) { MoveAcknowledgePacket p = (MoveAcknowledgePacket)packet; Mobile player = (Mobile)WorldModel.Entities.GetPlayerEntity(); @@ -588,20 +587,20 @@ private void ReceiveMoveAck(IRecvPacket packet) player.Notoriety = p.Notoriety; } - private void ReceiveMoveRej(IRecvPacket packet) + void ReceiveMoveRej(IRecvPacket packet) { MovementRejectPacket p = (MovementRejectPacket)packet; Mobile player = (Mobile)WorldModel.Entities.GetPlayerEntity(); player.PlayerMobile_MoveEventRej(p.Sequence, p.X, p.Y, p.Z, p.Direction); } - private void ReceivePlayerMove(IRecvPacket packet) + void ReceivePlayerMove(IRecvPacket packet) { PlayerMovePacket p = (PlayerMovePacket)packet; announce_UnhandledPacket(packet); } - private void ReceiveRejectMoveItemRequest(IRecvPacket packet) + void ReceiveRejectMoveItemRequest(IRecvPacket packet) { LiftRejectionPacket p = (LiftRejectionPacket)packet; m_World.Interaction.ChatMessage("Could not pick up item: " + p.ErrorMessage); @@ -612,7 +611,7 @@ private void ReceiveRejectMoveItemRequest(IRecvPacket packet) // Corpse handling // ====================================================================== - private void ReceiveCorpseClothing(IRecvPacket packet) + void ReceiveCorpseClothing(IRecvPacket packet) { CorpseClothingPacket p = (CorpseClothingPacket)packet; Corpse corpse = WorldModel.Entities.GetObject(p.CorpseSerial, false); @@ -630,14 +629,14 @@ private void ReceiveCorpseClothing(IRecvPacket packet) // Combat handling // ====================================================================== - private void ReceiveChangeCombatant(IRecvPacket packet) + void ReceiveChangeCombatant(IRecvPacket packet) { ChangeCombatantPacket p = (ChangeCombatantPacket)packet; if (p.Serial > 0x00000000) m_World.Interaction.LastTarget = p.Serial; } - private void ReceiveDamage(IRecvPacket packet) + void ReceiveDamage(IRecvPacket packet) { DamagePacket p = (DamagePacket)packet; Mobile entity = WorldModel.Entities.GetObject(p.Serial, false); @@ -647,7 +646,7 @@ private void ReceiveDamage(IRecvPacket packet) m_World.Interaction.ChatMessage(string.Format("{0} takes {1} damage!", entity.Name, p.Damage)); } - private void ReceiveOnSwing(IRecvPacket packet) + void ReceiveOnSwing(IRecvPacket packet) { SwingPacket p = (SwingPacket)packet; // this changes our last target - does this behavior match legacy? @@ -657,13 +656,13 @@ private void ReceiveOnSwing(IRecvPacket packet) } } - private void ReceiveWarMode(IRecvPacket packet) + void ReceiveWarMode(IRecvPacket packet) { WarModePacket p = (WarModePacket)packet; ((Mobile)WorldModel.Entities.GetPlayerEntity()).Flags.IsWarMode = p.WarMode; } - private void ReceiveUpdateMana(IRecvPacket packet) + void ReceiveUpdateMana(IRecvPacket packet) { UpdateManaPacket p = (UpdateManaPacket)packet; Mobile entity = WorldModel.Entities.GetObject(p.Serial, false); @@ -672,7 +671,7 @@ private void ReceiveUpdateMana(IRecvPacket packet) entity.Mana.Update(p.Current, p.Max); } - private void ReceiveUpdateStamina(IRecvPacket packet) + void ReceiveUpdateStamina(IRecvPacket packet) { UpdateStaminaPacket p = (UpdateStaminaPacket)packet; Mobile entity = WorldModel.Entities.GetObject(p.Serial, false); @@ -681,7 +680,7 @@ private void ReceiveUpdateStamina(IRecvPacket packet) entity.Stamina.Update(p.Current, p.Max); } - private void ReceiveUpdateHealth(IRecvPacket packet) + void ReceiveUpdateHealth(IRecvPacket packet) { UpdateHealthPacket p = (UpdateHealthPacket)packet; Mobile entity = WorldModel.Entities.GetObject(p.Serial, false); @@ -694,7 +693,7 @@ private void ReceiveUpdateHealth(IRecvPacket packet) // Chat / messaging handling // ====================================================================== - private void ReceiveCLILOCMessage(IRecvPacket packet) + void ReceiveCLILOCMessage(IRecvPacket packet) { MessageLocalizedPacket p = (MessageLocalizedPacket)packet; @@ -704,19 +703,19 @@ private void ReceiveCLILOCMessage(IRecvPacket packet) ReceiveTextMessage(p.MessageType, strCliLoc, p.Font, p.Hue, p.Serial, p.SpeakerName, true); } - private void ReceiveAsciiMessage(IRecvPacket packet) + void ReceiveAsciiMessage(IRecvPacket packet) { AsciiMessagePacket p = (AsciiMessagePacket)packet; ReceiveTextMessage(p.MsgType, p.Text, p.Font, p.Hue, p.Serial, p.SpeakerName, false); } - private void ReceiveUnicodeMessage(IRecvPacket packet) + void ReceiveUnicodeMessage(IRecvPacket packet) { UnicodeMessagePacket p = (UnicodeMessagePacket)packet; ReceiveTextMessage(p.MsgType, p.Text, p.Font, p.Hue, p.Serial, p.SpeakerName, true); } - private void ReceiveMessageLocalizedAffix(IRecvPacket packet) + void ReceiveMessageLocalizedAffix(IRecvPacket packet) { MessageLocalizedAffixPacket p = (MessageLocalizedAffixPacket)packet; @@ -727,7 +726,7 @@ private void ReceiveMessageLocalizedAffix(IRecvPacket packet) ReceiveTextMessage(p.MessageType, localizedString, p.Font, p.Hue, p.Serial, p.SpeakerName, true); } - private string constructCliLoc(string baseCliloc, string arg = null, bool capitalize = false) + string constructCliLoc(string baseCliloc, string arg = null, bool capitalize = false) { if (string.IsNullOrEmpty(baseCliloc)) return string.Empty; @@ -785,7 +784,7 @@ private string constructCliLoc(string baseCliloc, string arg = null, bool capita } } - private void ReceiveTextMessage(MessageTypes msgType, string text, int font, ushort hue, Serial serial, string speakerName, bool asUnicode) + void ReceiveTextMessage(MessageTypes msgType, string text, int font, ushort hue, Serial serial, string speakerName, bool asUnicode) { // PlayerState.Journaling.AddEntry(text, font, hue, speakerName, asUnicode); Overhead overhead; @@ -835,14 +834,17 @@ private void ReceiveTextMessage(MessageTypes msgType, string text, int font, ush m_World.Interaction.ChatMessage("[SPELL] " + text, font, hue, asUnicode); break; case MessageTypes.Guild: - m_World.Interaction.ChatMessage("[UILD] " + text, font, hue, asUnicode); + m_World.Interaction.ChatMessage($"[GUILD] {speakerName}: {text}", font, hue, asUnicode); break; case MessageTypes.Alliance: - m_World.Interaction.ChatMessage("[ALLIANCE] " + text, font, hue, asUnicode); + m_World.Interaction.ChatMessage($"[ALLIANCE] {speakerName}: {text}", font, hue, asUnicode); break; case MessageTypes.Command: m_World.Interaction.ChatMessage("[COMMAND] " + text, font, hue, asUnicode); break; + case MessageTypes.PartyDisplayOnly: + m_World.Interaction.ChatMessage($"[PARTY] {speakerName}: {text}", font, hue, asUnicode); + break; case MessageTypes.Information: m_World.Interaction.CreateLabel(msgType, serial, text, font, hue, asUnicode); break; @@ -856,7 +858,7 @@ private void ReceiveTextMessage(MessageTypes msgType, string text, int font, ush // Gump & Menu handling // ====================================================================== - private void ReceiveResurrectionMenu(IRecvPacket packet) + void ReceiveResurrectionMenu(IRecvPacket packet) { ResurrectionMenuPacket p = (ResurrectionMenuPacket)packet; switch (p.ResurrectionAction) @@ -870,13 +872,13 @@ private void ReceiveResurrectionMenu(IRecvPacket packet) } } - private void ReceivePopupMessage(IRecvPacket packet) + void ReceivePopupMessage(IRecvPacket packet) { PopupMessagePacket p = (PopupMessagePacket)packet; MsgBoxGump.Show(p.Message, MsgBoxTypes.OkOnly); } - private void ReceiveOpenBuyWindow(IRecvPacket packet) + void ReceiveOpenBuyWindow(IRecvPacket packet) { VendorBuyListPacket p = (VendorBuyListPacket)packet; Item entity = WorldModel.Entities.GetObject(p.VendorPackSerial, false); @@ -886,20 +888,20 @@ private void ReceiveOpenBuyWindow(IRecvPacket packet) m_UserInterface.AddControl(new VendorBuyGump(entity, p), 200, 200); } - private void ReceiveSellList(IRecvPacket packet) + void ReceiveSellList(IRecvPacket packet) { VendorSellListPacket p = (VendorSellListPacket)packet; m_UserInterface.RemoveControl(); m_UserInterface.AddControl(new VendorSellGump(p), 200, 200); } - private void ReceiveOpenPaperdoll(IRecvPacket packet) + void ReceiveOpenPaperdoll(IRecvPacket packet) { OpenPaperdollPacket p = packet as OpenPaperdollPacket; if (m_UserInterface.GetControl(p.Serial) == null) m_UserInterface.AddControl(new PaperDollGump(p.Serial, p.MobileTitle), 400, 100); } - private void ReceiveCompressedGump(IRecvPacket packet) + void ReceiveCompressedGump(IRecvPacket packet) { CompressedGumpPacket p = (CompressedGumpPacket)packet; if (p.HasData) @@ -913,19 +915,20 @@ private void ReceiveCompressedGump(IRecvPacket packet) } } - private void ReceiveDisplayGumpFast(IRecvPacket packet) + void ReceiveDisplayGumpFast(IRecvPacket packet) { announce_UnhandledPacket(packet); } - private void ReceiveDisplayMenu(IRecvPacket packet) + void ReceiveDisplayMenu(IRecvPacket packet) { announce_UnhandledPacket(packet); } - private bool TryParseGumplings(string gumpData, out string[] pieces) + bool TryParseGumplings(string gumpData, out string[] pieces) { - List i = new List(); ; + List i = new List(); + ; int dataIndex = 0; while (dataIndex < gumpData.Length) { @@ -958,18 +961,18 @@ private bool TryParseGumplings(string gumpData, out string[] pieces) // Other packets // - private void ReceiveNewSubserver(IRecvPacket packet) + void ReceiveNewSubserver(IRecvPacket packet) { SubServerPacket p = (SubServerPacket)packet; announce_UnhandledPacket(packet); } - private void ReceiveObjectHelpResponse(IRecvPacket packet) + void ReceiveObjectHelpResponse(IRecvPacket packet) { announce_UnhandledPacket(packet); } - private void ReceiveObjectPropertyList(IRecvPacket packet) + void ReceiveObjectPropertyList(IRecvPacket packet) { ObjectPropertyListPacket p = (ObjectPropertyListPacket)packet; @@ -996,7 +999,7 @@ private void ReceiveObjectPropertyList(IRecvPacket packet) } } - private void ReceiveSendCustomHouse(IRecvPacket packet) + void ReceiveSendCustomHouse(IRecvPacket packet) { CustomHousePacket p = (CustomHousePacket)packet; CustomHousing.UpdateCustomHouseData(p.HouseSerial, p.RevisionHash, p.PlaneCount, p.Planes); @@ -1009,7 +1012,7 @@ private void ReceiveSendCustomHouse(IRecvPacket packet) } } - private void ReceiveSkillsList(IRecvPacket packet) + void ReceiveSkillsList(IRecvPacket packet) { foreach (SendSkillsPacket_SkillEntry skill in ((SendSkillsPacket)packet).Skills) { @@ -1024,7 +1027,7 @@ private void ReceiveSkillsList(IRecvPacket packet) } } - private void ReceiveStatusInfo(IRecvPacket packet) + void ReceiveStatusInfo(IRecvPacket packet) { StatusInfoPacket p = (StatusInfoPacket)packet; @@ -1059,18 +1062,18 @@ private void ReceiveStatusInfo(IRecvPacket packet) mobile.PlayerCanChangeName = p.NameChangeFlag; } - private void ReceiveTime(IRecvPacket packet) + void ReceiveTime(IRecvPacket packet) { TimePacket p = (TimePacket)packet; m_World.Interaction.ChatMessage(string.Format("The current server time is {0}:{1}:{2}", p.Hour, p.Minute, p.Second)); } - private void ReceiveTipNotice(IRecvPacket packet) + void ReceiveTipNotice(IRecvPacket packet) { announce_UnhandledPacket(packet); } - private void ReceiveToolTipRevision(IRecvPacket packet) + void ReceiveToolTipRevision(IRecvPacket packet) { ObjectPropertyListUpdatePacket p = (ObjectPropertyListUpdatePacket)packet; AEntity entity = WorldModel.Entities.GetObject(p.Serial, false); @@ -1087,117 +1090,133 @@ private void ReceiveToolTipRevision(IRecvPacket packet) } } - private void announce_UnhandledPacket(IRecvPacket packet) + void announce_UnhandledPacket(IRecvPacket packet) { Tracer.Warn(string.Format("Client: Unhandled {0} [ID:{1}]", packet.Name, packet.Id)); } - private void announce_UnhandledPacket(IRecvPacket packet, string addendum) + void announce_UnhandledPacket(IRecvPacket packet, string addendum) { Tracer.Warn(string.Format("Client: Unhandled {0} [ID:{1}] {2}]", packet.Name, packet.Id, addendum)); } - private void ReceiveExtended0x78(IRecvPacket packet) + void ReceiveExtended0x78(IRecvPacket packet) { announce_UnhandledPacket(packet); } - private void ReceiveGeneralInfo(IRecvPacket packet) + void ReceiveGeneralInfo(IRecvPacket packet) { // Documented here: http://docs.polserver.com/packets/index.php?Packet=0xBF GeneralInfoPacket p = (GeneralInfoPacket)packet; switch (p.Subcommand) { - case 0x04: // Close generic gump + case GeneralInfoPacket.CloseGump: + CloseGumpInfo closeGumpInfo = p.Info as CloseGumpInfo; + AControl control = m_UserInterface.GetControlByTypeID(closeGumpInfo.GumpTypeID); + (control as Gump)?.OnButtonClick(closeGumpInfo.GumpButtonID); + break; + case GeneralInfoPacket.Party: + PartyInfo partyInfo = p.Info as PartyInfo; + switch (partyInfo.SubsubCommand) { - AControl control = m_UserInterface.GetControlByTypeID(p.CloseGumpTypeID); - if (control is Gump) - { - (control as Gump).OnButtonClick(p.CloseGumpButtonID); - } + case PartyInfo.CommandPartyList: + PlayerState.Partying.ReceivePartyMemberList(partyInfo.Info as PartyMemberListInfo); + break; + case PartyInfo.CommandRemoveMember: + PlayerState.Partying.ReceiveRemovePartyMember(partyInfo.Info as PartyRemoveMemberInfo); + break; + case PartyInfo.CommandPrivateMessage: + case PartyInfo.CommandPublicMessage: + PartyMessageInfo msg = partyInfo.Info as PartyMessageInfo; + PartyMember member = PlayerState.Partying.GetMember(msg.Source); + // note: msx752 identified hue 50 for "targeted to : " and 34 for "Help me.. I'm stunned !!" + ushort hue = (ushort)(msg.IsPrivate ? Settings.UserInterface.PartyPrivateMsgColor : Settings.UserInterface.PartyMsgColor); + ReceiveTextMessage(MessageTypes.PartyDisplayOnly, msg.Message, 3, hue, 0xFFFFFFF, member.Name, true); + break; + case PartyInfo.CommandInvitation: + PlayerState.Partying.ReceiveInvitation(partyInfo.Info as PartyInvitationInfo); + break; } break; - case 0x06: // party system - announce_UnhandledPacket(packet, "subcommand " + p.Subcommand); + case GeneralInfoPacket.SetMap: + MapIndexInfo mapInfo = p.Info as MapIndexInfo; + m_World.MapIndex = mapInfo.MapID; break; - case 0x08: // set map - m_World.MapIndex = p.MapID; + case GeneralInfoPacket.ContextMenu: + ContextMenuInfo menuInfo = p.Info as ContextMenuInfo; + InputManager input = ServiceRegistry.GetService(); + m_UserInterface.AddControl(new ContextMenuGump(menuInfo.Menu), input.MousePosition.X - 10, input.MousePosition.Y - 20); break; - case 0x14: // return context menu - { - InputManager input = ServiceRegistry.GetService(); - m_UserInterface.AddControl(new ContextMenuGump(p.ContextMenu), input.MousePosition.X - 10, input.MousePosition.Y - 20); - break; - } - case 0x18: // Enable map-diff (files) / number of maps - // as of 6.0.0.0, this only tells us the number of maps. - TileMatrixDataPatch.EnableMapDiffs(p.MapDiffs); + case GeneralInfoPacket.MapDiff: + TileMatrixDataPatch.EnableMapDiffs(p.Info as MapDiffInfo); m_World.Map.ReloadStatics(); break; - case 0x19: // Extended stats - if (p.ExtendedStatsSerial != WorldModel.PlayerSerial) + case GeneralInfoPacket.ExtendedStats: + ExtendedStatsInfo extendedStats = p.Info as ExtendedStatsInfo; + if (extendedStats.Serial != WorldModel.PlayerSerial) + { Tracer.Warn("Extended Stats packet (0xBF subcommand 0x19) received for a mobile not our own."); + } else { - PlayerState.StatLocks.StrengthLock = p.ExtendedStatsLocks.Strength; - PlayerState.StatLocks.DexterityLock = p.ExtendedStatsLocks.Dexterity; - PlayerState.StatLocks.IntelligenceLock = p.ExtendedStatsLocks.Intelligence; + PlayerState.StatLocks.StrengthLock = extendedStats.Locks.Strength; + PlayerState.StatLocks.DexterityLock = extendedStats.Locks.Dexterity; + PlayerState.StatLocks.IntelligenceLock = extendedStats.Locks.Intelligence; } break; - case 0x1B: // spellbook data - SpellbookData spellbook = p.Spellbook; + case GeneralInfoPacket.SpellBookContents: + SpellbookData spellbook = (p.Info as SpellBookContentsInfo).Spellbook; WorldModel.Entities.GetObject(spellbook.Serial, true).ReceiveSpellData(spellbook.BookType, spellbook.SpellsBitfield); break; - case 0x1D: // House revision state - if (CustomHousing.IsHashCurrent(p.HouseRevisionState.Serial, p.HouseRevisionState.Hash)) + case GeneralInfoPacket.HouseRevision: + HouseRevisionInfo houseInfo = p.Info as HouseRevisionInfo; + if (CustomHousing.IsHashCurrent(houseInfo.Revision.Serial, houseInfo.Revision.Hash)) { - Multi multi = WorldModel.Entities.GetObject(p.HouseRevisionState.Serial, false); + Multi multi = WorldModel.Entities.GetObject(houseInfo.Revision.Serial, false); if (multi == null) { // received a house revision for a multi that does not exist. } else { - if (multi.CustomHouseRevision != p.HouseRevisionState.Hash) + if (multi.CustomHouseRevision != houseInfo.Revision.Hash) { - CustomHouse house = CustomHousing.GetCustomHouseData(p.HouseRevisionState.Serial); + CustomHouse house = CustomHousing.GetCustomHouseData(houseInfo.Revision.Serial); multi.AddCustomHousingTiles(house); } } } else { - m_Network.Send(new RequestCustomHousePacket(p.HouseRevisionState.Serial)); + m_Network.Send(new RequestCustomHousePacket(houseInfo.Revision.Serial)); } break; - case 0x21: // (AOS) Ability icon confirm. + case GeneralInfoPacket.AOSAbilityIconConfirm: // (AOS) Ability icon confirm. // no data, just (bf 00 05 21) - // ??? - break; - default: - announce_UnhandledPacket(packet, "subcommand " + p.Subcommand); + // What do we do with this??? break; } } - private void ReceiveGlobalQueueCount(IRecvPacket packet) + void ReceiveGlobalQueueCount(IRecvPacket packet) { GlobalQueuePacket p = (GlobalQueuePacket)packet; m_World.Interaction.ChatMessage("System: There are currently " + p.Count + " available calls in the global queue."); } - private void ReceiveInvalidMapEnable(IRecvPacket packet) + void ReceiveInvalidMapEnable(IRecvPacket packet) { announce_UnhandledPacket(packet); } - private void ReceiveOpenWebBrowser(IRecvPacket packet) + void ReceiveOpenWebBrowser(IRecvPacket packet) { OpenWebBrowserPacket p = (OpenWebBrowserPacket)packet; Process.Start("iexplore.exe", p.WebsiteUrl); } - private void ReceiveOverallLightLevel(IRecvPacket packet) + void ReceiveOverallLightLevel(IRecvPacket packet) { // byte iLightLevel = reader.ReadByte(); // 0x00 - day @@ -1210,7 +1229,7 @@ private void ReceiveOverallLightLevel(IRecvPacket packet) ((WorldView)m_World.GetView()).Isometric.Lighting.OverallLightning = p.LightLevel; } - private void ReceivePersonalLightLevel(IRecvPacket packet) + void ReceivePersonalLightLevel(IRecvPacket packet) { // int iCreatureID = reader.ReadInt(); // byte iLightLevel = reader.ReadByte(); @@ -1224,26 +1243,26 @@ private void ReceivePersonalLightLevel(IRecvPacket packet) ((WorldView)m_World.GetView()).Isometric.Lighting.PersonalLightning = p.LightLevel; } - private void ReceivePlayMusic(IRecvPacket packet) + void ReceivePlayMusic(IRecvPacket packet) { PlayMusicPacket p = (PlayMusicPacket)packet; AudioService service = ServiceRegistry.GetService(); service.PlayMusic(p.MusicID); } - private void ReceivePlaySoundEffect(IRecvPacket packet) + void ReceivePlaySoundEffect(IRecvPacket packet) { PlaySoundEffectPacket p = (PlaySoundEffectPacket)packet; AudioService service = ServiceRegistry.GetService(); service.PlaySound(p.SoundModel, spamCheck: true); } - private void ReceiveQuestArrow(IRecvPacket packet) + void ReceiveQuestArrow(IRecvPacket packet) { announce_UnhandledPacket(packet); } - private void ReceiveRequestNameResponse(IRecvPacket packet) + void ReceiveRequestNameResponse(IRecvPacket packet) { RequestNameResponsePacket p = (RequestNameResponsePacket)packet; Mobile mobile = WorldModel.Entities.GetObject(p.Serial, false); @@ -1256,7 +1275,7 @@ private void ReceiveRequestNameResponse(IRecvPacket packet) /// Handle a season change packet. /// /// Should be of type SeasonChangePacket. - private void ReceiveSeasonalInformation(IRecvPacket packet) + void ReceiveSeasonalInformation(IRecvPacket packet) { SeasonChangePacket p = (SeasonChangePacket)packet; if (p.SeasonChanged) @@ -1265,7 +1284,7 @@ private void ReceiveSeasonalInformation(IRecvPacket packet) } } - private void ReceiveSetWeather(IRecvPacket packet) + void ReceiveSetWeather(IRecvPacket packet) { announce_UnhandledPacket(packet); } diff --git a/dev/Ultima/World/WorldInteraction.cs b/dev/Ultima/World/WorldInteraction.cs index a42a7dc3..c440124d 100644 --- a/dev/Ultima/World/WorldInteraction.cs +++ b/dev/Ultima/World/WorldInteraction.cs @@ -8,6 +8,7 @@ * (at your option) any later version. * ***************************************************************************/ + #region usings using System; using System.Collections.Generic; @@ -17,7 +18,6 @@ using UltimaXNA.Ultima.Data; using UltimaXNA.Ultima.Network.Client; using UltimaXNA.Ultima.Player; -using UltimaXNA.Ultima.Resources; using UltimaXNA.Ultima.UI.WorldGumps; using UltimaXNA.Ultima.World.Entities; using UltimaXNA.Ultima.World.Entities.Items; @@ -26,8 +26,7 @@ using UltimaXNA.Ultima.UI; #endregion -namespace UltimaXNA.Ultima.World -{ +namespace UltimaXNA.Ultima.World { /// /// Hosts methods for interacting with the world. /// @@ -56,42 +55,6 @@ public Serial LastTarget } } - public void SendSpeech(string text, ChatMode mode) // used by chatwindow. - { - MessageTypes speechType = MessageTypes.Normal; - int hue = 0; - - switch (mode) - { - case ChatMode.Default: - speechType = MessageTypes.Normal; - hue = Settings.UserInterface.SpeechColor; - break; - case ChatMode.Whisper: - speechType = MessageTypes.Whisper; - hue = Settings.UserInterface.SpeechColor; - break; - case ChatMode.Emote: - speechType = MessageTypes.Emote; - hue = Settings.UserInterface.EmoteColor; - break; - case ChatMode.Party: - // not yet implemented - speechType = MessageTypes.Normal; - hue = Settings.UserInterface.SpeechColor; - break; - case ChatMode.Guild: - speechType = MessageTypes.Guild; - hue = Settings.UserInterface.GuildMsgColor; - break; - case ChatMode.Alliance: - speechType = MessageTypes.Alliance; - hue = Settings.UserInterface.AllianceMsgColor; - break; - } - m_Network.Send(new AsciiSpeechPacket(speechType, 0, hue + 2, "ENU", text)); - } - /// /// Informs the server we have single-clicked on an entity. Also requests a context menu. /// @@ -231,7 +194,7 @@ public void CreateLabel(MessageTypes msgType, Serial serial, string text, int fo } else { - ChatMessage("[LABEL] " + text, font, hue, asUnicode); + ChatMessage(text, font, hue, asUnicode); } } diff --git a/dev/Ultima/World/WorldViews/IsometricLighting.cs b/dev/Ultima/World/WorldViews/IsometricLighting.cs index d833a4a3..21caaa6e 100644 --- a/dev/Ultima/World/WorldViews/IsometricLighting.cs +++ b/dev/Ultima/World/WorldViews/IsometricLighting.cs @@ -20,6 +20,11 @@ public class IsometricLighting private float m_LightDirection = 4.12f; private float m_LightHeight = -0.75f; + public IsometricLighting() + { + RecalculateLightningValues(); + } + public int PersonalLightning { set { m_LightLevelPersonal = value; RecalculateLightningValues(); } diff --git a/dev/UltimaXNA.csproj b/dev/UltimaXNA.csproj index 19a17d29..177ebbed 100644 --- a/dev/UltimaXNA.csproj +++ b/dev/UltimaXNA.csproj @@ -130,7 +130,23 @@ - + + + + + + + + + + + + + + + + + @@ -290,6 +306,15 @@ + + + + + + + + + @@ -323,9 +348,13 @@ + + + + @@ -535,7 +564,7 @@ - + diff --git a/plugins/ExamplePlugin/HueTestGump.cs b/plugins/ExamplePlugin/DebugHuesGumpModule.cs similarity index 96% rename from plugins/ExamplePlugin/HueTestGump.cs rename to plugins/ExamplePlugin/DebugHuesGumpModule.cs index 528fcdfc..e976624b 100644 --- a/plugins/ExamplePlugin/HueTestGump.cs +++ b/plugins/ExamplePlugin/DebugHuesGumpModule.cs @@ -1,18 +1,17 @@ using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using System.IO; +using UltimaXNA.Core.Graphics; +using UltimaXNA.Core.Patterns; using UltimaXNA.Core.Resources; using UltimaXNA.Core.UI; using UltimaXNA.Ultima.Resources; using UltimaXNA.Ultima.UI; using UltimaXNA.Ultima.UI.Controls; -using UltimaXNA.Core.Graphics; -using UltimaXNA.Core.Patterns; -using System; using UltimaXNA.Ultima.UI.LoginGumps; namespace UltimaXNA.Ultima.Login { - class HueTestGumpModule : IModule { + class DebugHuesGumpModule : IModule { public string Name => "DebugHueTest"; public void Load() { @@ -24,15 +23,15 @@ public void Unload() { } void OnClickDebugGump() { - ServiceRegistry.GetService().AddControl(new HueTestGump(), 0, 0); + ServiceRegistry.GetService().AddControl(new DebugHuesGump(), 0, 0); } } - class HueTestGump : Gump { + class DebugHuesGump : Gump { TextLabel m_Label; HuedControl m_HueDisplay; - public HueTestGump() + public DebugHuesGump() : base(0, 0) { OverHue = -1; } diff --git a/plugins/ExamplePlugin/DebugOutputGumpsModule.cs b/plugins/ExamplePlugin/DebugOutputGumpsModule.cs new file mode 100644 index 00000000..2d751689 --- /dev/null +++ b/plugins/ExamplePlugin/DebugOutputGumpsModule.cs @@ -0,0 +1,53 @@ +using Microsoft.Xna.Framework.Graphics; +using System.Threading; +using System.Threading.Tasks; +using UltimaXNA; +using UltimaXNA.Core.Diagnostics.Tracing; +using UltimaXNA.Core.Patterns; +using UltimaXNA.Core.Resources; +using UltimaXNA.Ultima.UI.LoginGumps; + +namespace ExamplePlugin { + class DebugOutputGumpsModule : IModule { + public string Name => "DebugOutputGumpsModule"; + Task m_WriteGumps; + CancellationTokenSource m_WriteGumpsCancel; + + public void Load() { + // LoginGump.AddButton("Debug:Gumps", OnClickDebugGump); + } + + public void Unload() { + // LoginGump.RemoveButton(OnClickDebugGump); + } + + void OnClickDebugGump() { + if (m_WriteGumps == null) { + Tracer.Info("Writing all gump textures to /Gumps folder. Progress may be slow. Click Debug:Gumps button again to cancel."); + // This is ugly! I have no idea how to do parallelism. Someone help me? + m_WriteGumpsCancel = new CancellationTokenSource(); + m_WriteGumps = new Task( + () => { + System.IO.Directory.CreateDirectory("Gumps"); + IResourceProvider resources = ServiceRegistry.GetService(); + for (int i = 0; i < 0x10000; i++) { + Texture2D texture = resources.GetUITexture(i); + if (texture != null) { + texture.SaveAsJpeg(new System.IO.FileStream($"Gumps/{i:D6}.jpg", System.IO.FileMode.Create), texture.Width, texture.Height); + } + if (m_WriteGumpsCancel.Token.IsCancellationRequested) { + break; + } + } + m_WriteGumpsCancel = null; + m_WriteGumps = null; + }, m_WriteGumpsCancel.Token); + m_WriteGumps.Start(); + } + else { + Tracer.Info("Canceled writing gump textures."); + m_WriteGumpsCancel.Cancel(); + } + } + } +} diff --git a/plugins/ExamplePlugin/ExamplePlugin.csproj b/plugins/ExamplePlugin/ExamplePlugin.csproj index 50bd99a6..6f4ac7ca 100644 --- a/plugins/ExamplePlugin/ExamplePlugin.csproj +++ b/plugins/ExamplePlugin/ExamplePlugin.csproj @@ -49,8 +49,10 @@ - + + + diff --git a/plugins/ExamplePlugin/HueTestState.cs b/plugins/ExamplePlugin/HueTestState.cs deleted file mode 100644 index ada0964b..00000000 --- a/plugins/ExamplePlugin/HueTestState.cs +++ /dev/null @@ -1,155 +0,0 @@ -using Microsoft.Xna.Framework; -using Microsoft.Xna.Framework.Graphics; -using System.IO; -using UltimaXNA.Core.Graphics; -using UltimaXNA.Core.UI; -using UltimaXNA.Ultima.Resources; -using UltimaXNA.Ultima.UI.Controls; -using UltimaXNA.Ultima.UI; -using UltimaXNA.Core.Resources; - -namespace UltimaXNA.Ultima.Login.States -{ - /*class HueTestState : AState - { - Gump m_Gump; - TextLabel m_Label; - HuedControl m_HueDisplay; - - UserInterfaceService m_UserInterface; - - public HueTestState() - : base() - { - OverHue = -1; - - m_UserInterface = ServiceRegistry.GetService(); - } - - public static string Caption - { - get - { - if (OverHue <= -1) - { - return "Over: None"; - } - else - { - return string.Format("Over: {0} [Hue index {1} 0x{1:x}]", OverHue, OverHue - 2); - } - } - } - - public static int OverHue - { - get; - set; - } - - public override void Intitialize() - { - base.Intitialize(); - - m_Gump = (Gump)m_UserInterface.AddControl(new Gump(0, 0), 0, 0); - m_Gump.Size = new Point(800, 600); - m_Gump.AddControl(new ResizePic(m_Gump, 5, 5, 3000, 790, 590)); - - int rowwidth = 60; - - // caption string - m_Label = (TextLabel)m_Gump.AddControl(new TextLabel(m_Gump, 50, 8, 0, null)); - - // object that is hued based on the current overhue. - m_HueDisplay = (HuedControl)m_Gump.AddControl(new HuedControl(m_Gump, 8305)); - m_Gump.LastControl.Position = new Point(745, 15); - ((HuedControl)m_Gump.LastControl).Hue = 0; - - // unhued object - m_Gump.AddControl(new HuedControl(m_Gump)); - m_Gump.LastControl.Position = new Point(-5, 10); - ((HuedControl)m_Gump.LastControl).Hue = 0; - - // hue index 1 (uo hue -1), aka one of the "True Black" hues - m_Gump.AddControl(new HuedControl(m_Gump)); - m_Gump.LastControl.Position = new Point(3, 10); - ((HuedControl)m_Gump.LastControl).Hue = 1; - - for (int i = 0; i < 3000; i++) - { - m_Gump.AddControl(new HuedControl(m_Gump)); - m_Gump.LastControl.Position = new Point((i % rowwidth) * 11 - 5, (i / rowwidth) * 10 + 28); - ((HuedControl)m_Gump.LastControl).Hue = i + 2; - } - - using (FileStream file = new FileStream("hues0.png", FileMode.Create)) - { - HueData.HueTexture0.SaveAsPng(file, HueData.HueTexture0.Width, HueData.HueTexture0.Height); - } - - using (FileStream file = new FileStream("hues1.png", FileMode.Create)) - { - HueData.HueTexture1.SaveAsPng(file, HueData.HueTexture1.Width, HueData.HueTexture1.Height); - } - } - - public override void Update(double totalTime, double frameTime) - { - m_Label.Text = Caption; - m_HueDisplay.Hue = OverHue <= -1 ? 0 : OverHue; - base.Update(totalTime, frameTime); - } - - class HuedControl : AControl - { - private int m_StaticTextureID = 0; - - public HuedControl(AControl parent, int staticID = 0x1bf5) - : base(parent) - { - HandlesMouseInput = true; - m_StaticTextureID = staticID; - } - - public int Hue = 0; - - private Texture2D m_texture; - - public override void Draw(SpriteBatchUI spriteBatch, Point position) - { - if (m_texture == null) - { - IResourceProvider provider = ServiceRegistry.GetService(); - m_texture = provider.GetItemTexture(m_StaticTextureID); - Size = new Point(m_texture.Width, m_texture.Height); - } - spriteBatch.Draw2D(m_texture, new Vector3(position.X, position.Y, 0), Utility.GetHueVector(Hue)); - base.Draw(spriteBatch, position); - } - - protected override void OnMouseOver(int x, int y) - { - OverHue = Hue; - } - - protected override void OnMouseOut(int x, int y) - { - OverHue = -1; - } - - protected override bool IsPointWithinControl(int x, int y) - { - if (m_texture != null) - { - uint[] pixel = new uint[m_texture.Width * m_texture.Height]; - m_texture.GetData(pixel); - if (pixel[y * m_texture.Width + x] != 0x00000000) - { - return true; - } - } - return false; - } - } - }*/ -} diff --git a/plugins/ExamplePlugin/MapRenderer/MapRendererModule.cs b/plugins/ExamplePlugin/MapRenderer/MapRendererModule.cs new file mode 100644 index 00000000..ad6a2aec --- /dev/null +++ b/plugins/ExamplePlugin/MapRenderer/MapRendererModule.cs @@ -0,0 +1,81 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using System.IO; +using UltimaXNA; +using UltimaXNA.Core.Graphics; +using UltimaXNA.Core.Patterns; +using UltimaXNA.Ultima.UI.LoginGumps; +using UltimaXNA.Ultima.World.Entities; +using UltimaXNA.Ultima.World.EntityViews; +using UltimaXNA.Ultima.World.Input; +using UltimaXNA.Ultima.World.Maps; +using UltimaXNA.Ultima.World.WorldViews; + +namespace ExamplePlugin.MapRenderer +{ + class MapRendererModule : IModule + { + public string Name => "Map Renderer"; + + public void Load() + { + // LoginGump.AddButton("Debug:Map", OnClick); + } + + public void Unload() + { + LoginGump.RemoveButton(OnClick); + } + + const int Width = 44 * 8; + const int Height = 44 * 8; + const int WidthExtra = 44 * 4; + const int HeightExtra = 44 * 4; + const int HeightExtra2 = 256 * 4; + + void OnClick() + { + Directory.CreateDirectory("Chunks"); + IsometricLighting lighting = new IsometricLighting(); + MouseOverList mouseOverNull = new MouseOverList(new MousePicking()); + SpriteBatch3D spritebatch = ServiceRegistry.GetService(); + RenderTarget2D render = new RenderTarget2D(spritebatch.GraphicsDevice, Width + WidthExtra * 2, Height + HeightExtra * 2 + HeightExtra2); + Map map = new Map(0); + for (int chunky = 0; chunky < 10; chunky++) + { + for (int chunkx = 0; chunkx < 10; chunkx++) + { + spritebatch.GraphicsDevice.SetRenderTarget(render); + spritebatch.Reset(); + uint cx = (uint)chunkx + 200; + uint cy = (uint)chunky + 200; + map.CenterPosition = new Point((int)(cx << 3), (int)(cy << 3)); + MapChunk chunk = map.GetMapChunk(cx, cy); + chunk.LoadStatics(map.MapData, map); + int z = 0; + for (int i = 0; i < 64; i++) + { + int y = (i / 8); + int x = (i % 8); + int sy = y * 22 + x * 22 + HeightExtra + HeightExtra2; + int sx = 22 * 8 - y * 22 + x * 22 + WidthExtra; + MapTile tile = chunk.Tiles[i]; + tile.ForceSort(); + for (int j = 0; j < tile.Entities.Count; j++) + { + AEntity e = tile.Entities[j]; + AEntityView view = e.GetView(); + view.Draw(spritebatch, new Vector3(sx, sy, 0), mouseOverNull, map, false); + } + } + spritebatch.SetLightIntensity(lighting.IsometricLightLevel); + spritebatch.SetLightDirection(lighting.IsometricLightDirection); + spritebatch.GraphicsDevice.Clear(Color.Transparent); + spritebatch.FlushSprites(true); + spritebatch.GraphicsDevice.SetRenderTarget(null); + render.SaveAsPng(new FileStream($"Chunks/{cy}-{cx}.png", FileMode.Create), render.Width, render.Height); + } + } + } + } +}