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:
mdodkins 2024-08-08 04:10:23 +01:00 committed by GitHub
parent 883d873c1c
commit 91e37fb8d4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 548 additions and 357 deletions

View file

@ -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;