diff --git a/Projects/Server.Tests/Tests/Network/Packets/Outgoing/MobilePacketTests.cs b/Projects/Server.Tests/Tests/Network/Packets/Outgoing/MobilePacketTests.cs index e70a20c44..89117bb1e 100644 --- a/Projects/Server.Tests/Tests/Network/Packets/Outgoing/MobilePacketTests.cs +++ b/Projects/Server.Tests/Tests/Network/Packets/Outgoing/MobilePacketTests.cs @@ -174,150 +174,86 @@ namespace Server.Tests.Network AssertThat.Equal(result.Buffer[0].AsSpan(0), expected); } - [Fact] - public void TestMobileStatusCompact() + [Theory] + [InlineData(true)] + [InlineData(false)] + public void TestMobileStatusCompact(bool canBeRenamed) { - var m = new Mobile(0x1); + var m = new Mobile(0x1) { Name = "Random Mobile 1" }; m.DefaultMobileInit(); + m.Str = 50; + m.Hits = 100; + m.Int = 75; + m.Mana = 100; + m.Dex = 25; + m.Stam = 100; - var canBeRenamed = false; + var expected = new MobileStatusCompact(canBeRenamed, m).Compile(); - var data = new MobileStatusCompact(canBeRenamed, m).Compile(); + using var ns = PacketTestUtilities.CreateTestNetState(); + ns.SendMobileStatusCompact(m, canBeRenamed); - Span expectedData = stackalloc byte[43]; - var pos = 0; - - expectedData.Write(ref pos, (byte)0x11); // Packet ID - expectedData.Write(ref pos, (ushort)expectedData.Length); // Length - - expectedData.Write(ref pos, m.Serial); - expectedData.WriteAsciiFixed(ref pos, m.Name ?? "", 30); - - expectedData.WriteReverseAttribute(ref pos, m.Hits, m.HitsMax, true); - expectedData.Write(ref pos, canBeRenamed); - -#if NO_LOCAL_INIT - expectedData.Write(ref pos, (byte)0); // type -#endif - - AssertThat.Equal(data, expectedData); + var result = ns.SendPipe.Reader.TryRead(); + AssertThat.Equal(result.Buffer[0].AsSpan(0), expected); } - [Theory, InlineData(ProtocolChanges.Version70610), InlineData(ProtocolChanges.Version400a), - InlineData(ProtocolChanges.Version502b)] - public void TestMobileStatusExtended(ProtocolChanges changes) + [Theory] + [InlineData(ProtocolChanges.Version70610)] + [InlineData(ProtocolChanges.Version400a)] + [InlineData(ProtocolChanges.Version502b)] + public void TestMobileStatus(ProtocolChanges changes) { - var beholder = new Mobile(0x1) - { - Name = "Random Mobile 1" - }; + var beholder = new Mobile(0x1) { Name = "Random Mobile 1" }; beholder.DefaultMobileInit(); + beholder.Str = 50; + beholder.Hits = 100; + beholder.Int = 75; + beholder.Mana = 100; + beholder.Dex = 25; + beholder.Stam = 100; - var beheld = new Mobile(0x2) - { - Name = "Random Mobile 2" - }; + var beheld = new Mobile(0x2) { Name = "Random Mobile 2" }; beheld.DefaultMobileInit(); + beheld.Str = 50; + beheld.Hits = 100; + beheld.Int = 75; + beheld.Mana = 100; + beheld.Dex = 25; + beheld.Stam = 100; - var ns = new NetState(null) - { - ProtocolChanges = changes - }; + using var ns = PacketTestUtilities.CreateTestNetState(); + ns.ProtocolChanges = changes; - var data = new MobileStatus(beholder, beheld, ns).Compile(); + var expected = new MobileStatus(beholder, beheld, ns).Compile(); + ns.SendMobileStatus(beholder, beheld); - Span expectedData = stackalloc byte[121]; // Max Size - var pos = 0; + var result = ns.SendPipe.Reader.TryRead(); + AssertThat.Equal(result.Buffer[0].AsSpan(0), expected); + } - expectedData.Write(ref pos, (byte)0x11); - pos += 2; // Length + [Theory] + [InlineData(ProtocolChanges.Version70610)] + [InlineData(ProtocolChanges.Version400a)] + [InlineData(ProtocolChanges.Version502b)] + public void TestMobileStatusExtendedSelf(ProtocolChanges changes) + { + var m = new Mobile(0x1) { Name = "Random Mobile 1" }; + m.DefaultMobileInit(); + m.Str = 50; + m.Hits = 100; + m.Int = 75; + m.Mana = 100; + m.Dex = 25; + m.Stam = 100; - int type; - var notSelf = beholder != beheld; + using var ns = PacketTestUtilities.CreateTestNetState(); + ns.ProtocolChanges = changes; - if (notSelf) - { - type = 0; - } - else if (Core.HS && ns.ExtendedStatus) - { - type = 6; - } - else if (Core.ML && ns.SupportsExpansion(Expansion.ML)) - { - type = 5; - } - else - { - type = Core.AOS ? 4 : 3; - } + var expected = new MobileStatusExtended(m, ns).Compile(); + ns.SendMobileStatus(m, m); - expectedData.Write(ref pos, beheld.Serial); - expectedData.WriteAsciiFixed(ref pos, beheld.Name, 30); - - expectedData.WriteReverseAttribute(ref pos, beheld.Hits, beheld.HitsMax, notSelf); - - expectedData.Write(ref pos, beheld.CanBeRenamedBy(beheld)); - expectedData.Write(ref pos, (byte)type); - - if (type > 0) - { - expectedData.Write(ref pos, beheld.Female); - expectedData.Write(ref pos, (ushort)beheld.Str); - expectedData.Write(ref pos, (ushort)beheld.Dex); - expectedData.Write(ref pos, (ushort)beheld.Int); - - expectedData.WriteReverseAttribute(ref pos, beheld.Stam, beheld.StamMax, notSelf); - expectedData.WriteReverseAttribute(ref pos, beheld.Mana, beheld.ManaMax, notSelf); - - expectedData.Write(ref pos, beheld.TotalGold); - expectedData.Write( - ref pos, - (ushort)(Core.AOS ? beheld.PhysicalResistance : (int)(beheld.ArmorRating + 0.5)) - ); - expectedData.Write(ref pos, (ushort)(Mobile.BodyWeight + beheld.TotalWeight)); - - if (type >= 5) - { - expectedData.Write(ref pos, (ushort)beheld.MaxWeight); - expectedData.Write(ref pos, (byte)(beheld.Race.RaceID + 1)); // 0x00 for a non-ML enabled account - } - - expectedData.Write(ref pos, (ushort)beheld.StatCap); - expectedData.Write(ref pos, (byte)beheld.Followers); - expectedData.Write(ref pos, (byte)beheld.FollowersMax); - - if (type >= 4) - { - expectedData.Write(ref pos, (ushort)beheld.FireResistance); - expectedData.Write(ref pos, (ushort)beheld.ColdResistance); - expectedData.Write(ref pos, (ushort)beheld.PoisonResistance); - expectedData.Write(ref pos, (ushort)beheld.EnergyResistance); - expectedData.Write(ref pos, (ushort)beheld.Luck); - } - - var min = 0; - var max = 0; - beheld.Weapon?.GetStatusDamage(beheld, out min, out max); - - expectedData.Write(ref pos, (ushort)min); - expectedData.Write(ref pos, (ushort)max); - - expectedData.Write(ref pos, beheld.TithingPoints); - - if (type >= 6) - { - for (var i = 0; i < 15; ++i) - { - expectedData.Write(ref pos, (ushort)beheld.GetAOSStatus(i)); - } - } - } - - expectedData.Slice(1, 2).Write((ushort)pos); // Length - - expectedData = expectedData.Slice(0, pos); - AssertThat.Equal(data, expectedData); + var result = ns.SendPipe.Reader.TryRead(); + AssertThat.Equal(result.Buffer[0].AsSpan(0), expected); } [Fact] diff --git a/Projects/Server.Tests/Tests/Network/Packets/Outgoing/MobilePackets.cs b/Projects/Server.Tests/Tests/Network/Packets/Outgoing/MobilePackets.cs index 9d57c2e45..115c96ffc 100644 --- a/Projects/Server.Tests/Tests/Network/Packets/Outgoing/MobilePackets.cs +++ b/Projects/Server.Tests/Tests/Network/Packets/Outgoing/MobilePackets.cs @@ -220,4 +220,246 @@ namespace Server.Tests.Network } } } + + public sealed class MobileStatusCompact : Packet + { + public MobileStatusCompact(bool canBeRenamed, Mobile m) : base(0x11) + { + EnsureCapacity(43); + + Stream.Write(m.Serial); + Stream.WriteAsciiFixed(m.Name ?? "", 30); + + AttributeNormalizer.WriteReverse(Stream, m.Hits, m.HitsMax); + + Stream.Write(canBeRenamed); + + Stream.Write((byte)0); // type + } + } + + public sealed class MobileStatusExtended : Packet + { + public MobileStatusExtended(Mobile m) : this(m, m.NetState) + { + } + + public MobileStatusExtended(Mobile m, NetState ns) : base(0x11) + { + var name = m.Name ?? ""; + + int type; + + if (Core.HS && ns?.ExtendedStatus == true) + { + type = 6; + EnsureCapacity(121); + } + else if (Core.ML && ns?.SupportsExpansion(Expansion.ML) == true) + { + type = 5; + EnsureCapacity(91); + } + else + { + type = Core.AOS ? 4 : 3; + EnsureCapacity(88); + } + + Stream.Write(m.Serial); + Stream.WriteAsciiFixed(name, 30); + + Stream.Write((short)m.Hits); + Stream.Write((short)m.HitsMax); + + Stream.Write(m.CanBeRenamedBy(m)); + + Stream.Write((byte)type); + + Stream.Write(m.Female); + + Stream.Write((short)m.Str); + Stream.Write((short)m.Dex); + Stream.Write((short)m.Int); + + Stream.Write((short)m.Stam); + Stream.Write((short)m.StamMax); + + Stream.Write((short)m.Mana); + Stream.Write((short)m.ManaMax); + + Stream.Write(m.TotalGold); + Stream.Write((short)(Core.AOS ? m.PhysicalResistance : (int)(m.ArmorRating + 0.5))); + Stream.Write((short)(Mobile.BodyWeight + m.TotalWeight)); + + if (type >= 5) + { + Stream.Write((short)m.MaxWeight); + Stream.Write((byte)(m.Race?.RaceID + 1 ?? 0)); // Would be 0x00 if it's a non-ML enabled account but... + } + + Stream.Write((short)m.StatCap); + + Stream.Write((byte)m.Followers); + Stream.Write((byte)m.FollowersMax); + + if (type >= 4) + { + Stream.Write((short)m.FireResistance); // Fire + Stream.Write((short)m.ColdResistance); // Cold + Stream.Write((short)m.PoisonResistance); // Poison + Stream.Write((short)m.EnergyResistance); // Energy + Stream.Write((short)m.Luck); // Luck + + var weapon = m.Weapon; + + if (weapon != null) + { + weapon.GetStatusDamage(m, out var min, out var max); + Stream.Write((short)min); // Damage min + Stream.Write((short)max); // Damage max + } + else + { + Stream.Write((short)0); // Damage min + Stream.Write((short)0); // Damage max + } + + Stream.Write(m.TithingPoints); + } + + if (type >= 6) + { + for (var i = 0; i < 15; ++i) + { + Stream.Write((short)m.GetAOSStatus(i)); + } + } + } + } + + public sealed class MobileStatus : Packet + { + public MobileStatus(Mobile beholder, Mobile beheld) : this(beholder, beheld, beheld.NetState) + { + } + + public MobileStatus(Mobile beholder, Mobile beheld, NetState ns) : base(0x11) + { + var name = beheld.Name ?? ""; + + int type; + + if (beholder != beheld) + { + type = 0; + EnsureCapacity(43); + } + else if (Core.HS && ns?.ExtendedStatus == true) + { + type = 6; + EnsureCapacity(121); + } + else if (Core.ML && ns?.SupportsExpansion(Expansion.ML) == true) + { + type = 5; + EnsureCapacity(91); + } + else + { + type = Core.AOS ? 4 : 3; + EnsureCapacity(88); + } + + Stream.Write(beheld.Serial); + + Stream.WriteAsciiFixed(name, 30); + + if (beholder == beheld) + { + WriteAttr(beheld.Hits, beheld.HitsMax); + } + else + { + WriteAttrNorm(beheld.Hits, beheld.HitsMax); + } + + Stream.Write(beheld.CanBeRenamedBy(beholder)); + + Stream.Write((byte)type); + + if (type <= 0) + { + return; + } + + Stream.Write(beheld.Female); + + Stream.Write((short)beheld.Str); + Stream.Write((short)beheld.Dex); + Stream.Write((short)beheld.Int); + + WriteAttr(beheld.Stam, beheld.StamMax); + WriteAttr(beheld.Mana, beheld.ManaMax); + + Stream.Write(beheld.TotalGold); + Stream.Write((short)(Core.AOS ? beheld.PhysicalResistance : (int)(beheld.ArmorRating + 0.5))); + Stream.Write((short)(Mobile.BodyWeight + beheld.TotalWeight)); + + if (type >= 5) + { + Stream.Write((short)beheld.MaxWeight); + Stream.Write((byte)(beheld.Race.RaceID + 1)); // Would be 0x00 if it's a non-ML enabled account but... + } + + Stream.Write((short)beheld.StatCap); + + Stream.Write((byte)beheld.Followers); + Stream.Write((byte)beheld.FollowersMax); + + if (type >= 4) + { + Stream.Write((short)beheld.FireResistance); // Fire + Stream.Write((short)beheld.ColdResistance); // Cold + Stream.Write((short)beheld.PoisonResistance); // Poison + Stream.Write((short)beheld.EnergyResistance); // Energy + Stream.Write((short)beheld.Luck); // Luck + + var weapon = beheld.Weapon; + + if (weapon != null) + { + weapon.GetStatusDamage(beheld, out var min, out var max); + Stream.Write((short)min); // Damage min + Stream.Write((short)max); // Damage max + } + else + { + Stream.Write((short)0); // Damage min + Stream.Write((short)0); // Damage max + } + + Stream.Write(beheld.TithingPoints); + } + + if (type >= 6) + { + for (var i = 0; i < 15; ++i) + { + Stream.Write((short)beheld.GetAOSStatus(i)); + } + } + } + + private void WriteAttr(int current, int maximum) + { + Stream.Write((short)current); + Stream.Write((short)maximum); + } + + private void WriteAttrNorm(int current, int maximum) + { + AttributeNormalizer.WriteReverse(Stream, current, maximum); + } + } } diff --git a/Projects/Server/Mobiles/Mobile.cs b/Projects/Server/Mobiles/Mobile.cs index a9181c149..f6643208d 100644 --- a/Projects/Server/Mobiles/Mobile.cs +++ b/Projects/Server/Mobiles/Mobile.cs @@ -2920,9 +2920,7 @@ namespace Server public virtual void ProcessDelta() { - var m = this; - var delta = m.m_DeltaFlags; - + var delta = m_DeltaFlags; if (delta == MobileDelta.None) { return; @@ -2930,8 +2928,8 @@ namespace Server var attrs = delta & MobileDelta.Attributes; - m.m_DeltaFlags = MobileDelta.None; - m.m_InDeltaQueue = false; + m_DeltaFlags = MobileDelta.None; + m_InDeltaQueue = false; bool sendHits = false, sendStam = false, sendMana = false, sendAll = false, sendAny = false; bool sendIncoming = false, sendNonlocalIncoming = false; @@ -3016,7 +3014,7 @@ namespace Server if ((delta & MobileDelta.Hair) != 0) { - if (m.HairItemID <= 0) + if (HairItemID <= 0) { removeHair = true; } @@ -3026,7 +3024,7 @@ namespace Server if ((delta & MobileDelta.FacialHair) != 0) { - if (m.FacialHairItemID <= 0) + if (FacialHairItemID <= 0) { removeFacialHair = true; } @@ -3037,62 +3035,62 @@ namespace Server Span mobileMovingPackets = stackalloc byte[OutgoingMobilePackets.MobileMovingPacketCacheLength]; mobileMovingPackets.InitializePackets(OutgoingMobilePackets.MobileMovingPacketLength); - var ourState = m.m_NetState; + var ourState = m_NetState; if (ourState != null) { if (sendUpdate) { ourState.Sequence = 0; - ourState.Send(new MobileUpdate(m, ourState.StygianAbyss)); + ourState.Send(new MobileUpdate(this, ourState.StygianAbyss)); } if (sendIncoming) { - ourState.Send(new MobileIncoming(ourState, m, m)); + ourState.Send(new MobileIncoming(ourState, this, this)); } if (sendMoving || !ourState.StygianAbyss && (sendHealthbarPoison || sendHealthbarYellow)) { - ourState.SendMobileMovingUsingCache(mobileMovingPackets, m, m); + ourState.SendMobileMovingUsingCache(mobileMovingPackets, this, this); } if (ourState.StygianAbyss) { if (sendHealthbarPoison) { - ourState.SendMobileHealthbar(m, Healthbar.Poison); + ourState.SendMobileHealthbar(this, Healthbar.Poison); } if (sendHealthbarYellow) { - ourState.SendMobileHealthbar(m, Healthbar.Yellow); + ourState.SendMobileHealthbar(this, Healthbar.Yellow); } } if (sendPublicStats || sendPrivateStats) { - ourState.Send(new MobileStatusExtended(m, m_NetState)); + ourState.SendMobileStatus(this); } else if (sendAll) { - ourState.SendMobileAttributes(m); + ourState.SendMobileAttributes(this); } else if (sendAny) { if (sendHits) { - ourState.SendMobileHits(m); + ourState.SendMobileHits(this); } if (sendMana) { - ourState.SendMobileMana(m); + ourState.SendMobileMana(this); } if (sendStam) { - ourState.SendMobileStam(m); + ourState.SendMobileStam(this); } } @@ -3116,11 +3114,11 @@ namespace Server { if (removeHair) { - ourState.Send(new RemoveHair(m)); + ourState.Send(new RemoveHair(this)); } else { - ourState.Send(new HairEquipUpdate(m)); + ourState.Send(new HairEquipUpdate(this)); } } @@ -3128,11 +3126,11 @@ namespace Server { if (removeFacialHair) { - ourState.Send(new RemoveFacialHair(m)); + ourState.Send(new RemoveFacialHair(this)); } else { - ourState.Send(new FacialHairEquipUpdate(m)); + ourState.Send(new FacialHairEquipUpdate(this)); } } @@ -3142,155 +3140,167 @@ namespace Server } } + // TODO: Is it even valid to send packets to our state while we have a null map? Look into failing fast + if (m_Map == null) + { + return; + } + sendMoving = sendMoving || sendNonlocalMoving; sendIncoming = sendIncoming || sendNonlocalIncoming; sendHits = sendHits || sendAll; - if (m.m_Map != null && (sendRemove || sendIncoming || sendPublicStats || sendHits || sendMoving || - sendOPLUpdate || sendHair || sendFacialHair || sendHealthbarPoison || - sendHealthbarYellow)) + if (!(sendRemove || sendIncoming || sendPublicStats || sendHits || sendMoving || + sendOPLUpdate || sendHair || sendFacialHair || sendHealthbarPoison || + sendHealthbarYellow)) { - Packet statPacketTrue = null; - Packet statPacketFalse = null; - Packet hairPacket = null; - Packet facialhairPacket = null; - Packet hbpPacket = null; - Packet hbyPacket = null; + return; + } - var eable = m.Map.GetClientsInRange(m.m_Location); + Packet hairPacket = null; + Packet facialhairPacket = null; - Span hbpBuffer = stackalloc byte[OutgoingMobilePackets.MobileHealthbarPacketLength]; - hbpBuffer.InitializePacket(); + var eable = Map.GetClientsInRange(m_Location); - Span hbyBuffer = stackalloc byte[OutgoingMobilePackets.MobileHealthbarPacketLength]; - hbyBuffer.InitializePacket(); + Span statBufferTrue = stackalloc byte[OutgoingMobilePackets.MobileStatusCompactLength]; + statBufferTrue.InitializePacket(); - Span deadBuffer = stackalloc byte[OutgoingMobilePackets.BondedStatusPacketLength]; - deadBuffer.InitializePacket(); + Span statBufferFalse = stackalloc byte[OutgoingMobilePackets.MobileStatusCompactLength]; + statBufferFalse.InitializePacket(); - Span removeEntity = stackalloc byte[OutgoingEntityPackets.RemoveEntityLength]; - removeEntity.InitializePacket(); + Span hbpBuffer = stackalloc byte[OutgoingMobilePackets.MobileHealthbarPacketLength]; + hbpBuffer.InitializePacket(); - Span hitsPacket = stackalloc byte[OutgoingMobilePackets.MobileAttributePacketLength]; - hitsPacket.InitializePacket(); + Span hbyBuffer = stackalloc byte[OutgoingMobilePackets.MobileHealthbarPacketLength]; + hbyBuffer.InitializePacket(); - foreach (var state in eable) + Span deadBuffer = stackalloc byte[OutgoingMobilePackets.BondedStatusPacketLength]; + deadBuffer.InitializePacket(); + + Span removeEntity = stackalloc byte[OutgoingEntityPackets.RemoveEntityLength]; + removeEntity.InitializePacket(); + + Span hitsPacket = stackalloc byte[OutgoingMobilePackets.MobileAttributePacketLength]; + hitsPacket.InitializePacket(); + + foreach (var state in eable) + { + var beholder = state.Mobile; + + if (beholder == this || !beholder.CanSee(this)) { - var beholder = state.Mobile; - - if (beholder == m || !beholder.CanSee(m)) - { - continue; - } - - if (sendRemove) - { - if (removeEntity[0] == 0) - { - OutgoingEntityPackets.CreateRemoveEntity(removeEntity, Serial); - } - - state.Send(removeEntity); - } - - if (sendIncoming) - { - state.Send(new MobileIncoming(state, beholder, m)); - - if (m.IsDeadBondedPet) - { - if (deadBuffer[0] == 0) - { - OutgoingMobilePackets.CreateBondedStatus(deadBuffer, m.Serial, true); - } - - state.Send(deadBuffer); - } - } - - if (sendMoving || !state.StygianAbyss && (sendHealthbarPoison || sendHealthbarYellow)) - { - state.SendMobileMovingUsingCache(mobileMovingPackets, beholder, m); - } - - if (state.StygianAbyss) - { - if (sendHealthbarPoison) - { - if (hbpBuffer[0] == 0) - { - OutgoingMobilePackets.CreateMobileHealthbar(hbpBuffer, m, Healthbar.Poison); - } - - state.Send(hbpPacket); - } - - if (sendHealthbarYellow) - { - if (hbyBuffer[0] == 0) - { - OutgoingMobilePackets.CreateMobileHealthbar(hbyBuffer, m, Healthbar.Yellow); - } - - state.Send(hbyPacket); - } - } - - if (sendPublicStats) - { - if (m.CanBeRenamedBy(beholder)) - { - statPacketTrue ??= Packet.Acquire(new MobileStatusCompact(true, m)); - - state.Send(statPacketTrue); - } - else - { - statPacketFalse ??= Packet.Acquire(new MobileStatusCompact(false, m)); - - state.Send(statPacketFalse); - } - } - else if (sendHits) - { - if (hitsPacket[0] == 0) - { - OutgoingMobilePackets.CreateMobileHits(hitsPacket, m, true); - } - - state.Send(hitsPacket); - } - - if (sendHair) - { - hairPacket ??= removeHair - ? Packet.Acquire(new RemoveHair(m)) - : Packet.Acquire(new HairEquipUpdate(m)); - - state.Send(hairPacket); - } - - if (sendFacialHair) - { - facialhairPacket ??= removeFacialHair - ? Packet.Acquire(new RemoveFacialHair(m)) - : Packet.Acquire(new FacialHairEquipUpdate(m)); - - state.Send(facialhairPacket); - } - - SendOPLPacketTo(state); + continue; } - Packet.Release(statPacketTrue); - Packet.Release(statPacketFalse); - Packet.Release(hairPacket); - Packet.Release(facialhairPacket); - Packet.Release(hbpPacket); - Packet.Release(hbyPacket); + if (sendRemove) + { + if (removeEntity[0] == 0) + { + OutgoingEntityPackets.CreateRemoveEntity(removeEntity, Serial); + } - eable.Free(); + state.Send(removeEntity); + } + + if (sendIncoming) + { + state.Send(new MobileIncoming(state, beholder, this)); + + if (IsDeadBondedPet) + { + if (deadBuffer[0] == 0) + { + OutgoingMobilePackets.CreateBondedStatus(deadBuffer, Serial, true); + } + + state.Send(deadBuffer); + } + } + + if (sendMoving || !state.StygianAbyss && (sendHealthbarPoison || sendHealthbarYellow)) + { + state.SendMobileMovingUsingCache(mobileMovingPackets, beholder, this); + } + + if (state.StygianAbyss) + { + if (sendHealthbarPoison) + { + if (hbpBuffer[0] == 0) + { + OutgoingMobilePackets.CreateMobileHealthbar(hbpBuffer, this, Healthbar.Poison); + } + + state.Send(hbpBuffer); + } + + if (sendHealthbarYellow) + { + if (hbyBuffer[0] == 0) + { + OutgoingMobilePackets.CreateMobileHealthbar(hbyBuffer, this, Healthbar.Yellow); + } + + state.Send(hbyBuffer); + } + } + + if (sendPublicStats) + { + if (CanBeRenamedBy(beholder)) + { + if (statBufferTrue[0] == 0) + { + OutgoingMobilePackets.CreateMobileStatusCompact(statBufferTrue, this, true); + } + + state.Send(statBufferTrue); + } + else + { + if (statBufferFalse[0] == 0) + { + OutgoingMobilePackets.CreateMobileStatusCompact(statBufferFalse, this, false); + } + + state.Send(statBufferFalse); + } + } + else if (sendHits) + { + if (hitsPacket[0] == 0) + { + OutgoingMobilePackets.CreateMobileHits(hitsPacket, this, true); + } + + state.Send(hitsPacket); + } + + if (sendHair) + { + hairPacket ??= removeHair + ? Packet.Acquire(new RemoveHair(this)) + : Packet.Acquire(new HairEquipUpdate(this)); + + state.Send(hairPacket); + } + + if (sendFacialHair) + { + facialhairPacket ??= removeFacialHair + ? Packet.Acquire(new RemoveFacialHair(this)) + : Packet.Acquire(new FacialHairEquipUpdate(this)); + + state.Send(facialhairPacket); + } + + SendOPLPacketTo(state); } + + Packet.Release(hairPacket); + Packet.Release(facialhairPacket); + + eable.Free(); } public ISpawner Spawner { get; set; } @@ -8097,7 +8107,7 @@ namespace Server { if (from.Map == Map && Utility.InUpdateRange(this, from) && from.CanSee(this)) { - from.Send(new MobileStatus(from, this, m_NetState)); + from.m_NetState.SendMobileStatus(from, this); } if (from == this) diff --git a/Projects/Server/Network/Packets/IncomingAccountPackets.cs b/Projects/Server/Network/Packets/IncomingAccountPackets.cs index 165db02d4..979179a21 100644 --- a/Projects/Server/Network/Packets/IncomingAccountPackets.cs +++ b/Projects/Server/Network/Packets/IncomingAccountPackets.cs @@ -324,16 +324,16 @@ namespace Server.Network state.Send(new MobileUpdate(m, state.StygianAbyss)); state.Send(new MobileIncoming(state, m, m)); - // state.Send( new MobileAttributes( m ) ); - state.Send(new MobileStatus(m, m)); + + state.SendMobileStatus(m); state.SendSetWarMode(m.Warmode); m.SendEverything(); state.SendSupportedFeature(); state.Send(new MobileUpdate(m, state.StygianAbyss)); - // state.Send( new MobileAttributes( m ) ); - state.Send(new MobileStatus(m, m)); + + state.SendMobileStatus(m); state.SendSetWarMode(m.Warmode); state.Send(new MobileIncoming(state, m, m)); diff --git a/Projects/Server/Network/Packets/MobilePackets.cs b/Projects/Server/Network/Packets/MobilePackets.cs index 632afd723..a439e168c 100644 --- a/Projects/Server/Network/Packets/MobilePackets.cs +++ b/Projects/Server/Network/Packets/MobilePackets.cs @@ -17,248 +17,6 @@ using System.Threading; namespace Server.Network { - public sealed class MobileStatusCompact : Packet - { - public MobileStatusCompact(bool canBeRenamed, Mobile m) : base(0x11) - { - EnsureCapacity(43); - - Stream.Write(m.Serial); - Stream.WriteAsciiFixed(m.Name ?? "", 30); - - AttributeNormalizer.WriteReverse(Stream, m.Hits, m.HitsMax); - - Stream.Write(canBeRenamed); - - Stream.Write((byte)0); // type - } - } - - public sealed class MobileStatusExtended : Packet - { - public MobileStatusExtended(Mobile m) : this(m, m.NetState) - { - } - - public MobileStatusExtended(Mobile m, NetState ns) : base(0x11) - { - var name = m.Name ?? ""; - - int type; - - if (Core.HS && ns?.ExtendedStatus == true) - { - type = 6; - EnsureCapacity(121); - } - else if (Core.ML && ns?.SupportsExpansion(Expansion.ML) == true) - { - type = 5; - EnsureCapacity(91); - } - else - { - type = Core.AOS ? 4 : 3; - EnsureCapacity(88); - } - - Stream.Write(m.Serial); - Stream.WriteAsciiFixed(name, 30); - - Stream.Write((short)m.Hits); - Stream.Write((short)m.HitsMax); - - Stream.Write(m.CanBeRenamedBy(m)); - - Stream.Write((byte)type); - - Stream.Write(m.Female); - - Stream.Write((short)m.Str); - Stream.Write((short)m.Dex); - Stream.Write((short)m.Int); - - Stream.Write((short)m.Stam); - Stream.Write((short)m.StamMax); - - Stream.Write((short)m.Mana); - Stream.Write((short)m.ManaMax); - - Stream.Write(m.TotalGold); - Stream.Write((short)(Core.AOS ? m.PhysicalResistance : (int)(m.ArmorRating + 0.5))); - Stream.Write((short)(Mobile.BodyWeight + m.TotalWeight)); - - if (type >= 5) - { - Stream.Write((short)m.MaxWeight); - Stream.Write((byte)(m.Race.RaceID + 1)); // Would be 0x00 if it's a non-ML enabled account but... - } - - Stream.Write((short)m.StatCap); - - Stream.Write((byte)m.Followers); - Stream.Write((byte)m.FollowersMax); - - if (type >= 4) - { - Stream.Write((short)m.FireResistance); // Fire - Stream.Write((short)m.ColdResistance); // Cold - Stream.Write((short)m.PoisonResistance); // Poison - Stream.Write((short)m.EnergyResistance); // Energy - Stream.Write((short)m.Luck); // Luck - - var weapon = m.Weapon; - - if (weapon != null) - { - weapon.GetStatusDamage(m, out var min, out var max); - Stream.Write((short)min); // Damage min - Stream.Write((short)max); // Damage max - } - else - { - Stream.Write((short)0); // Damage min - Stream.Write((short)0); // Damage max - } - - Stream.Write(m.TithingPoints); - } - - if (type >= 6) - { - for (var i = 0; i < 15; ++i) - { - Stream.Write((short)m.GetAOSStatus(i)); - } - } - } - } - - public sealed class MobileStatus : Packet - { - public MobileStatus(Mobile beholder, Mobile beheld) : this(beholder, beheld, beheld.NetState) - { - } - - public MobileStatus(Mobile beholder, Mobile beheld, NetState ns) : base(0x11) - { - var name = beheld.Name ?? ""; - - int type; - - if (beholder != beheld) - { - type = 0; - EnsureCapacity(43); - } - else if (Core.HS && ns?.ExtendedStatus == true) - { - type = 6; - EnsureCapacity(121); - } - else if (Core.ML && ns?.SupportsExpansion(Expansion.ML) == true) - { - type = 5; - EnsureCapacity(91); - } - else - { - type = Core.AOS ? 4 : 3; - EnsureCapacity(88); - } - - Stream.Write(beheld.Serial); - - Stream.WriteAsciiFixed(name, 30); - - if (beholder == beheld) - { - WriteAttr(beheld.Hits, beheld.HitsMax); - } - else - { - WriteAttrNorm(beheld.Hits, beheld.HitsMax); - } - - Stream.Write(beheld.CanBeRenamedBy(beholder)); - - Stream.Write((byte)type); - - if (type <= 0) - { - return; - } - - Stream.Write(beheld.Female); - - Stream.Write((short)beheld.Str); - Stream.Write((short)beheld.Dex); - Stream.Write((short)beheld.Int); - - WriteAttr(beheld.Stam, beheld.StamMax); - WriteAttr(beheld.Mana, beheld.ManaMax); - - Stream.Write(beheld.TotalGold); - Stream.Write((short)(Core.AOS ? beheld.PhysicalResistance : (int)(beheld.ArmorRating + 0.5))); - Stream.Write((short)(Mobile.BodyWeight + beheld.TotalWeight)); - - if (type >= 5) - { - Stream.Write((short)beheld.MaxWeight); - Stream.Write((byte)(beheld.Race.RaceID + 1)); // Would be 0x00 if it's a non-ML enabled account but... - } - - Stream.Write((short)beheld.StatCap); - - Stream.Write((byte)beheld.Followers); - Stream.Write((byte)beheld.FollowersMax); - - if (type >= 4) - { - Stream.Write((short)beheld.FireResistance); // Fire - Stream.Write((short)beheld.ColdResistance); // Cold - Stream.Write((short)beheld.PoisonResistance); // Poison - Stream.Write((short)beheld.EnergyResistance); // Energy - Stream.Write((short)beheld.Luck); // Luck - - var weapon = beheld.Weapon; - - if (weapon != null) - { - weapon.GetStatusDamage(beheld, out var min, out var max); - Stream.Write((short)min); // Damage min - Stream.Write((short)max); // Damage max - } - else - { - Stream.Write((short)0); // Damage min - Stream.Write((short)0); // Damage max - } - - Stream.Write(beheld.TithingPoints); - } - - if (type >= 6) - { - for (var i = 0; i < 15; ++i) - { - Stream.Write((short)beheld.GetAOSStatus(i)); - } - } - } - - private void WriteAttr(int current, int maximum) - { - Stream.Write((short)current); - Stream.Write((short)maximum); - } - - private void WriteAttrNorm(int current, int maximum) - { - AttributeNormalizer.WriteReverse(Stream, current, maximum); - } - } - public sealed class MobileUpdate : Packet { public MobileUpdate(Mobile m, bool stygianAbyss) : base(0x20, 19) diff --git a/Projects/Server/Network/Packets/OutgoingMobilePackets.cs b/Projects/Server/Network/Packets/OutgoingMobilePackets.cs index 3afe90b6d..8fddde255 100644 --- a/Projects/Server/Network/Packets/OutgoingMobilePackets.cs +++ b/Projects/Server/Network/Packets/OutgoingMobilePackets.cs @@ -15,6 +15,7 @@ using System; using System.Buffers; +using System.IO; using System.Runtime.CompilerServices; namespace Server.Network @@ -31,6 +32,8 @@ namespace Server.Network public const int MobileAnimationPacketLength = 14; public const int NewMobileAnimationPacketLength = 10; public const int MobileHealthbarPacketLength = 12; + public const int MobileStatusCompactLength = 43; + public const int MobileStatusMaxLength = 121; public static void CreateBondedStatus(Span buffer, Serial serial, bool bonded) { @@ -143,7 +146,9 @@ namespace Server.Network } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void WriteAttribute(this ref SpanWriter writer, int max, int cur, bool normalize = false, bool reverse = false) + private static void WriteAttribute( + this ref SpanWriter writer, int max, int cur, bool normalize = false, bool reverse = false + ) { if (normalize && max != 0) { @@ -374,5 +379,139 @@ namespace Server.Network writer.Write((short)healthbar); writer.Write((byte)level); // 0 is off for that bar type } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void CreateMobileStatusCompact(Span buffer, Mobile m, bool canBeRenamed) => + CreateMobileStatus(buffer, null, m, 0, canBeRenamed); + + public static void SendMobileStatusCompact(this NetState ns, Mobile m, bool canBeRenamed) + { + if (ns == null) + { + return; + } + + Span buffer = stackalloc byte[MobileStatusCompactLength]; + CreateMobileStatusCompact(buffer, m, canBeRenamed); + + ns.Send(buffer); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void SendMobileStatus(this NetState ns, Mobile m) => ns.SendMobileStatus(m, m); + + public static void SendMobileStatus(this NetState ns, Mobile beholder, Mobile beheld) + { + if (ns == null || beheld == null) + { + return; + } + + Span buffer = stackalloc byte[MobileStatusMaxLength]; + int version; + + if (beholder != beheld) + { + version = 0; + } + else if (Core.HS && ns.ExtendedStatus) + { + version = 6; + } + else if (Core.ML && ns.SupportsExpansion(Expansion.ML)) + { + version = 5; + } + else + { + version = Core.AOS ? 4 : 3; + } + + var length = CreateMobileStatus(buffer, beholder, beheld, version, beheld.CanBeRenamedBy(beholder)); + ns.Send(buffer.Slice(0, length)); + } + + public static int CreateMobileStatus( + Span buffer, Mobile beholder, Mobile beheld, int version, bool canBeRenamed + ) + { + var name = beheld.Name ?? ""; + + var writer = new SpanWriter(buffer); + writer.Write((byte)0x11); // Packet ID + writer.Seek(2, SeekOrigin.Current); + + writer.Write(beheld.Serial); + + writer.WriteAscii(name, 30); + + writer.WriteAttribute(beheld.HitsMax, beheld.Hits, version == 0, true); + + writer.Write(canBeRenamed); + + writer.Write((byte)version); + + if (version <= 0) + { + writer.WritePacketLength(); + return writer.Position; + } + + writer.Write(beheld.Female); + + writer.Write((short)beheld.Str); + writer.Write((short)beheld.Dex); + writer.Write((short)beheld.Int); + + writer.Write((short)beheld.Stam); + writer.Write((short)beheld.StamMax); + writer.Write((short)beheld.Mana); + writer.Write((short)beheld.ManaMax); + + writer.Write(beheld.TotalGold); + writer.Write((short)(Core.AOS ? beheld.PhysicalResistance : (int)(beheld.ArmorRating + 0.5))); + writer.Write((short)(Mobile.BodyWeight + beheld.TotalWeight)); + + if (version >= 5) + { + writer.Write((short)beheld.MaxWeight); + writer.Write((byte)(beheld.Race?.RaceID + 1 ?? 0)); // Would be 0x00 if it's a non-ML enabled account but... + } + + writer.Write((short)beheld.StatCap); + + writer.Write((byte)beheld.Followers); + writer.Write((byte)beheld.FollowersMax); + + if (version >= 4) + { + writer.Write((short)beheld.FireResistance); // Fire + writer.Write((short)beheld.ColdResistance); // Cold + writer.Write((short)beheld.PoisonResistance); // Poison + writer.Write((short)beheld.EnergyResistance); // Energy + writer.Write((short)beheld.Luck); // Luck + + var weapon = beheld.Weapon; + + int min = 0, max = 0; + weapon?.GetStatusDamage(beheld, out min, out max); + writer.Write((short)min); // Damage min + writer.Write((short)max); // Damage max + + writer.Write(beheld.TithingPoints); + } + + if (version >= 6) + { + for (var i = 0; i < 15; ++i) + { + writer.Write((short)beheld.GetAOSStatus(i)); + } + } + + writer.WritePacketLength(); + + return writer.Position; + } } } diff --git a/Projects/Server/SecureTrade.cs b/Projects/Server/SecureTrade.cs index 53daf3373..12a582c2e 100644 --- a/Projects/Server/SecureTrade.cs +++ b/Projects/Server/SecureTrade.cs @@ -17,7 +17,7 @@ namespace Server var from704565 = from.NetState?.NewSecureTrading == true; var to704565 = to.NetState?.NewSecureTrading == true; - from.Send(new MobileStatus(from, to)); + from.NetState.SendMobileStatus(from, to); from.NetState.SendUpdateSecureTrade(From.Container, false, false); from.NetState.SendSecureTradeEquip(To.Container, to); @@ -37,7 +37,7 @@ namespace Server ); } - to.Send(new MobileStatus(to, from)); + to.NetState.SendMobileStatus(to, from); to.NetState.SendUpdateSecureTrade(To.Container, false, false); to.NetState.SendSecureTradeEquip(From.Container, from); diff --git a/Projects/UOContent/Engines/Party/Party.cs b/Projects/UOContent/Engines/Party/Party.cs index 4a45b4da2..da5c5dd5c 100644 --- a/Projects/UOContent/Engines/Party/Party.cs +++ b/Projects/UOContent/Engines/Party/Party.cs @@ -89,7 +89,7 @@ namespace Server.Engines.PartySystem { if (!beholder.CanSee(beheld)) { - beholder.Send(new MobileStatusCompact(beheld.CanBeRenamedBy(beholder), beheld)); + beholder.NetState.SendMobileStatusCompact(beheld, beheld.CanBeRenamedBy(beholder)); } beholder.NetState.SendMobileAttributes(beheld, true); @@ -204,9 +204,9 @@ namespace Server.Engines.PartySystem if (f != m) { - f.Send(new MobileStatusCompact(m.CanBeRenamedBy(f), m)); + f.NetState.SendMobileStatusCompact(m, m.CanBeRenamedBy(f)); f.NetState?.Send(attrsPacket); - m.Send(new MobileStatusCompact(f.CanBeRenamedBy(m), f)); + ns.SendMobileStatusCompact(f, f.CanBeRenamedBy(m)); ns.SendMobileAttributes(f, true); } } @@ -537,9 +537,9 @@ namespace Server.Engines.PartySystem } m.NetState?.Send(buffer); - m.Send(new MobileStatusCompact(m_Mobile.CanBeRenamedBy(m), m_Mobile)); + m.NetState.SendMobileStatusCompact(m_Mobile, m_Mobile.CanBeRenamedBy(m)); m.NetState?.Send(attrsPacket); - m_Mobile.Send(new MobileStatusCompact(m.CanBeRenamedBy(m_Mobile), m)); + ns.SendMobileStatusCompact(m, m.CanBeRenamedBy(m_Mobile)); ns.SendMobileAttributes(m, true); } } diff --git a/Projects/UOContent/Mobiles/Vendors/BaseVendor.cs b/Projects/UOContent/Mobiles/Vendors/BaseVendor.cs index 0201b0ed6..e1c1c46ca 100644 --- a/Projects/UOContent/Mobiles/Vendors/BaseVendor.cs +++ b/Projects/UOContent/Mobiles/Vendors/BaseVendor.cs @@ -953,8 +953,7 @@ namespace Server.Mobiles from.NetState.SendVendorBuyContent(list); from.NetState.SendVendorBuyList(this, list); from.NetState.SendDisplayBuyList(Serial); - - from.Send(new MobileStatusExtended(from)); // make sure their gold amount is sent + from.NetState.SendMobileStatus(from); // make sure their gold amount is sent for (var i = 0; i < opls.Count; ++i) {