fix: Hair and facial hair "teleporting" when mobile dies several times (#1901)
### Summary * Added World.NewVirtual for creating virtual serial numbers * Reserved range 0x7EEEEEEE to 0x7FFFFFFF for virtual serials * Hair and Facial hair (for mobiles) now use virtual serials instead of FakeSerial() functions * Consolidated virtual hair to a single `VirtualHairInfo` class. Corpse hair and facial hair now persists across save/load and hair and facial hair no longer teleport to newest corpse.
This commit is contained in:
parent
883d873c1c
commit
91e37fb8d4
15 changed files with 548 additions and 357 deletions
|
|
@ -504,13 +504,16 @@ namespace Server.Tests.Network
|
|||
|
||||
var eq = beheld.Items;
|
||||
var count = eq.Count;
|
||||
var hair = beheld.Hair;
|
||||
var facialHair = beheld.FacialHair;
|
||||
|
||||
if (beheld.HairItemID > 0)
|
||||
|
||||
if (hair != null)
|
||||
{
|
||||
count++;
|
||||
}
|
||||
|
||||
if (beheld.FacialHairItemID > 0)
|
||||
if (facialHair != null)
|
||||
{
|
||||
count++;
|
||||
}
|
||||
|
|
@ -570,19 +573,19 @@ namespace Server.Tests.Network
|
|||
}
|
||||
}
|
||||
|
||||
if (beheld.HairItemID > 0)
|
||||
if (hair?.ItemId > 0)
|
||||
{
|
||||
if (m_DupedLayers![(int)Layer.Hair] != m_Version)
|
||||
{
|
||||
m_DupedLayers[(int)Layer.Hair] = m_Version;
|
||||
hue = beheld.HairHue;
|
||||
hue = hair.Hue;
|
||||
|
||||
if (beheld.SolidHueOverride >= 0)
|
||||
{
|
||||
hue = beheld.SolidHueOverride;
|
||||
}
|
||||
|
||||
var itemID = beheld.HairItemID & itemIdMask;
|
||||
var itemID = hair.ItemId & itemIdMask;
|
||||
var writeHue = newPacket || hue != 0;
|
||||
|
||||
if (!newPacket && writeHue)
|
||||
|
|
@ -590,7 +593,7 @@ namespace Server.Tests.Network
|
|||
itemID |= 0x8000;
|
||||
}
|
||||
|
||||
Stream.Write(HairInfo.FakeSerial(beheld.Serial));
|
||||
Stream.Write(hair.VirtualSerial);
|
||||
Stream.Write((ushort)itemID);
|
||||
Stream.Write((byte)Layer.Hair);
|
||||
|
||||
|
|
@ -601,19 +604,19 @@ namespace Server.Tests.Network
|
|||
}
|
||||
}
|
||||
|
||||
if (beheld.FacialHairItemID > 0)
|
||||
if (facialHair?.ItemId > 0)
|
||||
{
|
||||
if (m_DupedLayers![(int)Layer.FacialHair] != m_Version)
|
||||
{
|
||||
m_DupedLayers[(int)Layer.FacialHair] = m_Version;
|
||||
hue = beheld.FacialHairHue;
|
||||
hue = facialHair.Hue;
|
||||
|
||||
if (beheld.SolidHueOverride >= 0)
|
||||
{
|
||||
hue = beheld.SolidHueOverride;
|
||||
}
|
||||
|
||||
var itemID = beheld.FacialHairItemID & itemIdMask;
|
||||
var itemID = facialHair.ItemId & itemIdMask;
|
||||
var writeHue = newPacket || hue != 0;
|
||||
|
||||
if (!newPacket && writeHue)
|
||||
|
|
@ -621,7 +624,7 @@ namespace Server.Tests.Network
|
|||
itemID |= 0x8000;
|
||||
}
|
||||
|
||||
Stream.Write(FacialHairInfo.FakeSerial(beheld.Serial));
|
||||
Stream.Write(facialHair.VirtualSerial);
|
||||
Stream.Write((ushort)itemID);
|
||||
Stream.Write((byte)Layer.FacialHair);
|
||||
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ namespace Server.Tests
|
|||
var expected = new HairEquipUpdate(m).Compile();
|
||||
|
||||
var ns = PacketTestUtilities.CreateTestNetState();
|
||||
ns.SendHairEquipUpdatePacket(m, HairInfo.FakeSerial(m.Serial), m.HairItemID, m.HairHue, Layer.Hair);
|
||||
ns.SendHairEquipUpdatePacket(m, (uint)m.Hair.VirtualSerial, m.Hair.ItemId, m.Hair.Hue, Layer.Hair);
|
||||
|
||||
var result = ns.SendPipe.Reader.AvailableToRead();
|
||||
AssertThat.Equal(result, expected);
|
||||
|
|
@ -32,7 +32,7 @@ namespace Server.Tests
|
|||
var expected = new RemoveHair(m).Compile();
|
||||
|
||||
var ns = PacketTestUtilities.CreateTestNetState();
|
||||
ns.SendRemoveHairPacket(HairInfo.FakeSerial(m.Serial));
|
||||
ns.SendRemoveHairPacket((uint) m.Hair.VirtualSerial);
|
||||
|
||||
var result = ns.SendPipe.Reader.AvailableToRead();
|
||||
AssertThat.Equal(result, expected);
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ namespace Server.Network
|
|||
{
|
||||
var hue = parent.SolidHueOverride >= 0 ? parent.SolidHueOverride : parent.HairHue;
|
||||
|
||||
Stream.Write(HairInfo.FakeSerial(parent.Serial));
|
||||
Stream.Write(parent.Hair.VirtualSerial);
|
||||
Stream.Write((short)parent.HairItemID);
|
||||
Stream.Write((byte)0);
|
||||
Stream.Write((byte)Layer.Hair);
|
||||
|
|
@ -23,7 +23,7 @@ namespace Server.Network
|
|||
{
|
||||
var hue = parent.SolidHueOverride >= 0 ? parent.SolidHueOverride : parent.FacialHairHue;
|
||||
|
||||
Stream.Write(FacialHairInfo.FakeSerial(parent.Serial));
|
||||
Stream.Write(parent.FacialHair.VirtualSerial);
|
||||
Stream.Write((short)parent.FacialHairItemID);
|
||||
Stream.Write((byte)0);
|
||||
Stream.Write((byte)Layer.FacialHair);
|
||||
|
|
@ -37,7 +37,7 @@ namespace Server.Network
|
|||
public RemoveHair(Mobile parent)
|
||||
: base(0x1D, 5)
|
||||
{
|
||||
Stream.Write(HairInfo.FakeSerial(parent.Serial));
|
||||
Stream.Write(parent.Hair.VirtualSerial);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -46,7 +46,7 @@ namespace Server.Network
|
|||
public RemoveFacialHair(Mobile parent)
|
||||
: base(0x1D, 5)
|
||||
{
|
||||
Stream.Write(FacialHairInfo.FakeSerial(parent.Serial));
|
||||
Stream.Write(parent.FacialHair.VirtualSerial);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
24
Projects/Server.Tests/Tests/World/VirtualSerialTests.cs
Normal file
24
Projects/Server.Tests/Tests/World/VirtualSerialTests.cs
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
using Xunit;
|
||||
|
||||
namespace Server.Tests;
|
||||
|
||||
[Collection("Sequential Tests")]
|
||||
public class VirtualSerialTests : IClassFixture<ServerFixture>
|
||||
{
|
||||
[Fact]
|
||||
public void TestNewVirtualGetsAndRollover()
|
||||
{
|
||||
// Acquire virtual serials until we hit the max
|
||||
Serial lastSerial = World.NewVirtual;
|
||||
do
|
||||
{
|
||||
Serial virtualSerial = World.NewVirtual;
|
||||
Assert.Equal(lastSerial + 1, virtualSerial);
|
||||
lastSerial = (Serial) (uint) virtualSerial;
|
||||
} while (lastSerial != World.MaxVirtualSerial);
|
||||
|
||||
// Next one should be MaxItemSerial (the first VirtualSerial) due to rollover
|
||||
var nextSerial = World.NewVirtual;
|
||||
Assert.Equal(nextSerial, (Serial)World.ResetVirtualSerial);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using System.Buffers;
|
||||
using System.Runtime.CompilerServices;
|
||||
using ModernUO.Serialization;
|
||||
using Server.Network;
|
||||
|
||||
namespace Server;
|
||||
|
|
@ -65,83 +66,33 @@ public static class OutgoingVirtualHairPackets
|
|||
}
|
||||
}
|
||||
|
||||
public abstract class BaseHairInfo
|
||||
[SerializationGenerator(0, false)]
|
||||
public partial class VirtualHairInfo
|
||||
{
|
||||
protected BaseHairInfo(int itemid, int hue = 0)
|
||||
[SerializableField(0)]
|
||||
[SerializedCommandProperty(AccessLevel.GameMaster)]
|
||||
private int _itemId;
|
||||
|
||||
[SerializableField(1)]
|
||||
[SerializedCommandProperty(AccessLevel.GameMaster)]
|
||||
private int _hue;
|
||||
|
||||
public VirtualHairInfo() : this(0)
|
||||
{
|
||||
ItemID = itemid;
|
||||
Hue = hue;
|
||||
}
|
||||
|
||||
protected BaseHairInfo(IGenericReader reader)
|
||||
public VirtualHairInfo(int itemid, int hue = 0)
|
||||
{
|
||||
var version = reader.ReadInt();
|
||||
|
||||
switch (version)
|
||||
{
|
||||
case 0:
|
||||
{
|
||||
ItemID = reader.ReadInt();
|
||||
Hue = reader.ReadInt();
|
||||
break;
|
||||
}
|
||||
}
|
||||
_itemId = itemid;
|
||||
_hue = hue;
|
||||
VirtualSerial = World.NewVirtual;
|
||||
}
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public int ItemID { get; set; }
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public int Hue { get; set; }
|
||||
|
||||
public virtual void Serialize(IGenericWriter writer)
|
||||
[AfterDeserialization]
|
||||
private void AfterDeserialization()
|
||||
{
|
||||
writer.Write(0); // version
|
||||
writer.Write(ItemID);
|
||||
writer.Write(Hue);
|
||||
VirtualSerial = World.NewVirtual;
|
||||
}
|
||||
}
|
||||
|
||||
public class HairInfo : BaseHairInfo
|
||||
{
|
||||
public HairInfo(int itemid)
|
||||
: base(itemid)
|
||||
{
|
||||
}
|
||||
|
||||
public HairInfo(int itemid, int hue)
|
||||
: base(itemid, hue)
|
||||
{
|
||||
}
|
||||
|
||||
public HairInfo(IGenericReader reader)
|
||||
: base(reader)
|
||||
{
|
||||
}
|
||||
|
||||
// TODO: Can we make this higher for newer clients?
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static uint FakeSerial(Serial m) => 0x7FFFFFFF - 0x400 - m.Value * 4;
|
||||
}
|
||||
|
||||
public class FacialHairInfo : BaseHairInfo
|
||||
{
|
||||
public FacialHairInfo(int itemid)
|
||||
: base(itemid)
|
||||
{
|
||||
}
|
||||
|
||||
public FacialHairInfo(int itemid, int hue)
|
||||
: base(itemid, hue)
|
||||
{
|
||||
}
|
||||
|
||||
public FacialHairInfo(IGenericReader reader)
|
||||
: base(reader)
|
||||
{
|
||||
}
|
||||
|
||||
// TODO: Can we make this higher for newer clients?
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static uint FakeSerial(Serial m) => 0x7FFFFFFF - 0x400 - 1 - m.Value* 4;
|
||||
|
||||
public Serial VirtualSerial { get; private set; }
|
||||
}
|
||||
|
|
|
|||
22
Projects/Server/Migrations/Server.VirtualHairInfo.v0.json
generated
Normal file
22
Projects/Server/Migrations/Server.VirtualHairInfo.v0.json
generated
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"version": 0,
|
||||
"type": "Server.VirtualHairInfo",
|
||||
"properties": [
|
||||
{
|
||||
"name": "ItemId",
|
||||
"type": "int",
|
||||
"rule": "PrimitiveTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Hue",
|
||||
"type": "int",
|
||||
"rule": "PrimitiveTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
""
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -187,7 +187,7 @@ public delegate bool AllowBeneficialHandler(Mobile from, Mobile target);
|
|||
public delegate bool AllowHarmfulHandler(Mobile from, Mobile target);
|
||||
|
||||
public delegate Container CreateCorpseHandler(
|
||||
Mobile from, HairInfo hair, FacialHairInfo facialhair, List<Item> initialContent, List<Item> equippedItems
|
||||
Mobile from, List<Item> initialContent, List<Item> equippedItems
|
||||
);
|
||||
|
||||
public delegate int AOSStatusHandler(Mobile from, int index);
|
||||
|
|
@ -273,7 +273,9 @@ public partial class Mobile : IHued, IComparable<Mobile>, ISpawnable, IObjectPro
|
|||
private TimerExecutionToken _expireAggrTimerToken;
|
||||
private TimerExecutionToken _expireCombatantTimerToken;
|
||||
private TimerExecutionToken _expireCriminalTimerToken;
|
||||
private FacialHairInfo m_FacialHair;
|
||||
private VirtualHairInfo _facialHair;
|
||||
public VirtualHairInfo FacialHair => _facialHair ??= new VirtualHairInfo(FacialHairItemID, FacialHairHue);
|
||||
|
||||
private int m_Fame, m_Karma;
|
||||
private bool m_Female, m_Warmode, m_Hidden, m_Blessed, m_Flying;
|
||||
private int m_Followers, m_FollowersMax;
|
||||
|
|
@ -282,7 +284,9 @@ public partial class Mobile : IHued, IComparable<Mobile>, ISpawnable, IObjectPro
|
|||
private BaseGuild m_Guild;
|
||||
private string m_GuildTitle;
|
||||
|
||||
private HairInfo m_Hair;
|
||||
private VirtualHairInfo _hair;
|
||||
public VirtualHairInfo Hair => _hair ??= new VirtualHairInfo(HairItemID, HairHue);
|
||||
|
||||
private int m_Hits, m_Stam, m_Mana;
|
||||
|
||||
private Item m_Holding;
|
||||
|
|
@ -2165,59 +2169,62 @@ public partial class Mobile : IHued, IComparable<Mobile>, ISpawnable, IObjectPro
|
|||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public int HairItemID
|
||||
{
|
||||
get => m_Hair?.ItemID ?? 0;
|
||||
get => _hair?.ItemId ?? 0;
|
||||
set
|
||||
{
|
||||
if (m_Hair == null && value > 0)
|
||||
if (_hair == null && value > 0)
|
||||
{
|
||||
m_Hair = new HairInfo(value);
|
||||
_hair = new VirtualHairInfo(value);
|
||||
}
|
||||
else if (value <= 0)
|
||||
{
|
||||
m_Hair = null;
|
||||
_hair = null;
|
||||
}
|
||||
else if (m_Hair != null)
|
||||
else if (_hair != null)
|
||||
{
|
||||
m_Hair.ItemID = value;
|
||||
_hair.ItemId = value;
|
||||
}
|
||||
|
||||
Delta(MobileDelta.Hair);
|
||||
this.MarkDirty();
|
||||
}
|
||||
}
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public int FacialHairItemID
|
||||
{
|
||||
get => m_FacialHair?.ItemID ?? 0;
|
||||
get => _facialHair?.ItemId ?? 0;
|
||||
set
|
||||
{
|
||||
if (m_FacialHair == null && value > 0)
|
||||
if (_facialHair == null && value > 0)
|
||||
{
|
||||
m_FacialHair = new FacialHairInfo(value);
|
||||
_facialHair = new VirtualHairInfo(value);
|
||||
}
|
||||
else if (value <= 0)
|
||||
{
|
||||
m_FacialHair = null;
|
||||
_facialHair = null;
|
||||
}
|
||||
else if (m_FacialHair != null)
|
||||
else if (_facialHair != null)
|
||||
{
|
||||
m_FacialHair.ItemID = value;
|
||||
_facialHair.ItemId = value;
|
||||
}
|
||||
|
||||
Delta(MobileDelta.FacialHair);
|
||||
this.MarkDirty();
|
||||
}
|
||||
}
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public int HairHue
|
||||
{
|
||||
get => m_Hair?.Hue ?? 0;
|
||||
get => _hair?.Hue ?? 0;
|
||||
set
|
||||
{
|
||||
if (m_Hair != null)
|
||||
if (_hair != null)
|
||||
{
|
||||
m_Hair.Hue = value;
|
||||
_hair.Hue = value;
|
||||
Delta(MobileDelta.Hair);
|
||||
this.MarkDirty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2225,13 +2232,14 @@ public partial class Mobile : IHued, IComparable<Mobile>, ISpawnable, IObjectPro
|
|||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public int FacialHairHue
|
||||
{
|
||||
get => m_FacialHair?.Hue ?? 0;
|
||||
get => _facialHair?.Hue ?? 0;
|
||||
set
|
||||
{
|
||||
if (m_FacialHair != null)
|
||||
if (_facialHair != null)
|
||||
{
|
||||
m_FacialHair.Hue = value;
|
||||
_facialHair.Hue = value;
|
||||
Delta(MobileDelta.FacialHair);
|
||||
this.MarkDirty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2280,12 +2288,12 @@ public partial class Mobile : IHued, IComparable<Mobile>, ISpawnable, IObjectPro
|
|||
|
||||
byte hairflag = 0x00;
|
||||
|
||||
if (m_Hair != null)
|
||||
if (_hair != null)
|
||||
{
|
||||
hairflag |= 0x01;
|
||||
}
|
||||
|
||||
if (m_FacialHair != null)
|
||||
if (_facialHair != null)
|
||||
{
|
||||
hairflag |= 0x02;
|
||||
}
|
||||
|
|
@ -2294,12 +2302,12 @@ public partial class Mobile : IHued, IComparable<Mobile>, ISpawnable, IObjectPro
|
|||
|
||||
if ((hairflag & 0x01) != 0)
|
||||
{
|
||||
m_Hair?.Serialize(writer);
|
||||
_hair?.Serialize(writer);
|
||||
}
|
||||
|
||||
if ((hairflag & 0x02) != 0)
|
||||
{
|
||||
m_FacialHair?.Serialize(writer);
|
||||
_facialHair?.Serialize(writer);
|
||||
}
|
||||
|
||||
writer.Write(Race);
|
||||
|
|
@ -2434,8 +2442,8 @@ public partial class Mobile : IHued, IComparable<Mobile>, ISpawnable, IObjectPro
|
|||
m_Map?.OnLeave(this);
|
||||
m_Map = null;
|
||||
|
||||
m_Hair = null;
|
||||
m_FacialHair = null;
|
||||
_hair = null;
|
||||
_facialHair = null;
|
||||
m_MountItem = null;
|
||||
|
||||
World.RemoveEntity(this);
|
||||
|
|
@ -2708,14 +2716,14 @@ public partial class Mobile : IHued, IComparable<Mobile>, ISpawnable, IObjectPro
|
|||
sendFacialHair = true;
|
||||
}
|
||||
|
||||
var hairSerial = HairInfo.FakeSerial(Serial);
|
||||
var hairSerial = Hair.VirtualSerial;
|
||||
var hairLength = removeHair
|
||||
? OutgoingVirtualHairPackets.RemovePacketLength
|
||||
: OutgoingVirtualHairPackets.EquipUpdatePacketLength;
|
||||
|
||||
Span<byte> hairPacket = stackalloc byte[hairLength].InitializePacket();
|
||||
|
||||
var facialHairSerial = FacialHairInfo.FakeSerial(Serial);
|
||||
var facialHairSerial = FacialHair.VirtualSerial;
|
||||
var facialHairLength = removeFacialHair
|
||||
? OutgoingVirtualHairPackets.RemovePacketLength
|
||||
: OutgoingVirtualHairPackets.EquipUpdatePacketLength;
|
||||
|
|
@ -2806,14 +2814,14 @@ public partial class Mobile : IHued, IComparable<Mobile>, ISpawnable, IObjectPro
|
|||
{
|
||||
if (removeHair)
|
||||
{
|
||||
OutgoingVirtualHairPackets.CreateRemoveHairPacket(hairPacket, hairSerial);
|
||||
OutgoingVirtualHairPackets.CreateRemoveHairPacket(hairPacket, (uint)hairSerial);
|
||||
}
|
||||
else
|
||||
{
|
||||
OutgoingVirtualHairPackets.CreateHairEquipUpdatePacket(
|
||||
hairPacket,
|
||||
this,
|
||||
hairSerial,
|
||||
(uint)hairSerial,
|
||||
HairItemID,
|
||||
HairHue,
|
||||
Layer.Hair
|
||||
|
|
@ -2827,14 +2835,14 @@ public partial class Mobile : IHued, IComparable<Mobile>, ISpawnable, IObjectPro
|
|||
{
|
||||
if (removeFacialHair)
|
||||
{
|
||||
OutgoingVirtualHairPackets.CreateRemoveHairPacket(facialHairPacket, facialHairSerial);
|
||||
OutgoingVirtualHairPackets.CreateRemoveHairPacket(facialHairPacket, (uint)facialHairSerial);
|
||||
}
|
||||
else
|
||||
{
|
||||
OutgoingVirtualHairPackets.CreateHairEquipUpdatePacket(
|
||||
facialHairPacket,
|
||||
this,
|
||||
facialHairSerial,
|
||||
(uint)facialHairSerial,
|
||||
FacialHairItemID,
|
||||
FacialHairHue,
|
||||
Layer.FacialHair
|
||||
|
|
@ -2943,14 +2951,14 @@ public partial class Mobile : IHued, IComparable<Mobile>, ISpawnable, IObjectPro
|
|||
{
|
||||
if (removeHair)
|
||||
{
|
||||
OutgoingVirtualHairPackets.CreateRemoveHairPacket(hairPacket, hairSerial);
|
||||
OutgoingVirtualHairPackets.CreateRemoveHairPacket(hairPacket, (uint)hairSerial);
|
||||
}
|
||||
else
|
||||
{
|
||||
OutgoingVirtualHairPackets.CreateHairEquipUpdatePacket(
|
||||
hairPacket,
|
||||
this,
|
||||
hairSerial,
|
||||
(uint)hairSerial,
|
||||
HairItemID,
|
||||
HairHue,
|
||||
Layer.Hair
|
||||
|
|
@ -2964,14 +2972,14 @@ public partial class Mobile : IHued, IComparable<Mobile>, ISpawnable, IObjectPro
|
|||
{
|
||||
if (removeFacialHair)
|
||||
{
|
||||
OutgoingVirtualHairPackets.CreateRemoveHairPacket(facialHairPacket, facialHairSerial);
|
||||
OutgoingVirtualHairPackets.CreateRemoveHairPacket(facialHairPacket, (uint)facialHairSerial);
|
||||
}
|
||||
else
|
||||
{
|
||||
OutgoingVirtualHairPackets.CreateHairEquipUpdatePacket(
|
||||
facialHairPacket,
|
||||
this,
|
||||
facialHairSerial,
|
||||
(uint)facialHairSerial,
|
||||
FacialHairItemID,
|
||||
FacialHairHue,
|
||||
Layer.FacialHair
|
||||
|
|
@ -4764,19 +4772,7 @@ public partial class Mobile : IHued, IComparable<Mobile>, ISpawnable, IObjectPro
|
|||
}
|
||||
}
|
||||
|
||||
HairInfo hair = null;
|
||||
if (m_Hair != null)
|
||||
{
|
||||
hair = new HairInfo(m_Hair.ItemID, m_Hair.Hue);
|
||||
}
|
||||
|
||||
FacialHairInfo facialhair = null;
|
||||
if (m_FacialHair != null)
|
||||
{
|
||||
facialhair = new FacialHairInfo(m_FacialHair.ItemID, m_FacialHair.Hue);
|
||||
}
|
||||
|
||||
var c = CreateCorpseHandler?.Invoke(this, hair, facialhair, content, equip);
|
||||
var c = CreateCorpseHandler?.Invoke(this, content, equip);
|
||||
|
||||
if (m_Map != null)
|
||||
{
|
||||
|
|
@ -6092,12 +6088,14 @@ public partial class Mobile : IHued, IComparable<Mobile>, ISpawnable, IObjectPro
|
|||
|
||||
if ((hairflag & 0x01) != 0)
|
||||
{
|
||||
m_Hair = new HairInfo(reader);
|
||||
_hair = new VirtualHairInfo();
|
||||
_hair.Deserialize(reader);
|
||||
}
|
||||
|
||||
if ((hairflag & 0x02) != 0)
|
||||
{
|
||||
m_FacialHair = new FacialHairInfo(reader);
|
||||
_facialHair = new VirtualHairInfo();
|
||||
_facialHair.Deserialize(reader);
|
||||
}
|
||||
|
||||
goto case 29;
|
||||
|
|
|
|||
|
|
@ -672,7 +672,7 @@ public static class OutgoingMobilePackets
|
|||
itemID |= 0x8000;
|
||||
}
|
||||
|
||||
writer.Write(HairInfo.FakeSerial(beheld.Serial));
|
||||
writer.Write(beheld.Hair.VirtualSerial);
|
||||
writer.Write((ushort)itemID);
|
||||
writer.Write((byte)Layer.Hair);
|
||||
|
||||
|
|
@ -695,7 +695,7 @@ public static class OutgoingMobilePackets
|
|||
itemID |= 0x8000;
|
||||
}
|
||||
|
||||
writer.Write(FacialHairInfo.FakeSerial(beheld.Serial));
|
||||
writer.Write(beheld.FacialHair.VirtualSerial);
|
||||
writer.Write((ushort)itemID);
|
||||
writer.Write((byte)Layer.FacialHair);
|
||||
|
||||
|
|
|
|||
|
|
@ -53,13 +53,37 @@ public static class World
|
|||
|
||||
public const bool DirtyTrackingEnabled = false;
|
||||
public const uint ItemOffset = 0x40000000;
|
||||
public const uint MaxItemSerial = 0x7FFFFFFF;
|
||||
public const uint MaxItemSerial = 0x7EEEEEEE;
|
||||
public const uint MaxMobileSerial = ItemOffset - 1;
|
||||
|
||||
public const uint ResetVirtualSerial = MaxItemSerial;
|
||||
public const uint MaxVirtualSerial = 0x7FFFFFFF;
|
||||
private static uint _nextVirtualSerial = ResetVirtualSerial;
|
||||
|
||||
public static Serial NewMobile => _mobilePersistence.NewEntity;
|
||||
public static Serial NewItem => _itemPersistence.NewEntity;
|
||||
public static Serial NewGuild => _guildPersistence.NewEntity;
|
||||
|
||||
// Virtual things don't persist across saves
|
||||
public static Serial NewVirtual
|
||||
{
|
||||
get
|
||||
{
|
||||
#if THREADGUARD
|
||||
if (Thread.CurrentThread != Core.Thread)
|
||||
{
|
||||
logger.Error(
|
||||
"Attempted to get a new virtual serial from the wrong thread!\n{StackTrace}",
|
||||
new StackTrace()
|
||||
);
|
||||
}
|
||||
#endif
|
||||
var value = _nextVirtualSerial > MaxVirtualSerial ? ResetVirtualSerial : _nextVirtualSerial;
|
||||
_nextVirtualSerial = value + 1;
|
||||
return (Serial)value;
|
||||
}
|
||||
}
|
||||
|
||||
public static Dictionary<Serial, Item> Items => _itemPersistence.EntitiesBySerial;
|
||||
public static Dictionary<Serial, Mobile> Mobiles => _mobilePersistence.EntitiesBySerial;
|
||||
public static Dictionary<Serial, BaseGuild> Guilds => _guildPersistence.EntitiesBySerial;
|
||||
|
|
|
|||
|
|
@ -7,48 +7,51 @@ public sealed class CorpseEquip : Packet
|
|||
{
|
||||
public CorpseEquip(Mobile beholder, Corpse beheld) : base(0x89)
|
||||
{
|
||||
var list = beheld.EquipItems;
|
||||
var list = beheld.EquipItems;
|
||||
|
||||
var count = list.Count;
|
||||
if (beheld.Hair?.ItemID > 0)
|
||||
{
|
||||
count++;
|
||||
}
|
||||
var count = list.Count;
|
||||
var hair = beheld.Hair;
|
||||
var facialHair = beheld.FacialHair;
|
||||
|
||||
if (beheld.FacialHair?.ItemID > 0)
|
||||
{
|
||||
count++;
|
||||
}
|
||||
|
||||
EnsureCapacity(8 + count * 5);
|
||||
|
||||
Stream.Write(beheld.Serial);
|
||||
|
||||
for (var i = 0; i < list.Count; ++i)
|
||||
{
|
||||
var item = list[i];
|
||||
|
||||
if (!item.Deleted && beholder.CanSee(item) && item.Parent == beheld)
|
||||
{
|
||||
Stream.Write((byte)(item.Layer + 1));
|
||||
Stream.Write(item.Serial);
|
||||
}
|
||||
}
|
||||
|
||||
if (beheld.Hair?.ItemID > 0)
|
||||
{
|
||||
Stream.Write((byte)(Layer.Hair + 1));
|
||||
Stream.Write(HairInfo.FakeSerial(beheld.Owner.Serial) - 2);
|
||||
}
|
||||
|
||||
if (beheld.FacialHair?.ItemID > 0)
|
||||
{
|
||||
Stream.Write((byte)(Layer.FacialHair + 1));
|
||||
Stream.Write(FacialHairInfo.FakeSerial(beheld.Owner.Serial) - 2);
|
||||
}
|
||||
|
||||
Stream.Write((byte)Layer.Invalid);
|
||||
if (hair != null)
|
||||
{
|
||||
count++;
|
||||
}
|
||||
|
||||
if (facialHair != null)
|
||||
{
|
||||
count++;
|
||||
}
|
||||
|
||||
EnsureCapacity(8 + count * 5);
|
||||
|
||||
Stream.Write(beheld.Serial);
|
||||
|
||||
for (var i = 0; i < list.Count; ++i)
|
||||
{
|
||||
var item = list[i];
|
||||
|
||||
if (!item.Deleted && beholder.CanSee(item) && item.Parent == beheld)
|
||||
{
|
||||
Stream.Write((byte)(item.Layer + 1));
|
||||
Stream.Write(item.Serial);
|
||||
}
|
||||
}
|
||||
|
||||
if (hair?.ItemId > 0)
|
||||
{
|
||||
Stream.Write((byte)(Layer.Hair + 1));
|
||||
Stream.Write(hair.VirtualSerial);
|
||||
}
|
||||
|
||||
if (facialHair?.ItemId > 0)
|
||||
{
|
||||
Stream.Write((byte)(Layer.FacialHair + 1));
|
||||
Stream.Write(facialHair.VirtualSerial);
|
||||
}
|
||||
|
||||
Stream.Write((byte)Layer.Invalid);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class CorpseContent : Packet
|
||||
|
|
@ -56,77 +59,79 @@ public sealed class CorpseContent : Packet
|
|||
public CorpseContent(Mobile beholder, Corpse beheld)
|
||||
: base(0x3C)
|
||||
{
|
||||
var items = beheld.EquipItems;
|
||||
var count = items.Count;
|
||||
var items = beheld.EquipItems;
|
||||
var count = items.Count;
|
||||
var hair = beheld.Hair;
|
||||
var facialHair = beheld.FacialHair;
|
||||
|
||||
if (beheld.Hair?.ItemID > 0)
|
||||
{
|
||||
count++;
|
||||
}
|
||||
|
||||
if (beheld.FacialHair?.ItemID > 0)
|
||||
{
|
||||
count++;
|
||||
}
|
||||
|
||||
EnsureCapacity(5 + count * 19);
|
||||
|
||||
var pos = Stream.Position;
|
||||
|
||||
var written = 0;
|
||||
|
||||
Stream.Write((ushort)0);
|
||||
|
||||
for (var i = 0; i < items.Count; ++i)
|
||||
{
|
||||
var child = items[i];
|
||||
|
||||
if (!child.Deleted && child.Parent == beheld && beholder.CanSee(child))
|
||||
{
|
||||
Stream.Write(child.Serial);
|
||||
Stream.Write((ushort)child.ItemID);
|
||||
Stream.Write((byte)0); // signed, itemID offset
|
||||
Stream.Write((ushort)child.Amount);
|
||||
Stream.Write((short)child.X);
|
||||
Stream.Write((short)child.Y);
|
||||
Stream.Write(beheld.Serial);
|
||||
Stream.Write((ushort)child.Hue);
|
||||
|
||||
++written;
|
||||
}
|
||||
}
|
||||
|
||||
if (beheld.Hair?.ItemID > 0)
|
||||
{
|
||||
Stream.Write(HairInfo.FakeSerial(beheld.Owner.Serial) - 2);
|
||||
Stream.Write((ushort)beheld.Hair.ItemID);
|
||||
Stream.Write((byte)0); // signed, itemID offset
|
||||
Stream.Write((ushort)1);
|
||||
Stream.Write((short)0);
|
||||
Stream.Write((short)0);
|
||||
Stream.Write(beheld.Serial);
|
||||
Stream.Write((ushort)beheld.Hair.Hue);
|
||||
|
||||
++written;
|
||||
}
|
||||
|
||||
if (beheld.FacialHair?.ItemID > 0)
|
||||
{
|
||||
Stream.Write(FacialHairInfo.FakeSerial(beheld.Owner.Serial) - 2);
|
||||
Stream.Write((ushort)beheld.FacialHair.ItemID);
|
||||
Stream.Write((byte)0); // signed, itemID offset
|
||||
Stream.Write((ushort)1);
|
||||
Stream.Write((short)0);
|
||||
Stream.Write((short)0);
|
||||
Stream.Write(beheld.Serial);
|
||||
Stream.Write((ushort)beheld.FacialHair.Hue);
|
||||
|
||||
++written;
|
||||
}
|
||||
|
||||
Stream.Seek(pos, SeekOrigin.Begin);
|
||||
Stream.Write((ushort)written);
|
||||
if (hair != null)
|
||||
{
|
||||
count++;
|
||||
}
|
||||
|
||||
if (facialHair != null)
|
||||
{
|
||||
count++;
|
||||
}
|
||||
|
||||
EnsureCapacity(5 + count * 19);
|
||||
|
||||
var pos = Stream.Position;
|
||||
|
||||
var written = 0;
|
||||
|
||||
Stream.Write((ushort)0);
|
||||
|
||||
for (var i = 0; i < items.Count; ++i)
|
||||
{
|
||||
var child = items[i];
|
||||
|
||||
if (!child.Deleted && child.Parent == beheld && beholder.CanSee(child))
|
||||
{
|
||||
Stream.Write(child.Serial);
|
||||
Stream.Write((ushort)child.ItemID);
|
||||
Stream.Write((byte)0); // signed, itemID offset
|
||||
Stream.Write((ushort)child.Amount);
|
||||
Stream.Write((short)child.X);
|
||||
Stream.Write((short)child.Y);
|
||||
Stream.Write(beheld.Serial);
|
||||
Stream.Write((ushort)child.Hue);
|
||||
|
||||
++written;
|
||||
}
|
||||
}
|
||||
|
||||
if (hair?.ItemId > 0)
|
||||
{
|
||||
Stream.Write(hair.VirtualSerial);
|
||||
Stream.Write((ushort)hair.ItemId);
|
||||
Stream.Write((byte)0); // signed, itemID offset
|
||||
Stream.Write((ushort)1);
|
||||
Stream.Write((short)0);
|
||||
Stream.Write((short)0);
|
||||
Stream.Write(beheld.Serial);
|
||||
Stream.Write((ushort)hair.Hue);
|
||||
|
||||
++written;
|
||||
}
|
||||
|
||||
if (facialHair?.ItemId > 0)
|
||||
{
|
||||
Stream.Write(facialHair.VirtualSerial);
|
||||
Stream.Write((ushort)facialHair.ItemId);
|
||||
Stream.Write((byte)0); // signed, itemID offset
|
||||
Stream.Write((ushort)1);
|
||||
Stream.Write((short)0);
|
||||
Stream.Write((short)0);
|
||||
Stream.Write(beheld.Serial);
|
||||
Stream.Write((ushort)facialHair.Hue);
|
||||
|
||||
++written;
|
||||
}
|
||||
|
||||
Stream.Seek(pos, SeekOrigin.Begin);
|
||||
Stream.Write((ushort)written);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class CorpseContent6017 : Packet
|
||||
|
|
@ -134,78 +139,80 @@ public sealed class CorpseContent6017 : Packet
|
|||
public CorpseContent6017(Mobile beholder, Corpse beheld)
|
||||
: base(0x3C)
|
||||
{
|
||||
var items = beheld.EquipItems;
|
||||
var count = items.Count;
|
||||
var items = beheld.EquipItems;
|
||||
var count = items.Count;
|
||||
var hair = beheld.Hair;
|
||||
var facialHair = beheld.FacialHair;
|
||||
|
||||
if (beheld.Hair?.ItemID > 0)
|
||||
{
|
||||
count++;
|
||||
}
|
||||
|
||||
if (beheld.FacialHair?.ItemID > 0)
|
||||
{
|
||||
count++;
|
||||
}
|
||||
|
||||
EnsureCapacity(5 + count * 20);
|
||||
|
||||
var pos = Stream.Position;
|
||||
|
||||
var written = 0;
|
||||
|
||||
Stream.Write((ushort)0);
|
||||
|
||||
for (var i = 0; i < items.Count; ++i)
|
||||
{
|
||||
var child = items[i];
|
||||
|
||||
if (!child.Deleted && child.Parent == beheld && beholder.CanSee(child))
|
||||
{
|
||||
Stream.Write(child.Serial);
|
||||
Stream.Write((ushort)child.ItemID);
|
||||
Stream.Write((byte)0); // signed, itemID offset
|
||||
Stream.Write((ushort)child.Amount);
|
||||
Stream.Write((short)child.X);
|
||||
Stream.Write((short)child.Y);
|
||||
Stream.Write((byte)0); // Grid Location?
|
||||
Stream.Write(beheld.Serial);
|
||||
Stream.Write((ushort)child.Hue);
|
||||
|
||||
++written;
|
||||
}
|
||||
}
|
||||
|
||||
if (beheld.Hair?.ItemID > 0)
|
||||
{
|
||||
Stream.Write(HairInfo.FakeSerial(beheld.Owner.Serial) - 2);
|
||||
Stream.Write((ushort)beheld.Hair.ItemID);
|
||||
Stream.Write((byte)0); // signed, itemID offset
|
||||
Stream.Write((ushort)1);
|
||||
Stream.Write((short)0);
|
||||
Stream.Write((short)0);
|
||||
Stream.Write((byte)0); // Grid Location?
|
||||
Stream.Write(beheld.Serial);
|
||||
Stream.Write((ushort)beheld.Hair.Hue);
|
||||
|
||||
++written;
|
||||
}
|
||||
|
||||
if (beheld.FacialHair?.ItemID > 0)
|
||||
{
|
||||
Stream.Write(FacialHairInfo.FakeSerial(beheld.Owner.Serial) - 2);
|
||||
Stream.Write((ushort)beheld.FacialHair.ItemID);
|
||||
Stream.Write((byte)0); // signed, itemID offset
|
||||
Stream.Write((ushort)1);
|
||||
Stream.Write((short)0);
|
||||
Stream.Write((short)0);
|
||||
Stream.Write((byte)0); // Grid Location?
|
||||
Stream.Write(beheld.Serial);
|
||||
Stream.Write((ushort)beheld.FacialHair.Hue);
|
||||
|
||||
++written;
|
||||
}
|
||||
|
||||
Stream.Seek(pos, SeekOrigin.Begin);
|
||||
Stream.Write((ushort)written);
|
||||
if (hair != null)
|
||||
{
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
if (facialHair != null)
|
||||
{
|
||||
count++;
|
||||
}
|
||||
|
||||
EnsureCapacity(5 + count * 20);
|
||||
|
||||
var pos = Stream.Position;
|
||||
|
||||
var written = 0;
|
||||
|
||||
Stream.Write((ushort)0);
|
||||
|
||||
for (var i = 0; i < items.Count; ++i)
|
||||
{
|
||||
var child = items[i];
|
||||
|
||||
if (!child.Deleted && child.Parent == beheld && beholder.CanSee(child))
|
||||
{
|
||||
Stream.Write(child.Serial);
|
||||
Stream.Write((ushort)child.ItemID);
|
||||
Stream.Write((byte)0); // signed, itemID offset
|
||||
Stream.Write((ushort)child.Amount);
|
||||
Stream.Write((short)child.X);
|
||||
Stream.Write((short)child.Y);
|
||||
Stream.Write((byte)0); // Grid Location?
|
||||
Stream.Write(beheld.Serial);
|
||||
Stream.Write((ushort)child.Hue);
|
||||
|
||||
++written;
|
||||
}
|
||||
}
|
||||
|
||||
if (hair?.ItemId > 0)
|
||||
{
|
||||
Stream.Write(hair.VirtualSerial);
|
||||
Stream.Write((ushort)hair.ItemId);
|
||||
Stream.Write((byte)0); // signed, itemID offset
|
||||
Stream.Write((ushort)1);
|
||||
Stream.Write((short)0);
|
||||
Stream.Write((short)0);
|
||||
Stream.Write((byte)0); // Grid Location?
|
||||
Stream.Write(beheld.Serial);
|
||||
Stream.Write((ushort)hair.Hue);
|
||||
|
||||
++written;
|
||||
}
|
||||
|
||||
if (facialHair?.ItemId > 0)
|
||||
{
|
||||
Stream.Write(facialHair.VirtualSerial);
|
||||
Stream.Write((ushort)facialHair.ItemId);
|
||||
Stream.Write((byte)0); // signed, itemID offset
|
||||
Stream.Write((ushort)1);
|
||||
Stream.Write((short)0);
|
||||
Stream.Write((short)0);
|
||||
Stream.Write((byte)0); // Grid Location?
|
||||
Stream.Write(beheld.Serial);
|
||||
Stream.Write((ushort)facialHair.Hue);
|
||||
|
||||
++written;
|
||||
}
|
||||
|
||||
Stream.Seek(pos, SeekOrigin.Begin);
|
||||
Stream.Write((ushort)written);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,17 +51,17 @@ public partial class SchmendrickApprenticeCorpse : Corpse
|
|||
new Spellbook()
|
||||
];
|
||||
|
||||
private static HairInfo GetHair()
|
||||
private static VirtualHairInfo GetHair()
|
||||
{
|
||||
_hairHue = Race.Human.RandomHairHue();
|
||||
return new HairInfo(Race.Human.RandomHair(false), _hairHue);
|
||||
return new VirtualHairInfo(Race.Human.RandomHair(false), _hairHue);
|
||||
}
|
||||
|
||||
private static FacialHairInfo GetFacialHair()
|
||||
private static VirtualHairInfo GetFacialHair()
|
||||
{
|
||||
_hairHue = Race.Human.RandomHairHue();
|
||||
|
||||
return new FacialHairInfo(Race.Human.RandomFacialHair(false), _hairHue);
|
||||
return new VirtualHairInfo(Race.Human.RandomFacialHair(false), _hairHue);
|
||||
}
|
||||
|
||||
public override void AddNameProperty(IPropertyList list)
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ public partial class MilitiaFighter : BaseCreature
|
|||
[SerializationGenerator(0, false)]
|
||||
public partial class MilitiaFighterCorpse : Corpse
|
||||
{
|
||||
public MilitiaFighterCorpse(Mobile owner, HairInfo hair, FacialHairInfo facialhair, List<Item> equipItems) : base(
|
||||
public MilitiaFighterCorpse(Mobile owner, VirtualHairInfo hair, VirtualHairInfo facialhair, List<Item> equipItems) : base(
|
||||
owner,
|
||||
hair,
|
||||
facialhair,
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ public enum CorpseFlag
|
|||
SelfLooted = 0x00000080
|
||||
}
|
||||
|
||||
[SerializationGenerator(13, false)]
|
||||
[SerializationGenerator(14, false)]
|
||||
public partial class Corpse : Container, ICarvable
|
||||
{
|
||||
public static readonly TimeSpan MonsterLootRightSacrifice = TimeSpan.FromMinutes(2.0);
|
||||
|
|
@ -132,6 +132,16 @@ public partial class Corpse : Container, ICarvable
|
|||
[SerializedCommandProperty(AccessLevel.GameMaster)]
|
||||
private List<Item> _equipItems;
|
||||
|
||||
[CanBeNull]
|
||||
[SerializableField(14, setter: "private")]
|
||||
[SerializedCommandProperty(AccessLevel.GameMaster)]
|
||||
private VirtualHairInfo _hair;
|
||||
|
||||
[CanBeNull]
|
||||
[SerializableField(15, setter: "private")]
|
||||
[SerializedCommandProperty(AccessLevel.GameMaster)]
|
||||
private VirtualHairInfo _facialHair;
|
||||
|
||||
// Why was this public?
|
||||
// public override bool IsPublicContainer => true;
|
||||
|
||||
|
|
@ -139,7 +149,7 @@ public partial class Corpse : Container, ICarvable
|
|||
{
|
||||
}
|
||||
|
||||
public Corpse(Mobile owner, HairInfo hair, FacialHairInfo facialhair, List<Item> equipItems)
|
||||
public Corpse(Mobile owner, VirtualHairInfo hair, VirtualHairInfo facialHair, List<Item> equipItems)
|
||||
: base(0x2006)
|
||||
{
|
||||
// To suppress console warnings, stackable must be true
|
||||
|
|
@ -163,8 +173,15 @@ public partial class Corpse : Container, ICarvable
|
|||
_kills = owner.Kills;
|
||||
SetFlag(CorpseFlag.Criminal, owner.Criminal);
|
||||
|
||||
Hair = hair;
|
||||
FacialHair = facialhair;
|
||||
if (hair?.ItemId > 0)
|
||||
{
|
||||
_hair = new VirtualHairInfo(hair.ItemId, hair.Hue);
|
||||
}
|
||||
|
||||
if (facialHair?.ItemId > 0)
|
||||
{
|
||||
_facialHair = new VirtualHairInfo(facialHair.ItemId, facialHair.Hue);
|
||||
}
|
||||
|
||||
// This corpse does not turn to bones if: the owner is not a player
|
||||
SetFlag(CorpseFlag.NoBones, !owner.Player);
|
||||
|
|
@ -237,6 +254,26 @@ public partial class Corpse : Container, ICarvable
|
|||
DevourCorpse();
|
||||
}
|
||||
|
||||
// Added corpse hair and corpse facial hair
|
||||
private void MigrateFrom(V13Content content)
|
||||
{
|
||||
_restoreEquip = content.RestoreEquip;
|
||||
_flags = content.Flags;
|
||||
_timeOfDeath = content.TimeOfDeath;
|
||||
_restoreTable = content.RestoreTable;
|
||||
_decayTimer = new InternalTimer(this, content.DecayTimerDelay);
|
||||
_decayTimer.Start();
|
||||
_looters = content.Looters;
|
||||
_killer = content.Killer;
|
||||
_aggressors = content.Aggressors;
|
||||
_owner = content.Owner;
|
||||
_corpseName = content.CorpseName;
|
||||
_accessLevel = content.AccessLevel;
|
||||
_guild = content.Guild;
|
||||
_kills = content.Kills;
|
||||
_equipItems = content.EquipItems;
|
||||
}
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public virtual bool InstancedCorpse => Core.SE && Core.Now < TimeOfDeath + InstancedCorpseTime;
|
||||
|
||||
|
|
@ -248,10 +285,6 @@ public partial class Corpse : Container, ICarvable
|
|||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public string Forensicist { get; set; }
|
||||
|
||||
public HairInfo Hair { get; }
|
||||
|
||||
public FacialHairInfo FacialHair { get; }
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public bool IsBones => GetFlag(CorpseFlag.IsBones);
|
||||
|
||||
|
|
@ -492,14 +525,11 @@ public partial class Corpse : Container, ICarvable
|
|||
Mobile.CreateCorpseHandler += Mobile_CreateCorpseHandler;
|
||||
}
|
||||
|
||||
public static Container Mobile_CreateCorpseHandler(
|
||||
Mobile owner, HairInfo hair, FacialHairInfo facialhair,
|
||||
List<Item> initialContent, List<Item> equipItems
|
||||
)
|
||||
public static Container Mobile_CreateCorpseHandler(Mobile owner, List<Item> initialContent, List<Item> equipItems)
|
||||
{
|
||||
var c = owner is MilitiaFighter
|
||||
? new MilitiaFighterCorpse(owner, hair, facialhair, equipItems)
|
||||
: new Corpse(owner, hair, facialhair, equipItems);
|
||||
? new MilitiaFighterCorpse(owner, owner.Hair, owner.FacialHair, equipItems)
|
||||
: new Corpse(owner, owner.Hair, owner.FacialHair, equipItems);
|
||||
|
||||
owner.Corpse = c;
|
||||
|
||||
|
|
|
|||
|
|
@ -49,16 +49,16 @@ public static class CorpsePackets
|
|||
|
||||
if (beheld.Owner != null)
|
||||
{
|
||||
if (beheld.Hair?.ItemID > 0)
|
||||
if (beheld.Hair?.ItemId > 0)
|
||||
{
|
||||
writer.Write((byte)(Layer.Hair + 1));
|
||||
writer.Write(HairInfo.FakeSerial(beheld.Owner.Serial) - 2);
|
||||
writer.Write(beheld.Hair.VirtualSerial);
|
||||
}
|
||||
|
||||
if (beheld.FacialHair?.ItemID > 0)
|
||||
if (beheld.FacialHair?.ItemId > 0)
|
||||
{
|
||||
writer.Write((byte)(Layer.FacialHair + 1));
|
||||
writer.Write(FacialHairInfo.FakeSerial(beheld.Owner.Serial) - 2);
|
||||
writer.Write(beheld.FacialHair.VirtualSerial);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -76,15 +76,16 @@ public static class CorpsePackets
|
|||
}
|
||||
|
||||
var list = beheld.EquipItems;
|
||||
var hairItemID = beheld.Hair?.ItemID ?? 0;
|
||||
var facialHairItemID = beheld.FacialHair?.ItemID ?? 0;
|
||||
var hair = beheld.Hair;
|
||||
var facialHair = beheld.FacialHair;
|
||||
|
||||
var count = list.Count;
|
||||
if (hairItemID > 0)
|
||||
if (hair != null)
|
||||
{
|
||||
count++;
|
||||
}
|
||||
|
||||
if (facialHairItemID > 0)
|
||||
if (facialHair != null)
|
||||
{
|
||||
count++;
|
||||
}
|
||||
|
|
@ -120,10 +121,10 @@ public static class CorpsePackets
|
|||
|
||||
if (beheld.Owner != null)
|
||||
{
|
||||
if (hairItemID > 0)
|
||||
if (hair?.ItemId > 0)
|
||||
{
|
||||
writer.Write(HairInfo.FakeSerial(beheld.Owner.Serial) - 2);
|
||||
writer.Write((ushort)hairItemID);
|
||||
writer.Write(hair.VirtualSerial);
|
||||
writer.Write((ushort)hair.ItemId);
|
||||
writer.Write((byte)0); // signed, itemID offset
|
||||
writer.Write((ushort)1);
|
||||
writer.Write(0); // X/Y
|
||||
|
|
@ -132,15 +133,15 @@ public static class CorpsePackets
|
|||
writer.Write((byte)0); // Grid Location?
|
||||
}
|
||||
writer.Write(beheld.Serial);
|
||||
writer.Write((ushort)beheld.Hair!.Hue);
|
||||
writer.Write((ushort)hair.Hue);
|
||||
|
||||
++written;
|
||||
}
|
||||
|
||||
if (facialHairItemID > 0)
|
||||
if (facialHair?.ItemId > 0)
|
||||
{
|
||||
writer.Write(FacialHairInfo.FakeSerial(beheld.Owner.Serial) - 2);
|
||||
writer.Write((ushort)facialHairItemID);
|
||||
writer.Write(facialHair.VirtualSerial);
|
||||
writer.Write((ushort)facialHair.ItemId);
|
||||
writer.Write((byte)0); // signed, itemID offset
|
||||
writer.Write((ushort)1);
|
||||
writer.Write(0); // X/Y
|
||||
|
|
@ -149,7 +150,7 @@ public static class CorpsePackets
|
|||
writer.Write((byte)0); // Grid Location?
|
||||
}
|
||||
writer.Write(beheld.Serial);
|
||||
writer.Write((ushort)beheld.FacialHair!.Hue);
|
||||
writer.Write((ushort)facialHair.Hue);
|
||||
|
||||
++written;
|
||||
}
|
||||
|
|
|
|||
131
Projects/UOContent/Migrations/Server.Items.Corpse.v14.json
generated
Normal file
131
Projects/UOContent/Migrations/Server.Items.Corpse.v14.json
generated
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
{
|
||||
"version": 14,
|
||||
"type": "Server.Items.Corpse",
|
||||
"properties": [
|
||||
{
|
||||
"name": "RestoreEquip",
|
||||
"type": "System.Collections.Generic.List\u003CServer.Item\u003E",
|
||||
"rule": "ListMigrationRule",
|
||||
"ruleArguments": [
|
||||
"Server.Item",
|
||||
"SerializableInterfaceMigrationRule"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Flags",
|
||||
"type": "Server.Items.CorpseFlag",
|
||||
"rule": "EnumMigrationRule"
|
||||
},
|
||||
{
|
||||
"name": "TimeOfDeath",
|
||||
"type": "System.DateTime",
|
||||
"rule": "PrimitiveTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
"DeltaTime"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "RestoreTable",
|
||||
"type": "System.Collections.Generic.Dictionary\u003CServer.Item, Server.Point3D\u003E",
|
||||
"rule": "DictionaryMigrationRule",
|
||||
"ruleArguments": [
|
||||
"Server.Item",
|
||||
"SerializableInterfaceMigrationRule",
|
||||
"0",
|
||||
"Server.Point3D",
|
||||
"PrimitiveUOTypeMigrationRule",
|
||||
"1",
|
||||
"Point3D"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "DecayTimer",
|
||||
"type": "Server.Timer",
|
||||
"rule": "TimerMigrationRule",
|
||||
"ruleArguments": [
|
||||
"@TimerDrift"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Looters",
|
||||
"type": "System.Collections.Generic.HashSet\u003CServer.Mobile\u003E",
|
||||
"rule": "HashSetMigrationRule",
|
||||
"ruleArguments": [
|
||||
"Server.Mobile",
|
||||
"SerializableInterfaceMigrationRule"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Killer",
|
||||
"type": "Server.Mobile",
|
||||
"rule": "SerializableInterfaceMigrationRule"
|
||||
},
|
||||
{
|
||||
"name": "Aggressors",
|
||||
"type": "System.Collections.Generic.List\u003CServer.Mobile\u003E",
|
||||
"rule": "ListMigrationRule",
|
||||
"ruleArguments": [
|
||||
"Server.Mobile",
|
||||
"SerializableInterfaceMigrationRule"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Owner",
|
||||
"type": "Server.Mobile",
|
||||
"rule": "SerializableInterfaceMigrationRule"
|
||||
},
|
||||
{
|
||||
"name": "CorpseName",
|
||||
"type": "string",
|
||||
"rule": "PrimitiveTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "AccessLevel",
|
||||
"type": "Server.AccessLevel",
|
||||
"rule": "EnumMigrationRule"
|
||||
},
|
||||
{
|
||||
"name": "Guild",
|
||||
"type": "Server.Guilds.Guild",
|
||||
"rule": "SerializableInterfaceMigrationRule"
|
||||
},
|
||||
{
|
||||
"name": "Kills",
|
||||
"type": "int",
|
||||
"rule": "PrimitiveTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "EquipItems",
|
||||
"type": "System.Collections.Generic.List\u003CServer.Item\u003E",
|
||||
"rule": "ListMigrationRule",
|
||||
"ruleArguments": [
|
||||
"Server.Item",
|
||||
"SerializableInterfaceMigrationRule"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Hair",
|
||||
"type": "Server.VirtualHairInfo",
|
||||
"rule": "RawSerializableMigrationRule",
|
||||
"ruleArguments": [
|
||||
"",
|
||||
"@CanBeNull"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "FacialHair",
|
||||
"type": "Server.VirtualHairInfo",
|
||||
"rule": "RawSerializableMigrationRule",
|
||||
"ruleArguments": [
|
||||
"",
|
||||
"@CanBeNull"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue