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