From 5ea5fb43cca5cc7dddf1aa1507dfe1531ce929e1 Mon Sep 17 00:00:00 2001 From: Kamron Batman <3953314+kamronbatman@users.noreply.github.com> Date: Sat, 30 Sep 2023 01:30:47 -0700 Subject: [PATCH] fix: Codegens character statues (#1523) --- .../Character Statue Maker/CharacterStatue.cs | 1237 ++++++++--------- .../CharacterStatueMaker.cs | 284 ++-- .../CharacterStatuePlinth.cs | 222 ++- .../Server.Items.CharacterStatueMaker.v1.json | 19 + ...Server.Items.CharacterStatuePlinth.v0.json | 11 + .../Server.Mobiles.CharacterStatue.v1.json | 50 + ...Server.Mobiles.CharacterStatueDeed.v1.json | 24 + 7 files changed, 927 insertions(+), 920 deletions(-) create mode 100644 Projects/UOContent/Migrations/Server.Items.CharacterStatueMaker.v1.json create mode 100644 Projects/UOContent/Migrations/Server.Items.CharacterStatuePlinth.v0.json create mode 100644 Projects/UOContent/Migrations/Server.Mobiles.CharacterStatue.v1.json create mode 100644 Projects/UOContent/Migrations/Server.Mobiles.CharacterStatueDeed.v1.json diff --git a/Projects/UOContent/Engines/Veteran Rewards/Character Statue Maker/CharacterStatue.cs b/Projects/UOContent/Engines/Veteran Rewards/Character Statue Maker/CharacterStatue.cs index 12a279ea2..c5f4da5fd 100644 --- a/Projects/UOContent/Engines/Veteran Rewards/Character Statue Maker/CharacterStatue.cs +++ b/Projects/UOContent/Engines/Veteran Rewards/Character Statue Maker/CharacterStatue.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Runtime.CompilerServices; +using ModernUO.Serialization; using Server.Accounting; using Server.ContextMenus; using Server.Engines.VeteranRewards; @@ -11,704 +12,646 @@ using Server.Network; using Server.Spells; using Server.Targeting; -namespace Server.Mobiles +namespace Server.Mobiles; + +public enum StatueType { - public enum StatueType + Marble, + Jade, + Bronze +} + +public enum StatuePose +{ + Ready, + Casting, + Salute, + AllPraiseMe, + Fighting, + HandsOnHips +} + +public enum StatueMaterial +{ + Antique, + Dark, + Medium, + Light +} + +[SerializationGenerator(1)] +public partial class CharacterStatue : Mobile, IRewardItem +{ + private int m_Animation; + private int m_Frames; + + [InvalidateProperties] + [SerializableField(3)] + [SerializedCommandProperty(AccessLevel.GameMaster)] + private string _sculptedBy; + + [SerializableField(4)] + [SerializedCommandProperty(AccessLevel.GameMaster)] + private DateTime _sculptedOn; + + [SerializableField(5)] + [SerializedCommandProperty(AccessLevel.GameMaster)] + private CharacterStatuePlinth _plinth; + + [SerializableField(6)] + [SerializedCommandProperty(AccessLevel.GameMaster)] + private bool _isRewardItem; + + public CharacterStatue(Mobile from, StatueType statueType) { - Marble, - Jade, - Bronze + _statueType = statueType; + _pose = StatuePose.Ready; + _material = StatueMaterial.Antique; + + Direction = Direction.South; + AccessLevel = AccessLevel.Counselor; + Hits = HitsMax; + Blessed = true; + Frozen = true; + + CloneBody(from); + CloneClothes(from); + InvalidateHues(); } - public enum StatuePose + [SerializableProperty(0)] + [CommandProperty(AccessLevel.GameMaster)] + public StatueType StatueType { - Ready, - Casting, - Salute, - AllPraiseMe, - Fighting, - HandsOnHips - } - - public enum StatueMaterial - { - Antique, - Dark, - Medium, - Light - } - - public class CharacterStatue : Mobile, IRewardItem - { - private int m_Animation; - private int m_Frames; - private StatueMaterial m_Material; - private StatuePose m_Pose; - - private Mobile m_SculptedBy; - private StatueType m_Type; - - public CharacterStatue(Mobile from, StatueType type) + get => _statueType; + set { - m_Type = type; - m_Pose = StatuePose.Ready; - m_Material = StatueMaterial.Antique; - - Direction = Direction.South; - AccessLevel = AccessLevel.Counselor; - Hits = HitsMax; - Blessed = true; - Frozen = true; - - CloneBody(from); - CloneClothes(from); - InvalidateHues(); - } - - public CharacterStatue(Serial serial) : base(serial) - { - } - - [CommandProperty(AccessLevel.GameMaster)] - public StatueType StatueType - { - get => m_Type; - set - { - m_Type = value; - InvalidateHues(); - InvalidatePose(); - } - } - - [CommandProperty(AccessLevel.GameMaster)] - public StatuePose Pose - { - get => m_Pose; - set - { - m_Pose = value; - InvalidatePose(); - } - } - - [CommandProperty(AccessLevel.GameMaster)] - public StatueMaterial Material - { - get => m_Material; - set - { - m_Material = value; - InvalidateHues(); - InvalidatePose(); - } - } - - [CommandProperty(AccessLevel.GameMaster)] - public Mobile SculptedBy - { - get => m_SculptedBy; - set - { - m_SculptedBy = value; - InvalidateProperties(); - } - } - - [CommandProperty(AccessLevel.GameMaster)] - public DateTime SculptedOn { get; set; } - - public CharacterStatuePlinth Plinth { get; set; } - - [CommandProperty(AccessLevel.GameMaster)] - public bool IsRewardItem { get; set; } - - public override void OnDoubleClick(Mobile from) - { - DisplayPaperdollTo(from); - } - - public override void GetProperties(IPropertyList list) - { - base.GetProperties(list); - - if (m_SculptedBy != null) - { - if (m_SculptedBy.ShowFameTitle && (m_SculptedBy.Player || m_SculptedBy.Body.IsHuman) && - m_SculptedBy.Fame >= 10000) - { - list.Add( - 1076202, - $"{(m_SculptedBy.Female ? "Lady" : "Lord")} {m_SculptedBy.Name}" - ); // Sculpted by ~1_Name~ - } - else - { - list.Add(1076202, m_SculptedBy.Name); // Sculpted by ~1_Name~ - } - } - } - - public override void GetContextMenuEntries(Mobile from, List list) - { - base.GetContextMenuEntries(from, list); - - if (from.Alive && m_SculptedBy != null) - { - var house = BaseHouse.FindHouseAt(this); - - if (house?.IsCoOwner(from) == true || from.AccessLevel > AccessLevel.Counselor) - { - list.Add(new DemolishEntry(this)); - } - } - } - - public override void OnAfterDelete() - { - base.OnAfterDelete(); - - if (Plinth?.Deleted == false) - { - Plinth.Delete(); - } - } - - protected override void OnMapChange(Map oldMap) - { - InvalidatePose(); - - if (Plinth != null) - { - Plinth.Map = Map; - } - } - - protected override void OnLocationChange(Point3D oldLocation) - { - InvalidatePose(); - - if (Plinth != null) - { - Plinth.Location = new Point3D(X, Y, Z - 5); - } - } - - public override bool CanBeRenamedBy(Mobile from) => false; - - public override bool CanBeDamaged() => false; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void OnRequestedAnimation(Mobile from) - { - from.NetState.SendStatueAnimation(Serial, 1, m_Animation, m_Frames); - } - - public override void OnAosSingleClick(Mobile from) - { - } - - public override void Serialize(IGenericWriter writer) - { - base.Serialize(writer); - - writer.WriteEncodedInt(0); // version - - writer.Write((int)m_Type); - writer.Write((int)m_Pose); - writer.Write((int)m_Material); - - writer.Write(m_SculptedBy); - writer.Write(SculptedOn); - - writer.Write(Plinth); - writer.Write(IsRewardItem); - } - - public override void Deserialize(IGenericReader reader) - { - base.Deserialize(reader); - - var version = reader.ReadEncodedInt(); - - m_Type = (StatueType)reader.ReadInt(); - m_Pose = (StatuePose)reader.ReadInt(); - m_Material = (StatueMaterial)reader.ReadInt(); - - m_SculptedBy = reader.ReadEntity(); - SculptedOn = reader.ReadDateTime(); - - Plinth = reader.ReadEntity(); - IsRewardItem = reader.ReadBool(); - - InvalidatePose(); - - Frozen = true; - - if (m_SculptedBy == null || Map == Map.Internal) // Remove preview statues - { - Timer.StartTimer(Delete); - } - } - - public void Sculpt(Mobile by) - { - m_SculptedBy = by; - SculptedOn = Core.Now; - - InvalidateProperties(); - } - - public bool Demolish(Mobile by) - { - var deed = new CharacterStatueDeed(null); - - if (by.PlaceInBackpack(deed)) - { - Delete(); - - deed.Statue = this; - deed.StatueType = m_Type; - deed.IsRewardItem = IsRewardItem; - - Plinth?.Delete(); - - return true; - } - - by.SendLocalizedMessage(500720); // You don't have enough room in your backpack! - deed.Delete(); - - return false; - } - - public void Restore(CharacterStatue from) - { - m_Material = from.Material; - m_Pose = from.Pose; - - Direction = from.Direction; - - CloneBody(from); - CloneClothes(from); - + _statueType = value; InvalidateHues(); InvalidatePose(); + this.MarkDirty(); } + } - public void CloneBody(Mobile from) + [SerializableProperty(1)] + [CommandProperty(AccessLevel.GameMaster)] + public StatuePose Pose + { + get => _pose; + set { - Name = from.Name; - Body = from.Body; - Female = from.Female; - HairItemID = from.HairItemID; - FacialHairItemID = from.FacialHairItemID; + _pose = value; + InvalidatePose(); + this.MarkDirty(); } + } - public void CloneClothes(Mobile from) + [SerializableProperty(2)] + [CommandProperty(AccessLevel.GameMaster)] + public StatueMaterial Material + { + get => _material; + set { - for (var i = Items.Count - 1; i >= 0; i--) - { - Items[i].Delete(); - } - - for (var i = from.Items.Count - 1; i >= 0; i--) - { - var item = from.Items[i]; - - if (item.Layer != Layer.Backpack && item.Layer != Layer.Mount && item.Layer != Layer.Bank) - { - AddItem(CloneItem(item)); - } - } + _material = value; + InvalidateHues(); + InvalidatePose(); + this.MarkDirty(); } + } - public Item CloneItem(Item item) + public override void OnDoubleClick(Mobile from) + { + DisplayPaperdollTo(from); + } + + public override void GetProperties(IPropertyList list) + { + base.GetProperties(list); + + if (!string.IsNullOrEmpty(_sculptedBy)) { - var cloned = new Item(item.ItemID) - { - Layer = item.Layer, - Name = item.Name, - Hue = item.Hue, - Weight = item.Weight, - Movable = false - }; - - return cloned; + list.Add(1076202, _sculptedBy); // Sculpted by ~1_Name~ } + } - public void InvalidateHues() + public override void GetContextMenuEntries(Mobile from, List list) + { + base.GetContextMenuEntries(from, list); + + if (from.Alive && _sculptedBy != null) { - Hue = 0xB8F + (int)m_Type * 4 + (int)m_Material; + var house = BaseHouse.FindHouseAt(this); - HairHue = Hue; - - if (FacialHairItemID > 0) + if (house?.IsCoOwner(from) == true || from.AccessLevel > AccessLevel.Counselor) { - FacialHairHue = Hue; - } - - for (var i = Items.Count - 1; i >= 0; i--) - { - Items[i].Hue = Hue; - } - - Plinth?.InvalidateHue(); - } - - public void InvalidatePose() - { - switch (m_Pose) - { - case StatuePose.Ready: - m_Animation = 4; - m_Frames = 0; - break; - case StatuePose.Casting: - m_Animation = 16; - m_Frames = 2; - break; - case StatuePose.Salute: - m_Animation = 33; - m_Frames = 1; - break; - case StatuePose.AllPraiseMe: - m_Animation = 17; - m_Frames = 4; - break; - case StatuePose.Fighting: - m_Animation = 31; - m_Frames = 5; - break; - case StatuePose.HandsOnHips: - m_Animation = 6; - m_Frames = 1; - break; - } - - if (Map == null) - { - return; - } - - ProcessDelta(); - - var eable = Map.GetClientsInRange(Location); - Span animPacket = stackalloc byte[CharacterStatuePackets.StatueAnimationPacketLength].InitializePacket(); - - foreach (var state in eable) - { - state.Mobile.ProcessDelta(); - CharacterStatuePackets.CreateStatueAnimation(animPacket, Serial, 1, m_Animation, m_Frames); - state.Send(animPacket); - } - - eable.Free(); - } - - private class DemolishEntry : ContextMenuEntry - { - private readonly CharacterStatue m_Statue; - - public DemolishEntry(CharacterStatue statue) : base(6275, 2) => m_Statue = statue; - - public override void OnClick() - { - if (!m_Statue.Deleted) - { - m_Statue.Demolish(Owner.From); - } + list.Add(new DemolishEntry(this)); } } } - public class CharacterStatueDeed : Item, IRewardItem + public override void OnAfterDelete() { - private bool m_IsRewardItem; + base.OnAfterDelete(); - private StatueType m_Type; - - public CharacterStatueDeed(CharacterStatue statue) : base(0x14F0) + if (Plinth?.Deleted == false) { - Statue = statue; - - if (statue != null) - { - m_Type = statue.StatueType; - m_IsRewardItem = statue.IsRewardItem; - } - - LootType = LootType.Blessed; - Weight = 1.0; - } - - public CharacterStatueDeed(Serial serial) : base(serial) - { - } - - public override int LabelNumber - { - get - { - var t = m_Type; - - if (Statue != null) - { - t = Statue.StatueType; - } - - return t switch - { - StatueType.Marble => 1076189, - StatueType.Jade => 1076188, - StatueType.Bronze => 1076190, - _ => 1076173 - }; - } - } - - [CommandProperty(AccessLevel.GameMaster)] - public CharacterStatue Statue { get; set; } - - [CommandProperty(AccessLevel.GameMaster)] - public StatueType StatueType - { - get - { - if (Statue != null) - { - return Statue.StatueType; - } - - return m_Type; - } - set => m_Type = value; - } - - [CommandProperty(AccessLevel.GameMaster)] - public bool IsRewardItem - { - get => m_IsRewardItem; - set - { - m_IsRewardItem = value; - InvalidateProperties(); - } - } - - public override void GetProperties(IPropertyList list) - { - base.GetProperties(list); - - if (m_IsRewardItem) - { - list.Add(1076222); // 6th Year Veteran Reward - } - - if (Statue != null) - { - list.Add(1076231, Statue.Name); // Statue of ~1_Name~ - } - } - - public override void OnDoubleClick(Mobile from) - { - if (from.Account is Account acct && from.AccessLevel == AccessLevel.Player) - { - var time = TimeSpan.FromDays(RewardSystem.RewardInterval.TotalDays * 6) - acct.AccountAge; - - if (time > TimeSpan.Zero) - { - from.SendLocalizedMessage( - 1008126, - true, - Math.Ceiling(time.TotalDays / RewardSystem.RewardInterval.TotalDays) - .ToString() - ); // Your account is not old enough to use this item. Months until you can use this item : - return; - } - } - - if (IsChildOf(from.Backpack)) - { - if (!from.IsBodyMod) - { - from.SendLocalizedMessage(1076194); // Select a place where you would like to put your statue. - from.Target = new CharacterStatueTarget(this, StatueType); - } - else - { - from.SendLocalizedMessage(1073648); // You may only proceed while in your original state... - } - } - else - { - from.SendLocalizedMessage(1042001); // That must be in your pack for you to use it. - } - } - - public override void OnDelete() - { - base.OnDelete(); - - Statue?.Delete(); - } - - public override void Serialize(IGenericWriter writer) - { - base.Serialize(writer); - - writer.WriteEncodedInt(1); // version - - writer.Write((int)m_Type); - - writer.Write(Statue); - writer.Write(m_IsRewardItem); - } - - public override void Deserialize(IGenericReader reader) - { - base.Deserialize(reader); - - var version = reader.ReadEncodedInt(); - - if (version >= 1) - { - m_Type = (StatueType)reader.ReadInt(); - } - - Statue = reader.ReadEntity(); - m_IsRewardItem = reader.ReadBool(); + Plinth.Delete(); } } - public class CharacterStatueTarget : Target + protected override void OnMapChange(Map oldMap) { - private readonly Item m_Maker; - private readonly StatueType m_Type; + InvalidatePose(); - public CharacterStatueTarget(Item maker, StatueType type) : base(-1, true, TargetFlags.None) + if (Plinth != null) { - m_Maker = maker; - m_Type = type; + Plinth.Map = Map; + } + } + + protected override void OnLocationChange(Point3D oldLocation) + { + InvalidatePose(); + + if (Plinth != null) + { + Plinth.Location = new Point3D(X, Y, Z - 5); + } + } + + public override bool CanBeRenamedBy(Mobile from) => false; + + public override bool CanBeDamaged() => false; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void OnRequestedAnimation(Mobile from) + { + from.NetState.SendStatueAnimation(Serial, 1, m_Animation, m_Frames); + } + + public override void OnAosSingleClick(Mobile from) + { + } + + private void Deserialize(IGenericReader reader, int version) + { + _statueType = (StatueType)reader.ReadInt(); + _pose = (StatuePose)reader.ReadInt(); + _material = (StatueMaterial)reader.ReadInt(); + + Timer.DelayCall(sculpter => _sculptedBy = sculpter?.RawName, reader.ReadEntity()); + SculptedOn = reader.ReadDateTime(); + + Plinth = reader.ReadEntity(); + IsRewardItem = reader.ReadBool(); + } + + [AfterDeserialization(false)] + private void AfterDeserialization() + { + InvalidatePose(); + + Frozen = true; + + if (_sculptedBy == null || Map == Map.Internal) // Remove preview statues + { + Delete(); + } + } + + public void Sculpt(Mobile by) + { + if (by.ShowFameTitle && (by.Player || by.Body.IsHuman) && + by.Fame >= 10000) + { + _sculptedBy = $"{(by.Female ? "Lady" : "Lord")} {by.Name}"; + } + else + { + _sculptedBy = by.RawName; } - protected override void OnTarget(Mobile from, object targeted) + SculptedOn = Core.Now; + + InvalidateProperties(); + } + + public bool Demolish(Mobile by) + { + var deed = new CharacterStatueDeed(null); + + if (by.PlaceInBackpack(deed)) { - var p = targeted as IPoint3D; - var map = from.Map; + Delete(); - if (p == null || map == null || m_Maker?.Deleted != false) - { - return; - } + deed.Statue = this; + deed.StatueType = _statueType; + deed.IsRewardItem = IsRewardItem; - if (!m_Maker.IsChildOf(from.Backpack)) - { - from.SendLocalizedMessage(1042001); // That must be in your pack for you to use it. - return; - } + Plinth?.Delete(); - SpellHelper.GetSurfaceTop(ref p); - BaseHouse house = null; - var loc = new Point3D(p); - - if (targeted is Item item && !item.IsLockedDown && !item.IsSecure && item is not AddonComponent) - { - from.SendLocalizedMessage(1076191); // Statues can only be placed in houses. - return; - } - - if (from.IsBodyMod) - { - from.SendLocalizedMessage(1073648); // You may only proceed while in your original state... - return; - } - - var result = CouldFit(loc, map, from, ref house); - - if (result == AddonFitResult.Valid) - { - var statue = new CharacterStatue(from, m_Type); - var plinth = new CharacterStatuePlinth(statue); - - house.Addons.Add(plinth); - - if (m_Maker is IRewardItem rewardItem) - { - statue.IsRewardItem = rewardItem.IsRewardItem; - } - - statue.Plinth = plinth; - plinth.MoveToWorld(loc, map); - statue.InvalidatePose(); - - /* - * TODO: Previously the maker wasn't deleted until after statue - * customization, leading to redeeding issues. Exact OSI behavior - * needs looking into. - */ - m_Maker.Delete(); - statue.Sculpt(from); - - from.CloseGump(); - from.SendGump(new CharacterStatueGump(m_Maker, statue, from)); - return; - } - - if (result == AddonFitResult.Blocked) - { - from.SendLocalizedMessage(500269); // You cannot build that there. - return; - } - - if (result == AddonFitResult.NotInHouse) - { - // Statues can only be placed in houses where you are the owner or co-owner. - from.SendLocalizedMessage(1076192); - return; - } - - if (result == AddonFitResult.DoorTooClose) - { - from.SendLocalizedMessage(500271); // You cannot build near the door. - } + return true; } - public static AddonFitResult CouldFit(Point3D p, Map map, Mobile from, ref BaseHouse house) + by.SendLocalizedMessage(500720); // You don't have enough room in your backpack! + deed.Delete(); + + return false; + } + + public void Restore(CharacterStatue from) + { + _material = from.Material; + _pose = from.Pose; + + Direction = from.Direction; + + CloneBody(from); + CloneClothes(from); + + InvalidateHues(); + InvalidatePose(); + } + + public void CloneBody(Mobile from) + { + Name = from.Name; + Body = from.Body; + Female = from.Female; + HairItemID = from.HairItemID; + FacialHairItemID = from.FacialHairItemID; + } + + public void CloneClothes(Mobile from) + { + for (var i = Items.Count - 1; i >= 0; i--) { - if (!map.CanFit(p.X, p.Y, p.Z, 20, true)) - { - return AddonFitResult.Blocked; - } - - if (!BaseAddon.CheckHouse(from, p, map, 20, out house)) - { - return AddonFitResult.NotInHouse; - } - - return CheckDoors(p, 20, house); + Items[i].Delete(); } - public static AddonFitResult CheckDoors(Point3D p, int height, BaseHouse house) + for (var i = from.Items.Count - 1; i >= 0; i--) { - var doors = house.Doors; + var item = from.Items[i]; - for (var i = 0; i < doors.Count; i++) + if (item.Layer != Layer.Backpack && item.Layer != Layer.Mount && item.Layer != Layer.Bank) { - var door = doors[i]; - - var doorLoc = door.GetWorldLocation(); - var doorHeight = door.ItemData.CalcHeight; - - if (Utility.InRange(doorLoc, p, 1) && - (p.Z == doorLoc.Z || p.Z + height > doorLoc.Z && doorLoc.Z + doorHeight > p.Z)) - { - return AddonFitResult.DoorTooClose; - } + AddItem(CloneItem(item)); } + } + } - return AddonFitResult.Valid; + public Item CloneItem(Item item) + { + var cloned = new Item(item.ItemID) + { + Layer = item.Layer, + Name = item.Name, + Hue = item.Hue, + Weight = item.Weight, + Movable = false + }; + + return cloned; + } + + public void InvalidateHues() + { + Hue = 0xB8F + (int)_statueType * 4 + (int)_material; + + HairHue = Hue; + + if (FacialHairItemID > 0) + { + FacialHairHue = Hue; + } + + for (var i = Items.Count - 1; i >= 0; i--) + { + Items[i].Hue = Hue; + } + + Plinth?.InvalidateHue(); + } + + public void InvalidatePose() + { + switch (_pose) + { + case StatuePose.Ready: + m_Animation = 4; + m_Frames = 0; + break; + case StatuePose.Casting: + m_Animation = 16; + m_Frames = 2; + break; + case StatuePose.Salute: + m_Animation = 33; + m_Frames = 1; + break; + case StatuePose.AllPraiseMe: + m_Animation = 17; + m_Frames = 4; + break; + case StatuePose.Fighting: + m_Animation = 31; + m_Frames = 5; + break; + case StatuePose.HandsOnHips: + m_Animation = 6; + m_Frames = 1; + break; + } + + if (Map == null) + { + return; + } + + ProcessDelta(); + + var eable = Map.GetClientsInRange(Location); + Span animPacket = stackalloc byte[CharacterStatuePackets.StatueAnimationPacketLength].InitializePacket(); + + foreach (var state in eable) + { + state.Mobile.ProcessDelta(); + CharacterStatuePackets.CreateStatueAnimation(animPacket, Serial, 1, m_Animation, m_Frames); + state.Send(animPacket); + } + + eable.Free(); + } + + private class DemolishEntry : ContextMenuEntry + { + private readonly CharacterStatue m_Statue; + + public DemolishEntry(CharacterStatue statue) : base(6275, 2) => m_Statue = statue; + + public override void OnClick() + { + if (!m_Statue.Deleted) + { + m_Statue.Demolish(Owner.From); + } } } } + +[SerializationGenerator(1)] +public partial class CharacterStatueDeed : Item, IRewardItem +{ + [SerializableField(1)] + [SerializedCommandProperty(AccessLevel.GameMaster)] + private CharacterStatue _statue; + + [InvalidateProperties] + [SerializableField(2)] + [SerializedCommandProperty(AccessLevel.GameMaster)] + private bool _isRewardItem; + + public CharacterStatueDeed(CharacterStatue statue) : base(0x14F0) + { + Statue = statue; + + if (statue != null) + { + _statueType = statue.StatueType; + _isRewardItem = statue.IsRewardItem; + } + + LootType = LootType.Blessed; + Weight = 1.0; + } + + public override int LabelNumber + { + get + { + var t = _statueType; + + if (Statue != null) + { + t = Statue.StatueType; + } + + return t switch + { + StatueType.Marble => 1076189, + StatueType.Jade => 1076188, + StatueType.Bronze => 1076190, + _ => 1076173 + }; + } + } + + [SerializableProperty(0)] + [CommandProperty(AccessLevel.GameMaster)] + public StatueType StatueType + { + get => Statue?.StatueType ?? _statueType; + set + { + _statueType = value; + this.MarkDirty(); + } + } + + public override void GetProperties(IPropertyList list) + { + base.GetProperties(list); + + if (_isRewardItem) + { + list.Add(1076222); // 6th Year Veteran Reward + } + + if (Statue != null) + { + list.Add(1076231, Statue.Name); // Statue of ~1_Name~ + } + } + + public override void OnDoubleClick(Mobile from) + { + if (from.Account is Account acct && from.AccessLevel == AccessLevel.Player) + { + var time = TimeSpan.FromDays(RewardSystem.RewardInterval.TotalDays * 6) - acct.AccountAge; + + if (time > TimeSpan.Zero) + { + from.SendLocalizedMessage( + 1008126, + true, + Math.Ceiling(time.TotalDays / RewardSystem.RewardInterval.TotalDays) + .ToString() + ); // Your account is not old enough to use this item. Months until you can use this item : + return; + } + } + + if (IsChildOf(from.Backpack)) + { + if (!from.IsBodyMod) + { + from.SendLocalizedMessage(1076194); // Select a place where you would like to put your statue. + from.Target = new CharacterStatueTarget(this, StatueType); + } + else + { + from.SendLocalizedMessage(1073648); // You may only proceed while in your original state... + } + } + else + { + from.SendLocalizedMessage(1042001); // That must be in your pack for you to use it. + } + } + + public override void OnDelete() + { + base.OnDelete(); + + Statue?.Delete(); + } + + private void Deserialize(IGenericReader reader, int version) + { + + _statueType = (StatueType)reader.ReadInt(); + _statue = reader.ReadEntity(); + _isRewardItem = reader.ReadBool(); + } +} + +public class CharacterStatueTarget : Target +{ + private readonly Item _maker; + private readonly StatueType _statueType; + + public CharacterStatueTarget(Item maker, StatueType type) : base(-1, true, TargetFlags.None) + { + _maker = maker; + _statueType = type; + } + + protected override void OnTarget(Mobile from, object targeted) + { + var p = targeted as IPoint3D; + var map = from.Map; + + if (p == null || map == null || _maker?.Deleted != false) + { + return; + } + + if (!_maker.IsChildOf(from.Backpack)) + { + from.SendLocalizedMessage(1042001); // That must be in your pack for you to use it. + return; + } + + SpellHelper.GetSurfaceTop(ref p); + BaseHouse house = null; + var loc = new Point3D(p); + + if (targeted is Item item && !item.IsLockedDown && !item.IsSecure && item is not AddonComponent) + { + from.SendLocalizedMessage(1076191); // Statues can only be placed in houses. + return; + } + + if (from.IsBodyMod) + { + from.SendLocalizedMessage(1073648); // You may only proceed while in your original state... + return; + } + + var result = CouldFit(loc, map, from, ref house); + + if (result == AddonFitResult.Valid) + { + var statue = new CharacterStatue(from, _statueType); + var plinth = new CharacterStatuePlinth(statue); + + house.Addons.Add(plinth); + + if (_maker is IRewardItem rewardItem) + { + statue.IsRewardItem = rewardItem.IsRewardItem; + } + + statue.Plinth = plinth; + plinth.MoveToWorld(loc, map); + statue.InvalidatePose(); + + /* + * TODO: Previously the maker wasn't deleted until after statue + * customization, leading to redeeding issues. Exact OSI behavior + * needs looking into. + */ + _maker.Delete(); + statue.Sculpt(from); + + from.CloseGump(); + from.SendGump(new CharacterStatueGump(_maker, statue, from)); + return; + } + + if (result == AddonFitResult.Blocked) + { + from.SendLocalizedMessage(500269); // You cannot build that there. + return; + } + + if (result == AddonFitResult.NotInHouse) + { + // Statues can only be placed in houses where you are the owner or co-owner. + from.SendLocalizedMessage(1076192); + return; + } + + if (result == AddonFitResult.DoorTooClose) + { + from.SendLocalizedMessage(500271); // You cannot build near the door. + } + } + + public static AddonFitResult CouldFit(Point3D p, Map map, Mobile from, ref BaseHouse house) + { + if (!map.CanFit(p.X, p.Y, p.Z, 20, true)) + { + return AddonFitResult.Blocked; + } + + if (!BaseAddon.CheckHouse(from, p, map, 20, out house)) + { + return AddonFitResult.NotInHouse; + } + + return CheckDoors(p, 20, house); + } + + public static AddonFitResult CheckDoors(Point3D p, int height, BaseHouse house) + { + var doors = house.Doors; + + for (var i = 0; i < doors.Count; i++) + { + var door = doors[i]; + + var doorLoc = door.GetWorldLocation(); + var doorHeight = door.ItemData.CalcHeight; + + if (Utility.InRange(doorLoc, p, 1) && + (p.Z == doorLoc.Z || p.Z + height > doorLoc.Z && doorLoc.Z + doorHeight > p.Z)) + { + return AddonFitResult.DoorTooClose; + } + } + + return AddonFitResult.Valid; + } +} diff --git a/Projects/UOContent/Engines/Veteran Rewards/Character Statue Maker/CharacterStatueMaker.cs b/Projects/UOContent/Engines/Veteran Rewards/Character Statue Maker/CharacterStatueMaker.cs index b9e7315cd..0e8ea37b6 100644 --- a/Projects/UOContent/Engines/Veteran Rewards/Character Statue Maker/CharacterStatueMaker.cs +++ b/Projects/UOContent/Engines/Veteran Rewards/Character Statue Maker/CharacterStatueMaker.cs @@ -1,187 +1,163 @@ +using ModernUO.Serialization; using Server.Engines.VeteranRewards; using Server.Mobiles; -namespace Server.Items +namespace Server.Items; + +[SerializationGenerator(1)] +public partial class CharacterStatueMaker : Item, IRewardItem { - public class CharacterStatueMaker : Item, IRewardItem + [InvalidateProperties] + [SerializableField(0)] + [SerializedCommandProperty(AccessLevel.GameMaster)] + private bool _isRewardItem; + + public CharacterStatueMaker(StatueType type) : base(0x32F0) { - private bool m_IsRewardItem; - private StatueType m_Type; + _statueType = type; - public CharacterStatueMaker(StatueType type) : base(0x32F0) + InvalidateHue(); + + LootType = LootType.Blessed; + Weight = 5.0; + } + + public override int LabelNumber => 1076173; // Character Statue Maker + + [SerializableProperty(1)] + [CommandProperty(AccessLevel.GameMaster)] + public StatueType StatueType + { + get => _statueType; + set { - m_Type = type; - + _statueType = value; InvalidateHue(); + this.MarkDirty(); + } + } - LootType = LootType.Blessed; - Weight = 5.0; + public override void OnDoubleClick(Mobile from) + { + if (_isRewardItem && !RewardSystem.CheckIsUsableBy(from, this, new object[] { _statueType })) + { + return; } - public CharacterStatueMaker(Serial serial) : base(serial) + if (IsChildOf(from.Backpack)) { - } - - public override int LabelNumber => 1076173; // Character Statue Maker - - [CommandProperty(AccessLevel.GameMaster)] - public StatueType StatueType - { - get => m_Type; - set + if (!from.IsBodyMod) { - m_Type = value; - InvalidateHue(); - } - } - - [CommandProperty(AccessLevel.GameMaster)] - public bool IsRewardItem - { - get => m_IsRewardItem; - set - { - m_IsRewardItem = value; - InvalidateProperties(); - } - } - - public override void OnDoubleClick(Mobile from) - { - if (m_IsRewardItem && !RewardSystem.CheckIsUsableBy(from, this, new object[] { m_Type })) - { - return; - } - - if (IsChildOf(from.Backpack)) - { - if (!from.IsBodyMod) - { - from.SendLocalizedMessage(1076194); // Select a place where you would like to put your statue. - from.Target = new CharacterStatueTarget(this, m_Type); - } - else - { - from.SendLocalizedMessage(1073648); // You may only proceed while in your original state... - } + from.SendLocalizedMessage(1076194); // Select a place where you would like to put your statue. + from.Target = new CharacterStatueTarget(this, _statueType); } else { - from.SendLocalizedMessage(1042001); // That must be in your pack for you to use it. + from.SendLocalizedMessage(1073648); // You may only proceed while in your original state... } } - - public override void GetProperties(IPropertyList list) + else { - base.GetProperties(list); - - if (m_IsRewardItem) - { - list.Add(1076222); // 6th Year Veteran Reward - } - } - - public override void Serialize(IGenericWriter writer) - { - base.Serialize(writer); - - writer.WriteEncodedInt(0); // version - - writer.Write(m_IsRewardItem); - writer.Write((int)m_Type); - } - - public override void Deserialize(IGenericReader reader) - { - base.Deserialize(reader); - - var version = reader.ReadEncodedInt(); - - m_IsRewardItem = reader.ReadBool(); - m_Type = (StatueType)reader.ReadInt(); - } - - public void InvalidateHue() - { - Hue = 0xB8F + (int)m_Type * 4; + from.SendLocalizedMessage(1042001); // That must be in your pack for you to use it. } } - public class MarbleStatueMaker : CharacterStatueMaker + public override void GetProperties(IPropertyList list) { - [Constructible] - public MarbleStatueMaker() : base(StatueType.Marble) + base.GetProperties(list); + + if (_isRewardItem) { - } - - public MarbleStatueMaker(Serial serial) : base(serial) - { - } - - public override void Serialize(IGenericWriter writer) - { - base.Serialize(writer); - - writer.WriteEncodedInt(0); // version - } - - public override void Deserialize(IGenericReader reader) - { - base.Deserialize(reader); - - var version = reader.ReadEncodedInt(); + list.Add(1076222); // 6th Year Veteran Reward } } - public class JadeStatueMaker : CharacterStatueMaker + private void Deserialize(IGenericReader reader, int version) { - [Constructible] - public JadeStatueMaker() : base(StatueType.Jade) - { - } - - public JadeStatueMaker(Serial serial) : base(serial) - { - } - - public override void Serialize(IGenericWriter writer) - { - base.Serialize(writer); - - writer.WriteEncodedInt(0); // version - } - - public override void Deserialize(IGenericReader reader) - { - base.Deserialize(reader); - - var version = reader.ReadEncodedInt(); - } + _isRewardItem = reader.ReadBool(); + _statueType = (StatueType)reader.ReadInt(); } - public class BronzeStatueMaker : CharacterStatueMaker + public void InvalidateHue() { - [Constructible] - public BronzeStatueMaker() : base(StatueType.Bronze) - { - } - - public BronzeStatueMaker(Serial serial) : base(serial) - { - } - - public override void Serialize(IGenericWriter writer) - { - base.Serialize(writer); - - writer.WriteEncodedInt(0); // version - } - - public override void Deserialize(IGenericReader reader) - { - base.Deserialize(reader); - - var version = reader.ReadEncodedInt(); - } + Hue = 0xB8F + (int)_statueType * 4; + } +} + +public class MarbleStatueMaker : CharacterStatueMaker +{ + [Constructible] + public MarbleStatueMaker() : base(StatueType.Marble) + { + } + + public MarbleStatueMaker(Serial serial) : base(serial) + { + } + + public override void Serialize(IGenericWriter writer) + { + base.Serialize(writer); + + writer.WriteEncodedInt(0); // version + } + + public override void Deserialize(IGenericReader reader) + { + base.Deserialize(reader); + + var version = reader.ReadEncodedInt(); + } +} + +public class JadeStatueMaker : CharacterStatueMaker +{ + [Constructible] + public JadeStatueMaker() : base(StatueType.Jade) + { + } + + public JadeStatueMaker(Serial serial) : base(serial) + { + } + + public override void Serialize(IGenericWriter writer) + { + base.Serialize(writer); + + writer.WriteEncodedInt(0); // version + } + + public override void Deserialize(IGenericReader reader) + { + base.Deserialize(reader); + + var version = reader.ReadEncodedInt(); + } +} + +public class BronzeStatueMaker : CharacterStatueMaker +{ + [Constructible] + public BronzeStatueMaker() : base(StatueType.Bronze) + { + } + + public BronzeStatueMaker(Serial serial) : base(serial) + { + } + + public override void Serialize(IGenericWriter writer) + { + base.Serialize(writer); + + writer.WriteEncodedInt(0); // version + } + + public override void Deserialize(IGenericReader reader) + { + base.Deserialize(reader); + + var version = reader.ReadEncodedInt(); } } diff --git a/Projects/UOContent/Engines/Veteran Rewards/Character Statue Maker/CharacterStatuePlinth.cs b/Projects/UOContent/Engines/Veteran Rewards/Character Statue Maker/CharacterStatuePlinth.cs index 1d9414d9e..6298eeb46 100644 --- a/Projects/UOContent/Engines/Veteran Rewards/Character Statue Maker/CharacterStatuePlinth.cs +++ b/Projects/UOContent/Engines/Veteran Rewards/Character Statue Maker/CharacterStatuePlinth.cs @@ -1,144 +1,128 @@ +using ModernUO.Serialization; using Server.Gumps; using Server.Mobiles; using Server.Multis; -namespace Server.Items +namespace Server.Items; + +[SerializationGenerator(0)] +public partial class CharacterStatuePlinth : Static, IAddon { - public class CharacterStatuePlinth : Static, IAddon + [SerializableField(0, setter: "private")] + private CharacterStatue _statue; + + public CharacterStatuePlinth(CharacterStatue statue) : base(0x32F2) { - private CharacterStatue m_Statue; + _statue = statue; - public CharacterStatuePlinth(CharacterStatue statue) : base(0x32F2) + InvalidateHue(); + } + + public override int LabelNumber => 1076201; // Character Statue + public Item Deed => new CharacterStatueDeed(_statue); + + public virtual bool CouldFit(IPoint3D p, Map map) + { + var point = new Point3D(p.X, p.Y, p.Z); + + if (map?.CanFit(point, 20) != true) { - m_Statue = statue; - - InvalidateHue(); - } - - public CharacterStatuePlinth(Serial serial) : base(serial) - { - } - - public override int LabelNumber => 1076201; // Character Statue - public Item Deed => new CharacterStatueDeed(m_Statue); - - public virtual bool CouldFit(IPoint3D p, Map map) - { - var point = new Point3D(p.X, p.Y, p.Z); - - if (map?.CanFit(point, 20) != true) - { - return false; - } - - var house = BaseHouse.FindHouseAt(point, map, 20); - - if (house == null) - { - return false; - } - - var result = CharacterStatueTarget.CheckDoors(point, 20, house); - - if (result == AddonFitResult.Valid) - { - return true; - } - return false; } - public override void OnAfterDelete() - { - base.OnAfterDelete(); + var house = BaseHouse.FindHouseAt(point, map, 20); - if (m_Statue?.Deleted == false) - { - m_Statue.Delete(); - } + if (house == null) + { + return false; } - public override void OnMapChange() + var result = CharacterStatueTarget.CheckDoors(point, 20, house); + + if (result == AddonFitResult.Valid) { - if (m_Statue != null) - { - m_Statue.Map = Map; - } + return true; } - public override void OnLocationChange(Point3D oldLocation) + return false; + } + + public override void OnAfterDelete() + { + base.OnAfterDelete(); + + if (_statue?.Deleted == false) { - if (m_Statue != null) - { - m_Statue.Location = new Point3D(X, Y, Z + 5); - } + _statue.Delete(); + } + } + + public override void OnMapChange() + { + if (_statue != null) + { + _statue.Map = Map; + } + } + + public override void OnLocationChange(Point3D oldLocation) + { + if (_statue != null) + { + _statue.Location = new Point3D(X, Y, Z + 5); + } + } + + public override void OnDoubleClick(Mobile from) + { + if (_statue != null) + { + from.SendGump(new CharacterPlinthGump(_statue)); + } + } + + [AfterDeserialization(false)] + private void AfterDeserialization() + { + if (string.IsNullOrEmpty(_statue?.SculptedBy) || Map == Map.Internal) + { + Delete(); + } + } + + public void InvalidateHue() + { + if (_statue != null) + { + Hue = 0xB8F + (int)_statue.StatueType * 4 + (int)_statue.Material; + } + } + + private class CharacterPlinthGump : Gump + { + public CharacterPlinthGump(CharacterStatue statue) : base(60, 30) + { + Closable = true; + Disposable = true; + Draggable = true; + Resizable = false; + + AddPage(0); + AddImage(0, 0, 0x24F4); + AddHtml(55, 50, 150, 20, statue.Name); + AddHtml(55, 75, 150, 20, statue.SculptedOn.ToString()); + AddHtmlLocalized(55, 100, 150, 20, GetTypeNumber(statue.StatueType), 0); } - public override void OnDoubleClick(Mobile from) + public static int GetTypeNumber(StatueType type) { - if (m_Statue != null) + return type switch { - from.SendGump(new CharacterPlinthGump(m_Statue)); - } - } - - public override void Serialize(IGenericWriter writer) - { - base.Serialize(writer); - - writer.WriteEncodedInt(0); // version - - writer.Write(m_Statue); - } - - public override void Deserialize(IGenericReader reader) - { - base.Deserialize(reader); - - var version = reader.ReadEncodedInt(); - - m_Statue = reader.ReadEntity(); - - if (m_Statue?.SculptedBy == null || Map == Map.Internal) - { - Timer.StartTimer(Delete); - } - } - - public void InvalidateHue() - { - if (m_Statue != null) - { - Hue = 0xB8F + (int)m_Statue.StatueType * 4 + (int)m_Statue.Material; - } - } - - private class CharacterPlinthGump : Gump - { - public CharacterPlinthGump(CharacterStatue statue) : base(60, 30) - { - Closable = true; - Disposable = true; - Draggable = true; - Resizable = false; - - AddPage(0); - AddImage(0, 0, 0x24F4); - AddHtml(55, 50, 150, 20, statue.Name); - AddHtml(55, 75, 150, 20, statue.SculptedOn.ToString()); - AddHtmlLocalized(55, 100, 150, 20, GetTypeNumber(statue.StatueType), 0); - } - - public int GetTypeNumber(StatueType type) - { - return type switch - { - StatueType.Marble => 1076181, - StatueType.Jade => 1076180, - StatueType.Bronze => 1076230, - _ => 1076181 - }; - } + StatueType.Marble => 1076181, + StatueType.Jade => 1076180, + StatueType.Bronze => 1076230, + _ => 1076181 + }; } } } diff --git a/Projects/UOContent/Migrations/Server.Items.CharacterStatueMaker.v1.json b/Projects/UOContent/Migrations/Server.Items.CharacterStatueMaker.v1.json new file mode 100644 index 000000000..ad1fa8505 --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Items.CharacterStatueMaker.v1.json @@ -0,0 +1,19 @@ +{ + "version": 1, + "type": "Server.Items.CharacterStatueMaker", + "properties": [ + { + "name": "IsRewardItem", + "type": "bool", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + }, + { + "name": "StatueType", + "type": "Server.Mobiles.StatueType", + "rule": "EnumMigrationRule" + } + ] +} \ No newline at end of file diff --git a/Projects/UOContent/Migrations/Server.Items.CharacterStatuePlinth.v0.json b/Projects/UOContent/Migrations/Server.Items.CharacterStatuePlinth.v0.json new file mode 100644 index 000000000..18092d4a8 --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Items.CharacterStatuePlinth.v0.json @@ -0,0 +1,11 @@ +{ + "version": 0, + "type": "Server.Items.CharacterStatuePlinth", + "properties": [ + { + "name": "Statue", + "type": "Server.Mobiles.CharacterStatue", + "rule": "SerializableInterfaceMigrationRule" + } + ] +} \ No newline at end of file diff --git a/Projects/UOContent/Migrations/Server.Mobiles.CharacterStatue.v1.json b/Projects/UOContent/Migrations/Server.Mobiles.CharacterStatue.v1.json new file mode 100644 index 000000000..5889c151d --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Mobiles.CharacterStatue.v1.json @@ -0,0 +1,50 @@ +{ + "version": 1, + "type": "Server.Mobiles.CharacterStatue", + "properties": [ + { + "name": "StatueType", + "type": "Server.Mobiles.StatueType", + "rule": "EnumMigrationRule" + }, + { + "name": "Pose", + "type": "Server.Mobiles.StatuePose", + "rule": "EnumMigrationRule" + }, + { + "name": "Material", + "type": "Server.Mobiles.StatueMaterial", + "rule": "EnumMigrationRule" + }, + { + "name": "SculptedBy", + "type": "string", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + }, + { + "name": "SculptedOn", + "type": "System.DateTime", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + }, + { + "name": "Plinth", + "type": "Server.Items.CharacterStatuePlinth", + "rule": "SerializableInterfaceMigrationRule" + }, + { + "name": "IsRewardItem", + "type": "bool", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + } + ] +} \ No newline at end of file diff --git a/Projects/UOContent/Migrations/Server.Mobiles.CharacterStatueDeed.v1.json b/Projects/UOContent/Migrations/Server.Mobiles.CharacterStatueDeed.v1.json new file mode 100644 index 000000000..d99f25348 --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Mobiles.CharacterStatueDeed.v1.json @@ -0,0 +1,24 @@ +{ + "version": 1, + "type": "Server.Mobiles.CharacterStatueDeed", + "properties": [ + { + "name": "StatueType", + "type": "Server.Mobiles.StatueType", + "rule": "EnumMigrationRule" + }, + { + "name": "Statue", + "type": "Server.Mobiles.CharacterStatue", + "rule": "SerializableInterfaceMigrationRule" + }, + { + "name": "IsRewardItem", + "type": "bool", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + } + ] +} \ No newline at end of file