diff --git a/.gitignore b/.gitignore index 24d5cd8dd..04da66351 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ /Distribution/Configuration/modernuo.json /Distribution/Configuration/email-settings.json /Distribution/Configuration/throttles.json +/Distribution/Configuration/tot.json /Distribution/Logs /Distribution/Archives /Distribution/Backups diff --git a/Projects/UOContent/Migrations/Server.Mobiles.Actor.v0.json b/Projects/UOContent/Migrations/Server.Mobiles.Actor.v0.json new file mode 100644 index 000000000..ae1222189 --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Mobiles.Actor.v0.json @@ -0,0 +1,4 @@ +{ + "version": 0, + "type": "Server.Mobiles.Actor" +} \ No newline at end of file diff --git a/Projects/UOContent/Migrations/Server.Mobiles.Artist.v0.json b/Projects/UOContent/Migrations/Server.Mobiles.Artist.v0.json new file mode 100644 index 000000000..71829b025 --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Mobiles.Artist.v0.json @@ -0,0 +1,4 @@ +{ + "version": 0, + "type": "Server.Mobiles.Artist" +} \ No newline at end of file diff --git a/Projects/UOContent/Migrations/Server.Mobiles.Banker.v0.json b/Projects/UOContent/Migrations/Server.Mobiles.Banker.v0.json new file mode 100644 index 000000000..55e39fcc1 --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Mobiles.Banker.v0.json @@ -0,0 +1,4 @@ +{ + "version": 0, + "type": "Server.Mobiles.Banker" +} \ No newline at end of file diff --git a/Projects/UOContent/Migrations/Server.Mobiles.BaseEscortable.v2.json b/Projects/UOContent/Migrations/Server.Mobiles.BaseEscortable.v2.json new file mode 100644 index 000000000..ebf18ed73 --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Mobiles.BaseEscortable.v2.json @@ -0,0 +1,43 @@ +{ + "version": 2, + "type": "Server.Mobiles.BaseEscortable", + "properties": [ + { + "name": "DestinationString", + "type": "string", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + }, + { + "name": "DeleteTimer", + "type": "Server.Timer", + "rule": "TimerMigrationRule", + "ruleArguments": [ + "@TimerDrift" + ] + }, + { + "name": "MlQuestType", + "type": "System.Type", + "rule": "PrimitiveTypeMigrationRule" + }, + { + "name": "MlQuestDestinationMessage", + "type": "Server.TextDefinition", + "rule": "PrimitiveUOTypeMigrationRule", + "ruleArguments": [ + "TextDefinition" + ] + }, + { + "name": "MlQuestPaymentMessage", + "type": "Server.TextDefinition", + "rule": "PrimitiveUOTypeMigrationRule", + "ruleArguments": [ + "TextDefinition" + ] + } + ] +} \ No newline at end of file diff --git a/Projects/UOContent/Migrations/Server.Mobiles.BrideGroom.v0.json b/Projects/UOContent/Migrations/Server.Mobiles.BrideGroom.v0.json new file mode 100644 index 000000000..8027fd021 --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Mobiles.BrideGroom.v0.json @@ -0,0 +1,4 @@ +{ + "version": 0, + "type": "Server.Mobiles.BrideGroom" +} \ No newline at end of file diff --git a/Projects/UOContent/Migrations/Server.Mobiles.EscortableMage.v0.json b/Projects/UOContent/Migrations/Server.Mobiles.EscortableMage.v0.json new file mode 100644 index 000000000..1235b5480 --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Mobiles.EscortableMage.v0.json @@ -0,0 +1,4 @@ +{ + "version": 0, + "type": "Server.Mobiles.EscortableMage" +} \ No newline at end of file diff --git a/Projects/UOContent/Migrations/Server.Mobiles.Gypsy.v0.json b/Projects/UOContent/Migrations/Server.Mobiles.Gypsy.v0.json new file mode 100644 index 000000000..42c0322d9 --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Mobiles.Gypsy.v0.json @@ -0,0 +1,4 @@ +{ + "version": 0, + "type": "Server.Mobiles.Gypsy" +} \ No newline at end of file diff --git a/Projects/UOContent/Migrations/Server.Mobiles.HarborMaster.v0.json b/Projects/UOContent/Migrations/Server.Mobiles.HarborMaster.v0.json new file mode 100644 index 000000000..4c9c61b55 --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Mobiles.HarborMaster.v0.json @@ -0,0 +1,4 @@ +{ + "version": 0, + "type": "Server.Mobiles.HarborMaster" +} \ No newline at end of file diff --git a/Projects/UOContent/Migrations/Server.Mobiles.Merchant.v0.json b/Projects/UOContent/Migrations/Server.Mobiles.Merchant.v0.json new file mode 100644 index 000000000..2e55bd019 --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Mobiles.Merchant.v0.json @@ -0,0 +1,4 @@ +{ + "version": 0, + "type": "Server.Mobiles.Merchant" +} \ No newline at end of file diff --git a/Projects/UOContent/Migrations/Server.Mobiles.Messenger.v0.json b/Projects/UOContent/Migrations/Server.Mobiles.Messenger.v0.json new file mode 100644 index 000000000..b57e81bdf --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Mobiles.Messenger.v0.json @@ -0,0 +1,4 @@ +{ + "version": 0, + "type": "Server.Mobiles.Messenger" +} \ No newline at end of file diff --git a/Projects/UOContent/Migrations/Server.Mobiles.Minter.v0.json b/Projects/UOContent/Migrations/Server.Mobiles.Minter.v0.json new file mode 100644 index 000000000..082aefbcf --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Mobiles.Minter.v0.json @@ -0,0 +1,4 @@ +{ + "version": 0, + "type": "Server.Mobiles.Minter" +} \ No newline at end of file diff --git a/Projects/UOContent/Migrations/Server.Mobiles.Ninja.v0.json b/Projects/UOContent/Migrations/Server.Mobiles.Ninja.v0.json new file mode 100644 index 000000000..40ef01648 --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Mobiles.Ninja.v0.json @@ -0,0 +1,4 @@ +{ + "version": 0, + "type": "Server.Mobiles.Ninja" +} \ No newline at end of file diff --git a/Projects/UOContent/Migrations/Server.Mobiles.Noble.v0.json b/Projects/UOContent/Migrations/Server.Mobiles.Noble.v0.json new file mode 100644 index 000000000..ab6ce39a6 --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Mobiles.Noble.v0.json @@ -0,0 +1,4 @@ +{ + "version": 0, + "type": "Server.Mobiles.Noble" +} \ No newline at end of file diff --git a/Projects/UOContent/Migrations/Server.Mobiles.Peasant.v0.json b/Projects/UOContent/Migrations/Server.Mobiles.Peasant.v0.json new file mode 100644 index 000000000..840d0aff3 --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Mobiles.Peasant.v0.json @@ -0,0 +1,4 @@ +{ + "version": 0, + "type": "Server.Mobiles.Peasant" +} \ No newline at end of file diff --git a/Projects/UOContent/Migrations/Server.Mobiles.Samurai.v0.json b/Projects/UOContent/Migrations/Server.Mobiles.Samurai.v0.json new file mode 100644 index 000000000..c5b305f52 --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Mobiles.Samurai.v0.json @@ -0,0 +1,4 @@ +{ + "version": 0, + "type": "Server.Mobiles.Samurai" +} \ No newline at end of file diff --git a/Projects/UOContent/Migrations/Server.Mobiles.Sculptor.v0.json b/Projects/UOContent/Migrations/Server.Mobiles.Sculptor.v0.json new file mode 100644 index 000000000..b3e724a4f --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Mobiles.Sculptor.v0.json @@ -0,0 +1,4 @@ +{ + "version": 0, + "type": "Server.Mobiles.Sculptor" +} \ No newline at end of file diff --git a/Projects/UOContent/Migrations/Server.Mobiles.SeekerOfAdventure.v0.json b/Projects/UOContent/Migrations/Server.Mobiles.SeekerOfAdventure.v0.json new file mode 100644 index 000000000..94f5fa89e --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Mobiles.SeekerOfAdventure.v0.json @@ -0,0 +1,4 @@ +{ + "version": 0, + "type": "Server.Mobiles.SeekerOfAdventure" +} \ No newline at end of file diff --git a/Projects/UOContent/Migrations/Server.Mobiles.TownCrier.v0.json b/Projects/UOContent/Migrations/Server.Mobiles.TownCrier.v0.json new file mode 100644 index 000000000..c3c9b0917 --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Mobiles.TownCrier.v0.json @@ -0,0 +1,4 @@ +{ + "version": 0, + "type": "Server.Mobiles.TownCrier" +} \ No newline at end of file diff --git a/Projects/UOContent/Migrations/Server.Mobiles.Townfolk.Prisoner.v0.json b/Projects/UOContent/Migrations/Server.Mobiles.Townfolk.Prisoner.v0.json new file mode 100644 index 000000000..f5bc906ca --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Mobiles.Townfolk.Prisoner.v0.json @@ -0,0 +1,4 @@ +{ + "version": 0, + "type": "Server.Mobiles.Townfolk.Prisoner" +} \ No newline at end of file diff --git a/Projects/UOContent/Mobiles/BaseCreature.cs b/Projects/UOContent/Mobiles/BaseCreature.cs index 4f2393191..b81e5bdbb 100644 --- a/Projects/UOContent/Mobiles/BaseCreature.cs +++ b/Projects/UOContent/Mobiles/BaseCreature.cs @@ -1091,31 +1091,9 @@ namespace Server.Mobiles public HonorContext ReceivedHonorContext { get; set; } - public List MLQuests - { - get - { - if (m_MLQuests == null) - { - if (StaticMLQuester) - { - m_MLQuests = MLQuestSystem.FindQuestList(GetType()); - } - else - { - m_MLQuests = ConstructQuestList(); - } - - if (m_MLQuests == null) - { - // return EmptyList, but don't cache it (run construction again next time) - return MLQuestSystem.EmptyList; - } - } - - return m_MLQuests; - } - } + public List MLQuests => + // Assign the quests if we don't have one, and if it is still null, return an empty list + (m_MLQuests ??= StaticMLQuester ? MLQuestSystem.FindQuestList(GetType()) : ConstructQuestList()) ?? MLQuestSystem.EmptyList; public virtual MonsterAbility[] GetMonsterAbilities() => null; diff --git a/Projects/UOContent/Mobiles/Townfolk/Actor.cs b/Projects/UOContent/Mobiles/Townfolk/Actor.cs index 7dbe6eec6..c13b8b087 100644 --- a/Projects/UOContent/Mobiles/Townfolk/Actor.cs +++ b/Projects/UOContent/Mobiles/Townfolk/Actor.cs @@ -1,65 +1,48 @@ +using ModernUO.Serialization; using Server.Items; -namespace Server.Mobiles +namespace Server.Mobiles; + +[SerializationGenerator(0, false)] +public partial class Actor : BaseCreature { - public class Actor : BaseCreature + [Constructible] + public Actor() : base(AIType.AI_Animal, FightMode.None) { - [Constructible] - public Actor() : base(AIType.AI_Animal, FightMode.None) + InitStats(31, 41, 51); + + SetSpeed(0.2, 0.4); + SpeechHue = Utility.RandomDyedHue(); + Hue = Race.Human.RandomSkinHue(); + + if (Female = Utility.RandomBool()) { - InitStats(31, 41, 51); - - SetSpeed(0.2, 0.4); - SpeechHue = Utility.RandomDyedHue(); - Hue = Race.Human.RandomSkinHue(); - - if (Female = Utility.RandomBool()) - { - Body = 0x191; - Name = NameList.RandomName("female"); - AddItem(new FancyDress(Utility.RandomDyedHue())); - Title = "the actress"; - } - else - { - Body = 0x190; - Name = NameList.RandomName("male"); - AddItem(new LongPants(Utility.RandomNeutralHue())); - AddItem(new FancyShirt(Utility.RandomDyedHue())); - Title = "the actor"; - } - - AddItem(new Boots(Utility.RandomNeutralHue())); - - Utility.AssignRandomHair(this); - - Container pack = new Backpack(); - - pack.DropItem(new Gold(250, 300)); - - pack.Movable = false; - - AddItem(pack); + Body = 0x191; + Name = NameList.RandomName("female"); + AddItem(new FancyDress(Utility.RandomDyedHue())); + Title = "the actress"; + } + else + { + Body = 0x190; + Name = NameList.RandomName("male"); + AddItem(new LongPants(Utility.RandomNeutralHue())); + AddItem(new FancyShirt(Utility.RandomDyedHue())); + Title = "the actor"; } - public Actor(Serial serial) : base(serial) - { - } + AddItem(new Boots(Utility.RandomNeutralHue())); - public override bool ClickTitle => false; + Utility.AssignRandomHair(this); - public override void Serialize(IGenericWriter writer) - { - base.Serialize(writer); + Container pack = new Backpack(); - writer.Write(0); // version - } + pack.DropItem(new Gold(250, 300)); - public override void Deserialize(IGenericReader reader) - { - base.Deserialize(reader); + pack.Movable = false; - var version = reader.ReadInt(); - } + AddItem(pack); } + + public override bool ClickTitle => false; } diff --git a/Projects/UOContent/Mobiles/Townfolk/Artist.cs b/Projects/UOContent/Mobiles/Townfolk/Artist.cs index de9567ab7..9e39817e5 100644 --- a/Projects/UOContent/Mobiles/Townfolk/Artist.cs +++ b/Projects/UOContent/Mobiles/Townfolk/Artist.cs @@ -1,68 +1,50 @@ +using ModernUO.Serialization; using Server.Items; -namespace Server.Mobiles +namespace Server.Mobiles; + +[SerializationGenerator(0, false)] +public partial class Artist : BaseCreature { - public class Artist : BaseCreature + [Constructible] + public Artist() : base(AIType.AI_Animal, FightMode.None) { - [Constructible] - public Artist() : base(AIType.AI_Animal, FightMode.None) + InitStats(31, 41, 51); + SetSkill(SkillName.Healing, 36, 68); + + SetSpeed(0.2, 0.4); + SpeechHue = Utility.RandomDyedHue(); + Title = "the artist"; + Hue = Race.Human.RandomSkinHue(); + + if (Female = Utility.RandomBool()) { - InitStats(31, 41, 51); - SetSkill(SkillName.Healing, 36, 68); - - SetSpeed(0.2, 0.4); - SpeechHue = Utility.RandomDyedHue(); - Title = "the artist"; - Hue = Race.Human.RandomSkinHue(); - - if (Female = Utility.RandomBool()) - { - Body = 0x191; - Name = NameList.RandomName("female"); - } - else - { - Body = 0x190; - Name = NameList.RandomName("male"); - } - - AddItem(new Doublet(Utility.RandomDyedHue())); - AddItem(new Sandals(Utility.RandomNeutralHue())); - AddItem(new ShortPants(Utility.RandomNeutralHue())); - AddItem(new HalfApron(Utility.RandomDyedHue())); - - Utility.AssignRandomHair(this); - - Container pack = new Backpack(); - - pack.DropItem(new Gold(250, 300)); - - pack.Movable = false; - - AddItem(pack); + Body = 0x191; + Name = NameList.RandomName("female"); + } + else + { + Body = 0x190; + Name = NameList.RandomName("male"); } - public Artist(Serial serial) - : base(serial) - { - } + AddItem(new Doublet(Utility.RandomDyedHue())); + AddItem(new Sandals(Utility.RandomNeutralHue())); + AddItem(new ShortPants(Utility.RandomNeutralHue())); + AddItem(new HalfApron(Utility.RandomDyedHue())); - public override bool CanTeach => true; + Utility.AssignRandomHair(this); - public override bool ClickTitle => false; + Container pack = new Backpack(); - public override void Serialize(IGenericWriter writer) - { - base.Serialize(writer); + pack.DropItem(new Gold(250, 300)); - writer.Write(0); // version - } + pack.Movable = false; - public override void Deserialize(IGenericReader reader) - { - base.Deserialize(reader); - - var version = reader.ReadInt(); - } + AddItem(pack); } + + public override bool CanTeach => true; + + public override bool ClickTitle => false; } diff --git a/Projects/UOContent/Mobiles/Townfolk/Banker.cs b/Projects/UOContent/Mobiles/Townfolk/Banker.cs index 627d0a9a1..77c2c696a 100644 --- a/Projects/UOContent/Mobiles/Townfolk/Banker.cs +++ b/Projects/UOContent/Mobiles/Townfolk/Banker.cs @@ -1,481 +1,464 @@ using System; using System.Collections.Generic; +using ModernUO.Serialization; using Server.Accounting; using Server.ContextMenus; using Server.Items; using Server.Network; -namespace Server.Mobiles +namespace Server.Mobiles; + +[SerializationGenerator(0, false)] +public partial class Banker : BaseVendor { - public class Banker : BaseVendor + private readonly List m_SBInfos = new(); + + [Constructible] + public Banker() : base("the banker") { - private readonly List m_SBInfos = new(); + } - [Constructible] - public Banker() : base("the banker") + protected override List SBInfos => m_SBInfos; + + public override NpcGuild NpcGuild => NpcGuild.MerchantsGuild; + + public override void InitSBInfo() + { + m_SBInfos.Add(new SBBanker()); + } + + public static int GetBalance(Mobile m) + { + long balance = 0; + + if (AccountGold.Enabled && m.Account != null) { + balance = m.Account.GetTotalGold(); + if (balance >= int.MaxValue) + { + return int.MaxValue; + } } - public Banker(Serial serial) : base(serial) + Container bank = m.FindBankNoCreate(); + + if (bank != null) { + foreach (var gold in bank.FindItemsByType()) + { + balance += gold.Amount; + } + + if (balance >= int.MaxValue) + { + return int.MaxValue; + } + + foreach (var check in bank.FindItemsByType()) + { + balance += check.Worth; + } } - protected override List SBInfos => m_SBInfos; + return (int)Math.Clamp(balance, 0, int.MaxValue); + } - public override NpcGuild NpcGuild => NpcGuild.MerchantsGuild; + public static int GetBalance(Mobile m, out List gold, out List checks) + { + long balance = 0; + gold = checks = new List(); - public override void InitSBInfo() + if (AccountGold.Enabled && m.Account != null) { - m_SBInfos.Add(new SBBanker()); + balance = m.Account.GetTotalGold(); + + if (balance >= int.MaxValue) + { + return int.MaxValue; + } } - public static int GetBalance(Mobile m) + Container bank = m.FindBankNoCreate(); + + if (bank != null) { - long balance = 0; - - if (AccountGold.Enabled && m.Account != null) + foreach (var g in bank.FindItemsByType()) { - balance = m.Account.GetTotalGold(); - if (balance >= int.MaxValue) - { - return int.MaxValue; - } + balance += g.Amount; + gold.Add(g); } - Container bank = m.FindBankNoCreate(); - - if (bank != null) + if (balance >= int.MaxValue) { - foreach (var gold in bank.FindItemsByType()) - { - balance += gold.Amount; - } - - if (balance >= int.MaxValue) - { - return int.MaxValue; - } - - foreach (var check in bank.FindItemsByType()) - { - balance += check.Worth; - } + return int.MaxValue; } - return (int)Math.Clamp(balance, 0, int.MaxValue); + foreach (var bc in bank.FindItemsByType()) + { + balance += bc.Worth; + checks.Add(bc); + } } - public static int GetBalance(Mobile m, out List gold, out List checks) + return (int)Math.Clamp(balance, 0, int.MaxValue); + } + + public static bool Withdraw(Mobile from, int amount) + { + // If for whatever reason the TOL checks fail, we should still try old methods for withdrawing currency. + if (AccountGold.Enabled && from.Account?.WithdrawGold(amount) == true) { - long balance = 0; - gold = checks = new List(); - - if (AccountGold.Enabled && m.Account != null) - { - balance = m.Account.GetTotalGold(); - - if (balance >= int.MaxValue) - { - return int.MaxValue; - } - } - - Container bank = m.FindBankNoCreate(); - - if (bank != null) - { - foreach (var g in bank.FindItemsByType()) - { - balance += g.Amount; - gold.Add(g); - } - - if (balance >= int.MaxValue) - { - return int.MaxValue; - } - - foreach (var bc in bank.FindItemsByType()) - { - balance += bc.Worth; - checks.Add(bc); - } - } - - return (int)Math.Clamp(balance, 0, int.MaxValue); - } - - public static bool Withdraw(Mobile from, int amount) - { - // If for whatever reason the TOL checks fail, we should still try old methods for withdrawing currency. - if (AccountGold.Enabled && from.Account?.WithdrawGold(amount) == true) - { - return true; - } - - var balance = GetBalance(from, out var gold, out var checks); - - if (balance < amount) - { - return false; - } - - for (var i = 0; amount > 0 && i < gold.Count; ++i) - { - if (gold[i].Amount <= amount) - { - amount -= gold[i].Amount; - gold[i].Delete(); - } - else - { - gold[i].Amount -= amount; - amount = 0; - } - } - - for (var i = 0; amount > 0 && i < checks.Count; ++i) - { - var check = (BankCheck)checks[i]; - - if (check.Worth <= amount) - { - amount -= check.Worth; - check.Delete(); - } - else - { - check.Worth -= amount; - amount = 0; - } - } - return true; } - public static bool Deposit(Mobile from, int amount) + var balance = GetBalance(from, out var gold, out var checks); + + if (balance < amount) { - // If for whatever reason the TOL checks fail, we should still try old methods for depositing currency. - if (AccountGold.Enabled && from.Account?.DepositGold(amount) == true) + return false; + } + + for (var i = 0; amount > 0 && i < gold.Count; ++i) + { + if (gold[i].Amount <= amount) { - return true; + amount -= gold[i].Amount; + gold[i].Delete(); } - - var box = from.FindBankNoCreate(); - - if (box == null) + else { - return false; + gold[i].Amount -= amount; + amount = 0; } + } - var items = new List(); + for (var i = 0; amount > 0 && i < checks.Count; ++i) + { + var check = (BankCheck)checks[i]; - while (amount > 0) + if (check.Worth <= amount) { - Item item; - if (amount < 5000) - { - item = new Gold(amount); - amount = 0; - } - else if (amount <= 1000000) - { - item = new BankCheck(amount); - amount = 0; - } - else - { - item = new BankCheck(1000000); - amount -= 1000000; - } - - if (box.TryDropItem(from, item, false)) - { - items.Add(item); - } - else - { - item.Delete(); - foreach (var curItem in items) - { - curItem.Delete(); - } - - return false; - } + amount -= check.Worth; + check.Delete(); } + else + { + check.Worth -= amount; + amount = 0; + } + } + return true; + } + + public static bool Deposit(Mobile from, int amount) + { + // If for whatever reason the TOL checks fail, we should still try old methods for depositing currency. + if (AccountGold.Enabled && from.Account?.DepositGold(amount) == true) + { return true; } - public static int DepositUpTo(Mobile from, int amount) + var box = from.FindBankNoCreate(); + + if (box == null) { - // If for whatever reason the TOL checks fail, we should still try old methods for depositing currency. - if (AccountGold.Enabled && from.Account?.DepositGold(amount) == true) - { - return amount; - } - - var box = from.FindBankNoCreate(); - - if (box == null) - { - return 0; - } - - var amountLeft = amount; - while (amountLeft > 0) - { - Item item; - int amountGiven; - - if (amountLeft < 5000) - { - item = new Gold(amountLeft); - amountGiven = amountLeft; - } - else if (amountLeft <= 1000000) - { - item = new BankCheck(amountLeft); - amountGiven = amountLeft; - } - else - { - item = new BankCheck(1000000); - amountGiven = 1000000; - } - - if (box.TryDropItem(from, item, false)) - { - amountLeft -= amountGiven; - } - else - { - item.Delete(); - break; - } - } - - return amount - amountLeft; + return false; } - public static void Deposit(Container cont, int amount) + var items = new List(); + + while (amount > 0) { - while (amount > 0) + Item item; + if (amount < 5000) { - Item item; + item = new Gold(amount); + amount = 0; + } + else if (amount <= 1000000) + { + item = new BankCheck(amount); + amount = 0; + } + else + { + item = new BankCheck(1000000); + amount -= 1000000; + } - if (amount < 5000) + if (box.TryDropItem(from, item, false)) + { + items.Add(item); + } + else + { + item.Delete(); + foreach (var curItem in items) { - item = new Gold(amount); - amount = 0; - } - else if (amount <= 1000000) - { - item = new BankCheck(amount); - amount = 0; - } - else - { - item = new BankCheck(1000000); - amount -= 1000000; + curItem.Delete(); } - cont.DropItem(item); + return false; } } - public override bool HandlesOnSpeech(Mobile from) => from.InRange(Location, 12) || base.HandlesOnSpeech(from); + return true; + } - public override void OnSpeech(SpeechEventArgs e) + public static int DepositUpTo(Mobile from, int amount) + { + // If for whatever reason the TOL checks fail, we should still try old methods for depositing currency. + if (AccountGold.Enabled && from.Account?.DepositGold(amount) == true) { - if (!e.Handled && e.Mobile.InRange(Location, 12)) + return amount; + } + + var box = from.FindBankNoCreate(); + + if (box == null) + { + return 0; + } + + var amountLeft = amount; + while (amountLeft > 0) + { + Item item; + int amountGiven; + + if (amountLeft < 5000) { - for (var i = 0; i < e.Keywords.Length; ++i) + item = new Gold(amountLeft); + amountGiven = amountLeft; + } + else if (amountLeft <= 1000000) + { + item = new BankCheck(amountLeft); + amountGiven = amountLeft; + } + else + { + item = new BankCheck(1000000); + amountGiven = 1000000; + } + + if (box.TryDropItem(from, item, false)) + { + amountLeft -= amountGiven; + } + else + { + item.Delete(); + break; + } + } + + return amount - amountLeft; + } + + public static void Deposit(Container cont, int amount) + { + while (amount > 0) + { + Item item; + + if (amount < 5000) + { + item = new Gold(amount); + amount = 0; + } + else if (amount <= 1000000) + { + item = new BankCheck(amount); + amount = 0; + } + else + { + item = new BankCheck(1000000); + amount -= 1000000; + } + + cont.DropItem(item); + } + } + + public override bool HandlesOnSpeech(Mobile from) => from.InRange(Location, 12) || base.HandlesOnSpeech(from); + + public override void OnSpeech(SpeechEventArgs e) + { + if (!e.Handled && e.Mobile.InRange(Location, 12)) + { + for (var i = 0; i < e.Keywords.Length; ++i) + { + var keyword = e.Keywords[i]; + + switch (keyword) { - var keyword = e.Keywords[i]; + case 0x0000: // *withdraw* + { + e.Handled = true; - switch (keyword) - { - case 0x0000: // *withdraw* + if (e.Mobile.Criminal) { - e.Handled = true; - - if (e.Mobile.Criminal) - { - Say(500389); // I will not do business with a criminal! - break; - } - - var split = e.Speech.Split(' '); - - if (split.Length >= 2) - { - var pack = e.Mobile.Backpack; - - if (!int.TryParse(split[1], out var amount)) - { - break; - } - - if (!Core.ML && amount > 5000 || Core.ML && amount > 60000) - { - Say(500381); // Thou canst not withdraw so much at one time! - } - else if (pack?.Deleted != false || !(pack.TotalWeight < pack.MaxWeight) || - !(pack.TotalItems < pack.MaxItems)) - { - Say(1048147); // Your backpack can't hold anything else. - } - else if (amount > 0) - { - var box = e.Mobile.FindBankNoCreate(); - - if (box == null || !Withdraw(e.Mobile, amount)) - { - Say(500384); // Ah, art thou trying to fool me? Thou hast not so much gold! - } - else - { - pack.DropItem(new Gold(amount)); - - Say(1010005); // Thou hast withdrawn gold from thy account. - } - } - } - + Say(500389); // I will not do business with a criminal! break; } - case 0x0001: // *balance* + + var split = e.Speech.Split(' '); + + if (split.Length >= 2) { - e.Handled = true; + var pack = e.Mobile.Backpack; - if (e.Mobile.Criminal) - { - Say(500389); // I will not do business with a criminal! - break; - } - - if (AccountGold.Enabled && e.Mobile.Account != null) - { - Say( - 1155855, - $"{e.Mobile.Account.TotalPlat:#,0}\t{e.Mobile.Account.TotalGold:#,0}" - ); // Thy current bank balance is ~1_AMOUNT~ platinum and ~2_AMOUNT~ gold. - } - else - { - // Thy current bank balance is ~1_AMOUNT~ gold. - Say(1042759, $"{GetBalance(e.Mobile):N0}"); - } - - break; - } - case 0x0002: // *bank* - { - e.Handled = true; - - if (e.Mobile.Criminal) - { - Say(500378); // Thou art a criminal and cannot access thy bank box. - break; - } - - e.Mobile.BankBox.Open(); - - break; - } - case 0x0003: // *check* - { - e.Handled = true; - - if (AccountGold.Enabled) + if (!int.TryParse(split[1], out var amount)) { break; } - if (e.Mobile.Criminal) + if (!Core.ML && amount > 5000 || Core.ML && amount > 60000) { - Say(500389); // I will not do business with a criminal! - break; + Say(500381); // Thou canst not withdraw so much at one time! } - - var split = e.Speech.Split(' '); - - if (split.Length >= 2) + else if (pack?.Deleted != false || !(pack.TotalWeight < pack.MaxWeight) || + !(pack.TotalItems < pack.MaxItems)) { - if (!int.TryParse(split[1], out var amount)) + Say(1048147); // Your backpack can't hold anything else. + } + else if (amount > 0) + { + var box = e.Mobile.FindBankNoCreate(); + + if (box == null || !Withdraw(e.Mobile, amount)) { - break; - } - - if (amount < 5000) - { - Say(1010006); // We cannot create checks for such a paltry amount of gold! - } - else if (amount > 1000000) - { - Say(1010007); // Our policies prevent us from creating checks worth that much! + Say(500384); // Ah, art thou trying to fool me? Thou hast not so much gold! } else { - var check = new BankCheck(amount); + pack.DropItem(new Gold(amount)); - var box = e.Mobile.BankBox; - - if (!box.TryDropItem(e.Mobile, check, false)) - { - Say(500386); // There's not enough room in your bankbox for the check! - check.Delete(); - } - else if (!box.ConsumeTotal(typeof(Gold), amount)) - { - Say(500384); // Ah, art thou trying to fool me? Thou hast not so much gold! - check.Delete(); - } - else - { - Say( - 1042673, // Into your bank box I have placed a check in the amount of: - AffixType.Append, - amount.ToString(), - "" - ); - } + Say(1010005); // Thou hast withdrawn gold from thy account. } } + } + break; + } + case 0x0001: // *balance* + { + e.Handled = true; + + if (e.Mobile.Criminal) + { + Say(500389); // I will not do business with a criminal! break; } - } + + if (AccountGold.Enabled && e.Mobile.Account != null) + { + Say( + 1155855, + $"{e.Mobile.Account.TotalPlat:#,0}\t{e.Mobile.Account.TotalGold:#,0}" + ); // Thy current bank balance is ~1_AMOUNT~ platinum and ~2_AMOUNT~ gold. + } + else + { + // Thy current bank balance is ~1_AMOUNT~ gold. + Say(1042759, $"{GetBalance(e.Mobile):N0}"); + } + + break; + } + case 0x0002: // *bank* + { + e.Handled = true; + + if (e.Mobile.Criminal) + { + Say(500378); // Thou art a criminal and cannot access thy bank box. + break; + } + + e.Mobile.BankBox.Open(); + + break; + } + case 0x0003: // *check* + { + e.Handled = true; + + if (AccountGold.Enabled) + { + break; + } + + if (e.Mobile.Criminal) + { + Say(500389); // I will not do business with a criminal! + break; + } + + var split = e.Speech.Split(' '); + + if (split.Length >= 2) + { + if (!int.TryParse(split[1], out var amount)) + { + break; + } + + if (amount < 5000) + { + Say(1010006); // We cannot create checks for such a paltry amount of gold! + } + else if (amount > 1000000) + { + Say(1010007); // Our policies prevent us from creating checks worth that much! + } + else + { + var check = new BankCheck(amount); + + var box = e.Mobile.BankBox; + + if (!box.TryDropItem(e.Mobile, check, false)) + { + Say(500386); // There's not enough room in your bankbox for the check! + check.Delete(); + } + else if (!box.ConsumeTotal(typeof(Gold), amount)) + { + Say(500384); // Ah, art thou trying to fool me? Thou hast not so much gold! + check.Delete(); + } + else + { + Say( + 1042673, // Into your bank box I have placed a check in the amount of: + AffixType.Append, + amount.ToString(), + "" + ); + } + } + } + + break; + } } } - - base.OnSpeech(e); } - public override void AddCustomContextEntries(Mobile from, List list) + base.OnSpeech(e); + } + + public override void AddCustomContextEntries(Mobile from, List list) + { + if (from.Alive) { - if (from.Alive) - { - list.Add(new OpenBankEntry(this)); - } - - base.AddCustomContextEntries(from, list); + list.Add(new OpenBankEntry(this)); } - public override void Serialize(IGenericWriter writer) - { - base.Serialize(writer); - - writer.Write(0); // version - } - - public override void Deserialize(IGenericReader reader) - { - base.Deserialize(reader); - - var version = reader.ReadInt(); - } + base.AddCustomContextEntries(from, list); } } diff --git a/Projects/UOContent/Mobiles/Townfolk/BaseEscortable.cs b/Projects/UOContent/Mobiles/Townfolk/BaseEscortable.cs index ec336af9d..be7a067fa 100644 --- a/Projects/UOContent/Mobiles/Townfolk/BaseEscortable.cs +++ b/Projects/UOContent/Mobiles/Townfolk/BaseEscortable.cs @@ -1,821 +1,917 @@ using System; using System.Collections; using System.Collections.Generic; +using ModernUO.Serialization; +using Server.Buffers; using Server.ContextMenus; using Server.Engines.MLQuests; using Server.Engines.MLQuests.Definitions; using Server.Engines.MLQuests.Objectives; using Server.Engines.Virtues; using Server.Items; +using Server.Logging; using Server.Misc; +using Server.Network; using Server.Regions; using EDI = Server.Mobiles.EscortDestinationInfo; -namespace Server.Mobiles +namespace Server.Mobiles; + +[SerializationGenerator(2, false)] +public partial class BaseEscortable : BaseCreature { - public class BaseEscortable : BaseCreature + private static readonly ILogger logger = LogFactory.GetLogger(typeof(BaseEscortable)); + + public static readonly TimeSpan EscortDelay = TimeSpan.FromMinutes(5.0); + + public static readonly TimeSpan AbandonDelay = + MLQuestSystem.Enabled ? TimeSpan.FromMinutes(1.0) : TimeSpan.FromMinutes(2.0); + + public static readonly TimeSpan DeleteTime = + MLQuestSystem.Enabled ? TimeSpan.FromSeconds(100) : TimeSpan.FromSeconds(30); + + // Classic list + // Used when: !MLQuestSystem.Enabled && !Core.ML + private static readonly string[] _townNames = { - public static readonly TimeSpan EscortDelay = TimeSpan.FromMinutes(5.0); + "Cove", "Britain", "Jhelom", + "Minoc", "Ocllo", "Trinsic", + "Vesper", "Yew", "Skara Brae", + "Nujel'm", "Moonglow", "Magincia" + }; - public static readonly TimeSpan AbandonDelay = - MLQuestSystem.Enabled ? TimeSpan.FromMinutes(1.0) : TimeSpan.FromMinutes(2.0); + // ML list, pre-ML quest system + // Used when: !MLQuestSystem.Enabled && Core.ML + private static readonly string[] _mlTownNames = + { + "Cove", "Serpent's Hold", "Jhelom", "Nujel'm" + }; - public static readonly TimeSpan DeleteTime = - MLQuestSystem.Enabled ? TimeSpan.FromSeconds(100) : TimeSpan.FromSeconds(30); + // ML quest system general list + // Used when: MLQuestSystem.Enabled && !Region.IsPartOf( "Haven Island" ) + private static readonly Dictionary m_MLQuestTypes = new() + { + { typeof(EscortToYew), (Array.Empty(), 0) }, + { typeof(EscortToVesper), (Array.Empty(), 0) }, + { typeof(EscortToTrinsic), (Array.Empty(), 0) }, + { typeof(EscortToSkaraBrae), (Array.Empty(), 0) }, + { typeof(EscortToSerpentsHold), (Array.Empty(), 0) }, + { typeof(EscortToNujelm), (Array.Empty(), 0) }, + { typeof(EscortToMoonglow), (Array.Empty(), 0) }, + { typeof(EscortToMinoc), (Array.Empty(), 0) }, + { typeof(EscortToMagincia), (Array.Empty(), 0) }, + { typeof(EscortToJhelom), (Array.Empty(), 0) }, + { typeof(EscortToCove), (Array.Empty(), 0) }, + { typeof(EscortToBritain), (Array.Empty(), 0) } + // Ocllo was removed in pub 56 + // { typeof(EscortToOcllo), (Array.Empty(), 0) } + }; - // Classic list - // Used when: !MLQuestSystem.Enabled && !Core.ML - private static readonly string[] m_TownNames = + // ML quest system New Haven list + // Used when: MLQuestSystem.Enabled && Region.IsPartOf("Haven Island") + // TODO: Find out if these specific quest messages are for special one-off quest characters + // that teach you where things are in a city instead of the randomly spawned ones. + private static readonly Dictionary m_MLQuestTypesNH = new() + { + // I am missing several components for my new potions, and I need to find the local alchemist. + // My daughter is sick , and I need medicine. Do you know the way to the local alchemist? + // I need some potions before I set out for a long journey. Can you take me to the alchemist in The Bottled Imp? + // I’m looking to go to the Alchemist's shop. Will you take me? + { typeof(EscortToNHAlchemist), (new[] { 1042767, 1042768, 1042769, 1042824 }, 1042811) }, + + // I need new string for my lute, yet I do not know the way to the local music shop, could you take me? + // I was hoping to hire a bard for my birthday party. Can you take me to one? + // I fear my talent for music is less than my desire to learn, yet still I would like to try. Can you take me to the local music shop? + // I’m looking to go to the music center. Will you take me? + { typeof(EscortToNHBard), (new [] { 1042770, 1042771, 1042772, 1042825 }, 1042812) }, + + // A family heirloom, our armoire, is falling apart. I need to see the local carpenter. Would you guide me to her? + // My goat has broken through our fence, and I need new boards. Can you direct me to the local wood worker? + // I need a hammer and nails. Never mind for what. Take me to the local carpenter or leave me be. + // I’m looking to go to the local woodworker. Will you take me? + { typeof(EscortToNHCarpenter), (new [] { 1042773, 1042774, 1042775, 1042829 }, 1042816) }, + + //TODO: Add woodsman (camping, tracker, etc) + // 1042776 - I have a job for the local woodsman. I have lost my dog. Can you take me to see him? + // 1042777 - The tracker here is a good man. I know he will help me find my pet scorpion. Do you know the way to his house? + // 1042778 - I am hoping to learn something about camping. Can you take me to the local woodsman? + // 1042836 - I’m looking to go to the local woodsman's. Will you take me? + // 1042814 - Lead on! Payment will be made when we arrive at the weapon trainer's. + + // Me spouse's cookin' is too good. I fear me belly is outgrowin' me clothes. I need to find the local seamstress. Can you take me to her? + // I want to learn how to sew. Can you take me to see the tailor? + // I need new clothes for a party, and I was wondering if you could take me to the tailor? + // I’m looking to go to the local tailor. Will you take me? + { typeof(EscortToNHTailor), (new [] { 1042779, 1042780, 1042781, 1042828, }, 1042815) }, + + // I need to deposit some gold. You look like a trustworthy soul, so could you direct me to the local bank? + // A rich relative of mine said they deposited some gold in to my account. Would you be able to lead me to the bank? + // I have a debt I need to pay off at the bank. Do you know the way there? + // I’m looking to go to the city bank. Will you take me? + { typeof(EscortToNHBank), (new [] { 1042782, 1042783, 1042784, 1042832 }, 1042819) }, + + // I wish to travel and see the world, but I fear I need martial skills. Would you direct me to the local weapons trainer? + // I need a sword to accompany me on a journey. Would escort me to the local fighter's union? + // I need someone to help me rid my home of mongbats. Please take me to the local swordfighter. + // I’m looking to go to the weapon trainer's. Will you take me? + { typeof(EscortToNHWarrior), (new [] { 1042785, 1042786, 1042787, 1042827 }, 1042814) }, + + // My new house requires blessing, but I am not a mage and I have not the scroll. Would you take me to the local mages guild? + // I need a wizard. I can't say why. You'll take me to one, or won't you? + // You there. Take me to see a sorcerer so I can turn a friend back in to a human. He is currently a cat and keeps demanding milk. + // I’m looking to go to the magic shop. Will you take me? + { typeof(EscortToNHMage), (new [] { 1042788, 1042789, 1042790, 1042833 }, 1042820) }, + + // Psst - I hate to admit it, but I am lost. Can you take me to a place where they sell maps? + // I am trying to confirm the location of dungeons around here. Would you take me to a map maker so I might buy supplies? + // Where am I? Who am I? Do you know me? Hmmm - on second thought, I think I best stick with where I am first. Do you know where I can get a map? + // I’m looking to go to the local Map maker's. Will you take me? + { typeof(EscortToNHMapmaker), (new [] { 1042791, 1042792, 1042793, 1042835 }, 1042822) }, + + // I am in search of a loaf of fresh bread. Do you know where I might find some? + // I need to find some spices for my stew. The local chef might have some for me, can you take me to see him? + // I need something to eat. I am starving. Can you take me to the inn? + // I’m looking to go to the New Haven Inn. Will you take me? + { typeof(EscortToNHInn), (new [] { 1042794, 1042795, 1042796, 1042834 }, 1042821) }, + + // Hey you! I need to find a farmer because all my plants keep dying. Please take me to one. + // Do you know where I might find a person who sells seeds for crops? + // I am hoping to swap soil stories with a local farmer, but I cannot find one. Can you take me to one? + // I’m looking to go to a local farm. Will you take me? + // { typeof(EscortToNHFarm), (new[] { 1042797, 1042798, 1042799, 1042830 }, 1042817) }, // Farm destination was remove + + // Hey! I need to visit the local fisherman to ask about what kind of bait he is using. I keep pulling up sea serpents. Where is he? + // You know, I would really like some fish, but I do not know where a fisherman is. Do you? + // I have heard of a magical fish that grants wishes. I bet THAT fisherman knows where the fish is. Please take me to him. + // I’m looking to go to the fishing wharf. Will you take me? + { typeof(EscortToNHDocks), (new [] { 1042800, 1042801, 1042802, 1042826 }, 1042813) }, + + // I need arrows to hunt rabbits for me rabbit stew. Can you take me to the local archer? + // I have a huge amount of feathers for the local Fletcher. Where might I find him? + // You there. Do you know the way to the local archer? + // I’m looking to go to the local archery range. Will you take me? + { typeof(EscortToNHBowyer), (new [] { 1042803, 1042804, 1042805, 1042831 }, 1042818) }, + }; + + [SerializableField(0, setter: "private")] + private string _destinationString; + + [TimerDrift] + [SerializableField(1)] + private Timer _deleteTimer; + + [DeserializeTimerField(1)] + private void DeserializeDeleteTimer(TimeSpan delay) + { + if (delay >= TimeSpan.Zero) { - "Cove", "Britain", "Jhelom", - "Minoc", "Ocllo", "Trinsic", - "Vesper", "Yew", "Skara Brae", - "Nujel'm", "Moonglow", "Magincia" - }; - - // ML list, pre-ML quest system - // Used when: !MLQuestSystem.Enabled && Core.ML - private static readonly string[] m_MLTownNames = - { - "Cove", "Serpent's Hold", "Jhelom", - "Nujel'm" - }; - - // ML quest system general list - // Used when: MLQuestSystem.Enabled && !Region.IsPartOf( "Haven Island" ) - private static readonly Type[] m_MLQuestTypes = - { - typeof(EscortToYew), - typeof(EscortToVesper), - typeof(EscortToTrinsic), - typeof(EscortToSkaraBrae), - typeof(EscortToSerpentsHold), - typeof(EscortToNujelm), - typeof(EscortToMoonglow), - typeof(EscortToMinoc), - typeof(EscortToMagincia), - typeof(EscortToJhelom), - typeof(EscortToCove), - typeof(EscortToBritain) - // Ocllo was removed in pub 56 - // typeof( EscortToOcllo ) - }; - - // ML quest system New Haven list - // Used when: MLQuestSystem.Enabled && Region.IsPartOf( "Haven Island" ) - private static readonly Type[] m_MLQuestTypesNH = - { - typeof(EscortToNHAlchemist), - typeof(EscortToNHBard), - typeof(EscortToNHWarrior), - typeof(EscortToNHTailor), - typeof(EscortToNHCarpenter), - typeof(EscortToNHMapmaker), - typeof(EscortToNHMage), - typeof(EscortToNHInn), - // Farm destination was removed - // typeof( EscortToNHFarm ), - typeof(EscortToNHDocks), - typeof(EscortToNHBowyer), - typeof(EscortToNHBank) - }; - - private bool m_DeleteCorpse; - - private DateTime m_DeleteTime; - private Timer m_DeleteTimer; - - private EDI m_Destination; - private string m_DestinationString; - - private DateTime m_LastSeenEscorter; - - private MLQuest m_MLQuest; - - [Constructible] - public BaseEscortable() : base(AIType.AI_Melee, FightMode.Aggressor, 22) - { - InitBody(); - InitOutfit(); - - Fame = 200; - Karma = 4000; - - SetSpeed(0.2, 1.0); + Timer.DelayCall(delay, Delete); } + } - public BaseEscortable(Serial serial) - : base(serial) + [SerializableField(2)] + private Type _mlQuestType; + + [SerializableField(3)] + private TextDefinition _mlQuestDestinationMessage; + + [SerializableField(4)] + private TextDefinition _mlQuestPaymentMessage; + + private bool _deleteCorpse; + + private EDI _destination; + + private DateTime _lastSeenEscorter; + + // Not serialized + private List _mlQuest; + + [Constructible] + public BaseEscortable() : base(AIType.AI_Melee, FightMode.Aggressor, 22) + { + InitBody(); + InitOutfit(); + + Fame = 200; + Karma = 4000; + + SetSpeed(0.2, 1.0); + } + + public override bool StaticMLQuester => false; // Suppress automatic quest registration on creation/deserialization + + public override bool CanShout => !Controlled && !IsBeingDeleted; + + public bool IsBeingDeleted => _deleteTimer != null; + + public override bool Commandable => false; // Our master cannot boss us around! + public override bool DeleteCorpseOnDeath => _deleteCorpse; + + [CommandProperty(AccessLevel.GameMaster)] + public string Destination + { + get => _destination?.Name; + set { + DestinationString = value; + _destination = EDI.Find(value); } + } - public override bool StaticMLQuester => false; // Suppress automatic quest registration on creation/deserialization + public static Dictionary EscortTable { get; } = new(); - public override bool CanShout => !Controlled && !IsBeingDeleted; - - public bool IsBeingDeleted => m_DeleteTimer != null; - - public override bool Commandable => false; // Our master cannot boss us around! - public override bool DeleteCorpseOnDeath => m_DeleteCorpse; - - [CommandProperty(AccessLevel.GameMaster)] - public string Destination + protected override List ConstructQuestList() + { + if (_mlQuestType == null) { - get => m_Destination?.Name; - set + var reg = Region; + var types = reg.IsPartOf("Haven Island") ? m_MLQuestTypesNH : m_MLQuestTypes; + + // Get a rented buffer + var list = STArrayPool.Shared.Rent(types.Keys.Count); + + // Copy to the new list + types.Keys.CopyTo(list, 0); + + // Create a span that is the right size + var listSpan = list.AsSpan(0, types.Keys.Count); // We need a span to manipulate + + // Shuffle + listSpan.Shuffle(); + + // Sequentially choose a quest until we have a valid one. + for (var i = 0; i < listSpan.Length; ++i) { - m_DestinationString = value; - m_Destination = EDI.Find(value); - } - } + var questType = list[i]; - public static Dictionary EscortTable { get; } = new(); + var quest = MLQuestSystem.FindQuest(questType); - protected override List ConstructQuestList() - { - if (m_MLQuest == null) - { - var reg = Region; - var list = reg.IsPartOf("Haven Island") ? m_MLQuestTypesNH : m_MLQuestTypes; - - var randomIdx = Utility.Random(list.Length); - - for (var i = 0; i < list.Length; ++i) + if (quest != null) { - var questType = list[randomIdx]; + var okay = true; - var quest = MLQuestSystem.FindQuest(questType); - - if (quest != null) + foreach (var obj in quest.Objectives) { - var okay = true; - - foreach (var obj in quest.Objectives) + if (obj is EscortObjective objective && objective.Destination.Contains(reg)) { - if (obj is EscortObjective objective && objective.Destination.Contains(reg)) - { - okay = false; // We're already there! - break; - } - } - - if (okay) - { - m_MLQuest = quest; + okay = false; // We're already there! break; } } - else if (MLQuestSystem.Debug) + + if (okay) { - Console.WriteLine( - "Warning: Escortable cannot be assigned quest type '{0}', it is not registered", - questType.Name - ); + var (destinationMessage, paymentMessage) = types[questType]; + _mlQuestType = questType; + _mlQuestDestinationMessage = destinationMessage?.Length > 0 ? destinationMessage.RandomElement() : 0; + _mlQuestPaymentMessage = paymentMessage; + // Cached by BaseCreature in m_MLQuests + _mlQuest = new List(1) { quest }; + break; } - - randomIdx = (randomIdx + 1) % list.Length; } - - if (m_MLQuest == null) + else if (MLQuestSystem.Debug) { - if (MLQuestSystem.Debug) - { - Console.WriteLine("Warning: No suitable quest found for escort {0}", Serial); - } - - return null; + logger.Warning("Escortable cannot be assigned quest type '{Type}' because the quest was not registered.", questType); } } - var result = new List { m_MLQuest }; + if (_mlQuest != null && MLQuestSystem.Debug) + { + logger.Warning("No suitable quest found for '{Serial}'", Serial); + } - return result; + // Return the rented array + STArrayPool.Shared.Return(list); } - public override void Shout(PlayerMobile pm) + return _mlQuest; + } + + public override void Shout(PlayerMobile pm) + { + /* + * 1072301 - You there! Care to hear how to earn some easy gold? + * 1072302 - Adventurer! I have an offer for you. + * 1072303 - Wait! I have an opportunity for you to make some gold! + */ + MLQuestSystem.Tell(this, pm, Utility.Random(1072301, 3)); + } + + public virtual void InitBody() + { + SetStr(90, 100); + SetDex(90, 100); + SetInt(15, 25); + + Hue = Race.Human.RandomSkinHue(); + + if (Female = Utility.RandomBool()) { - /* - * 1072301 - You there! Care to hear how to earn some easy gold? - * 1072302 - Adventurer! I have an offer for you. - * 1072303 - Wait! I have an opportunity for you to make some gold! - */ - MLQuestSystem.Tell(this, pm, Utility.Random(1072301, 3)); + Body = 401; + Name = NameList.RandomName("female"); } - - public virtual void InitBody() + else { - SetStr(90, 100); - SetDex(90, 100); - SetInt(15, 25); - - Hue = Race.Human.RandomSkinHue(); - - if (Female = Utility.RandomBool()) - { - Body = 401; - Name = NameList.RandomName("female"); - } - else - { - Body = 400; - Name = NameList.RandomName("male"); - } + Body = 400; + Name = NameList.RandomName("male"); } + } - public virtual void InitOutfit() + public virtual void InitOutfit() + { + AddItem(new FancyShirt(Utility.RandomNeutralHue())); + AddItem(new ShortPants(Utility.RandomNeutralHue())); + AddItem(new Boots(Utility.RandomNeutralHue())); + + Utility.AssignRandomHair(this); + + PackGold(200, 250); + } + + public virtual bool SayDestinationTo(Mobile m) + { + var dest = GetDestination(); + + if (dest == null || !m.Alive) { - AddItem(new FancyShirt(Utility.RandomNeutralHue())); - AddItem(new ShortPants(Utility.RandomNeutralHue())); - AddItem(new Boots(Utility.RandomNeutralHue())); - - Utility.AssignRandomHair(this); - - PackGold(200, 250); - } - - public virtual bool SayDestinationTo(Mobile m) - { - var dest = GetDestination(); - - if (dest == null || !m.Alive) - { - return false; - } - - var escorter = GetEscorter(); - - if (escorter == null) - { - Say( - $"I am looking to go to {(dest.Name == "Ocllo" && m.Map == Map.Trammel ? "Haven" : dest.Name)}, will you take me?" - ); - return true; - } - - if (escorter == m) - { - Say( - $"Lead on! Payment will be made when we arrive in {(dest.Name == "Ocllo" && m.Map == Map.Trammel ? "Haven" : dest.Name)}." - ); - return true; - } - return false; } - public virtual bool AcceptEscorter(Mobile m) + var escorter = GetEscorter(); + + if (escorter == null) { - var dest = GetDestination(); - - if (dest == null) + if (_mlQuestDestinationMessage != null) { - return false; - } - - if (GetEscorter() != null || !m.Alive) - { - return false; - } - - if (EscortTable.TryGetValue(m, out var escortable) && escortable?.Deleted == false && - escortable.GetEscorter() == m) - { - Say("I see you already have an escort."); - return false; - } - - if (m is PlayerMobile mobile && mobile.LastEscortTime + EscortDelay >= Core.Now) - { - var minutes = - (int)Math.Ceiling((mobile.LastEscortTime + EscortDelay - Core.Now).TotalMinutes); - - if (minutes == 1) + if (_mlQuestDestinationMessage.Number > 0) { - Say($"You must rest {minutes} minute before we set out on this journey."); - } - else - { - Say($"You must rest {minutes} minutes before we set out on this journey."); + Say(_mlQuestDestinationMessage.Number); + return true; } - return false; - } - - if (SetControlMaster(m)) - { - m_LastSeenEscorter = Core.Now; - - if (m is PlayerMobile playerMobile) + if (!string.IsNullOrEmpty(_mlQuestDestinationMessage.String)) { - playerMobile.LastEscortTime = Core.Now; + Say(_mlQuestDestinationMessage.String); + return true; } - - Say( - $"Lead on! Payment will be made when we arrive in {(dest.Name == "Ocllo" && m.Map == Map.Trammel ? "Haven" : dest.Name)}." - ); - EscortTable[m] = this; - StartFollow(); - return true; } - return false; - } - - public override bool HandlesOnSpeech(Mobile from) => - !MLQuestSystem.Enabled && (from.InRange(Location, 3) || base.HandlesOnSpeech(from)); - - public override void OnSpeech(SpeechEventArgs e) - { - base.OnSpeech(e); - - if (GetDestination() == null || e.Handled || !e.Mobile.InRange(Location, 3)) - { - return; - } - - if (e.HasKeyword(0x1D)) // *destination* - { - e.Handled = SayDestinationTo(e.Mobile); - } - else if (e.HasKeyword(0x1E)) // *i will take thee* - { - e.Handled = AcceptEscorter(e.Mobile); - } - } - - public override void OnAfterDelete() - { - m_DeleteTimer?.Stop(); - - m_DeleteTimer = null; - - base.OnAfterDelete(); - } - - public override void OnThink() - { - base.OnThink(); - CheckAtDestination(); - } - - protected override bool OnMove(Direction d) - { - if (!base.OnMove(d)) - { - return false; - } - - CheckAtDestination(); - + // I'm looking to go somewhere: + Say(1042807, AffixType.Append, dest.Name == "Ocllo" && m.Map == Map.Trammel ? "Haven" : dest.Name, ""); return true; } - // TODO: Pre-ML methods below, might be mergeable with the ML methods in EscortObjective - - public virtual void StartFollow() + if (escorter == m) { - StartFollow(GetEscorter()); - } - - public virtual void StartFollow(Mobile escorter) - { - if (escorter == null) + if (_mlQuestPaymentMessage != null) { - return; - } - - ActiveSpeed = 0.1; - PassiveSpeed = 0.2; - - ControlOrder = OrderType.Follow; - ControlTarget = escorter; - - if (IsPrisoner && CantWalk) - { - CantWalk = false; - } - - CurrentSpeed = 0.1; - } - - public virtual void StopFollow() - { - ActiveSpeed = 0.2; - PassiveSpeed = 1.0; - - ControlOrder = OrderType.None; - ControlTarget = null; - - CurrentSpeed = 1.0; - } - - public virtual Mobile GetEscorter() - { - if (!Controlled) - { - return null; - } - - var master = ControlMaster; - - if (MLQuestSystem.Enabled || master == null) - { - return master; - } - - if (master.Deleted || master.Map != Map || !master.InRange(Location, 30) || !master.Alive) - { - StopFollow(); - - var lastSeenDelay = Core.Now - m_LastSeenEscorter; - - if (lastSeenDelay >= AbandonDelay) + if (_mlQuestPaymentMessage.Number > 0) { - master.SendLocalizedMessage(1042473); // You have lost the person you were escorting. - Say(1005653); // Hmmm. I seem to have lost my master. - - SetControlMaster(null); - EscortTable.Remove(master); - - Timer.StartTimer(TimeSpan.FromSeconds(5.0), Delete); - return null; + Say(_mlQuestPaymentMessage.Number); + return true; } - ControlOrder = OrderType.Stay; - return master; + if (!string.IsNullOrEmpty(_mlQuestPaymentMessage.String)) + { + Say(_mlQuestPaymentMessage.String); + return true; + } } - if (ControlOrder != OrderType.Follow) + // Lead on! Payment will be made when we arrive at ~1_DESTINATION~! + Say(1042806, dest.Name == "Ocllo" && m.Map == Map.Trammel ? "Haven" : dest.Name); + return true; + } + + return false; + } + + public virtual bool AcceptEscorter(Mobile m) + { + var dest = GetDestination(); + + if (dest == null) + { + return false; + } + + if (GetEscorter() != null || !m.Alive) + { + return false; + } + + if (EscortTable.TryGetValue(m, out var escortable) && escortable?.Deleted == false && + escortable.GetEscorter() == m) + { + Say("I see you already have an escort."); + return false; + } + + if (m is PlayerMobile mobile && mobile.LastEscortTime + EscortDelay >= Core.Now) + { + var minutes = + (int)Math.Ceiling((mobile.LastEscortTime + EscortDelay - Core.Now).TotalMinutes); + + if (minutes == 1) { - StartFollow(master); + Say($"You must rest {minutes} minute before we set out on this journey."); + } + else + { + Say($"You must rest {minutes} minutes before we set out on this journey."); } - m_LastSeenEscorter = Core.Now; + return false; + } + + if (SetControlMaster(m)) + { + _lastSeenEscorter = Core.Now; + + if (m is PlayerMobile playerMobile) + { + playerMobile.LastEscortTime = Core.Now; + } + + Say( + $"Lead on! Payment will be made when we arrive in {(dest.Name == "Ocllo" && m.Map == Map.Trammel ? "Haven" : dest.Name)}." + ); + EscortTable[m] = this; + StartFollow(); + return true; + } + + return false; + } + + public override bool HandlesOnSpeech(Mobile from) => + !MLQuestSystem.Enabled && (from.InRange(Location, 3) || base.HandlesOnSpeech(from)); + + public override void OnSpeech(SpeechEventArgs e) + { + base.OnSpeech(e); + + if (GetDestination() == null || e.Handled || !e.Mobile.InRange(Location, 3)) + { + return; + } + + if (e.HasKeyword(0x1D)) // *destination* + { + e.Handled = SayDestinationTo(e.Mobile); + } + else if (e.HasKeyword(0x1E)) // *i will take thee* + { + e.Handled = AcceptEscorter(e.Mobile); + } + } + + public override void OnAfterDelete() + { + _deleteTimer?.Stop(); + + _deleteTimer = null; + + base.OnAfterDelete(); + } + + public override void OnThink() + { + base.OnThink(); + CheckAtDestination(); + } + + protected override bool OnMove(Direction d) + { + if (!base.OnMove(d)) + { + return false; + } + + CheckAtDestination(); + + return true; + } + + // TODO: Pre-ML methods below, might be mergeable with the ML methods in EscortObjective + + public virtual void StartFollow() + { + StartFollow(GetEscorter()); + } + + public virtual void StartFollow(Mobile escorter) + { + if (escorter == null) + { + return; + } + + ActiveSpeed = 0.1; + PassiveSpeed = 0.2; + + ControlOrder = OrderType.Follow; + ControlTarget = escorter; + + if (IsPrisoner && CantWalk) + { + CantWalk = false; + } + + CurrentSpeed = 0.1; + } + + public virtual void StopFollow() + { + ActiveSpeed = 0.2; + PassiveSpeed = 1.0; + + ControlOrder = OrderType.None; + ControlTarget = null; + + CurrentSpeed = 1.0; + } + + public virtual Mobile GetEscorter() + { + if (!Controlled) + { + return null; + } + + var master = ControlMaster; + + if (MLQuestSystem.Enabled || master == null) + { return master; } - public virtual void BeginDelete() + if (master.Deleted || master.Map != Map || !master.InRange(Location, 30) || !master.Alive) { - m_DeleteTimer?.Stop(); + StopFollow(); - m_DeleteTime = Core.Now + DeleteTime; + var lastSeenDelay = Core.Now - _lastSeenEscorter; - m_DeleteTimer = new DeleteTimer(this, m_DeleteTime - Core.Now); - m_DeleteTimer.Start(); + if (lastSeenDelay >= AbandonDelay) + { + master.SendLocalizedMessage(1042473); // You have lost the person you were escorting. + Say(1005653); // Hmmm. I seem to have lost my master. + + SetControlMaster(null); + EscortTable.Remove(master); + + Timer.StartTimer(TimeSpan.FromSeconds(5.0), Delete); + return null; + } + + ControlOrder = OrderType.Stay; + return master; } - public virtual bool CheckAtDestination() + if (ControlOrder != OrderType.Follow) { - if (MLQuestSystem.Enabled) + StartFollow(master); + } + + _lastSeenEscorter = Core.Now; + return master; + } + + public virtual void BeginDelete() + { + if (_deleteTimer != null) + { + _deleteTimer.Stop(); + _deleteTimer.Delay = DeleteTime; + _deleteTimer.Start(); + } + else + { + _deleteTimer = Timer.DelayCall(DeleteTime, Delete); + } + } + + public virtual bool CheckAtDestination() + { + if (MLQuestSystem.Enabled) + { + return false; + } + + var dest = GetDestination(); + + if (dest == null) + { + return false; + } + + var escorter = GetEscorter(); + + if (escorter == null) + { + return false; + } + + if (dest.Contains(Location)) + { + // We have arrived! I thank thee, ~1_PLAYER_NAME~! I have no further need of thy services. Here is thy pay. + Say(1042809, escorter.Name); + + // not going anywhere + _destination = null; + DestinationString = null; + + var cont = escorter.Backpack ?? escorter.BankBox; + + var gold = new Gold(500, 1000); + + if (!cont.TryDropItem(escorter, gold, false)) { - return false; + gold.MoveToWorld(escorter.Location, escorter.Map); } - var dest = GetDestination(); + StopFollow(); + SetControlMaster(null); + EscortTable.Remove(escorter); + BeginDelete(); - if (dest == null) + Titles.AwardFame(escorter, 10, true); + + var gainedPath = false; + + if (escorter is PlayerMobile pm) { - return false; - } - - var escorter = GetEscorter(); - - if (escorter == null) - { - return false; - } - - if (dest.Contains(Location)) - { - Say( - 1042809, - escorter.Name - ); // We have arrived! I thank thee, ~1_PLAYER_NAME~! I have no further need of thy services. Here is thy pay. - - // not going anywhere - m_Destination = null; - m_DestinationString = null; - - var cont = escorter.Backpack ?? escorter.BankBox; - - var gold = new Gold(500, 1000); - - if (!cont.TryDropItem(escorter, gold, false)) + var virtues = pm.GetOrCreateVirtues(); + if (virtues.CompassionGains > 0 && Core.Now > virtues.NextCompassionDay) { - gold.MoveToWorld(escorter.Location, escorter.Map); + virtues.NextCompassionDay = DateTime.MinValue; + virtues.CompassionGains = 0; } - StopFollow(); - SetControlMaster(null); - EscortTable.Remove(escorter); - BeginDelete(); - - Titles.AwardFame(escorter, 10, true); - - var gainedPath = false; - - if (escorter is PlayerMobile pm) + if (virtues.CompassionGains >= 5) // have already gained 5 times in one day, can gain no more { - var virtues = pm.GetOrCreateVirtues(); - if (virtues.CompassionGains > 0 && Core.Now > virtues.NextCompassionDay) + // You must wait about a day before you can gain in compassion again. + pm.SendLocalizedMessage(1053004); + } + else if (VirtueSystem.Award(pm, VirtueName.Compassion, IsPrisoner ? 400 : 200, ref gainedPath)) + { + if (gainedPath) { - virtues.NextCompassionDay = DateTime.MinValue; - virtues.CompassionGains = 0; + pm.SendLocalizedMessage(1053005); // You have achieved a path in compassion! + } + else + { + pm.SendLocalizedMessage(1053002); // You have gained in compassion. } - if (virtues.CompassionGains >= 5) // have already gained 5 times in one day, can gain no more + // in one day CompassionGains gets reset to 0 + virtues.NextCompassionDay = Core.Now + TimeSpan.FromDays(1.0); + + if (++virtues.CompassionGains >= 5) { // You must wait about a day before you can gain in compassion again. pm.SendLocalizedMessage(1053004); } - else if (VirtueSystem.Award(pm, VirtueName.Compassion, IsPrisoner ? 400 : 200, ref gainedPath)) - { - if (gainedPath) - { - pm.SendLocalizedMessage(1053005); // You have achieved a path in compassion! - } - else - { - pm.SendLocalizedMessage(1053002); // You have gained in compassion. - } - - // in one day CompassionGains gets reset to 0 - virtues.NextCompassionDay = Core.Now + TimeSpan.FromDays(1.0); - - if (++virtues.CompassionGains >= 5) - { - // You must wait about a day before you can gain in compassion again. - pm.SendLocalizedMessage(1053004); - } - } - else - { - // You have achieved the highest path of compassion and can no longer gain any further. - pm.SendLocalizedMessage(1053003); - } } - - return true; - } - - return false; - } - - public override bool OnBeforeDeath() - { - m_DeleteCorpse = Controlled || IsBeingDeleted; - - return base.OnBeforeDeath(); - } - - public override void Serialize(IGenericWriter writer) - { - base.Serialize(writer); - - writer.Write(1); // version - - var dest = GetDestination(); - - writer.Write(dest != null); - - if (dest != null) - { - writer.Write(dest.Name); - } - - writer.Write(m_DeleteTimer != null); - - if (m_DeleteTimer != null) - { - writer.WriteDeltaTime(m_DeleteTime); - } - - MLQuestSystem.WriteQuestRef(writer, StaticMLQuester ? null : m_MLQuest); - } - - public override void Deserialize(IGenericReader reader) - { - base.Deserialize(reader); - - var version = reader.ReadInt(); - - if (reader.ReadBool()) - { - m_DestinationString = - reader.ReadString(); // NOTE: We cannot EDI.Find here, regions have not yet been loaded :-( - } - - if (reader.ReadBool()) - { - m_DeleteTime = reader.ReadDeltaTime(); - m_DeleteTimer = new DeleteTimer(this, m_DeleteTime - Core.Now); - m_DeleteTimer.Start(); - } - - if (version >= 1) - { - var quest = MLQuestSystem.ReadQuestRef(reader); - - if (MLQuestSystem.Enabled && quest != null && !StaticMLQuester) + else { - m_MLQuest = quest; - } - } - } - - public override bool CanBeRenamedBy(Mobile from) => from.AccessLevel >= AccessLevel.GameMaster; - - public override void AddCustomContextEntries(Mobile from, List list) - { - if (from.Alive) - { - var escorter = GetEscorter(); - - if (!MLQuestSystem.Enabled && GetDestination() != null) - { - if (escorter == null || escorter == from) - { - list.Add(new AskDestinationEntry(this, from)); - } - - if (escorter == null) - { - list.Add(new AcceptEscortEntry(this, from)); - } - } - - if (escorter == from) - { - list.Add(new AbandonEscortEntry(this)); + // You have achieved the highest path of compassion and can no longer gain any further. + pm.SendLocalizedMessage(1053003); } } - base.AddCustomContextEntries(from, list); + return true; } - public virtual string[] GetPossibleDestinations() => Core.ML ? m_MLTownNames : m_TownNames; + return false; + } - public virtual string PickRandomDestination() + public override bool OnBeforeDeath() + { + _deleteCorpse = Controlled || IsBeingDeleted; + + return base.OnBeforeDeath(); + } + + private void Deserialize(IGenericReader reader, int version) + { + if (reader.ReadBool()) { - if (Map.Felucca.Regions.Count == 0 || Map == null || Map == Map.Internal || Location == Point3D.Zero) - { - return null; // Not yet fully initialized - } - - var possible = GetPossibleDestinations(); - string picked = null; - - while (picked == null) - { - picked = possible.RandomElement(); - var test = EDI.Find(picked); - - if (test.Contains(Location)) - { - picked = null; - } - } - - return picked; + // NOTE: We cannot EDI.Find here, regions have not yet been loaded :-( + _destinationString = reader.ReadString(); } - public EDI GetDestination() + if (reader.ReadBool()) { - if (MLQuestSystem.Enabled) - { - return null; - } - - if (m_DestinationString == null && m_DeleteTimer == null) - { - m_DestinationString = PickRandomDestination(); - } - - if (m_Destination != null && m_Destination.Name == m_DestinationString) - { - return m_Destination; - } - - if (Map.Felucca.Regions.Count > 0) - { - return m_Destination = EDI.Find(m_DestinationString); - } - - return m_Destination = null; + var deleteTime = reader.ReadDeltaTime(); + _deleteTimer = Timer.DelayCall(deleteTime - Core.Now, Delete); + _deleteTimer.Start(); } - private class DeleteTimer : Timer + var quest = MLQuestSystem.ReadQuestRef(reader); + + if (MLQuestSystem.Enabled && quest != null && !StaticMLQuester) { - private readonly Mobile m_Mobile; + _mlQuestType = quest.GetType(); + _mlQuest = new List(1) { quest }; - public DeleteTimer(Mobile m, TimeSpan delay) - : base(delay) + // This isn't serialized before codegen + if (m_MLQuestTypesNH.TryGetValue(_mlQuestType, out var tuple)) { - m_Mobile = m; - } - - protected override void OnTick() - { - m_Mobile.Delete(); + var (destinationMessages, paymentMessage) = tuple; + _mlQuestDestinationMessage = destinationMessages?.Length > 0 ? destinationMessages.RandomElement() : 0; + _mlQuestPaymentMessage = paymentMessage; } } } - public class EscortDestinationInfo + [AfterDeserialization] + private void AfterDeserialization() { - private static Dictionary m_Table; - - public EscortDestinationInfo(string name, Region region) + if (MLQuestSystem.Enabled && !StaticMLQuester && _mlQuest != null && _mlQuestType != null) { - Name = name; - Region = region; - } - - public string Name { get; } - - public Region Region { get; } - - public bool Contains(Point3D p) => Region.Contains(p); - - public static void Initialize() - { - ICollection list = Map.Felucca.Regions.Values; - - if (list.Count == 0) + var quest = MLQuestSystem.FindQuest(_mlQuestType); + if (quest != null) { - return; + _mlQuest = new List(1) { quest }; } + } + } - m_Table = new Dictionary(); + public override bool CanBeRenamedBy(Mobile from) => from.AccessLevel >= AccessLevel.GameMaster; - foreach (Region r in list) + public override void AddCustomContextEntries(Mobile from, List list) + { + if (from.Alive) + { + var escorter = GetEscorter(); + + if (!MLQuestSystem.Enabled && GetDestination() != null) { - if (r.Name != null && r is DungeonRegion or TownRegion) + if (escorter == null || escorter == from) { - m_Table[r.Name] = new EscortDestinationInfo(r.Name, r); + list.Add(new AskDestinationEntry(this, from)); + } + + if (escorter == null) + { + list.Add(new AcceptEscortEntry(this, from)); } } - } - public static EDI Find(string name) - { - if (name == null || m_Table == null) + if (escorter == from) { - return null; + list.Add(new AbandonEscortEntry(this)); } - - m_Table.TryGetValue(name, out var info); - return info; } + + base.AddCustomContextEntries(from, list); } - public class AskDestinationEntry : ContextMenuEntry + public virtual string[] GetPossibleDestinations() => Core.ML ? _mlTownNames : _townNames; + + public virtual string PickRandomDestination() { - private readonly Mobile m_From; - private readonly BaseEscortable m_Mobile; - - public AskDestinationEntry(BaseEscortable m, Mobile from) - : base(6100, 3) + if (Map.Felucca.Regions.Count == 0 || Map == null || Map == Map.Internal || Location == Point3D.Zero) { - m_Mobile = m; - m_From = from; + return null; // Not yet fully initialized } - public override void OnClick() + var possible = GetPossibleDestinations(); + string picked = null; + + while (picked == null) { - m_Mobile.SayDestinationTo(m_From); + picked = possible.RandomElement(); + var test = EDI.Find(picked); + + if (test.Contains(Location)) + { + picked = null; + } } + + return picked; } - public class AcceptEscortEntry : ContextMenuEntry + public EDI GetDestination() { - private readonly Mobile m_From; - private readonly BaseEscortable m_Mobile; - - public AcceptEscortEntry(BaseEscortable m, Mobile from) - : base(6101, 3) + if (MLQuestSystem.Enabled) { - m_Mobile = m; - m_From = from; + return null; } - public override void OnClick() + if (_destinationString == null && _deleteTimer == null) { - m_Mobile.AcceptEscorter(m_From); + _destinationString = PickRandomDestination(); } - } - public class AbandonEscortEntry : ContextMenuEntry - { - private readonly BaseEscortable m_Mobile; - - public AbandonEscortEntry(BaseEscortable m) - : base(6102, 3) => - m_Mobile = m; - - public override void OnClick() + if (_destination != null && _destination.Name == _destinationString) { - m_Mobile.Delete(); // OSI just seems to delete instantly + return _destination; } + + if (Map.Felucca.Regions.Count > 0) + { + return _destination = EDI.Find(_destinationString); + } + + return _destination = null; + } +} + +public class EscortDestinationInfo +{ + private static Dictionary _table; + + public EscortDestinationInfo(string name, Region region) + { + Name = name; + Region = region; + } + + public string Name { get; } + + public Region Region { get; } + + public bool Contains(Point3D p) => Region.Contains(p); + + public static void Initialize() + { + ICollection list = Map.Felucca.Regions.Values; + + if (list.Count == 0) + { + return; + } + + _table = new Dictionary(); + + foreach (Region r in list) + { + if (r.Name != null && r is DungeonRegion or TownRegion) + { + _table[r.Name] = new EscortDestinationInfo(r.Name, r); + } + } + } + + public static EDI Find(string name) + { + if (name == null || _table == null) + { + return null; + } + + _table.TryGetValue(name, out var info); + return info; + } +} + +public class AskDestinationEntry : ContextMenuEntry +{ + private readonly Mobile _from; + private readonly BaseEscortable _mobile; + + public AskDestinationEntry(BaseEscortable m, Mobile from) : base(6100, 3) + { + _mobile = m; + _from = from; + } + + public override void OnClick() + { + _mobile.SayDestinationTo(_from); + } +} + +public class AcceptEscortEntry : ContextMenuEntry +{ + private readonly Mobile _from; + private readonly BaseEscortable _mobile; + + public AcceptEscortEntry(BaseEscortable m, Mobile from) : base(6101, 3) + { + _mobile = m; + _from = from; + } + + public override void OnClick() + { + _mobile.AcceptEscorter(_from); + } +} + +public class AbandonEscortEntry : ContextMenuEntry +{ + private readonly BaseEscortable _mobile; + + public AbandonEscortEntry(BaseEscortable m) : base(6102, 3) => _mobile = m; + + public override void OnClick() + { + _mobile.Delete(); // OSI just seems to delete instantly } } diff --git a/Projects/UOContent/Mobiles/Townfolk/BrideGroom.cs b/Projects/UOContent/Mobiles/Townfolk/BrideGroom.cs index bd3caafd7..bcd90c3bc 100644 --- a/Projects/UOContent/Mobiles/Townfolk/BrideGroom.cs +++ b/Projects/UOContent/Mobiles/Townfolk/BrideGroom.cs @@ -1,93 +1,76 @@ +using ModernUO.Serialization; using Server.Items; -namespace Server.Mobiles +namespace Server.Mobiles; + +[SerializationGenerator(0, false)] +public partial class BrideGroom : BaseEscortable { - public class BrideGroom : BaseEscortable + [Constructible] + public BrideGroom() { - [Constructible] - public BrideGroom() + if (Female) { - if (Female) - { - Title = "the bride"; - } - else - { - Title = "the groom"; - } + Title = "the bride"; } - - public BrideGroom(Serial serial) : base(serial) + else { - } - - public override bool CanTeach => true; - public override bool ClickTitle => false; // Do not display 'the groom' when single-clicking - - private static int GetRandomHue() - { - return Utility.Random(6) switch - { - 0 => 0, - 1 => Utility.RandomBlueHue(), - 2 => Utility.RandomGreenHue(), - 3 => Utility.RandomRedHue(), - 4 => Utility.RandomYellowHue(), - 5 => Utility.RandomNeutralHue(), - _ => 0 - }; - } - - public override void InitOutfit() - { - if (Female) - { - AddItem(new FancyDress()); - } - else - { - AddItem(new FancyShirt()); - } - - var lowHue = GetRandomHue(); - - AddItem(new LongPants(lowHue)); - - if (Female) - { - AddItem(new Shoes(lowHue)); - } - else - { - AddItem(new Boots(lowHue)); - } - - if (Utility.RandomBool()) - { - HairItemID = 0x203B; - } - else - { - HairItemID = 0x203C; - } - - HairHue = Race.RandomHairHue(); - - PackGold(200, 250); - } - - public override void Serialize(IGenericWriter writer) - { - base.Serialize(writer); - - writer.Write(0); // version - } - - public override void Deserialize(IGenericReader reader) - { - base.Deserialize(reader); - - var version = reader.ReadInt(); + Title = "the groom"; } } + + public override bool CanTeach => true; + public override bool ClickTitle => false; // Do not display 'the groom' when single-clicking + + private static int GetRandomHue() + { + return Utility.Random(6) switch + { + 0 => 0, + 1 => Utility.RandomBlueHue(), + 2 => Utility.RandomGreenHue(), + 3 => Utility.RandomRedHue(), + 4 => Utility.RandomYellowHue(), + 5 => Utility.RandomNeutralHue(), + _ => 0 + }; + } + + public override void InitOutfit() + { + if (Female) + { + AddItem(new FancyDress()); + } + else + { + AddItem(new FancyShirt()); + } + + var lowHue = GetRandomHue(); + + AddItem(new LongPants(lowHue)); + + if (Female) + { + AddItem(new Shoes(lowHue)); + } + else + { + AddItem(new Boots(lowHue)); + } + + if (Utility.RandomBool()) + { + HairItemID = 0x203B; + } + else + { + HairItemID = 0x203C; + } + + HairHue = Race.RandomHairHue(); + + PackGold(200, 250); + } } diff --git a/Projects/UOContent/Mobiles/Townfolk/EscortableMage.cs b/Projects/UOContent/Mobiles/Townfolk/EscortableMage.cs index 764a44e2f..4ef47d652 100644 --- a/Projects/UOContent/Mobiles/Townfolk/EscortableMage.cs +++ b/Projects/UOContent/Mobiles/Townfolk/EscortableMage.cs @@ -1,75 +1,58 @@ +using ModernUO.Serialization; using Server.Items; -namespace Server.Mobiles +namespace Server.Mobiles; + +[SerializationGenerator(0, false)] +public partial class EscortableMage : BaseEscortable { - public class EscortableMage : BaseEscortable + [Constructible] + public EscortableMage() { - [Constructible] - public EscortableMage() - { - Title = "the mage"; + Title = "the mage"; - SetSkill(SkillName.EvalInt, 80.0, 100.0); - SetSkill(SkillName.Inscribe, 80.0, 100.0); - SetSkill(SkillName.Magery, 80.0, 100.0); - SetSkill(SkillName.Meditation, 80.0, 100.0); - SetSkill(SkillName.MagicResist, 80.0, 100.0); + SetSkill(SkillName.EvalInt, 80.0, 100.0); + SetSkill(SkillName.Inscribe, 80.0, 100.0); + SetSkill(SkillName.Magery, 80.0, 100.0); + SetSkill(SkillName.Meditation, 80.0, 100.0); + SetSkill(SkillName.MagicResist, 80.0, 100.0); + } + + public override bool CanTeach => true; + public override bool ClickTitle => false; // Do not display 'the mage' when single-clicking + + private static int GetRandomHue() + { + return Utility.Random(5) switch + { + 0 => Utility.RandomBlueHue(), + 1 => Utility.RandomGreenHue(), + 2 => Utility.RandomRedHue(), + 3 => Utility.RandomYellowHue(), + 4 => Utility.RandomNeutralHue(), + _ => Utility.RandomBlueHue() + }; + } + + public override void InitOutfit() + { + AddItem(new Robe(GetRandomHue())); + + var lowHue = GetRandomHue(); + + AddItem(new ShortPants(lowHue)); + + if (Female) + { + AddItem(new ThighBoots(lowHue)); + } + else + { + AddItem(new Boots(lowHue)); } - public EscortableMage(Serial serial) : base(serial) - { - } + Utility.AssignRandomHair(this); - public override bool CanTeach => true; - public override bool ClickTitle => false; // Do not display 'the mage' when single-clicking - - private static int GetRandomHue() - { - return Utility.Random(5) switch - { - 0 => Utility.RandomBlueHue(), - 1 => Utility.RandomGreenHue(), - 2 => Utility.RandomRedHue(), - 3 => Utility.RandomYellowHue(), - 4 => Utility.RandomNeutralHue(), - _ => Utility.RandomBlueHue() - }; - } - - public override void InitOutfit() - { - AddItem(new Robe(GetRandomHue())); - - var lowHue = GetRandomHue(); - - AddItem(new ShortPants(lowHue)); - - if (Female) - { - AddItem(new ThighBoots(lowHue)); - } - else - { - AddItem(new Boots(lowHue)); - } - - Utility.AssignRandomHair(this); - - PackGold(200, 250); - } - - public override void Serialize(IGenericWriter writer) - { - base.Serialize(writer); - - writer.Write(0); // version - } - - public override void Deserialize(IGenericReader reader) - { - base.Deserialize(reader); - - var version = reader.ReadInt(); - } + PackGold(200, 250); } } diff --git a/Projects/UOContent/Mobiles/Townfolk/Gypsy.cs b/Projects/UOContent/Mobiles/Townfolk/Gypsy.cs index b15e221d2..ab08cf4a9 100644 --- a/Projects/UOContent/Mobiles/Townfolk/Gypsy.cs +++ b/Projects/UOContent/Mobiles/Townfolk/Gypsy.cs @@ -1,74 +1,56 @@ +using ModernUO.Serialization; using Server.Items; -namespace Server.Mobiles +namespace Server.Mobiles; + +[SerializationGenerator(0, false)] +public partial class Gypsy : BaseCreature { - public class Gypsy : BaseCreature + [Constructible] + public Gypsy() : base(AIType.AI_Animal, FightMode.None) { - [Constructible] - public Gypsy() : base(AIType.AI_Animal, FightMode.None) + InitStats(31, 41, 51); + SetSkill(SkillName.Cooking, 65, 88); + SetSkill(SkillName.Snooping, 65, 88); + SetSkill(SkillName.Stealing, 65, 88); + + SetSpeed(0.2, 0.4); + SpeechHue = Utility.RandomDyedHue(); + Hue = Race.Human.RandomSkinHue(); + + if (Female = Utility.RandomBool()) { - InitStats(31, 41, 51); - SetSkill(SkillName.Cooking, 65, 88); - SetSkill(SkillName.Snooping, 65, 88); - SetSkill(SkillName.Stealing, 65, 88); - - SetSpeed(0.2, 0.4); - SpeechHue = Utility.RandomDyedHue(); - Hue = Race.Human.RandomSkinHue(); - - if (Female = Utility.RandomBool()) - { - Body = 0x191; - Name = NameList.RandomName("female"); - AddItem(new Kilt(Utility.RandomDyedHue())); - AddItem(new Shirt(Utility.RandomDyedHue())); - AddItem(new ThighBoots()); - Title = "the gypsy"; - } - else - { - Body = 0x190; - Name = NameList.RandomName("male"); - AddItem(new ShortPants(Utility.RandomNeutralHue())); - AddItem(new Shirt(Utility.RandomDyedHue())); - AddItem(new Sandals()); - Title = "the gypsy"; - } - - AddItem(new Bandana(Utility.RandomDyedHue())); - AddItem(new Dagger()); - - Utility.AssignRandomHair(this); - - Container pack = new Backpack(); - - pack.DropItem(new Gold(250, 300)); - - pack.Movable = false; - - AddItem(pack); + Body = 0x191; + Name = NameList.RandomName("female"); + AddItem(new Kilt(Utility.RandomDyedHue())); + AddItem(new Shirt(Utility.RandomDyedHue())); + AddItem(new ThighBoots()); + Title = "the gypsy"; + } + else + { + Body = 0x190; + Name = NameList.RandomName("male"); + AddItem(new ShortPants(Utility.RandomNeutralHue())); + AddItem(new Shirt(Utility.RandomDyedHue())); + AddItem(new Sandals()); + Title = "the gypsy"; } - public Gypsy(Serial serial) - : base(serial) - { - } + AddItem(new Bandana(Utility.RandomDyedHue())); + AddItem(new Dagger()); - public override bool CanTeach => true; - public override bool ClickTitle => false; + Utility.AssignRandomHair(this); - public override void Serialize(IGenericWriter writer) - { - base.Serialize(writer); + Container pack = new Backpack(); - writer.Write(0); // version - } + pack.DropItem(new Gold(250, 300)); - public override void Deserialize(IGenericReader reader) - { - base.Deserialize(reader); + pack.Movable = false; - var version = reader.ReadInt(); - } + AddItem(pack); } + + public override bool CanTeach => true; + public override bool ClickTitle => false; } diff --git a/Projects/UOContent/Mobiles/Townfolk/HarborMaster.cs b/Projects/UOContent/Mobiles/Townfolk/HarborMaster.cs index 844eb7876..25c35513e 100644 --- a/Projects/UOContent/Mobiles/Townfolk/HarborMaster.cs +++ b/Projects/UOContent/Mobiles/Townfolk/HarborMaster.cs @@ -1,70 +1,52 @@ +using ModernUO.Serialization; using Server.Items; -namespace Server.Mobiles +namespace Server.Mobiles; + +[SerializationGenerator(0, false)] +public partial class HarborMaster : BaseCreature { - public class HarborMaster : BaseCreature + [Constructible] + public HarborMaster() : base(AIType.AI_Animal, FightMode.None) { - [Constructible] - public HarborMaster() : base(AIType.AI_Animal, FightMode.None) + InitStats(31, 41, 51); + SetSkill(SkillName.Mining, 36, 68); + + SetSpeed(0.2, 0.4); + SpeechHue = Utility.RandomDyedHue(); + Hue = Race.Human.RandomSkinHue(); + Blessed = true; + + if (Female = Utility.RandomBool()) { - InitStats(31, 41, 51); - SetSkill(SkillName.Mining, 36, 68); - - SetSpeed(0.2, 0.4); - SpeechHue = Utility.RandomDyedHue(); - Hue = Race.Human.RandomSkinHue(); - Blessed = true; - - if (Female = Utility.RandomBool()) - { - Body = 0x191; - Name = NameList.RandomName("female"); - Title = "the Harbor Mistress"; - } - else - { - Body = 0x190; - Name = NameList.RandomName("male"); - Title = "the Harbor Master"; - } - - AddItem(new Shirt(Utility.RandomDyedHue())); - AddItem(new Boots()); - AddItem(new LongPants(Utility.RandomNeutralHue())); - AddItem(new QuarterStaff()); - - Utility.AssignRandomHair(this); - - Container pack = new Backpack(); - - pack.DropItem(new Gold(250, 300)); - - pack.Movable = false; - - AddItem(pack); + Body = 0x191; + Name = NameList.RandomName("female"); + Title = "the Harbor Mistress"; + } + else + { + Body = 0x190; + Name = NameList.RandomName("male"); + Title = "the Harbor Master"; } - public HarborMaster(Serial serial) - : base(serial) - { - } + AddItem(new Shirt(Utility.RandomDyedHue())); + AddItem(new Boots()); + AddItem(new LongPants(Utility.RandomNeutralHue())); + AddItem(new QuarterStaff()); - public override bool CanTeach => false; + Utility.AssignRandomHair(this); - public override bool ClickTitle => false; + Container pack = new Backpack(); - public override void Serialize(IGenericWriter writer) - { - base.Serialize(writer); + pack.DropItem(new Gold(250, 300)); - writer.Write(0); // version - } + pack.Movable = false; - public override void Deserialize(IGenericReader reader) - { - base.Deserialize(reader); - - var version = reader.ReadInt(); - } + AddItem(pack); } + + public override bool CanTeach => false; + + public override bool ClickTitle => false; } diff --git a/Projects/UOContent/Mobiles/Townfolk/Merchant.cs b/Projects/UOContent/Mobiles/Townfolk/Merchant.cs index 1628535e1..e5867c645 100644 --- a/Projects/UOContent/Mobiles/Townfolk/Merchant.cs +++ b/Projects/UOContent/Mobiles/Townfolk/Merchant.cs @@ -1,90 +1,72 @@ +using ModernUO.Serialization; using Server.Items; -namespace Server.Mobiles +namespace Server.Mobiles; + +[SerializationGenerator(0, false)] +public partial class Merchant : BaseEscortable { - public class Merchant : BaseEscortable + [Constructible] + public Merchant() { - [Constructible] - public Merchant() + Title = "the merchant"; + SetSkill(SkillName.ItemID, 55.0, 78.0); + SetSkill(SkillName.ArmsLore, 55, 78); + } + + public override bool CanTeach => true; + public override bool ClickTitle => false; // Do not display 'the merchant' when single-clicking + + private static int GetRandomHue() + { + return Utility.Random(6) switch { - Title = "the merchant"; - SetSkill(SkillName.ItemID, 55.0, 78.0); - SetSkill(SkillName.ArmsLore, 55, 78); + 0 => 0, + 1 => Utility.RandomBlueHue(), + 2 => Utility.RandomGreenHue(), + 3 => Utility.RandomRedHue(), + 4 => Utility.RandomYellowHue(), + 5 => Utility.RandomNeutralHue(), + _ => 0 + }; + } + + public override void InitOutfit() + { + if (Female) + { + AddItem(new PlainDress()); + } + else + { + AddItem(new Shirt(GetRandomHue())); } - public Merchant(Serial serial) - : base(serial) + var lowHue = GetRandomHue(); + + AddItem(new ThighBoots()); + + if (Female) { + AddItem(new FancyDress(lowHue)); + } + else + { + AddItem(new FancyShirt(lowHue)); } - public override bool CanTeach => true; - public override bool ClickTitle => false; // Do not display 'the merchant' when single-clicking + AddItem(new LongPants(lowHue)); - private static int GetRandomHue() + if (!Female) { - return Utility.Random(6) switch - { - 0 => 0, - 1 => Utility.RandomBlueHue(), - 2 => Utility.RandomGreenHue(), - 3 => Utility.RandomRedHue(), - 4 => Utility.RandomYellowHue(), - 5 => Utility.RandomNeutralHue(), - _ => 0 - }; + AddItem(new BodySash(lowHue)); } - public override void InitOutfit() - { - if (Female) - { - AddItem(new PlainDress()); - } - else - { - AddItem(new Shirt(GetRandomHue())); - } + // if (!Female) + // AddItem( new Longsword() ); - var lowHue = GetRandomHue(); + Utility.AssignRandomHair(this); - AddItem(new ThighBoots()); - - if (Female) - { - AddItem(new FancyDress(lowHue)); - } - else - { - AddItem(new FancyShirt(lowHue)); - } - - AddItem(new LongPants(lowHue)); - - if (!Female) - { - AddItem(new BodySash(lowHue)); - } - - // if (!Female) - // AddItem( new Longsword() ); - - Utility.AssignRandomHair(this); - - PackGold(200, 250); - } - - public override void Serialize(IGenericWriter writer) - { - base.Serialize(writer); - - writer.Write(0); // version - } - - public override void Deserialize(IGenericReader reader) - { - base.Deserialize(reader); - - var version = reader.ReadInt(); - } + PackGold(200, 250); } } diff --git a/Projects/UOContent/Mobiles/Townfolk/Messenger.cs b/Projects/UOContent/Mobiles/Townfolk/Messenger.cs index 6410e45fe..0b57dd50b 100644 --- a/Projects/UOContent/Mobiles/Townfolk/Messenger.cs +++ b/Projects/UOContent/Mobiles/Townfolk/Messenger.cs @@ -1,76 +1,59 @@ +using ModernUO.Serialization; using Server.Items; -namespace Server.Mobiles +namespace Server.Mobiles; + +[SerializationGenerator(0, false)] +public partial class Messenger : BaseEscortable { - public class Messenger : BaseEscortable + [Constructible] + public Messenger() => Title = "the messenger"; + + public override bool CanTeach => true; + public override bool ClickTitle => false; // Do not display 'the messenger' when single-clicking + + private static int GetRandomHue() { - [Constructible] - public Messenger() => Title = "the messenger"; - - public Messenger(Serial serial) : base(serial) + return Utility.Random(6) switch { + 1 => Utility.RandomBlueHue(), + 2 => Utility.RandomGreenHue(), + 3 => Utility.RandomRedHue(), + 4 => Utility.RandomYellowHue(), + 5 => Utility.RandomNeutralHue(), + _ => 0 + }; + } + + public override void InitOutfit() + { + if (Female) + { + AddItem(new PlainDress()); + } + else + { + AddItem(new Shirt(GetRandomHue())); } - public override bool CanTeach => true; - public override bool ClickTitle => false; // Do not display 'the messenger' when single-clicking + var lowHue = GetRandomHue(); - private static int GetRandomHue() + AddItem(new ShortPants(lowHue)); + + if (Female) { - return Utility.Random(6) switch - { - 1 => Utility.RandomBlueHue(), - 2 => Utility.RandomGreenHue(), - 3 => Utility.RandomRedHue(), - 4 => Utility.RandomYellowHue(), - 5 => Utility.RandomNeutralHue(), - _ => 0 - }; + AddItem(new Boots(lowHue)); + } + else + { + AddItem(new Shoes(lowHue)); } - public override void InitOutfit() - { - if (Female) - { - AddItem(new PlainDress()); - } - else - { - AddItem(new Shirt(GetRandomHue())); - } + var randomHair = Utility.Random(4); + HairItemID = randomHair == 4 ? 0x203B : 0x2048 + randomHair; - var lowHue = GetRandomHue(); + HairHue = Race.RandomHairHue(); - AddItem(new ShortPants(lowHue)); - - if (Female) - { - AddItem(new Boots(lowHue)); - } - else - { - AddItem(new Shoes(lowHue)); - } - - var randomHair = Utility.Random(4); - HairItemID = randomHair == 4 ? 0x203B : 0x2048 + randomHair; - - HairHue = Race.RandomHairHue(); - - PackGold(200, 250); - } - - public override void Serialize(IGenericWriter writer) - { - base.Serialize(writer); - - writer.Write(0); // version - } - - public override void Deserialize(IGenericReader reader) - { - base.Deserialize(reader); - - var version = reader.ReadInt(); - } + PackGold(200, 250); } } diff --git a/Projects/UOContent/Mobiles/Townfolk/Minter.cs b/Projects/UOContent/Mobiles/Townfolk/Minter.cs index 7cac3ab7b..f1aad53c7 100644 --- a/Projects/UOContent/Mobiles/Townfolk/Minter.cs +++ b/Projects/UOContent/Mobiles/Townfolk/Minter.cs @@ -1,28 +1,12 @@ -namespace Server.Mobiles +using ModernUO.Serialization; + +namespace Server.Mobiles; + +[SerializationGenerator(0, false)] +public partial class Minter : Banker { - public class Minter : Banker - { - [Constructible] - public Minter() => Title = "the minter"; + [Constructible] + public Minter() => Title = "the minter"; - public Minter(Serial serial) : base(serial) - { - } - - public override NpcGuild NpcGuild => NpcGuild.MerchantsGuild; - - public override void Serialize(IGenericWriter writer) - { - base.Serialize(writer); - - writer.Write(0); // version - } - - public override void Deserialize(IGenericReader reader) - { - base.Deserialize(reader); - - var version = reader.ReadInt(); - } - } + public override NpcGuild NpcGuild => NpcGuild.MerchantsGuild; } diff --git a/Projects/UOContent/Mobiles/Townfolk/Ninja.cs b/Projects/UOContent/Mobiles/Townfolk/Ninja.cs index 0026e20d1..1448da209 100644 --- a/Projects/UOContent/Mobiles/Townfolk/Ninja.cs +++ b/Projects/UOContent/Mobiles/Townfolk/Ninja.cs @@ -1,78 +1,61 @@ +using ModernUO.Serialization; using Server.Items; -namespace Server.Mobiles +namespace Server.Mobiles; + +[SerializationGenerator(0)] +public partial class Ninja : BaseCreature { - public class Ninja : BaseCreature + [Constructible] + public Ninja() : base(AIType.AI_Melee, FightMode.Aggressor) { - [Constructible] - public Ninja() : base(AIType.AI_Melee, FightMode.Aggressor) + Title = "the ninja"; + InitStats(100, 100, 25); + + SetSkill(SkillName.Fencing, 64.0, 80.0); + SetSkill(SkillName.Macing, 64.0, 80.0); + SetSkill(SkillName.Ninjitsu, 60.0, 80.0); + SetSkill(SkillName.Parry, 64.0, 80.0); + SetSkill(SkillName.Tactics, 64.0, 85.0); + SetSkill(SkillName.Swords, 64.0, 85.0); + + SetSpeed(0.2, 0.4); + SpeechHue = Utility.RandomDyedHue(); + Hue = Race.Human.RandomSkinHue(); + + if (Female = Utility.RandomBool()) { - Title = "the ninja"; - InitStats(100, 100, 25); - - SetSkill(SkillName.Fencing, 64.0, 80.0); - SetSkill(SkillName.Macing, 64.0, 80.0); - SetSkill(SkillName.Ninjitsu, 60.0, 80.0); - SetSkill(SkillName.Parry, 64.0, 80.0); - SetSkill(SkillName.Tactics, 64.0, 85.0); - SetSkill(SkillName.Swords, 64.0, 85.0); - - SetSpeed(0.2, 0.4); - SpeechHue = Utility.RandomDyedHue(); - Hue = Race.Human.RandomSkinHue(); - - if (Female = Utility.RandomBool()) - { - Body = 0x191; - Name = NameList.RandomName("female"); - } - else - { - Body = 0x190; - Name = NameList.RandomName("male"); - } - - if (!Female) - { - AddItem(new LeatherNinjaHood()); - } - - AddItem(new LeatherNinjaPants()); - AddItem(new LeatherNinjaBelt()); - AddItem(new LeatherNinjaJacket()); - AddItem(new NinjaTabi()); - - var hairHue = Utility.RandomNondyedHue(); - - Utility.AssignRandomHair(this, hairHue); - - if (Utility.Random(7) != 0) - { - Utility.AssignRandomFacialHair(this, hairHue); - } - - PackGold(250, 300); + Body = 0x191; + Name = NameList.RandomName("female"); + } + else + { + Body = 0x190; + Name = NameList.RandomName("male"); } - public Ninja(Serial serial) : base(serial) + if (!Female) { + AddItem(new LeatherNinjaHood()); } - public override bool CanTeach => true; - public override bool ClickTitle => false; + AddItem(new LeatherNinjaPants()); + AddItem(new LeatherNinjaBelt()); + AddItem(new LeatherNinjaJacket()); + AddItem(new NinjaTabi()); - public override void Serialize(IGenericWriter writer) + var hairHue = Utility.RandomNondyedHue(); + + Utility.AssignRandomHair(this, hairHue); + + if (Utility.Random(7) != 0) { - base.Serialize(writer); - - writer.WriteEncodedInt(0); // version + Utility.AssignRandomFacialHair(this, hairHue); } - public override void Deserialize(IGenericReader reader) - { - base.Deserialize(reader); - - var version = reader.ReadEncodedInt(); - } + PackGold(250, 300); } + + public override bool CanTeach => true; + public override bool ClickTitle => false; } diff --git a/Projects/UOContent/Mobiles/Townfolk/Noble.cs b/Projects/UOContent/Mobiles/Townfolk/Noble.cs index 46cbe227b..4e1edc08a 100644 --- a/Projects/UOContent/Mobiles/Townfolk/Noble.cs +++ b/Projects/UOContent/Mobiles/Townfolk/Noble.cs @@ -1,92 +1,75 @@ +using ModernUO.Serialization; using Server.Items; -namespace Server.Mobiles +namespace Server.Mobiles; + +[SerializationGenerator(0, false)] +public partial class Noble : BaseEscortable { - public class Noble : BaseEscortable + [Constructible] + public Noble() { - [Constructible] - public Noble() - { - Title = "the noble"; + Title = "the noble"; - SetSkill(SkillName.Parry, 80.0, 100.0); - SetSkill(SkillName.Swords, 80.0, 100.0); - SetSkill(SkillName.Tactics, 80.0, 100.0); + SetSkill(SkillName.Parry, 80.0, 100.0); + SetSkill(SkillName.Swords, 80.0, 100.0); + SetSkill(SkillName.Tactics, 80.0, 100.0); + } + + public override bool CanTeach => true; + public override bool ClickTitle => false; // Do not display 'the noble' when single-clicking + + private static int GetRandomHue() + { + return Utility.Random(6) switch + { + 1 => Utility.RandomBlueHue(), + 2 => Utility.RandomGreenHue(), + 3 => Utility.RandomRedHue(), + 4 => Utility.RandomYellowHue(), + 5 => Utility.RandomNeutralHue(), + _ => 0 + }; + } + + public override void InitOutfit() + { + if (Female) + { + AddItem(new FancyDress()); + } + else + { + AddItem(new FancyShirt(GetRandomHue())); } - public Noble(Serial serial) : base(serial) + var lowHue = GetRandomHue(); + + AddItem(new ShortPants(lowHue)); + + if (Female) { + AddItem(new ThighBoots(lowHue)); + } + else + { + AddItem(new Boots(lowHue)); } - public override bool CanTeach => true; - public override bool ClickTitle => false; // Do not display 'the noble' when single-clicking - - private static int GetRandomHue() + if (!Female) { - return Utility.Random(6) switch - { - 1 => Utility.RandomBlueHue(), - 2 => Utility.RandomGreenHue(), - 3 => Utility.RandomRedHue(), - 4 => Utility.RandomYellowHue(), - 5 => Utility.RandomNeutralHue(), - _ => 0 - }; + AddItem(new BodySash(lowHue)); } - public override void InitOutfit() + AddItem(new Cloak(GetRandomHue())); + + if (!Female) { - if (Female) - { - AddItem(new FancyDress()); - } - else - { - AddItem(new FancyShirt(GetRandomHue())); - } - - var lowHue = GetRandomHue(); - - AddItem(new ShortPants(lowHue)); - - if (Female) - { - AddItem(new ThighBoots(lowHue)); - } - else - { - AddItem(new Boots(lowHue)); - } - - if (!Female) - { - AddItem(new BodySash(lowHue)); - } - - AddItem(new Cloak(GetRandomHue())); - - if (!Female) - { - AddItem(new Longsword()); - } - - Utility.AssignRandomHair(this); - - PackGold(200, 250); + AddItem(new Longsword()); } - public override void Serialize(IGenericWriter writer) - { - base.Serialize(writer); + Utility.AssignRandomHair(this); - writer.Write(0); // version - } - - public override void Deserialize(IGenericReader reader) - { - base.Deserialize(reader); - - var version = reader.ReadInt(); - } + PackGold(200, 250); } } diff --git a/Projects/UOContent/Mobiles/Townfolk/Peasant.cs b/Projects/UOContent/Mobiles/Townfolk/Peasant.cs index a25c68234..26d905d39 100644 --- a/Projects/UOContent/Mobiles/Townfolk/Peasant.cs +++ b/Projects/UOContent/Mobiles/Townfolk/Peasant.cs @@ -1,82 +1,65 @@ +using ModernUO.Serialization; using Server.Items; -namespace Server.Mobiles +namespace Server.Mobiles; + +[SerializationGenerator(0, false)] +public partial class Peasant : BaseEscortable { - public class Peasant : BaseEscortable + [Constructible] + public Peasant() => Title = "the peasant"; + + public override bool CanTeach => true; + public override bool ClickTitle => false; // Do not display 'the peasant' when single-clicking + + private static int GetRandomHue() { - [Constructible] - public Peasant() => Title = "the peasant"; - - public Peasant(Serial serial) : base(serial) + return Utility.Random(6) switch { + 0 => 0, + 1 => Utility.RandomBlueHue(), + 2 => Utility.RandomGreenHue(), + 3 => Utility.RandomRedHue(), + 4 => Utility.RandomYellowHue(), + 5 => Utility.RandomNeutralHue(), + _ => 0 + }; + } + + public override void InitOutfit() + { + if (Female) + { + AddItem(new PlainDress()); + } + else + { + AddItem(new Shirt(GetRandomHue())); } - public override bool CanTeach => true; - public override bool ClickTitle => false; // Do not display 'the peasant' when single-clicking + var lowHue = GetRandomHue(); - private static int GetRandomHue() + AddItem(new ShortPants(lowHue)); + + if (Female) { - return Utility.Random(6) switch - { - 0 => 0, - 1 => Utility.RandomBlueHue(), - 2 => Utility.RandomGreenHue(), - 3 => Utility.RandomRedHue(), - 4 => Utility.RandomYellowHue(), - 5 => Utility.RandomNeutralHue(), - _ => 0 - }; + AddItem(new Boots(lowHue)); + } + else + { + AddItem(new Shoes(lowHue)); } - public override void InitOutfit() - { - if (Female) - { - AddItem(new PlainDress()); - } - else - { - AddItem(new Shirt(GetRandomHue())); - } + // if (!Female) + // AddItem( new BodySash( lowHue ) ); - var lowHue = GetRandomHue(); + // AddItem( new Cloak( GetRandomHue() ) ); - AddItem(new ShortPants(lowHue)); + // if (!Female) + // AddItem( new Longsword() ); - if (Female) - { - AddItem(new Boots(lowHue)); - } - else - { - AddItem(new Shoes(lowHue)); - } + Utility.AssignRandomHair(this); - // if (!Female) - // AddItem( new BodySash( lowHue ) ); - - // AddItem( new Cloak( GetRandomHue() ) ); - - // if (!Female) - // AddItem( new Longsword() ); - - Utility.AssignRandomHair(this); - - PackGold(200, 250); - } - - public override void Serialize(IGenericWriter writer) - { - base.Serialize(writer); - - writer.Write(0); // version - } - - public override void Deserialize(IGenericReader reader) - { - base.Deserialize(reader); - - var version = reader.ReadInt(); - } + PackGold(200, 250); } } diff --git a/Projects/UOContent/Mobiles/Townfolk/Prisoner.cs b/Projects/UOContent/Mobiles/Townfolk/Prisoner.cs index e4615d149..0eaaa3841 100644 --- a/Projects/UOContent/Mobiles/Townfolk/Prisoner.cs +++ b/Projects/UOContent/Mobiles/Townfolk/Prisoner.cs @@ -1,88 +1,70 @@ -using Server.Engines.MLQuests; +using ModernUO.Serialization; +using Server.Engines.MLQuests; using Server.Items; -namespace Server.Mobiles.Townfolk +namespace Server.Mobiles.Townfolk; + +[SerializationGenerator(0, false)] +public partial class Prisoner : BaseEscortable { - public class Prisoner : BaseEscortable + [Constructible] + public Prisoner() { - [Constructible] - public Prisoner() - { - Title = Female ? "the noblewoman" : "the nobleman"; + Title = Female ? "the noblewoman" : "the nobleman"; - CantWalk = true; - IsPrisoner = true; + CantWalk = true; + IsPrisoner = true; + } + + public override bool CanTeach => true; + public override bool ClickTitle => false; + + public override void InitOutfit() + { + if (Female) + { + AddItem(new FancyDress(Utility.RandomNondyedHue())); + } + else + { + AddItem(new FancyShirt(Utility.RandomNondyedHue())); + AddItem(new LongPants(Utility.RandomNondyedHue())); } - public Prisoner(Serial serial) - : base(serial) + if (Utility.RandomBool()) { + AddItem(new Boots()); + } + else + { + AddItem(new ThighBoots()); } - public override bool CanTeach => true; - public override bool ClickTitle => false; + Utility.AssignRandomHair(this); + Utility.AssignRandomFacialHair(this, HairHue); + } - public override void InitOutfit() + public override void Shout(PlayerMobile pm) + { + /* + * 502261 - HELP! + * 502262 - Help me! + * 502263 - Canst thou aid me?! + * 502264 - Help a poor prisoner! + * 502265 - Help! Please! + * 502266 - Aaah! Help me! + * 502267 - Go and get some help! + */ + MLQuestSystem.Tell(this, pm, Utility.Random(502261, 7)); + } + + public override void OnMovement(Mobile m, Point3D oldLocation) + { + base.OnMovement(m, oldLocation); + + if (CantWalk && InRange(m, 1) && !InRange(oldLocation, 1) && (!m.Hidden || m.AccessLevel == AccessLevel.Player)) { - if (Female) - { - AddItem(new FancyDress(Utility.RandomNondyedHue())); - } - else - { - AddItem(new FancyShirt(Utility.RandomNondyedHue())); - AddItem(new LongPants(Utility.RandomNondyedHue())); - } - - if (Utility.RandomBool()) - { - AddItem(new Boots()); - } - else - { - AddItem(new ThighBoots()); - } - - Utility.AssignRandomHair(this); - Utility.AssignRandomFacialHair(this, HairHue); - } - - public override void Shout(PlayerMobile pm) - { - /* - * 502261 - HELP! - * 502262 - Help me! - * 502263 - Canst thou aid me?! - * 502264 - Help a poor prisoner! - * 502265 - Help! Please! - * 502266 - Aaah! Help me! - * 502267 - Go and get some help! - */ - MLQuestSystem.Tell(this, pm, Utility.Random(502261, 7)); - } - - public override void OnMovement(Mobile m, Point3D oldLocation) - { - base.OnMovement(m, oldLocation); - - if (CantWalk && InRange(m, 1) && !InRange(oldLocation, 1) && (!m.Hidden || m.AccessLevel == AccessLevel.Player)) - { - Say(502268); // Quickly, I beg thee! Unlock my chains! If thou dost look at me close thou canst see them. - } - } - - public override void Serialize(IGenericWriter writer) - { - base.Serialize(writer); - - writer.Write(0); // version - } - - public override void Deserialize(IGenericReader reader) - { - base.Deserialize(reader); - - var version = reader.ReadInt(); + Say(502268); // Quickly, I beg thee! Unlock my chains! If thou dost look at me close thou canst see them. } } } diff --git a/Projects/UOContent/Mobiles/Townfolk/Samurai.cs b/Projects/UOContent/Mobiles/Townfolk/Samurai.cs index 14e9f1072..16be05303 100644 --- a/Projects/UOContent/Mobiles/Townfolk/Samurai.cs +++ b/Projects/UOContent/Mobiles/Townfolk/Samurai.cs @@ -1,113 +1,116 @@ +using ModernUO.Serialization; using Server.Items; -namespace Server.Mobiles +namespace Server.Mobiles; + +[SerializationGenerator(0)] +public partial class Samurai : BaseCreature { - public class Samurai : BaseCreature + [Constructible] + public Samurai() : base(AIType.AI_Melee, FightMode.Aggressor) { - [Constructible] - public Samurai() : base(AIType.AI_Melee, FightMode.Aggressor) + Title = "the samurai"; + + InitStats(100, 100, 25); + + SetSkill(SkillName.ArmsLore, 64.0, 80.0); + SetSkill(SkillName.Bushido, 64.0, 85.0); + SetSkill(SkillName.Parry, 64.0, 80.0); + SetSkill(SkillName.Swords, 64.0, 85.0); + + SetSpeed(0.2, 0.4); + SpeechHue = Utility.RandomDyedHue(); + Hue = Race.Human.RandomSkinHue(); + + if (Female = Utility.RandomBool()) { - Title = "the samurai"; + Body = 0x191; + Name = NameList.RandomName("female"); + } + else + { + Body = 0x190; + Name = NameList.RandomName("male"); + } - InitStats(100, 100, 25); - - SetSkill(SkillName.ArmsLore, 64.0, 80.0); - SetSkill(SkillName.Bushido, 64.0, 85.0); - SetSkill(SkillName.Parry, 64.0, 80.0); - SetSkill(SkillName.Swords, 64.0, 85.0); - - SetSpeed(0.2, 0.4); - SpeechHue = Utility.RandomDyedHue(); - Hue = Race.Human.RandomSkinHue(); - - if (Female = Utility.RandomBool()) - { - Body = 0x191; - Name = NameList.RandomName("female"); - } - else - { - Body = 0x190; - Name = NameList.RandomName("male"); - } - - switch (Utility.Random(3)) - { - case 0: + switch (Utility.Random(3)) + { + case 0: + { AddItem(new Lajatang()); break; - case 1: + } + case 1: + { AddItem(new Wakizashi()); break; - case 2: + } + case 2: + { AddItem(new NoDachi()); break; - } + } + } - switch (Utility.Random(3)) - { - case 0: + switch (Utility.Random(3)) + { + case 0: + { AddItem(new LeatherSuneate()); break; - case 1: + } + case 1: + { AddItem(new PlateSuneate()); break; - case 2: + } + case 2: + { AddItem(new StuddedHaidate()); break; - } + } + } - switch (Utility.Random(4)) - { - case 0: + switch (Utility.Random(4)) + { + case 0: + { AddItem(new LeatherJingasa()); break; - case 1: + } + case 1: + { AddItem(new ChainHatsuburi()); break; - case 2: + } + case 2: + { AddItem(new HeavyPlateJingasa()); break; - case 3: + } + case 3: + { AddItem(new DecorativePlateKabuto()); break; - } - - AddItem(new LeatherDo()); - AddItem(new LeatherHiroSode()); - AddItem(new SamuraiTabi(Utility.RandomNondyedHue())); // TODO: Hue - - var hairHue = Utility.RandomNondyedHue(); - - Utility.AssignRandomHair(this, hairHue); - - if (Utility.Random(7) != 0) - { - Utility.AssignRandomFacialHair(this, hairHue); - } - - PackGold(250, 300); + } } - public Samurai(Serial serial) : base(serial) + AddItem(new LeatherDo()); + AddItem(new LeatherHiroSode()); + AddItem(new SamuraiTabi(Utility.RandomNondyedHue())); // TODO: Hue + + var hairHue = Utility.RandomNondyedHue(); + + Utility.AssignRandomHair(this, hairHue); + + if (Utility.Random(7) != 0) { + Utility.AssignRandomFacialHair(this, hairHue); } - public override bool CanTeach => true; - public override bool ClickTitle => false; - - 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(); - } + PackGold(250, 300); } + + public override bool CanTeach => true; + public override bool ClickTitle => false; } diff --git a/Projects/UOContent/Mobiles/Townfolk/Sculptor.cs b/Projects/UOContent/Mobiles/Townfolk/Sculptor.cs index 15c126cdb..00f23353c 100644 --- a/Projects/UOContent/Mobiles/Townfolk/Sculptor.cs +++ b/Projects/UOContent/Mobiles/Townfolk/Sculptor.cs @@ -1,65 +1,47 @@ +using ModernUO.Serialization; using Server.Items; -namespace Server.Mobiles +namespace Server.Mobiles; + +[SerializationGenerator(0, false)] +public partial class Sculptor : BaseCreature { - public class Sculptor : BaseCreature + [Constructible] + public Sculptor() : base(AIType.AI_Animal, FightMode.None) { - [Constructible] - public Sculptor() : base(AIType.AI_Animal, FightMode.None) + InitStats(31, 41, 51); + + SetSpeed(0.2, 0.4); + SpeechHue = Utility.RandomDyedHue(); + Title = "the sculptor"; + Hue = Race.Human.RandomSkinHue(); + + if (Female = Utility.RandomBool()) { - InitStats(31, 41, 51); - - SetSpeed(0.2, 0.4); - SpeechHue = Utility.RandomDyedHue(); - Title = "the sculptor"; - Hue = Race.Human.RandomSkinHue(); - - if (Female = Utility.RandomBool()) - { - Body = 0x191; - Name = NameList.RandomName("female"); - AddItem(new Kilt(Utility.RandomNeutralHue())); - } - else - { - Body = 0x190; - Name = NameList.RandomName("male"); - AddItem(new LongPants(Utility.RandomNeutralHue())); - } - - AddItem(new Doublet(Utility.RandomNeutralHue())); - AddItem(new HalfApron()); - - Utility.AssignRandomHair(this); - - Container pack = new Backpack(); - - pack.DropItem(new Gold(250, 300)); - - pack.Movable = false; - - AddItem(pack); + Body = 0x191; + Name = NameList.RandomName("female"); + AddItem(new Kilt(Utility.RandomNeutralHue())); + } + else + { + Body = 0x190; + Name = NameList.RandomName("male"); + AddItem(new LongPants(Utility.RandomNeutralHue())); } - public Sculptor(Serial serial) - : base(serial) - { - } + AddItem(new Doublet(Utility.RandomNeutralHue())); + AddItem(new HalfApron()); - public override bool ClickTitle => false; + Utility.AssignRandomHair(this); - public override void Serialize(IGenericWriter writer) - { - base.Serialize(writer); + Container pack = new Backpack(); - writer.Write(0); // version - } + pack.DropItem(new Gold(250, 300)); - public override void Deserialize(IGenericReader reader) - { - base.Deserialize(reader); + pack.Movable = false; - var version = reader.ReadInt(); - } + AddItem(pack); } + + public override bool ClickTitle => false; } diff --git a/Projects/UOContent/Mobiles/Townfolk/SeekerOfAdventure.cs b/Projects/UOContent/Mobiles/Townfolk/SeekerOfAdventure.cs index fd14c5a38..c9b22393d 100644 --- a/Projects/UOContent/Mobiles/Townfolk/SeekerOfAdventure.cs +++ b/Projects/UOContent/Mobiles/Townfolk/SeekerOfAdventure.cs @@ -1,97 +1,84 @@ +using ModernUO.Serialization; using Server.Items; -namespace Server.Mobiles +namespace Server.Mobiles; + +[SerializationGenerator(0, false)] +public partial class SeekerOfAdventure : BaseEscortable { - public class SeekerOfAdventure : BaseEscortable + private static readonly string[] _dungeons = { - private static readonly string[] m_Dungeons = + "Covetous", "Deceit", "Despise", + "Destard", "Hythloth", "Shame", // Old Code for Pre-ML shards. + "Wrong" + }; + + private static readonly string[] _mlDestinations = + { + "Cove", "Serpent's Hold", "Jhelom", // ML List + "Nujel'm" + }; + + [Constructible] + public SeekerOfAdventure() => Title = "the seeker of adventure"; + + public SeekerOfAdventure(Serial serial) : base(serial) + { + } + + public override bool ClickTitle => false; // Do not display 'the seeker of adventure' when single-clicking + + public override string[] GetPossibleDestinations() => Core.ML ? _mlDestinations : _dungeons; + + private static int GetRandomHue() + { + return Utility.Random(6) switch { - "Covetous", "Deceit", "Despise", - "Destard", "Hythloth", "Shame", // Old Code for Pre-ML shards. - "Wrong" + 0 => 0, + 1 => Utility.RandomBlueHue(), + 2 => Utility.RandomGreenHue(), + 3 => Utility.RandomRedHue(), + 4 => Utility.RandomYellowHue(), + 5 => Utility.RandomNeutralHue(), + _ => 0 }; + } - private static readonly string[] m_MLDestinations = + public override void InitOutfit() + { + if (Female) { - "Cove", "Serpent's Hold", "Jhelom", // ML List - "Nujel'm" - }; - - [Constructible] - public SeekerOfAdventure() => Title = "the seeker of adventure"; - - public SeekerOfAdventure(Serial serial) : base(serial) + AddItem(new FancyDress(GetRandomHue())); + } + else { + AddItem(new FancyShirt(GetRandomHue())); } - public override bool ClickTitle => false; // Do not display 'the seeker of adventure' when single-clicking + var lowHue = GetRandomHue(); - public override string[] GetPossibleDestinations() => Core.ML ? m_MLDestinations : m_Dungeons; + AddItem(new ShortPants(lowHue)); - private static int GetRandomHue() + if (Female) { - return Utility.Random(6) switch - { - 0 => 0, - 1 => Utility.RandomBlueHue(), - 2 => Utility.RandomGreenHue(), - 3 => Utility.RandomRedHue(), - 4 => Utility.RandomYellowHue(), - 5 => Utility.RandomNeutralHue(), - _ => 0 - }; + AddItem(new ThighBoots(lowHue)); + } + else + { + AddItem(new Boots(lowHue)); } - public override void InitOutfit() + if (!Female) { - if (Female) - { - AddItem(new FancyDress(GetRandomHue())); - } - else - { - AddItem(new FancyShirt(GetRandomHue())); - } - - var lowHue = GetRandomHue(); - - AddItem(new ShortPants(lowHue)); - - if (Female) - { - AddItem(new ThighBoots(lowHue)); - } - else - { - AddItem(new Boots(lowHue)); - } - - if (!Female) - { - AddItem(new BodySash(lowHue)); - } - - AddItem(new Cloak(GetRandomHue())); - - AddItem(new Longsword()); - - Utility.AssignRandomHair(this); - - PackGold(100, 150); + AddItem(new BodySash(lowHue)); } - public override void Serialize(IGenericWriter writer) - { - base.Serialize(writer); + AddItem(new Cloak(GetRandomHue())); - writer.Write(0); // version - } + AddItem(new Longsword()); - public override void Deserialize(IGenericReader reader) - { - base.Deserialize(reader); + Utility.AssignRandomHair(this); - var version = reader.ReadInt(); - } + PackGold(100, 150); } } diff --git a/Projects/UOContent/Mobiles/Townfolk/TownCrier.cs b/Projects/UOContent/Mobiles/Townfolk/TownCrier.cs index 82f8c37cb..fbe99d3bc 100644 --- a/Projects/UOContent/Mobiles/Townfolk/TownCrier.cs +++ b/Projects/UOContent/Mobiles/Townfolk/TownCrier.cs @@ -1,440 +1,495 @@ using System; using System.Collections.Generic; using System.Text; +using ModernUO.Serialization; using Server.Gumps; using Server.Items; using Server.Network; using Server.Prompts; -namespace Server.Mobiles +namespace Server.Mobiles; + +public interface ITownCrierEntryList { - public interface ITownCrierEntryList + List Entries { get; } + TownCrierEntry GetRandomEntry(); + TownCrierEntry AddEntry(string[] lines, TimeSpan duration); + void RemoveEntry(TownCrierEntry entry); +} + +public class GlobalTownCrierEntryList : ITownCrierEntryList +{ + private static GlobalTownCrierEntryList m_Instance; + + public static GlobalTownCrierEntryList Instance => m_Instance ??= new GlobalTownCrierEntryList(); + + public bool IsEmpty => Entries == null || Entries.Count == 0; + + public List Entries { get; private set; } + + public TownCrierEntry GetRandomEntry() { - List Entries { get; } - TownCrierEntry GetRandomEntry(); - TownCrierEntry AddEntry(string[] lines, TimeSpan duration); - void RemoveEntry(TownCrierEntry entry); + for (var i = (Entries?.Count ?? 0) - 1; i >= 0; --i) + { + if (i >= Entries!.Count) + { + continue; + } + + var tce = Entries[i]; + + if (tce.Expired) + { + RemoveEntry(tce); + } + } + + return Entries?.RandomElement(); } - public class GlobalTownCrierEntryList : ITownCrierEntryList + public TownCrierEntry AddEntry(string[] lines, TimeSpan duration) { - private static GlobalTownCrierEntryList m_Instance; + Entries ??= new List(); - public static GlobalTownCrierEntryList Instance => m_Instance ??= new GlobalTownCrierEntryList(); + var tce = new TownCrierEntry(lines, duration); - public bool IsEmpty => Entries == null || Entries.Count == 0; + Entries.Add(tce); - public List Entries { get; private set; } + var instances = TownCrier.Instances; - public TownCrierEntry GetRandomEntry() + for (var i = 0; i < instances.Count; ++i) { - for (var i = (Entries?.Count ?? 0) - 1; i >= 0; --i) - { - if (i >= Entries!.Count) - { - continue; - } - - var tce = Entries[i]; - - if (tce.Expired) - { - RemoveEntry(tce); - } - } - - return Entries?.RandomElement(); + instances[i].ForceBeginAutoShout(); } - public TownCrierEntry AddEntry(string[] lines, TimeSpan duration) + return tce; + } + + public void RemoveEntry(TownCrierEntry tce) + { + if (Entries == null) { - Entries ??= new List(); - - var tce = new TownCrierEntry(lines, duration); - - Entries.Add(tce); - - var instances = TownCrier.Instances; - - for (var i = 0; i < instances.Count; ++i) - { - instances[i].ForceBeginAutoShout(); - } - - return tce; + return; } - public void RemoveEntry(TownCrierEntry tce) + Entries.Remove(tce); + + if (Entries.Count == 0) { - if (Entries == null) - { - return; - } - - Entries.Remove(tce); - - if (Entries.Count == 0) - { - Entries = null; - } - } - - public static void Initialize() - { - CommandSystem.Register("TownCriers", AccessLevel.GameMaster, TownCriers_OnCommand); - } - - [Usage("TownCriers"), Description("Manages the global town crier list.")] - public static void TownCriers_OnCommand(CommandEventArgs e) - { - e.Mobile.SendGump(new TownCrierGump(e.Mobile, Instance)); + Entries = null; } } - public class TownCrierEntry + public static void Initialize() { - public TownCrierEntry(string[] lines, TimeSpan duration) - { - Lines = lines; - - ExpireTime = Core.Now + duration.Clamp(TimeSpan.Zero, TimeSpan.FromDays(365)); - } - - public string[] Lines { get; } - - public DateTime ExpireTime { get; } - - public bool Expired => Core.Now >= ExpireTime; + CommandSystem.Register("TownCriers", AccessLevel.GameMaster, TownCriers_OnCommand); } - public class TownCrierDurationPrompt : Prompt + [Usage("TownCriers"), Description("Manages the global town crier list.")] + public static void TownCriers_OnCommand(CommandEventArgs e) { - private readonly ITownCrierEntryList m_Owner; + e.Mobile.SendGump(new TownCrierGump(e.Mobile, Instance)); + } +} - public TownCrierDurationPrompt(ITownCrierEntryList owner) => m_Owner = owner; +public class TownCrierEntry +{ + public TownCrierEntry(string[] lines, TimeSpan duration) + { + Lines = lines; - public override void OnResponse(Mobile from, string text) + ExpireTime = Core.Now + duration.Clamp(TimeSpan.Zero, TimeSpan.FromDays(365)); + } + + public string[] Lines { get; } + + public DateTime ExpireTime { get; } + + public bool Expired => Core.Now >= ExpireTime; +} + +public class TownCrierDurationPrompt : Prompt +{ + private readonly ITownCrierEntryList m_Owner; + + public TownCrierDurationPrompt(ITownCrierEntryList owner) => m_Owner = owner; + + public override void OnResponse(Mobile from, string text) + { + if (!TimeSpan.TryParse(text, out var ts)) { - if (!TimeSpan.TryParse(text, out var ts)) - { - from.SendMessage("Value was not properly formatted. Use: "); - from.SendGump(new TownCrierGump(from, m_Owner)); - return; - } - - if (ts < TimeSpan.Zero) - { - ts = TimeSpan.Zero; - } - - from.SendMessage($"Duration set to: {ts}"); - from.SendMessage("Enter the first line to shout:"); - - from.Prompt = new TownCrierLinesPrompt(m_Owner, null, new List(), ts); - } - - public override void OnCancel(Mobile from) - { - from.SendLocalizedMessage(502980); // Message entry cancelled. + from.SendMessage("Value was not properly formatted. Use: "); from.SendGump(new TownCrierGump(from, m_Owner)); + return; } + + if (ts < TimeSpan.Zero) + { + ts = TimeSpan.Zero; + } + + from.SendMessage($"Duration set to: {ts}"); + from.SendMessage("Enter the first line to shout:"); + + from.Prompt = new TownCrierLinesPrompt(m_Owner, null, new List(), ts); } - public class TownCrierLinesPrompt : Prompt + public override void OnCancel(Mobile from) { - private readonly TimeSpan m_Duration; - private readonly TownCrierEntry m_Entry; - private readonly List m_Lines; - private readonly ITownCrierEntryList m_Owner; + from.SendLocalizedMessage(502980); // Message entry cancelled. + from.SendGump(new TownCrierGump(from, m_Owner)); + } +} - public TownCrierLinesPrompt(ITownCrierEntryList owner, TownCrierEntry entry, List lines, TimeSpan duration) +public class TownCrierLinesPrompt : Prompt +{ + private readonly TimeSpan m_Duration; + private readonly TownCrierEntry m_Entry; + private readonly List m_Lines; + private readonly ITownCrierEntryList m_Owner; + + public TownCrierLinesPrompt(ITownCrierEntryList owner, TownCrierEntry entry, List lines, TimeSpan duration) + { + m_Owner = owner; + m_Entry = entry; + m_Lines = lines; + m_Duration = duration; + } + + public override void OnResponse(Mobile from, string text) + { + m_Lines.Add(text); + + from.SendMessage("Enter the next line to shout, or press if the message is finished."); + from.Prompt = new TownCrierLinesPrompt(m_Owner, m_Entry, m_Lines, m_Duration); + } + + public override void OnCancel(Mobile from) + { + if (m_Entry != null) { - m_Owner = owner; - m_Entry = entry; - m_Lines = lines; - m_Duration = duration; + m_Owner.RemoveEntry(m_Entry); } - public override void OnResponse(Mobile from, string text) + if (m_Lines.Count > 0) { - m_Lines.Add(text); - - from.SendMessage("Enter the next line to shout, or press if the message is finished."); - from.Prompt = new TownCrierLinesPrompt(m_Owner, m_Entry, m_Lines, m_Duration); + m_Owner.AddEntry(m_Lines.ToArray(), m_Duration); + from.SendMessage("Message has been set."); } - - public override void OnCancel(Mobile from) + else { if (m_Entry != null) { - m_Owner.RemoveEntry(m_Entry); - } - - if (m_Lines.Count > 0) - { - m_Owner.AddEntry(m_Lines.ToArray(), m_Duration); - from.SendMessage("Message has been set."); + from.SendMessage("Message deleted."); } else { - if (m_Entry != null) + from.SendLocalizedMessage(502980); // Message entry cancelled. + } + } + + from.SendGump(new TownCrierGump(from, m_Owner)); + } +} + +public class TownCrierGump : Gump +{ + private readonly Mobile m_From; + private readonly ITownCrierEntryList m_Owner; + + public TownCrierGump(Mobile from, ITownCrierEntryList owner) : base(50, 50) + { + m_From = from; + m_Owner = owner; + + from.CloseGump(); + + AddPage(0); + + var entries = owner.Entries; + + owner.GetRandomEntry(); // force expiration checks + + var count = entries?.Count ?? 0; + + AddImageTiled(0, 0, 300, 38 + (count == 0 ? 20 : count * 85), 0xA40); + AddAlphaRegion(1, 1, 298, 36 + (count == 0 ? 20 : count * 85)); + + AddHtml(8, 8, 300 - 8 - 30, 20, "
TOWN CRIER MESSAGES
"); + + AddButton(300 - 8 - 30, 8, 0xFAB, 0xFAD, 1); + + if (count == 0) + { + AddHtml(8, 30, 284, 20, "The crier has no news."); + } + else + { + for (var i = 0; i < entries!.Count; ++i) + { + var tce = entries[i]; + + var toExpire = Utility.Max(tce.ExpireTime - Core.Now, TimeSpan.Zero); + + var sb = new StringBuilder(); + + sb.Append("[Expires: "); + + if (toExpire.TotalHours >= 1) { - from.SendMessage("Message deleted."); + sb.Append((int)toExpire.TotalHours); + sb.Append(':'); + sb.Append(toExpire.Minutes.ToString("D2")); } else { - from.SendLocalizedMessage(502980); // Message entry cancelled. + sb.Append(toExpire.Minutes); } - } - from.SendGump(new TownCrierGump(from, m_Owner)); - } - } + sb.Append(':'); + sb.Append(toExpire.Seconds.ToString("D2")); - public class TownCrierGump : Gump - { - private readonly Mobile m_From; - private readonly ITownCrierEntryList m_Owner; + sb.Append("] "); - public TownCrierGump(Mobile from, ITownCrierEntryList owner) : base(50, 50) - { - m_From = from; - m_Owner = owner; - - from.CloseGump(); - - AddPage(0); - - var entries = owner.Entries; - - owner.GetRandomEntry(); // force expiration checks - - var count = entries?.Count ?? 0; - - AddImageTiled(0, 0, 300, 38 + (count == 0 ? 20 : count * 85), 0xA40); - AddAlphaRegion(1, 1, 298, 36 + (count == 0 ? 20 : count * 85)); - - AddHtml(8, 8, 300 - 8 - 30, 20, "
TOWN CRIER MESSAGES
"); - - AddButton(300 - 8 - 30, 8, 0xFAB, 0xFAD, 1); - - if (count == 0) - { - AddHtml(8, 30, 284, 20, "The crier has no news."); - } - else - { - for (var i = 0; i < entries!.Count; ++i) + for (var j = 0; j < tce.Lines.Length; ++j) { - var tce = entries[i]; - - var toExpire = Utility.Max(tce.ExpireTime - Core.Now, TimeSpan.Zero); - - var sb = new StringBuilder(); - - sb.Append("[Expires: "); - - if (toExpire.TotalHours >= 1) + if (j > 0) { - sb.Append((int)toExpire.TotalHours); - sb.Append(':'); - sb.Append(toExpire.Minutes.ToString("D2")); - } - else - { - sb.Append(toExpire.Minutes); + sb.Append("
"); } - sb.Append(':'); - sb.Append(toExpire.Seconds.ToString("D2")); - - sb.Append("] "); - - for (var j = 0; j < tce.Lines.Length; ++j) - { - if (j > 0) - { - sb.Append("
"); - } - - sb.Append(tce.Lines[j]); - } - - AddHtml(8, 35 + i * 85, 254, 80, sb.ToString(), true, true); - - AddButton(300 - 8 - 26, 35 + i * 85, 0x15E1, 0x15E5, 2 + i); + sb.Append(tce.Lines[j]); } - } - } - public override void OnResponse(NetState sender, RelayInfo info) - { - if (info.ButtonID == 1) - { - m_From.SendMessage("Enter the duration for the new message. Format: "); - m_From.Prompt = new TownCrierDurationPrompt(m_Owner); - } - else if (info.ButtonID > 1) - { - var entries = m_Owner.Entries; - var index = info.ButtonID - 2; + AddHtml(8, 35 + i * 85, 254, 80, sb.ToString(), true, true); - if (index < entries?.Count) - { - var tce = entries[index]; - var ts = Utility.Max(tce.ExpireTime - Core.Now, TimeSpan.Zero); - - m_From.SendMessage($"Editing entry #{index + 1}."); - m_From.SendMessage("Enter the first line to shout:"); - m_From.Prompt = new TownCrierLinesPrompt(m_Owner, tce, new List(), ts); - } + AddButton(300 - 8 - 26, 35 + i * 85, 0x15E1, 0x15E5, 2 + i); } } } - public class TownCrier : Mobile, ITownCrierEntryList + public override void OnResponse(NetState sender, RelayInfo info) { - private Timer _autoShoutTimer; - private Timer _newsTimer; - - [Constructible] - public TownCrier() + if (info.ButtonID == 1) { - Instances.Add(this); + m_From.SendMessage("Enter the duration for the new message. Format: "); + m_From.Prompt = new TownCrierDurationPrompt(m_Owner); + } + else if (info.ButtonID > 1) + { + var entries = m_Owner.Entries; + var index = info.ButtonID - 2; - InitStats(100, 100, 25); - - Title = "the town crier"; - Hue = Race.Human.RandomSkinHue(); - - if (!Core.AOS) + if (index < entries?.Count) { - NameHue = 0x35; + var tce = entries[index]; + var ts = Utility.Max(tce.ExpireTime - Core.Now, TimeSpan.Zero); + + m_From.SendMessage($"Editing entry #{index + 1}."); + m_From.SendMessage("Enter the first line to shout:"); + m_From.Prompt = new TownCrierLinesPrompt(m_Owner, tce, new List(), ts); } + } + } +} - if (Female = Utility.RandomBool()) - { - Body = 0x191; - Name = NameList.RandomName("female"); - } - else - { - Body = 0x190; - Name = NameList.RandomName("male"); - } +[SerializationGenerator(0, false)] +public partial class TownCrier : Mobile, ITownCrierEntryList +{ + private Timer _autoShoutTimer; + private Timer _newsTimer; - AddItem(new FancyShirt(Utility.RandomBlueHue())); + [Constructible] + public TownCrier() + { + Instances.Add(this); - var skirt = Utility.Random(2) switch - { - 0 => (Item)new Skirt(), - 1 => new Kilt(), - _ => new Kilt() - }; + InitStats(100, 100, 25); - skirt.Hue = Utility.RandomGreenHue(); + Title = "the town crier"; + Hue = Race.Human.RandomSkinHue(); - AddItem(skirt); - - AddItem(new FeatheredHat(Utility.RandomGreenHue())); - - var boots = Utility.Random(2) switch - { - 0 => (Item)new Boots(), - 1 => new ThighBoots(), - _ => new ThighBoots() - }; - - AddItem(boots); - - Utility.AssignRandomHair(this); + if (!Core.AOS) + { + NameHue = 0x35; } - public TownCrier(Serial serial) : base(serial) + if (Female = Utility.RandomBool()) { - Instances.Add(this); + Body = 0x191; + Name = NameList.RandomName("female"); + } + else + { + Body = 0x190; + Name = NameList.RandomName("male"); } - public static List Instances { get; } = new(); + AddItem(new FancyShirt(Utility.RandomBlueHue())); - public List Entries { get; private set; } - - public TownCrierEntry GetRandomEntry() + var skirt = Utility.Random(2) switch { - var count = Entries?.Count ?? 0; - for (var i = count - 1; i >= 0; --i) - { - if (i >= Entries!.Count) - { - continue; - } + 0 => (Item)new Skirt(), + 1 => new Kilt(), + _ => new Kilt() + }; - var tce = Entries[i]; + skirt.Hue = Utility.RandomGreenHue(); - if (tce.Expired) - { - RemoveEntry(tce); - } - } + AddItem(skirt); - var entry = GlobalTownCrierEntryList.Instance.GetRandomEntry(); - if (count > 0 && entry != null && Utility.RandomBool()) - { - return entry; - } + AddItem(new FeatheredHat(Utility.RandomGreenHue())); - return Entries?.RandomElement(); - } - - public TownCrierEntry AddEntry(string[] lines, TimeSpan duration) + var boots = Utility.Random(2) switch { - Entries ??= new List(); + 0 => (Item)new Boots(), + 1 => new ThighBoots(), + _ => new ThighBoots() + }; - var tce = new TownCrierEntry(lines, duration); + AddItem(boots); - Entries.Add(tce); + Utility.AssignRandomHair(this); + } - ForceBeginAutoShout(); + public TownCrier(Serial serial) : base(serial) + { + Instances.Add(this); + } - return tce; - } + public static List Instances { get; } = new(); - public void RemoveEntry(TownCrierEntry tce) + public List Entries { get; private set; } + + public TownCrierEntry GetRandomEntry() + { + var count = Entries?.Count ?? 0; + for (var i = count - 1; i >= 0; --i) { - if (Entries == null) + if (i >= Entries!.Count) { - return; + continue; } - Entries.Remove(tce); + var tce = Entries[i]; - if (Entries.Count == 0) + if (tce.Expired) { - Entries = null; - } - - if (Entries == null && GlobalTownCrierEntryList.Instance.IsEmpty) - { - _autoShoutTimer?.Stop(); - _autoShoutTimer = null; + RemoveEntry(tce); } } - public void ForceBeginAutoShout() + var entry = GlobalTownCrierEntryList.Instance.GetRandomEntry(); + if (count > 0 && entry != null && Utility.RandomBool()) { - _autoShoutTimer ??= Timer.DelayCall(TimeSpan.FromSeconds(5.0), TimeSpan.FromMinutes(1.0), AutoShout_Callback); + return entry; } - private void AutoShout_Callback() + return Entries?.RandomElement(); + } + + public TownCrierEntry AddEntry(string[] lines, TimeSpan duration) + { + Entries ??= new List(); + + var tce = new TownCrierEntry(lines, duration); + + Entries.Add(tce); + + ForceBeginAutoShout(); + + return tce; + } + + public void RemoveEntry(TownCrierEntry tce) + { + if (Entries == null) { + return; + } + + Entries.Remove(tce); + + if (Entries.Count == 0) + { + Entries = null; + } + + if (Entries == null && GlobalTownCrierEntryList.Instance.IsEmpty) + { + _autoShoutTimer?.Stop(); + _autoShoutTimer = null; + } + } + + public void ForceBeginAutoShout() + { + _autoShoutTimer ??= Timer.DelayCall(TimeSpan.FromSeconds(5.0), TimeSpan.FromMinutes(1.0), AutoShout_Callback); + } + + private void AutoShout_Callback() + { + var tce = GetRandomEntry(); + + if (tce == null) + { + _autoShoutTimer.Stop(); + _autoShoutTimer = null; + } + else if (_newsTimer == null) + { + _newsTimer = Timer.DelayCall( + TimeSpan.FromSeconds(1.0), + TimeSpan.FromSeconds(3.0), + tce.Lines.Length, + () => ShoutNews_Callback(tce) + ); + + PublicOverheadMessage(MessageType.Regular, 0x3B2, 502976); // Hear ye! Hear ye! + } + } + + private void ShoutNews_Callback(TownCrierEntry tce) + { + var index = _newsTimer.Index; + if (index >= tce.Lines.Length) + { + _newsTimer.Stop(); + _newsTimer = null; + } + else + { + PublicOverheadMessage(MessageType.Regular, 0x3B2, false, tce.Lines[index]); + } + } + + public override void OnDoubleClick(Mobile from) + { + if (from.AccessLevel >= AccessLevel.GameMaster) + { + from.SendGump(new TownCrierGump(from, this)); + } + else + { + base.OnDoubleClick(from); + } + } + + public override bool HandlesOnSpeech(Mobile from) => _newsTimer == null && from.Alive && InRange(from, 12); + + public override void OnSpeech(SpeechEventArgs e) + { + if (_newsTimer == null && e.HasKeyword(0x30) && e.Mobile.Alive && InRange(e.Mobile, 12)) // *news* + { + Direction = GetDirectionTo(e.Mobile); + var tce = GetRandomEntry(); if (tce == null) { - _autoShoutTimer.Stop(); - _autoShoutTimer = null; + PublicOverheadMessage(MessageType.Regular, 0x3B2, 1005643); // I have no news at this time. } - else if (_newsTimer == null) + else { _newsTimer = Timer.DelayCall( TimeSpan.FromSeconds(1.0), @@ -443,89 +498,16 @@ namespace Server.Mobiles () => ShoutNews_Callback(tce) ); - PublicOverheadMessage(MessageType.Regular, 0x3B2, 502976); // Hear ye! Hear ye! - } - } - - private void ShoutNews_Callback(TownCrierEntry tce) - { - var index = _newsTimer.Index; - if (index >= tce.Lines.Length) - { - _newsTimer.Stop(); - _newsTimer = null; - } - else - { - PublicOverheadMessage(MessageType.Regular, 0x3B2, false, tce.Lines[index]); - } - } - - public override void OnDoubleClick(Mobile from) - { - if (from.AccessLevel >= AccessLevel.GameMaster) - { - from.SendGump(new TownCrierGump(from, this)); - } - else - { - base.OnDoubleClick(from); - } - } - - public override bool HandlesOnSpeech(Mobile from) => _newsTimer == null && from.Alive && InRange(from, 12); - - public override void OnSpeech(SpeechEventArgs e) - { - if (_newsTimer == null && e.HasKeyword(0x30) && e.Mobile.Alive && InRange(e.Mobile, 12)) // *news* - { - Direction = GetDirectionTo(e.Mobile); - - var tce = GetRandomEntry(); - - if (tce == null) - { - PublicOverheadMessage(MessageType.Regular, 0x3B2, 1005643); // I have no news at this time. - } - else - { - _newsTimer = Timer.DelayCall( - TimeSpan.FromSeconds(1.0), - TimeSpan.FromSeconds(3.0), - tce.Lines.Length, - () => ShoutNews_Callback(tce) - ); - - PublicOverheadMessage(MessageType.Regular, 0x3B2, 502978); // Some of the latest news! - } - } - } - - public override bool CanBeDamaged() => false; - - public override void OnDelete() - { - Instances.Remove(this); - base.OnDelete(); - } - - public override void Serialize(IGenericWriter writer) - { - base.Serialize(writer); - - writer.Write(0); // version - } - - public override void Deserialize(IGenericReader reader) - { - base.Deserialize(reader); - - var version = reader.ReadInt(); - - if (Core.AOS && NameHue == 0x35) - { - NameHue = -1; + PublicOverheadMessage(MessageType.Regular, 0x3B2, 502978); // Some of the latest news! } } } + + public override bool CanBeDamaged() => false; + + public override void OnDelete() + { + Instances.Remove(this); + base.OnDelete(); + } }