From f77dd918115f6d29a5c30baa180549c432e5e43a Mon Sep 17 00:00:00 2001 From: Kamron Batman <3953314+kamronbatman@users.noreply.github.com> Date: Wed, 19 Jul 2023 21:43:47 -0700 Subject: [PATCH] fix: Overhauls virtue system (#1376) ## MAJOR CHANGE (API BREAKING) Moved the virtues to it's own system _VirtueSystem_. This should make it easier to extend or remove the virtue system. Virtues will be saved to a new folder called _Virtues_. ### Motivation The motivation was also two-fold, performance/stability, and to fix bugs. First, virtues is the second system (first is murders), that has a pre-world-save check on _every mobile in the game_ to atrophy virtue stats. This is taxing since it freexes the world and makes world saves take longer. Every mobile had Gain/Loss dates for each virtue, whether they needed them or not. Most players don't even use the virtue system, so this will increase performance considerably. Second, when I tried to optimize/refactor the code, I found several bugs that needed to be fixed. ### Major API Changes - [X] The properties on players related to virtues are gone. Use `pm.GetVirtues()?.` instead. - [X] Virtues were removed from non-player Mobiles. - [X] `pm.JusticeProtectors` was removed. Use `JusticeVirtue.GetProtector()` or `JusticeVirtue.GetProtected()` instead. ### Screenshots Props-1 props-2 --- Projects/Server/Events/EventSink.cs | 10 - Projects/Server/Mobiles/Mobile.Migrations.cs | 14 +- Projects/Server/Mobiles/Mobile.cs | 38 +- Projects/Server/VirtueInfo.cs | 149 ----- .../Engines/CannedEvil/ChampionSpawn.cs | 89 ++- .../Engines/Harvest/Core/HarvestDefinition.cs | 1 - .../ML Quests/Objectives/EscortObjective.cs | 5 +- .../Witch Apprentice/Mobiles/Grizelda.cs | 3 +- .../UOContent/Engines/Virtues/Compassion.cs | 50 +- Projects/UOContent/Engines/Virtues/Honor.cs | 539 +++++------------- .../UOContent/Engines/Virtues/HonorContext.cs | 261 +++++++++ .../UOContent/Engines/Virtues/IHonorTarget.cs | 6 + Projects/UOContent/Engines/Virtues/Justice.cs | 510 ++++++++++------- .../UOContent/Engines/Virtues/Sacrifice.cs | 229 ++++---- Projects/UOContent/Engines/Virtues/Valor.cs | 26 +- .../Engines/Virtues/VirtueContext.cs | 195 +++++++ .../UOContent/Engines/Virtues/VirtueGump.cs | 279 ++++----- .../UOContent/Engines/Virtues/VirtueHelper.cs | 232 -------- .../Engines/Virtues/VirtueInfoGump.cs | 235 ++++---- .../Engines/Virtues/VirtueStatusGump.cs | 196 +++---- .../UOContent/Engines/Virtues/VirtueSystem.cs | 375 ++++++++++++ Projects/UOContent/Gumps/HonorSelf.cs | 1 + Projects/UOContent/Gumps/ResurrectGump.cs | 5 +- .../Special/Solen Items/BallOfSummoning.cs | 1 - .../UOContent/Items/Weapons/BaseWeapon.cs | 3 +- ...rver.Engines.Virtues.VirtueContext.v0.json | 119 ++++ Projects/UOContent/Misc/Poison.cs | 6 +- Projects/UOContent/Mobiles/AI/BaseAI.cs | 3 +- Projects/UOContent/Mobiles/BaseCreature.cs | 3 +- Projects/UOContent/Mobiles/PlayerMobile.cs | 181 +++--- .../UOContent/Mobiles/Special/BaseChampion.cs | 63 +- .../UOContent/Mobiles/Special/Harrower.cs | 44 +- .../Mobiles/Townfolk/BaseEscortable.cs | 34 +- Projects/UOContent/Multis/Boats/BaseBoat.cs | 10 +- .../Network/Packets/IncomingPlayerPackets.cs | 10 +- Projects/UOContent/Skills/AnimalTaming.cs | 18 +- Projects/UOContent/Skills/AntiMacroSystem.cs | 1 - 37 files changed, 2105 insertions(+), 1839 deletions(-) delete mode 100644 Projects/Server/VirtueInfo.cs create mode 100644 Projects/UOContent/Engines/Virtues/HonorContext.cs create mode 100644 Projects/UOContent/Engines/Virtues/IHonorTarget.cs create mode 100644 Projects/UOContent/Engines/Virtues/VirtueContext.cs delete mode 100644 Projects/UOContent/Engines/Virtues/VirtueHelper.cs create mode 100644 Projects/UOContent/Engines/Virtues/VirtueSystem.cs create mode 100644 Projects/UOContent/Migrations/Server.Engines.Virtues.VirtueContext.v0.json diff --git a/Projects/Server/Events/EventSink.cs b/Projects/Server/Events/EventSink.cs index 3ffdcbd4d..f806c2f46 100644 --- a/Projects/Server/Events/EventSink.cs +++ b/Projects/Server/Events/EventSink.cs @@ -78,16 +78,6 @@ public static partial class EventSink public static event Action PlayerDeath; public static void InvokePlayerDeath(Mobile m) => PlayerDeath?.Invoke(m); - public static event Action VirtueGumpRequest; - - public static void InvokeVirtueGumpRequest(Mobile beholder, Mobile beheld) => - VirtueGumpRequest?.Invoke(beholder, beheld); - - public static event Action VirtueItemRequest; - - public static void InvokeVirtueItemRequest(Mobile beholder, Mobile beheld, int gumpID) => - VirtueItemRequest?.Invoke(beholder, beheld, gumpID); - public static event Action VirtueMacroRequest; public static void InvokeVirtueMacroRequest(Mobile mobile, int virtueID) => diff --git a/Projects/Server/Mobiles/Mobile.Migrations.cs b/Projects/Server/Mobiles/Mobile.Migrations.cs index 7087c399c..81f56f930 100644 --- a/Projects/Server/Mobiles/Mobile.Migrations.cs +++ b/Projects/Server/Mobiles/Mobile.Migrations.cs @@ -19,7 +19,7 @@ public partial class Mobile // Migrating murders to the murder system public static Dictionary MurderMigrations { get; private set; } - public static void AddToMurderMigrations(Mobile m, int shortTermMurders) + public static void AddToMurderMigration(Mobile m, int shortTermMurders) { if (shortTermMurders > 0) { @@ -27,4 +27,16 @@ public partial class Mobile MurderMigrations[m] = shortTermMurders; } } + + // Migrating VirtueInfo to VirtueSystem + public static Dictionary VirtueMigrations { get; private set; } + + public static void AddToVirtueMigration(Mobile m, int[] values) + { + if (values != null) + { + VirtueMigrations ??= new Dictionary(); + VirtueMigrations[m] = values; + } + } } diff --git a/Projects/Server/Mobiles/Mobile.cs b/Projects/Server/Mobiles/Mobile.cs index 8827a6fdf..1b1c12b9c 100644 --- a/Projects/Server/Mobiles/Mobile.cs +++ b/Projects/Server/Mobiles/Mobile.cs @@ -426,9 +426,6 @@ public partial class Mobile : IHued, IComparable, ISpawnable, IObjectPro } } - [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] - public VirtueInfo Virtues { get; private set; } - public object Party { get; set; } public List SkillMods => _skillMods; @@ -2264,7 +2261,7 @@ public partial class Mobile : IHued, IComparable, ISpawnable, IObjectPro public virtual void Serialize(IGenericWriter writer) { - writer.Write(35); // version + writer.Write(36); // version writer.WriteDeltaTime(LastStrGain); writer.WriteDeltaTime(LastIntGain); @@ -2302,7 +2299,7 @@ public partial class Mobile : IHued, IComparable, ISpawnable, IObjectPro writer.Write(CantWalk); - VirtueInfo.Serialize(writer, Virtues); + // VirtueInfo.Serialize(writer, Virtues); writer.Write(Thirst); writer.Write(BAC); @@ -6044,6 +6041,7 @@ public partial class Mobile : IHued, IComparable, ISpawnable, IObjectPro switch (version) { + case 36: // Moved virtues to VirtueSystem case 35: // Moved short term murders to PlayerMurderSystem case 34: // Moved Stabled to PlayerMobile case 33: // Removed created @@ -6129,7 +6127,27 @@ public partial class Mobile : IHued, IComparable, ISpawnable, IObjectPro case 19: // Just removed variables case 18: { - Virtues = new VirtueInfo(reader); + if (version < 36) + { + reader.ReadByte(); // VirtueInfo version + + int mask = reader.ReadByte(); + + if (mask != 0) + { + var virtueValues = new int[8]; + + for (var i = 0; i < 8; ++i) + { + if ((mask & (1 << i)) != 0) + { + virtueValues[i] = reader.ReadInt(); + } + } + + AddToVirtueMigration(this, virtueValues); + } + } goto case 17; } @@ -6145,7 +6163,7 @@ public partial class Mobile : IHued, IComparable, ISpawnable, IObjectPro if (version < 35) { // Migrated to PlayerMurderSystem - AddToMurderMigrations(this, reader.ReadInt()); + AddToMurderMigration(this, reader.ReadInt()); } if (version <= 24) @@ -6257,11 +6275,6 @@ public partial class Mobile : IHued, IComparable, ISpawnable, IObjectPro } case 0: { - if (version < 18) - { - Virtues = new VirtueInfo(); - } - if (version < 11) { m_DisplayGuildTitle = true; @@ -7687,7 +7700,6 @@ public partial class Mobile : IHued, IComparable, ISpawnable, IObjectPro AutoPageNotify = true; Aggressors = new List(); Aggressed = new List(); - Virtues = new VirtueInfo(); DamageEntries = new List(); NextSkillTime = Core.TickCount; diff --git a/Projects/Server/VirtueInfo.cs b/Projects/Server/VirtueInfo.cs deleted file mode 100644 index 448451458..000000000 --- a/Projects/Server/VirtueInfo.cs +++ /dev/null @@ -1,149 +0,0 @@ -namespace Server; - -[PropertyObject] -public class VirtueInfo -{ - public VirtueInfo() - { - } - - public VirtueInfo(IGenericReader reader) - { - int version = reader.ReadByte(); - - switch (version) - { - case 1: // Changed the values throughout the virtue system - case 0: - { - int mask = reader.ReadByte(); - - if (mask != 0) - { - Values = new int[8]; - - for (var i = 0; i < 8; ++i) - { - if ((mask & (1 << i)) != 0) - { - Values[i] = reader.ReadInt(); - } - } - } - - break; - } - } - - if (version == 0) - { - Compassion *= 200; - Sacrifice *= 250; // Even though 40 (the max) only gives 10k, It's because it was formerly too easy - - // No direct conversion factor for Justice, this is just an approximation - Justice *= 500; - - // All the other virtues haven't been defined at 'version 0' point in time in the scripts. - } - } - - public int[] Values { get; private set; } - - [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] - public int Humility - { - get => GetValue(0); - set => SetValue(0, value); - } - - [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] - public int Sacrifice - { - get => GetValue(1); - set => SetValue(1, value); - } - - [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] - public int Compassion - { - get => GetValue(2); - set => SetValue(2, value); - } - - [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] - public int Spirituality - { - get => GetValue(3); - set => SetValue(3, value); - } - - [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] - public int Valor - { - get => GetValue(4); - set => SetValue(4, value); - } - - [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] - public int Honor - { - get => GetValue(5); - set => SetValue(5, value); - } - - [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] - public int Justice - { - get => GetValue(6); - set => SetValue(6, value); - } - - [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] - public int Honesty - { - get => GetValue(7); - set => SetValue(7, value); - } - - public int GetValue(int index) => Values?[index] ?? 0; - - public void SetValue(int index, int value) - { - Values ??= new int[8]; - Values[index] = value; - } - - public override string ToString() => "..."; - - public static void Serialize(IGenericWriter writer, VirtueInfo info) - { - writer.Write((byte)1); // version - - if (info.Values == null) - { - writer.Write((byte)0); - } - else - { - var mask = 0; - - for (var i = 0; i < 8; ++i) - { - if (info.Values[i] != 0) - { - mask |= 1 << i; - } - } - - writer.Write((byte)mask); - - for (var i = 0; i < 8; ++i) - { - if (info.Values[i] != 0) - { - writer.Write(info.Values[i]); - } - } - } - } -} diff --git a/Projects/UOContent/Engines/CannedEvil/ChampionSpawn.cs b/Projects/UOContent/Engines/CannedEvil/ChampionSpawn.cs index 6e0755423..36f830c8c 100755 --- a/Projects/UOContent/Engines/CannedEvil/ChampionSpawn.cs +++ b/Projects/UOContent/Engines/CannedEvil/ChampionSpawn.cs @@ -16,6 +16,7 @@ using System; using System.Net; using System.Collections.Generic; +using Server.Engines.Virtues; using Server.Gumps; using Server.Items; using Server.Mobiles; @@ -388,7 +389,7 @@ namespace Server.Engines.CannedEvil private static PowerScroll CreateRandomFelPS() => PowerScroll.CreateRandomNoCraft(5, 5); - public static void GiveScrollOfTranscendenceFelTo (Mobile killer, ScrollofTranscendence SoTF) + public static void GiveScrollOfTranscendenceFelTo(Mobile killer, ScrollofTranscendence SoTF) { if (SoTF == null || killer == null) //sanity { @@ -401,42 +402,37 @@ namespace Server.Engines.CannedEvil { killer.AddToBackpack(SoTF); } + else if (killer.Corpse is { Deleted: false }) + { + killer.Corpse.DropItem(SoTF); + } else { - if (killer.Corpse is { Deleted: false }) - { - killer.Corpse.DropItem(SoTF); - } - else - { - killer.AddToBackpack(SoTF); - } + killer.AddToBackpack(SoTF); } // Justice reward var pm = (PlayerMobile)killer; - for (var j = 0; j < pm.JusticeProtectors.Count; ++j) + var prot = JusticeVirtue.GetProtector(pm); + if (prot == null || prot.Map != killer.Map || prot.Kills >= 5 || prot.Criminal || + !JusticeVirtue.CheckMapRegion(killer, prot)) { - Mobile prot = pm.JusticeProtectors[j]; - if (prot.Map != killer.Map || prot.Kills >= 5 || prot.Criminal || !JusticeVirtue.CheckMapRegion(killer, prot)) - { - continue; - } + return; + } - var chance = VirtueHelper.GetLevel(prot, VirtueName.Justice) switch - { - VirtueLevel.Seeker => 60, - VirtueLevel.Follower => 80, - VirtueLevel.Knight => 100, - _ => 0 - }; + var chance = VirtueSystem.GetLevel(prot, VirtueName.Justice) switch + { + VirtueLevel.Seeker => 60, + VirtueLevel.Follower => 80, + VirtueLevel.Knight => 100, + _ => 0 + }; - if (chance > Utility.Random(100)) - { - prot.SendLocalizedMessage(1049368); // You have been rewarded for your dedication to Justice! - ScrollofTranscendence SoTFduplicate = new ScrollofTranscendence (SoTF.Skill, SoTF.Value); - prot.AddToBackpack(SoTFduplicate); - } + if (chance > 0 && chance > Utility.Random(100)) + { + prot.SendLocalizedMessage(1049368); // You have been rewarded for your dedication to Justice! + ScrollofTranscendence SoTFduplicate = new ScrollofTranscendence (SoTF.Skill, SoTF.Value); + prot.AddToBackpack(SoTFduplicate); } } @@ -464,28 +460,25 @@ namespace Server.Engines.CannedEvil // Justice reward var pm = (PlayerMobile)killer; - for (var j = 0; j < pm.JusticeProtectors.Count; ++j) + var prot = JusticeVirtue.GetProtector(pm); + if (prot == null || prot.Map != killer.Map || prot.Kills >= 5 || prot.Criminal || + !JusticeVirtue.CheckMapRegion(killer, prot)) { - Mobile prot = pm.JusticeProtectors[j]; - if (prot.Map != killer.Map || prot.Kills >= 5 || prot.Criminal || !JusticeVirtue.CheckMapRegion(killer, prot)) - { - continue; - } + return; + } - var chance = VirtueHelper.GetLevel(prot, VirtueName.Justice) switch - { - VirtueLevel.Seeker => 60, - VirtueLevel.Follower => 80, - VirtueLevel.Knight => 100, - _ => 0 - }; + var chance = VirtueSystem.GetLevel(prot, VirtueName.Justice) switch + { + VirtueLevel.Seeker => 60, + VirtueLevel.Follower => 80, + VirtueLevel.Knight => 100, + _ => 0 + }; - if (chance > Utility.Random(100)) - { - prot.SendLocalizedMessage(1049368); // You have been rewarded for your dedication to Justice! - //PowerScroll PSduplicate = new PowerScroll (PS.Skill, PS.Value); - prot.AddToBackpack(CreateRandomFelPS()); - } + if (chance > 0 && chance > Utility.Random(100)) + { + prot.SendLocalizedMessage(1049368); // You have been rewarded for your dedication to Justice! + prot.AddToBackpack(CreateRandomFelPS()); } } @@ -589,7 +582,7 @@ namespace Server.Engines.CannedEvil int pointsToGain = mobSubLevel * 40; - if (VirtueHelper.Award(pm, VirtueName.Valor, pointsToGain, ref gainedPath)) + if (VirtueSystem.Award(pm, VirtueName.Valor, pointsToGain, ref gainedPath)) { if (gainedPath) { diff --git a/Projects/UOContent/Engines/Harvest/Core/HarvestDefinition.cs b/Projects/UOContent/Engines/Harvest/Core/HarvestDefinition.cs index 456cf9931..e3cc82a56 100644 --- a/Projects/UOContent/Engines/Harvest/Core/HarvestDefinition.cs +++ b/Projects/UOContent/Engines/Harvest/Core/HarvestDefinition.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Runtime.CompilerServices; -using Server.Items; using Server.Random; namespace Server.Engines.Harvest diff --git a/Projects/UOContent/Engines/ML Quests/Objectives/EscortObjective.cs b/Projects/UOContent/Engines/ML Quests/Objectives/EscortObjective.cs index ea5741bc8..f33e9137c 100644 --- a/Projects/UOContent/Engines/ML Quests/Objectives/EscortObjective.cs +++ b/Projects/UOContent/Engines/ML Quests/Objectives/EscortObjective.cs @@ -1,4 +1,5 @@ using System; +using Server.Engines.Virtues; using Server.Gumps; using Server.Misc; using Server.Mobiles; @@ -146,10 +147,10 @@ namespace Server.Engines.MLQuests.Objectives } else { - VirtueHelper.AwardVirtue( + VirtueSystem.AwardVirtue( pm, VirtueName.Compassion, - m_Escort is BaseEscortable escortable && escortable.IsPrisoner ? 400 : 200 + (m_Escort as BaseEscortable)?.IsPrisoner == true ? 400 : 200 ); } diff --git a/Projects/UOContent/Engines/Quests/Witch Apprentice/Mobiles/Grizelda.cs b/Projects/UOContent/Engines/Quests/Witch Apprentice/Mobiles/Grizelda.cs index 1f9e051d8..7b0b9e6a0 100644 --- a/Projects/UOContent/Engines/Quests/Witch Apprentice/Mobiles/Grizelda.cs +++ b/Projects/UOContent/Engines/Quests/Witch Apprentice/Mobiles/Grizelda.cs @@ -1,3 +1,4 @@ +using Server.Engines.Virtues; using Server.Items; using Server.Mobiles; @@ -165,7 +166,7 @@ namespace Server.Engines.Quests.Hag { var gainedPath = false; - if (VirtueHelper.Award( + if (VirtueSystem.Award( player, VirtueName.Sacrifice, 250, diff --git a/Projects/UOContent/Engines/Virtues/Compassion.cs b/Projects/UOContent/Engines/Virtues/Compassion.cs index 977d04cd6..a1131662d 100644 --- a/Projects/UOContent/Engines/Virtues/Compassion.cs +++ b/Projects/UOContent/Engines/Virtues/Compassion.cs @@ -2,37 +2,37 @@ using System; using System.Runtime.CompilerServices; using Server.Mobiles; -namespace Server +namespace Server.Engines.Virtues; + +public static class CompassionVirtue { - public static class CompassionVirtue + private const int LossAmount = 500; + private static readonly TimeSpan LossDelay = TimeSpan.FromDays(7.0); + + public static void Initialize() { - private const int LossAmount = 500; - private static readonly TimeSpan LossDelay = TimeSpan.FromDays(7.0); + VirtueGump.Register(105, OnVirtueUsed); + } - public static void Initialize() + public static void OnVirtueUsed(Mobile from) + { + from.SendLocalizedMessage(1053001); // This virtue is not activated through the virtue menu. + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool CanAtrophy(VirtueContext context) => context.LastCompassionLoss + LossDelay < Core.Now; + + public static void CheckAtrophy(PlayerMobile pm) + { + var virtues = pm.GetVirtues(); + if (virtues?.Compassion > 0 && CanAtrophy(virtues)) { - VirtueGump.Register(105, OnVirtueUsed); - } - - public static void OnVirtueUsed(Mobile from) - { - from.SendLocalizedMessage(1053001); // This virtue is not activated through the virtue menu. - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool ShouldAtrophy(PlayerMobile pm) => pm.LastCompassionLoss + LossDelay < Core.Now; - - public static void CheckAtrophy(PlayerMobile pm) - { - if (ShouldAtrophy(pm)) + if (VirtueSystem.Atrophy(pm, VirtueName.Compassion, LossAmount)) { - if (VirtueHelper.Atrophy(pm, VirtueName.Compassion, LossAmount)) - { - pm.SendLocalizedMessage(1114420); // You have lost some Compassion. - } - - pm.LastCompassionLoss = Core.Now; + pm.SendLocalizedMessage(1114420); // You have lost some Compassion. } + + virtues.LastCompassionLoss = Core.Now; } } } diff --git a/Projects/UOContent/Engines/Virtues/Honor.cs b/Projects/UOContent/Engines/Virtues/Honor.cs index 4b72b7468..a5ae4c554 100644 --- a/Projects/UOContent/Engines/Virtues/Honor.cs +++ b/Projects/UOContent/Engines/Virtues/Honor.cs @@ -1,446 +1,195 @@ using System; +using System.Runtime.CompilerServices; using Server.Gumps; using Server.Mobiles; using Server.Regions; using Server.Targeting; -namespace Server +namespace Server.Engines.Virtues; + +public static class HonorVirtue { - public static class HonorVirtue + public static readonly TimeSpan UseDelay = TimeSpan.FromMinutes(5.0); + + public static void Initialize() { - private static readonly TimeSpan UseDelay = TimeSpan.FromMinutes(5.0); + VirtueGump.Register(107, OnVirtueUsed); + } - public static void Initialize() + private static void OnVirtueUsed(Mobile from) + { + if (from.Alive) { - VirtueGump.Register(107, OnVirtueUsed); + from.SendLocalizedMessage(1063160); // Target what you wish to honor. + from.Target = new InternalTarget(); + } + } + + private static int GetHonorDuration(PlayerMobile from) => + VirtueSystem.GetLevel(from, VirtueName.Honor) switch + { + VirtueLevel.Seeker => 30, + VirtueLevel.Follower => 90, + VirtueLevel.Knight => 300, + _ => 0 + }; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool CanUse(VirtueContext context) => Core.Now - context.LastHonorUse >= UseDelay; + + private static void EmbraceHonor(PlayerMobile pm) + { + var virtues = pm.GetVirtues(); + + if (virtues?.HonorActive == true) + { + pm.SendLocalizedMessage(1063230); // You must wait awhile before you can embrace honor again. + return; } - private static void OnVirtueUsed(Mobile from) + if (GetHonorDuration(pm) == 0) { - if (from.Alive) - { - from.SendLocalizedMessage(1063160); // Target what you wish to honor. - from.Target = new InternalTarget(); - } + pm.SendLocalizedMessage(1063234); // You do not have enough honor to do that + return; } - private static int GetHonorDuration(Mobile from) + if (virtues != null) { - return VirtueHelper.GetLevel(from, VirtueName.Honor) switch - { - VirtueLevel.Seeker => 30, - VirtueLevel.Follower => 90, - VirtueLevel.Knight => 300, - _ => 0 - }; - } - - private static void EmbraceHonor(PlayerMobile pm) - { - if (pm.HonorActive) - { - pm.SendLocalizedMessage(1063230); // You must wait awhile before you can embrace honor again. - return; - } - - if (GetHonorDuration(pm) == 0) - { - pm.SendLocalizedMessage(1063234); // You do not have enough honor to do that - return; - } - - var waitTime = Core.Now - pm.LastHonorUse; + var waitTime = Core.Now - virtues.LastHonorUse; if (waitTime < UseDelay) { var remainingTime = UseDelay - waitTime; var remainingMinutes = (int)Math.Ceiling(remainingTime.TotalMinutes); - pm.SendLocalizedMessage( - 1063240, // You must wait ~1_HONOR_WAIT~ minutes before embracing honor again - remainingMinutes.ToString() - ); + // You must wait ~1_HONOR_WAIT~ minutes before embracing honor again + pm.SendLocalizedMessage(1063240, remainingMinutes.ToString()); return; } - - pm.SendGump(new HonorSelf(pm)); - } - - public static void ActivateEmbrace(PlayerMobile pm) - { - var duration = GetHonorDuration(pm); - - int usedPoints = pm.Virtues.Honor switch - { - < 4399 => 400, - < 10599 => 600, - _ => 1000 - }; - - VirtueHelper.Atrophy(pm, VirtueName.Honor, usedPoints); - - pm.HonorActive = true; - pm.SendLocalizedMessage(1063235); // You embrace your honor - - Timer.StartTimer( - TimeSpan.FromSeconds(duration), - () => - { - pm.HonorActive = false; - pm.LastHonorUse = Core.Now; - pm.SendLocalizedMessage(1063236); // You no longer embrace your honor - } - ); - } - - private static void Honor(PlayerMobile source, Mobile target) - { - var honorTarget = target as IHonorTarget; - var reg = source.Region.GetRegion(); - var map = source.Map; - - if (honorTarget == null) - { - return; - } - - if (honorTarget.ReceivedHonorContext != null) - { - if (honorTarget.ReceivedHonorContext.Source == source) - { - return; - } - - if (honorTarget.ReceivedHonorContext.CheckDistance()) - { - source.SendLocalizedMessage(1063233); // Somebody else is honoring this opponent - return; - } - } - - if (target.Hits < target.HitsMax) - { - source.SendLocalizedMessage(1063166); // You cannot honor this monster because it is too damaged. - return; - } - - if (target.Body.IsHuman && (target is not BaseCreature cret || !cret.AlwaysAttackable && !cret.AlwaysMurderer)) - { - if (reg?.IsDisabled() != true) - { - // Allow honor on blue if Out of guardzone - } - else if ((map?.Rules & MapRules.HarmfulRestrictions) == 0) - { - // Allow honor on blue if in Fel - } - else - { - source.SendLocalizedMessage(1001018); // You cannot perform negative acts - return; // cannot honor in trammel town on blue - } - } - - if (Core.ML && target is PlayerMobile) - { - source.SendLocalizedMessage(1075614); // You cannot honor other players. - return; - } - - source.SentHonorContext?.Cancel(); - - _ = new HonorContext(source, target); - - source.Direction = source.GetDirectionTo(target); - - if (!source.Mounted) - { - source.Animate(32, 5, 1, true, true, 0); - } } - private class InternalTarget : Target + pm.SendGump(new HonorSelf(pm)); + } + + public static void ActivateEmbrace(PlayerMobile pm) + { + var duration = GetHonorDuration(pm); + var virtues = pm.GetOrCreateVirtues(); + + int usedPoints = virtues.Honor switch { - public InternalTarget() : base(12, false, TargetFlags.None) => CheckLOS = true; + < 4399 => 400, + < 10599 => 600, + _ => 1000 + }; - protected override void OnTarget(Mobile from, object targeted) + VirtueSystem.Atrophy(pm, VirtueName.Honor, usedPoints); + + virtues.HonorActive = true; + pm.SendLocalizedMessage(1063235); // You embrace your honor + + Timer.DelayCall( + TimeSpan.FromSeconds(duration), + (m) => { - if (from is not PlayerMobile pm) - { - return; - } + // We get the virtues again, in case it was deleted/dereferenced + var v = m.GetOrCreateVirtues(); + v.HonorActive = false; + v.LastHonorUse = Core.Now; + m.SendLocalizedMessage(1063236); // You no longer embrace your honor + }, + pm + ); + } - if (targeted == pm) - { - EmbraceHonor(pm); - } - else if (targeted is Mobile mobile) - { - Honor(pm, mobile); - } + private static void Honor(PlayerMobile source, Mobile target) + { + if (target is not IHonorTarget honorTarget) + { + return; + } + + var reg = source.Region.GetRegion(); + var map = source.Map; + + if (honorTarget.ReceivedHonorContext != null) + { + if (honorTarget.ReceivedHonorContext.Source == source) + { + return; } - protected override void OnTargetOutOfRange(Mobile from, object targeted) + if (honorTarget.ReceivedHonorContext.CheckDistance()) { - from.SendLocalizedMessage(1063232); // You are too far away to honor your opponent + source.SendLocalizedMessage(1063233); // Somebody else is honoring this opponent + return; } } + + if (target.Hits < target.HitsMax) + { + source.SendLocalizedMessage(1063166); // You cannot honor this monster because it is too damaged. + return; + } + + if (target.Body.IsHuman && (target is not BaseCreature cret || !cret.AlwaysAttackable && !cret.AlwaysMurderer)) + { + if (reg?.IsDisabled() != true) + { + // Allow honor on blue if not in a guarded region + } + else if ((map?.Rules & MapRules.HarmfulRestrictions) == 0) + { + // Allow honor on blue if in Fel + } + else + { + source.SendLocalizedMessage(1001018); // You cannot perform negative acts + return; // cannot honor in trammel town on blue + } + } + + if (Core.ML && target is PlayerMobile) + { + source.SendLocalizedMessage(1075614); // You cannot honor other players. + return; + } + + source.SentHonorContext?.Cancel(); + + _ = new HonorContext(source, target); + + source.Direction = source.GetDirectionTo(target); + + if (!source.Mounted) + { + source.Animate(32, 5, 1, true, true, 0); + } } - public interface IHonorTarget + private class InternalTarget : Target { - HonorContext ReceivedHonorContext { get; set; } - } + public InternalTarget() : base(12, false, TargetFlags.None) => CheckLOS = true; - public class HonorContext - { - private readonly Point3D m_InitialLocation; - private readonly Map m_InitialMap; - - private readonly InternalTimer m_Timer; - private FirstHit m_FirstHit; - private double m_HonorDamage; - private bool m_Poisoned; - private int m_TotalDamage; - - public HonorContext(PlayerMobile source, Mobile target) + protected override void OnTarget(Mobile from, object targeted) { - Source = source; - Target = target; - - m_FirstHit = FirstHit.NotDelivered; - m_Poisoned = false; - - m_InitialLocation = source.Location; - m_InitialMap = source.Map; - - source.SentHonorContext = this; - ((IHonorTarget)target).ReceivedHonorContext = this; - - m_Timer = new InternalTimer(this); - m_Timer.Start(); - source._honorTime = Core.Now + TimeSpan.FromMinutes(40); - - Timer.StartTimer( - TimeSpan.FromMinutes(40), - () => - { - if (source._honorTime < Core.Now && source.SentHonorContext != null) - { - Cancel(); - } - } - ); - } - - public PlayerMobile Source { get; } - - public Mobile Target { get; } - - public int PerfectionDamageBonus { get; private set; } - - public int PerfectionLuckBonus => PerfectionDamageBonus * PerfectionDamageBonus / 10; - - public void OnSourceDamaged(Mobile from, int amount) - { - if (from != Target) + if (from is not PlayerMobile pm) { return; } - if (m_FirstHit == FirstHit.NotDelivered) + if (targeted == pm) { - m_FirstHit = FirstHit.Granted; + EmbraceHonor(pm); + } + else if (targeted is Mobile mobile) + { + Honor(pm, mobile); } } - public void OnTargetPoisoned() + protected override void OnTargetOutOfRange(Mobile from, object targeted) { - m_Poisoned = true; // Set this flag for OnTargetDamaged which will be called next - } - - public void OnTargetDamaged(Mobile from, int amount) - { - if (m_FirstHit == FirstHit.NotDelivered) - { - m_FirstHit = FirstHit.Delivered; - } - - if (m_Poisoned) - { - m_HonorDamage += amount * 0.8; - m_Poisoned = false; // Reset the flag - - return; - } - - m_TotalDamage += amount; - - if (from == Source) - { - if (Target.CanSee(Source) && Target.InLOS(Source) && (Source.InRange(Target, 1) - || Source.Location == m_InitialLocation && - Source.Map == m_InitialMap)) - { - m_HonorDamage += amount; - } - else - { - m_HonorDamage += amount * 0.8; - } - } - else if (from is BaseCreature creature && creature.GetMaster() == Source) - { - m_HonorDamage += amount * 0.8; - } - } - - public void OnTargetHit(Mobile from) - { - if (from != Source || PerfectionDamageBonus == 100) - { - return; - } - - var bushido = (int)from.Skills.Bushido.Value; - if (bushido < 50) - { - return; - } - - PerfectionDamageBonus += bushido / 10; - - if (PerfectionDamageBonus >= 100) - { - PerfectionDamageBonus = 100; - Source.SendLocalizedMessage(1063254); // You have Achieved Perfection in inflicting damage to this opponent! - } - else - { - Source.SendLocalizedMessage(1063255); // You gain in Perfection as you precisely strike your opponent. - } - } - - public void OnTargetMissed(Mobile from) - { - if (from != Source || PerfectionDamageBonus == 0) - { - return; - } - - PerfectionDamageBonus -= 25; - - if (PerfectionDamageBonus <= 0) - { - PerfectionDamageBonus = 0; - Source.SendLocalizedMessage(1063256); // You have lost all Perfection in fighting this opponent. - } - else - { - Source.SendLocalizedMessage(1063257); // You have lost some Perfection in fighting this opponent. - } - } - - public void OnSourceBeneficialAction(Mobile to) - { - if (to != Target) - { - return; - } - - if (PerfectionDamageBonus >= 0) - { - PerfectionDamageBonus = 0; - Source.SendLocalizedMessage(1063256); // You have lost all Perfection in fighting this opponent. - } - } - - public void OnSourceKilled() - { - } - - public void OnTargetKilled() - { - Cancel(); - - var targetFame = Target.Fame; - - if (PerfectionDamageBonus > 0) - { - var restore = Math.Min(PerfectionDamageBonus * (targetFame + 5000) / 25000, 10); - - Source.Hits += restore; - Source.Stam += restore; - Source.Mana += restore; - } - - if (Source.Virtues.Honor > targetFame) - { - return; - } - - var dGain = - targetFame / 100.0 * (m_HonorDamage / m_TotalDamage); // Initial honor gain is 100th of the monsters honor - - if (m_HonorDamage == m_TotalDamage && m_FirstHit == FirstHit.Granted) - { - dGain *= 1.5; // honor gain is increased alot more if the combat was fully honorable - } - else - { - dGain *= 0.9; - } - - // Minimum gain of 1 honor when the honor is under the monsters fame - var gain = Math.Clamp((int)dGain, 1, 200); - - if (VirtueHelper.IsHighestPath(Source, VirtueName.Honor)) - { - Source.SendLocalizedMessage(1063228); // You cannot gain more Honor. - return; - } - - var gainedPath = false; - if (VirtueHelper.Award(Source, VirtueName.Honor, gain, ref gainedPath)) - { - if (gainedPath) - { - Source.SendLocalizedMessage(1063226); // You have gained a path in Honor! - } - else - { - Source.SendLocalizedMessage(1063225); // You have gained in Honor. - } - } - } - - public bool CheckDistance() => true; - - public void Cancel() - { - Source.SentHonorContext = null; - ((IHonorTarget)Target).ReceivedHonorContext = null; - - m_Timer.Stop(); - } - - private enum FirstHit - { - NotDelivered, - Delivered, - Granted - } - - private class InternalTimer : Timer - { - private readonly HonorContext m_Context; - - public InternalTimer(HonorContext context) : base(TimeSpan.FromSeconds(1.0), TimeSpan.FromSeconds(1.0)) => - m_Context = context; - - protected override void OnTick() - { - m_Context.CheckDistance(); - } + from.SendLocalizedMessage(1063232); // You are too far away to honor your opponent } } } diff --git a/Projects/UOContent/Engines/Virtues/HonorContext.cs b/Projects/UOContent/Engines/Virtues/HonorContext.cs new file mode 100644 index 000000000..2865a213e --- /dev/null +++ b/Projects/UOContent/Engines/Virtues/HonorContext.cs @@ -0,0 +1,261 @@ +using System; +using Server.Mobiles; + +namespace Server.Engines.Virtues; + +public class HonorContext +{ + private readonly Point3D _initialLocation; + private readonly Map _initialMap; + + private readonly InternalTimer _timer; + private FirstHit _firstHit; + private double _honorDamage; + private bool _poisoned; + private int _totalDamage; + + public HonorContext(PlayerMobile source, Mobile target) + { + Source = source; + Target = target; + + _firstHit = FirstHit.NotDelivered; + _poisoned = false; + + _initialLocation = source.Location; + _initialMap = source.Map; + + source.SentHonorContext = this; + ((IHonorTarget)target).ReceivedHonorContext = this; + + _timer = new InternalTimer(this); + _timer.Start(); + source._honorTime = Core.Now + TimeSpan.FromMinutes(40); + + Timer.DelayCall( + TimeSpan.FromMinutes(40), + (m, c) => + { + if (m._honorTime < Core.Now && m.SentHonorContext != null) + { + c.Cancel(); + } + }, + source, + this + ); + } + + public PlayerMobile Source { get; } + + public Mobile Target { get; } + + public int PerfectionDamageBonus { get; private set; } + + public int PerfectionLuckBonus => PerfectionDamageBonus * PerfectionDamageBonus / 10; + + public void OnSourceDamaged(Mobile from, int amount) + { + if (from != Target) + { + return; + } + + if (_firstHit == FirstHit.NotDelivered) + { + _firstHit = FirstHit.Granted; + } + } + + public void OnTargetPoisoned() + { + _poisoned = true; // Set this flag for OnTargetDamaged which will be called next + } + + public void OnTargetDamaged(Mobile from, int amount) + { + if (_firstHit == FirstHit.NotDelivered) + { + _firstHit = FirstHit.Delivered; + } + + if (_poisoned) + { + _honorDamage += amount * 0.8; + _poisoned = false; // Reset the flag + + return; + } + + _totalDamage += amount; + + if (from == Source) + { + if (Target.CanSee(Source) && Target.InLOS(Source) && + (Source.InRange(Target, 1) || Source.Location == _initialLocation && Source.Map == _initialMap)) + { + _honorDamage += amount; + } + else + { + _honorDamage += amount * 0.8; + } + } + else if (from is BaseCreature creature && creature.GetMaster() == Source) + { + _honorDamage += amount * 0.8; + } + } + + public void OnTargetHit(Mobile from) + { + if (from != Source || PerfectionDamageBonus == 100) + { + return; + } + + var bushido = (int)from.Skills.Bushido.Value; + if (bushido < 50) + { + return; + } + + PerfectionDamageBonus += bushido / 10; + + if (PerfectionDamageBonus >= 100) + { + PerfectionDamageBonus = 100; + Source.SendLocalizedMessage(1063254); // You have Achieved Perfection in inflicting damage to this opponent! + } + else + { + Source.SendLocalizedMessage(1063255); // You gain in Perfection as you precisely strike your opponent. + } + } + + public void OnTargetMissed(Mobile from) + { + if (from != Source || PerfectionDamageBonus == 0) + { + return; + } + + PerfectionDamageBonus -= 25; + + if (PerfectionDamageBonus <= 0) + { + PerfectionDamageBonus = 0; + Source.SendLocalizedMessage(1063256); // You have lost all Perfection in fighting this opponent. + } + else + { + Source.SendLocalizedMessage(1063257); // You have lost some Perfection in fighting this opponent. + } + } + + public void OnSourceBeneficialAction(Mobile to) + { + if (to != Target) + { + return; + } + + if (PerfectionDamageBonus >= 0) + { + PerfectionDamageBonus = 0; + Source.SendLocalizedMessage(1063256); // You have lost all Perfection in fighting this opponent. + } + } + + public void OnSourceKilled() + { + } + + public void OnTargetKilled() + { + Cancel(); + + var targetFame = Target.Fame; + + if (PerfectionDamageBonus > 0) + { + var restore = Math.Min(PerfectionDamageBonus * (targetFame + 5000) / 25000, 10); + + Source.Hits += restore; + Source.Stam += restore; + Source.Mana += restore; + } + + if (Source.GetVirtues().Honor > targetFame) + { + return; + } + + // Initial honor gain is 100th of the monsters honor + var dGain = targetFame / 100.0 * (_honorDamage / _totalDamage); + + if (_honorDamage == _totalDamage && _firstHit == FirstHit.Granted) + { + dGain *= 1.5; // honor gain is increased a lot more if the combat was fully honorable + } + else + { + dGain *= 0.9; + } + + // Minimum gain of 1 honor when the honor is under the monsters fame + var gain = Math.Clamp((int)dGain, 1, 200); + + if (VirtueSystem.IsHighestPath(Source, VirtueName.Honor)) + { + Source.SendLocalizedMessage(1063228); // You cannot gain more Honor. + return; + } + + var gainedPath = false; + if (VirtueSystem.Award(Source, VirtueName.Honor, gain, ref gainedPath)) + { + if (gainedPath) + { + Source.SendLocalizedMessage(1063226); // You have gained a path in Honor! + } + else + { + Source.SendLocalizedMessage(1063225); // You have gained in Honor. + } + } + } + + public bool CheckDistance() => Utility.InRange(Source.Location, Target.Location, 18); + + public void Cancel() + { + Source.SentHonorContext = null; + ((IHonorTarget)Target).ReceivedHonorContext = null; + + _timer.Stop(); + } + + private enum FirstHit + { + NotDelivered, + Delivered, + Granted + } + + private class InternalTimer : Timer + { + private readonly HonorContext _context; + + public InternalTimer(HonorContext context) : base(TimeSpan.FromSeconds(1.0), TimeSpan.FromSeconds(1.0)) => + _context = context; + + protected override void OnTick() + { + if (!_context.CheckDistance()) + { + _context.Cancel(); + } + } + } +} diff --git a/Projects/UOContent/Engines/Virtues/IHonorTarget.cs b/Projects/UOContent/Engines/Virtues/IHonorTarget.cs new file mode 100644 index 000000000..e60b02400 --- /dev/null +++ b/Projects/UOContent/Engines/Virtues/IHonorTarget.cs @@ -0,0 +1,6 @@ +namespace Server.Engines.Virtues; + +public interface IHonorTarget +{ + HonorContext ReceivedHonorContext { get; set; } +} diff --git a/Projects/UOContent/Engines/Virtues/Justice.cs b/Projects/UOContent/Engines/Virtues/Justice.cs index d5779737d..36e509fee 100644 --- a/Projects/UOContent/Engines/Virtues/Justice.cs +++ b/Projects/UOContent/Engines/Virtues/Justice.cs @@ -5,263 +5,331 @@ using Server.Mobiles; using Server.Network; using Server.Targeting; -namespace Server +namespace Server.Engines.Virtues; + +public enum JusticeProtectorStatus : byte { + None, + Protector, + Protected +} + #pragma warning disable CA1052 // Cannot be static because its used as a generic for CanBeginAction. - public class JusticeVirtue +public class JusticeVirtue +{ + private const int LossAmount = 950; + private static readonly TimeSpan LossDelay = TimeSpan.FromDays(7.0); + + public static void AddProtection(PlayerMobile protector, PlayerMobile protectee) { - private const int LossAmount = 950; - private static readonly TimeSpan LossDelay = TimeSpan.FromDays(7.0); + var virtues = protectee.Virtues; + virtues.JusticeStatus = JusticeProtectorStatus.Protected; + virtues.JusticeProtection = protector; - public static void Initialize() + virtues = protector.Virtues; + virtues.JusticeStatus = JusticeProtectorStatus.Protector; + virtues.JusticeProtection = protectee; + } + + public static bool IsProtected(PlayerMobile pm) => + pm.GetVirtues() is { JusticeStatus: JusticeProtectorStatus.Protected, JusticeProtection: not null }; + + public static PlayerMobile GetProtector(PlayerMobile pm) => + pm.GetVirtues() is { JusticeStatus: JusticeProtectorStatus.Protected } virtues ? virtues.JusticeProtection : null; + + public static PlayerMobile GetProtected(PlayerMobile pm) => + pm.GetVirtues() is { JusticeStatus: JusticeProtectorStatus.Protector } virtues ? virtues.JusticeProtection : null; + + public static void CancelProtection(PlayerMobile pm) + { + if (pm.GetVirtues() is { JusticeStatus: not JusticeProtectorStatus.None } virtues) { - VirtueGump.Register(109, OnVirtueUsed); - } + var protector = virtues.JusticeProtection; + virtues.JusticeProtection = null; + virtues.JusticeStatus = JusticeProtectorStatus.None; - public static bool CheckMapRegion(Mobile first, Mobile second) - { - var map = first.Map; - - if (second.Map != map) + virtues = protector?.GetVirtues(); + if (virtues != null) { - return false; - } - - return GetMapRegion(map, first.Location) == GetMapRegion(map, second.Location); - } - - public static int GetMapRegion(Map map, Point3D loc) => - map is not { MapID: < 2 } ? 0 : - loc.X < 5120 ? 0 : - loc.Y < 2304 ? 1 : 2; - - public static void OnVirtueUsed(Mobile from) - { - if (!from.CheckAlive()) - { - return; - } - - if (from is not PlayerMobile protector) - { - return; - } - - if (!VirtueHelper.IsSeeker(protector, VirtueName.Justice)) - { - protector.SendLocalizedMessage(1049610); // You must reach the first path in this virtue to invoke it. - } - else if (!protector.CanBeginAction()) - { - protector.SendLocalizedMessage(1049370); // You must wait a while before offering your protection again. - } - else if (protector.JusticeProtectors.Count > 0) - { - protector.SendLocalizedMessage(1049542); // You cannot protect someone while being protected. - } - else if (protector.Map != Map.Felucca) - { - protector.SendLocalizedMessage(1049372); // You cannot use this ability here. - } - else - { - protector.BeginTarget(14, false, TargetFlags.None, OnVirtueTargeted); - protector.SendLocalizedMessage(1049366); // Choose the player you wish to protect. + virtues.JusticeProtection = null; + virtues.JusticeStatus = JusticeProtectorStatus.None; } } + } - public static void OnVirtueTargeted(Mobile from, object obj) + public static bool CancelProtection(PlayerMobile pm, out PlayerMobile protector) + { + if (pm.GetVirtues() is { JusticeStatus: not JusticeProtectorStatus.None } virtues) { - if (from is not PlayerMobile protector) + protector = virtues.JusticeProtection; + virtues.JusticeProtection = null; + virtues.JusticeStatus = JusticeProtectorStatus.None; + + virtues = protector?.GetVirtues(); + if (virtues != null) { - return; + virtues.JusticeProtection = null; + virtues.JusticeStatus = JusticeProtectorStatus.None; } - var pm = obj as PlayerMobile; - - if (!VirtueHelper.IsSeeker(protector, VirtueName.Justice)) - { - protector.SendLocalizedMessage(1049610); // You must reach the first path in this virtue to invoke it. - } - else if (!protector.CanBeginAction()) - { - protector.SendLocalizedMessage(1049370); // You must wait a while before offering your protection again. - } - else if (protector.JusticeProtectors.Count > 0) - { - protector.SendLocalizedMessage(1049542); // You cannot protect someone while being protected. - } - else if (protector.Map != Map.Felucca) - { - protector.SendLocalizedMessage(1049372); // You cannot use this ability here. - } - else if (pm == null) - { - protector.SendLocalizedMessage(1049678); // Only players can be protected. - } - else if (pm.Map != Map.Felucca) - { - protector.SendLocalizedMessage(1049372); // You cannot use this ability here. - } - else if (pm == protector || pm.Criminal || pm.Kills >= 5) - { - protector.SendLocalizedMessage(1049436); // That player cannot be protected. - } - else if (pm.JusticeProtectors.Count > 0) - { - protector.SendLocalizedMessage(1049369); // You cannot protect that player right now. - } - else if (pm.HasGump()) - { - protector.SendLocalizedMessage(1049369); // You cannot protect that player right now. - } - else - { - pm.SendGump(new AcceptProtectorGump(protector, pm)); - } + return true; } - public static void OnVirtueAccepted(PlayerMobile protector, PlayerMobile protectee) + protector = null; + return false; + } + + public static void Initialize() + { + VirtueGump.Register(109, OnVirtueUsed); + EventSink.PlayerDeleted += OnPlayerDeleted; + } + + private static void OnPlayerDeleted(Mobile m) + { + if (m is PlayerMobile pm) { - if (!VirtueHelper.IsSeeker(protector, VirtueName.Justice)) - { - protector.SendLocalizedMessage(1049610); // You must reach the first path in this virtue to invoke it. - } - else if (!protector.CanBeginAction()) - { - protector.SendLocalizedMessage(1049370); // You must wait a while before offering your protection again. - } - else if (protector.JusticeProtectors.Count > 0) - { - protector.SendLocalizedMessage(1049542); // You cannot protect someone while being protected. - } - else if (protector.Map != Map.Felucca) - { - protector.SendLocalizedMessage(1049372); // You cannot use this ability here. - } - else if (protectee.Map != Map.Felucca) - { - protector.SendLocalizedMessage(1049372); // You cannot use this ability here. - } - else if (protectee == protector || protectee.Criminal || protectee.Kills >= 5) - { - protector.SendLocalizedMessage(1049436); // That player cannot be protected. - } - else if (protectee.JusticeProtectors.Count > 0) - { - protector.SendLocalizedMessage(1049369); // You cannot protect that player right now. - } - else - { - protectee.JusticeProtectors.Add(protector); + CancelProtection(pm); + } + } - var args = $"{protector.Name}\t{protectee.Name}"; + public static bool CheckMapRegion(Mobile first, Mobile second) + { + var map = first.Map; - protectee.SendLocalizedMessage(1049451, args); // You are now being protected by ~1_NAME~. - protector.SendLocalizedMessage(1049452, args); // You are now protecting ~2_NAME~. - } + if (second.Map != map) + { + return false; } - public static void OnVirtueRejected(PlayerMobile protector, PlayerMobile protectee) + return GetMapRegion(map, first.Location) == GetMapRegion(map, second.Location); + } + + public static int GetMapRegion(Map map, Point3D loc) => + map is not { MapID: < 2 } ? 0 : + loc.X < 5120 ? 0 : + loc.Y < 2304 ? 1 : 2; + + public static void OnVirtueUsed(Mobile from) + { + if (!from.CheckAlive()) { + return; + } + + if (from is not PlayerMobile protector) + { + return; + } + + if (!VirtueSystem.IsSeeker(protector, VirtueName.Justice)) + { + protector.SendLocalizedMessage(1049610); // You must reach the first path in this virtue to invoke it. + } + else if (!protector.CanBeginAction()) + { + protector.SendLocalizedMessage(1049370); // You must wait a while before offering your protection again. + } + else if (IsProtected(protector)) + { + protector.SendLocalizedMessage(1049542); // You cannot protect someone while being protected. + } + else if (protector.Map != Map.Felucca) + { + protector.SendLocalizedMessage(1049372); // You cannot use this ability here. + } + else + { + protector.BeginTarget(14, false, TargetFlags.None, OnVirtueTargeted); + protector.SendLocalizedMessage(1049366); // Choose the player you wish to protect. + } + } + + public static void OnVirtueTargeted(Mobile from, object obj) + { + if (from is not PlayerMobile protector) + { + return; + } + + var pm = obj as PlayerMobile; + + if (!VirtueSystem.IsSeeker(protector, VirtueName.Justice)) + { + protector.SendLocalizedMessage(1049610); // You must reach the first path in this virtue to invoke it. + } + else if (!protector.CanBeginAction()) + { + protector.SendLocalizedMessage(1049370); // You must wait a while before offering your protection again. + } + else if (IsProtected(protector)) + { + protector.SendLocalizedMessage(1049542); // You cannot protect someone while being protected. + } + else if (protector.Map != Map.Felucca) + { + protector.SendLocalizedMessage(1049372); // You cannot use this ability here. + } + else if (pm == null) + { + protector.SendLocalizedMessage(1049678); // Only players can be protected. + } + else if (pm.Map != Map.Felucca) + { + protector.SendLocalizedMessage(1049372); // You cannot use this ability here. + } + else if (pm == protector || pm.Criminal || pm.Kills >= 5) + { + protector.SendLocalizedMessage(1049436); // That player cannot be protected. + } + else if (IsProtected(pm)) + { + protector.SendLocalizedMessage(1049369); // You cannot protect that player right now. + } + else if (pm.HasGump()) + { + protector.SendLocalizedMessage(1049369); // You cannot protect that player right now. + } + else + { + pm.SendGump(new AcceptProtectorGump(protector, pm)); + } + } + + public static void OnVirtueAccepted(PlayerMobile protector, PlayerMobile protectee) + { + if (!VirtueSystem.IsSeeker(protector, VirtueName.Justice)) + { + protector.SendLocalizedMessage(1049610); // You must reach the first path in this virtue to invoke it. + } + else if (!protector.CanBeginAction()) + { + protector.SendLocalizedMessage(1049370); // You must wait a while before offering your protection again. + } + else if (IsProtected(protector)) + { + protector.SendLocalizedMessage(1049542); // You cannot protect someone while being protected. + } + else if (protector.Map != Map.Felucca) + { + protector.SendLocalizedMessage(1049372); // You cannot use this ability here. + } + else if (protectee.Map != Map.Felucca) + { + protector.SendLocalizedMessage(1049372); // You cannot use this ability here. + } + else if (protectee == protector || protectee.Criminal || protectee.Kills >= 5) + { + protector.SendLocalizedMessage(1049436); // That player cannot be protected. + } + else if (IsProtected(protectee)) + { + protector.SendLocalizedMessage(1049369); // You cannot protect that player right now. + } + else + { + AddProtection(protectee, protector); + var args = $"{protector.Name}\t{protectee.Name}"; - protectee.SendLocalizedMessage(1049453, args); // You have declined protection from ~1_NAME~. - protector.SendLocalizedMessage(1049454, args); // ~2_NAME~ has declined your protection. - - if (protector.BeginAction()) - { - Timer.StartTimer(TimeSpan.FromMinutes(15.0), protector.EndAction); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool ShouldAtrophy(PlayerMobile pm) => pm.LastJusticeLoss + LossDelay < Core.Now; - - - public static void CheckAtrophy(PlayerMobile pm) - { - if (ShouldAtrophy(pm)) - { - if (VirtueHelper.Atrophy(pm, VirtueName.Justice, LossAmount)) - { - pm.SendLocalizedMessage(1049373); // You have lost some Justice. - } - - pm.LastJusticeLoss = Core.Now; - } + protectee.SendLocalizedMessage(1049451, args); // You are now being protected by ~1_NAME~. + protector.SendLocalizedMessage(1049452, args); // You are now protecting ~2_NAME~. } } - public class AcceptProtectorGump : Gump + public static void OnVirtueRejected(PlayerMobile protector, PlayerMobile protectee) { - private readonly PlayerMobile m_Protectee; - private readonly PlayerMobile m_Protector; + var args = $"{protector.Name}\t{protectee.Name}"; - public AcceptProtectorGump(PlayerMobile protector, PlayerMobile protectee) : base(150, 50) + protectee.SendLocalizedMessage(1049453, args); // You have declined protection from ~1_NAME~. + protector.SendLocalizedMessage(1049454, args); // ~2_NAME~ has declined your protection. + + if (protector.BeginAction()) { - m_Protector = protector; - m_Protectee = protectee; - - Closable = false; - - AddPage(0); - - AddBackground(0, 0, 396, 218, 3600); - - AddImageTiled(15, 15, 365, 190, 2624); - AddAlphaRegion(15, 15, 365, 190); - - AddHtmlLocalized( - 30, - 20, - 360, - 25, - 1049365, // Another player is offering you their protection: - 0x7FFF - ); - AddLabel(90, 55, 1153, protector.Name); - - AddImage(50, 45, 9005); - AddImageTiled(80, 80, 200, 1, 9107); - AddImageTiled(95, 82, 200, 1, 9157); - - AddRadio(30, 110, 9727, 9730, true, 1); - AddHtmlLocalized(65, 115, 300, 25, 1049444, 0x7FFF); // Yes, I would like their protection. - - AddRadio(30, 145, 9727, 9730, false, 0); - AddHtmlLocalized(65, 148, 300, 25, 1049445, 0x7FFF); // No thanks, I can take care of myself. - - AddButton(160, 175, 247, 248, 2); - - AddImage(215, 0, 50581); - - AddImageTiled(15, 14, 365, 1, 9107); - AddImageTiled(380, 14, 1, 190, 9105); - AddImageTiled(15, 205, 365, 1, 9107); - AddImageTiled(15, 14, 1, 190, 9105); - AddImageTiled(0, 0, 395, 1, 9157); - AddImageTiled(394, 0, 1, 217, 9155); - AddImageTiled(0, 216, 395, 1, 9157); - AddImageTiled(0, 0, 1, 217, 9155); + Timer.StartTimer(TimeSpan.FromMinutes(15.0), protector.EndAction); } + } - public override void OnResponse(NetState sender, RelayInfo info) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool CanAtrophy(VirtueContext context) => context.LastJusticeLoss + LossDelay < Core.Now; + + public static void CheckAtrophy(PlayerMobile pm) + { + var virtues = pm.GetVirtues(); + if (virtues?.Justice > 0 && CanAtrophy(virtues)) { - if (info.ButtonID == 2) + if (VirtueSystem.Atrophy(pm, VirtueName.Justice, LossAmount)) { - var okay = info.IsSwitched(1); + pm.SendLocalizedMessage(1049373); // You have lost some Justice. + } - if (okay) - { - JusticeVirtue.OnVirtueAccepted(m_Protector, m_Protectee); - } - else - { - JusticeVirtue.OnVirtueRejected(m_Protector, m_Protectee); - } + virtues.LastJusticeLoss = Core.Now; + } + } +} + +public class AcceptProtectorGump : Gump +{ + private readonly PlayerMobile _protectee; + private readonly PlayerMobile _protector; + + public AcceptProtectorGump(PlayerMobile protector, PlayerMobile protectee) : base(150, 50) + { + _protector = protector; + _protectee = protectee; + + Closable = false; + + AddPage(0); + + AddBackground(0, 0, 396, 218, 3600); + + AddImageTiled(15, 15, 365, 190, 2624); + AddAlphaRegion(15, 15, 365, 190); + + // Another player is offering you their protection: + AddHtmlLocalized(30, 20, 360, 25, 1049365, 0x7FFF); + AddLabel(90, 55, 1153, protector.Name); + + AddImage(50, 45, 9005); + AddImageTiled(80, 80, 200, 1, 9107); + AddImageTiled(95, 82, 200, 1, 9157); + + AddRadio(30, 110, 9727, 9730, true, 1); + AddHtmlLocalized(65, 115, 300, 25, 1049444, 0x7FFF); // Yes, I would like their protection. + + AddRadio(30, 145, 9727, 9730, false, 0); + AddHtmlLocalized(65, 148, 300, 25, 1049445, 0x7FFF); // No thanks, I can take care of myself. + + AddButton(160, 175, 247, 248, 2); + + AddImage(215, 0, 50581); + + AddImageTiled(15, 14, 365, 1, 9107); + AddImageTiled(380, 14, 1, 190, 9105); + AddImageTiled(15, 205, 365, 1, 9107); + AddImageTiled(15, 14, 1, 190, 9105); + AddImageTiled(0, 0, 395, 1, 9157); + AddImageTiled(394, 0, 1, 217, 9155); + AddImageTiled(0, 216, 395, 1, 9157); + AddImageTiled(0, 0, 1, 217, 9155); + } + + public override void OnResponse(NetState sender, RelayInfo info) + { + if (info.ButtonID == 2) + { + var okay = info.IsSwitched(1); + + if (okay) + { + JusticeVirtue.OnVirtueAccepted(_protector, _protectee); + } + else + { + JusticeVirtue.OnVirtueRejected(_protector, _protectee); } } } -#pragma warning restore CA1052 } +#pragma warning restore CA1052 diff --git a/Projects/UOContent/Engines/Virtues/Sacrifice.cs b/Projects/UOContent/Engines/Virtues/Sacrifice.cs index ec1ac8327..29d09f55a 100644 --- a/Projects/UOContent/Engines/Virtues/Sacrifice.cs +++ b/Projects/UOContent/Engines/Virtues/Sacrifice.cs @@ -4,82 +4,71 @@ using Server.Gumps; using Server.Mobiles; using Server.Targeting; -namespace Server +namespace Server.Engines.Virtues; + +public static class SacrificeVirtue { - public static class SacrificeVirtue + private const int LossAmount = 500; + public static readonly TimeSpan GainDelay = TimeSpan.FromDays(1.0); + public static readonly TimeSpan LossDelay = TimeSpan.FromDays(7.0); + + public static void Initialize() { - private const int LossAmount = 500; - private static readonly TimeSpan GainDelay = TimeSpan.FromDays(1.0); - private static readonly TimeSpan LossDelay = TimeSpan.FromDays(7.0); + VirtueGump.Register(110, OnVirtueUsed); + } - public static void Initialize() + public static void OnVirtueUsed(PlayerMobile from) + { + if (from.Hidden) { - VirtueGump.Register(110, OnVirtueUsed); + from.SendLocalizedMessage(1052015); // You cannot do that while hidden. + } + else if (from.Alive) + { + from.Target = new InternalTarget(); + } + else + { + Resurrect(from); + } + } + + public static void CheckAtrophy(PlayerMobile pm) + { + var virtues = pm.GetVirtues(); + if (virtues?.Sacrifice > 0 && CanAtrophy(virtues)) + { + if (VirtueSystem.Atrophy(pm, VirtueName.Sacrifice, LossAmount)) + { + pm.SendLocalizedMessage(1052041); // You have lost some Sacrifice. + } + + var level = VirtueSystem.GetLevel(pm, VirtueName.Sacrifice); + + virtues.AvailableResurrects = (int)level; + virtues.LastSacrificeLoss = Core.Now; + } + } + + public static void Resurrect(PlayerMobile from) + { + if (from.Alive) + { + return; } - public static void OnVirtueUsed(Mobile from) + if (from.Criminal) { - if (!from.Hidden) - { - if (from.Alive) - { - from.Target = new InternalTarget(); - } - else - { - Resurrect(from); - } - } - else - { - from.SendLocalizedMessage(1052015); // You cannot do that while hidden. - } + from.SendLocalizedMessage(1052007); // You cannot use this ability while flagged as a criminal. } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool ShouldAtrophy(PlayerMobile pm) => pm.LastSacrificeLoss + LossDelay < Core.Now; - - public static void CheckAtrophy(PlayerMobile pm) + else if (!VirtueSystem.IsSeeker(from, VirtueName.Sacrifice)) { - if (ShouldAtrophy(pm)) - { - if (VirtueHelper.Atrophy(pm, VirtueName.Sacrifice, LossAmount)) - { - pm.SendLocalizedMessage(1052041); // You have lost some Sacrifice. - } - - var level = VirtueHelper.GetLevel(pm, VirtueName.Sacrifice); - - pm.AvailableResurrects = (int)level; - pm.LastSacrificeLoss = Core.Now; - } + from.SendLocalizedMessage(1052004); // You cannot use this ability. } - - public static void Resurrect(Mobile from) + else { - if (from.Alive) - { - return; - } - - if (from is not PlayerMobile pm) - { - return; - } - - if (from.Criminal) - { - from.SendLocalizedMessage(1052007); // You cannot use this ability while flagged as a criminal. - } - else if (!VirtueHelper.IsSeeker(from, VirtueName.Sacrifice)) - { - from.SendLocalizedMessage(1052004); // You cannot use this ability. - } - else if (pm.AvailableResurrects <= 0) - { - from.SendLocalizedMessage(1052005); // You do not have any resurrections left. - } - else + var virtues = from.GetVirtues(); + if (virtues?.AvailableResurrects > 0) { /* * We need to wait for them to accept the gump or they can just use @@ -88,46 +77,55 @@ namespace Server from.CloseGump(); from.SendGump(new ResurrectGump(from, true)); } + else + { + from.SendLocalizedMessage(1052005); // You do not have any resurrections left. + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool CanGain(VirtueContext context) => Core.Now >= context.LastSacrificeGain + GainDelay; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool CanAtrophy(VirtueContext context) => context.LastSacrificeLoss + LossDelay < Core.Now; + + public static void Sacrifice(PlayerMobile from, object targeted) + { + if (!from.CheckAlive()) + { + return; } - public static void Sacrifice(Mobile from, object targeted) + if (targeted is not Mobile targ) { - if (!from.CheckAlive()) - { - return; - } + return; + } - if (from is not PlayerMobile pm) - { - return; - } - - if (targeted is not Mobile targ) - { - return; - } - - if (!ValidateCreature(targ)) - { - from.SendLocalizedMessage(1052014); // You cannot sacrifice your fame for that creature. - } - else if (targ.Hits * 100 / Math.Max(targ.HitsMax, 1) < 90) - { - from.SendLocalizedMessage(1052013); // You cannot sacrifice for this monster because it is too damaged. - } - else if (from.Hidden) - { - from.SendLocalizedMessage(1052015); // You cannot do that while hidden. - } - else if (VirtueHelper.IsHighestPath(from, VirtueName.Sacrifice)) - { - from.SendLocalizedMessage(1052068); // You have already attained the highest path in this virtue. - } - else if (from.Fame < 2500) - { - from.SendLocalizedMessage(1052017); // You do not have enough fame to sacrifice. - } - else if (Core.Now < pm.LastSacrificeGain + GainDelay) + if (!ValidateCreature(targ)) + { + from.SendLocalizedMessage(1052014); // You cannot sacrifice your fame for that creature. + } + else if (targ.Hits * 100 / Math.Max(targ.HitsMax, 1) < 90) + { + from.SendLocalizedMessage(1052013); // You cannot sacrifice for this monster because it is too damaged. + } + else if (from.Hidden) + { + from.SendLocalizedMessage(1052015); // You cannot do that while hidden. + } + else if (VirtueSystem.IsHighestPath(from, VirtueName.Sacrifice)) + { + from.SendLocalizedMessage(1052068); // You have already attained the highest path in this virtue. + } + else if (from.Fame < 2500) + { + from.SendLocalizedMessage(1052017); // You do not have enough fame to sacrifice. + } + else + { + var virtues = from.GetOrCreateVirtues(); + if (!CanGain(virtues)) { from.SendLocalizedMessage(1052016); // You must wait approximately one day before sacrificing again. } @@ -149,19 +147,19 @@ namespace Server Timer.StartTimer(TimeSpan.FromSeconds(1.0), targ.Delete); - pm.LastSacrificeGain = Core.Now; + virtues.LastSacrificeGain = Core.Now; var gainedPath = false; - if (VirtueHelper.Award(from, VirtueName.Sacrifice, toGain, ref gainedPath)) + if (VirtueSystem.Award(from, VirtueName.Sacrifice, toGain, ref gainedPath)) { if (gainedPath) { from.SendLocalizedMessage(1052008); // You have gained a path in Sacrifice! - if (pm.AvailableResurrects < 3) + if (virtues.AvailableResurrects < 3) { - ++pm.AvailableResurrects; + ++virtues.AvailableResurrects; } } else @@ -173,20 +171,23 @@ namespace Server from.SendLocalizedMessage(1052016); // You must wait approximately one day before sacrificing again. } } + } - public static bool ValidateCreature(Mobile m) => - (m is not BaseCreature creature || !creature.Controlled && !creature.Summoned) && - m is Lich or Succubus or Daemon or EvilMage or EnslavedGargoyle or GargoyleEnforcer; + public static bool ValidateCreature(Mobile m) => + (m is not BaseCreature creature || !creature.Controlled && !creature.Summoned) && + m is Lich or Succubus or Daemon or EvilMage or EnslavedGargoyle or GargoyleEnforcer; - private class InternalTarget : Target + private class InternalTarget : Target + { + public InternalTarget() : base(8, false, TargetFlags.None) { - public InternalTarget() : base(8, false, TargetFlags.None) - { - } + } - protected override void OnTarget(Mobile from, object targeted) + protected override void OnTarget(Mobile from, object targeted) + { + if (from is PlayerMobile pm) { - Sacrifice(from, targeted); + Sacrifice(pm, targeted); } } } diff --git a/Projects/UOContent/Engines/Virtues/Valor.cs b/Projects/UOContent/Engines/Virtues/Valor.cs index c2efaef39..e8a2e520b 100644 --- a/Projects/UOContent/Engines/Virtues/Valor.cs +++ b/Projects/UOContent/Engines/Virtues/Valor.cs @@ -4,7 +4,7 @@ using Server.Engines.CannedEvil; using Server.Mobiles; using Server.Targeting; -namespace Server; +namespace Server.Engines.Virtues; public static class ValorVirtue { @@ -26,22 +26,23 @@ public static class ValorVirtue } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool ShouldAtrophy(PlayerMobile pm) => pm.LastValorLoss + LossDelay < Core.Now; + public static bool CanAtrophy(VirtueContext context) => context.LastValorLoss + LossDelay < Core.Now; public static void CheckAtrophy(PlayerMobile pm) { - if (ShouldAtrophy(pm)) + var virtues = pm.GetVirtues(); + if (virtues?.Valor > 0 && CanAtrophy(virtues)) { - if (VirtueHelper.Atrophy(pm, VirtueName.Valor, LossAmount)) + if (VirtueSystem.Atrophy(pm, VirtueName.Valor, LossAmount)) { pm.SendLocalizedMessage(1054040); // You have lost some Valor. } - pm.LastValorLoss = Core.Now; + virtues.LastValorLoss = Core.Now; } } - public static void Valor(Mobile from, object targ) + public static void Valor(PlayerMobile from, object targ) { if (targ is not IdolOfTheChampion idol || idol.Deleted || idol.Spawn?.Deleted != false) { @@ -89,9 +90,9 @@ public static class ValorVirtue } } - if (from.Virtues.GetValue((int)VirtueName.Valor) >= needed) + if (from.GetVirtues()?.GetValue((int)VirtueName.Valor) >= needed) { - VirtueHelper.Atrophy(from, VirtueName.Valor, consumed); + VirtueSystem.Atrophy(from, VirtueName.Valor, consumed); // Your challenge is heard by the Champion of this region! Beware its wrath! from.SendLocalizedMessage(1054037); idol.Spawn.HasBeenAdvanced = true; @@ -103,9 +104,9 @@ public static class ValorVirtue from.SendLocalizedMessage(1054039); } } - else if (VirtueHelper.GetLevel(from, VirtueName.Valor) == VirtueLevel.Knight) + else if (VirtueSystem.GetLevel(from, VirtueName.Valor) == VirtueLevel.Knight) { - VirtueHelper.Atrophy(from, VirtueName.Valor, 11000); + VirtueSystem.Atrophy(from, VirtueName.Valor, 11000); // Your challenge is heard by the Champion of this region! Beware its wrath! from.SendLocalizedMessage(1054037); idol.Spawn.Start(); @@ -128,7 +129,10 @@ public static class ValorVirtue protected override void OnTarget(Mobile from, object targeted) { - Valor(from, targeted); + if (from is PlayerMobile pm) + { + Valor(pm, targeted); + } } } } diff --git a/Projects/UOContent/Engines/Virtues/VirtueContext.cs b/Projects/UOContent/Engines/Virtues/VirtueContext.cs new file mode 100644 index 000000000..3b26b5294 --- /dev/null +++ b/Projects/UOContent/Engines/Virtues/VirtueContext.cs @@ -0,0 +1,195 @@ +using System; +using ModernUO.Serialization; +using Server.Mobiles; + +namespace Server.Engines.Virtues; + +[PropertyObject] +[SerializationGenerator(0)] +public partial class VirtueContext +{ + [DeltaDateTime] + [SerializableField(0)] + [SerializedCommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)] + private DateTime _lastSacrificeGain; + + [SerializableFieldSaveFlag(0)] + private bool ShouldSerializeLastSacrificeGain() => !SacrificeVirtue.CanGain(this); + + [DeltaDateTime] + [SerializableField(1)] + [SerializedCommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)] + private DateTime _lastSacrificeLoss; + + [SerializableFieldSaveFlag(1)] + private bool ShouldSerializeLastSacrificeLoss() => !SacrificeVirtue.CanAtrophy(this); + + [SerializableField(2)] + [SerializedCommandProperty(AccessLevel.GameMaster)] + private int _availableResurrects; + + [SerializableFieldSaveFlag(2)] + private bool ShouldSerializeAvailableResurrects() => _availableResurrects > 0; + + [DeltaDateTime] + [SerializableField(3)] + [SerializedCommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)] + private DateTime _lastJusticeLoss; + + [SerializableFieldSaveFlag(3)] + private bool ShouldSerializeLastJusticeLoss() => !JusticeVirtue.CanAtrophy(this); + + [DeltaDateTime] + [SerializableField(4)] + [SerializedCommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)] + private DateTime _lastCompassionLoss; + + [SerializableFieldSaveFlag(4)] + private bool ShouldSerializeLastCompassionLoss() => !CompassionVirtue.CanAtrophy(this); + + [DeltaDateTime] + [SerializableField(5)] + [SerializedCommandProperty(AccessLevel.GameMaster)] + private DateTime _nextCompassionDay; + + [SerializableFieldSaveFlag(5)] + private bool ShouldSerializeNextCompassionDay() => _nextCompassionDay > Core.Now; + + [SerializableField(6)] + [SerializedCommandProperty(AccessLevel.GameMaster)] + private int _compassionGains; + + [SerializableFieldSaveFlag(6)] + private bool ShouldSerializeCompassionGains() => _compassionGains > 0; + + [DeltaDateTime] + [SerializableField(7)] + [SerializedCommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)] + private DateTime _lastValorLoss; + + [SerializableFieldSaveFlag(7)] + private bool ShouldSerializeValorLoss() => !ValorVirtue.CanAtrophy(this); + + [DeltaDateTime] + [SerializableField(8)] + [SerializedCommandProperty(AccessLevel.GameMaster)] + private DateTime _lastHonorUse; + + [SerializableFieldSaveFlag(8)] + private bool ShouldSerializeLastHonorUse() => !HonorVirtue.CanUse(this); + + [SerializableField(9)] + [SerializedCommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)] + private bool _honorActive; + + [SerializableFieldSaveFlag(9)] + private bool ShouldSerializeHonorActive() => _honorActive; + + [SerializableField(10)] + private PlayerMobile _justiceProtection; + + [SerializableFieldSaveFlag(10)] + private bool ShouldSerializeJusticeProtection() => _justiceProtection != null && _justiceStatus != JusticeProtectorStatus.None; + + [SerializableField(11)] + private JusticeProtectorStatus _justiceStatus; + + [SerializableFieldSaveFlag(11)] + private bool ShouldSerializeJusticeStatus() => _justiceProtection != null && _justiceStatus != JusticeProtectorStatus.None; + + [SerializableField(12, setter: "private")] + private int[] _values; + + [SerializableFieldSaveFlag(12)] + private bool ShouldSerializeValues() + { + if (_values == null) + { + return false; + } + + for (var i = 0; i < _values.Length; i++) + { + if (_values[i] > 0) + { + return true; + } + } + + return false; + } + + [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] + public int Humility + { + get => GetValue(0); + set => SetValue(0, value); + } + + [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] + public int Sacrifice + { + get => GetValue(1); + set => SetValue(1, value); + } + + [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] + public int Compassion + { + get => GetValue(2); + set => SetValue(2, value); + } + + [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] + public int Spirituality + { + get => GetValue(3); + set => SetValue(3, value); + } + + [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] + public int Valor + { + get => GetValue(4); + set => SetValue(4, value); + } + + [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] + public int Honor + { + get => GetValue(5); + set => SetValue(5, value); + } + + [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] + public int Justice + { + get => GetValue(6); + set => SetValue(6, value); + } + + [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] + public int Honesty + { + get => GetValue(7); + set => SetValue(7, value); + } + + public int GetValue(int index) => _values?[index] ?? 0; + + public void SetValue(int index, int value) + { + _values ??= new int[8]; + _values[index] = value; + } + + public override string ToString() => "..."; + + // Used to invalidate and delete the VirtueContext, usually during world load + public bool IsUsed() => ShouldSerializeLastSacrificeGain() || ShouldSerializeLastSacrificeLoss() || + ShouldSerializeAvailableResurrects() || ShouldSerializeLastJusticeLoss() || + ShouldSerializeJusticeStatus() || ShouldSerializeNextCompassionDay() || + ShouldSerializeCompassionGains() || ShouldSerializeValorLoss() || + ShouldSerializeLastHonorUse() || ShouldSerializeHonorActive() || + ShouldSerializeValues() || ShouldSerializeLastCompassionLoss(); +} diff --git a/Projects/UOContent/Engines/Virtues/VirtueGump.cs b/Projects/UOContent/Engines/Virtues/VirtueGump.cs index 5f9bc903e..d7e0e93e4 100644 --- a/Projects/UOContent/Engines/Virtues/VirtueGump.cs +++ b/Projects/UOContent/Engines/Virtues/VirtueGump.cs @@ -1,181 +1,154 @@ using System.Collections.Generic; using Server.Gumps; +using Server.Mobiles; using Server.Network; -namespace Server +namespace Server.Engines.Virtues; + +public delegate void OnVirtueUsed(PlayerMobile from); + +public class VirtueGump : Gump { - public delegate void OnVirtueUsed(Mobile from); + private static readonly Dictionary _callbacks = new(); - public class VirtueGump : Gump + private static readonly int[] _table = { - private static readonly Dictionary m_Callbacks = new(); + 0x0481, 0x0963, 0x0965, + 0x060A, 0x060F, 0x002A, + 0x08A4, 0x08A7, 0x0034, + 0x0965, 0x08FD, 0x0480, + 0x00EA, 0x0845, 0x0020, + 0x0011, 0x0269, 0x013D, + 0x08A1, 0x08A3, 0x0042, + 0x0543, 0x0547, 0x0061 + }; - private static readonly int[] m_Table = + public static void Register(int gumpID, OnVirtueUsed callback) + { + _callbacks[gumpID] = callback; + } + + public static void RequestVirtueGump(PlayerMobile beholder, PlayerMobile beheld) + { + if (beholder == beheld && beholder.Kills >= 5) { - 0x0481, 0x0963, 0x0965, - 0x060A, 0x060F, 0x002A, - 0x08A4, 0x08A7, 0x0034, - 0x0965, 0x08FD, 0x0480, - 0x00EA, 0x0845, 0x0020, - 0x0011, 0x0269, 0x013D, - 0x08A1, 0x08A3, 0x0042, - 0x0543, 0x0547, 0x0061 + beholder.SendLocalizedMessage(1049609); // Murderers cannot invoke this virtue. + } + else if (beholder.Map == beheld.Map && beholder.InRange(beheld, 12)) + { + beholder.CloseGump(); + beholder.SendGump(new VirtueGump(beholder, beheld)); + } + } + + public static void RequestVirtueItem(PlayerMobile beholder, Mobile beheld, int gumpID) + { + if (beholder != beheld) + { + return; + } + + beholder.CloseGump(); + + if (beholder.Kills >= 5) + { + beholder.SendLocalizedMessage(1049609); // Murderers cannot invoke this virtue. + return; + } + + if (_callbacks.TryGetValue(gumpID, out var callback)) + { + callback(beholder); + } + else + { + beholder.SendLocalizedMessage(1052066); // That virtue is not active yet. + } + } + + public static void RequestVirtueMacro(PlayerMobile beholder, int virtue) + { + var virtueID = virtue switch + { + 0 => 107, // Honor + 1 => 110, // Sacrifice + 2 => 112, // Valor; + _ => 0 }; - private readonly Mobile m_Beheld; + RequestVirtueItem(beholder, beholder, virtueID); + } - private readonly Mobile m_Beholder; + private readonly PlayerMobile _beheld; + private readonly PlayerMobile _beholder; - public VirtueGump(Mobile beholder, Mobile beheld) : base(0, 0) + public VirtueGump(PlayerMobile beholder, PlayerMobile beheld) : base(0, 0) + { + _beholder = beholder; + _beheld = beheld; + + Serial = beheld.Serial; + + AddPage(0); + + AddImage(30, 40, 104); + + AddPage(1); + + Add(new VirtueGumpItem(61, 71, 108, GetHueFor(0))); // Humility + Add(new VirtueGumpItem(123, 46, 112, GetHueFor(4))); // Valor + Add(new VirtueGumpItem(187, 70, 107, GetHueFor(5))); // Honor + Add(new VirtueGumpItem(35, 135, 110, GetHueFor(1))); // Sacrifice + Add(new VirtueGumpItem(211, 133, 105, GetHueFor(2))); // Compassion + Add(new VirtueGumpItem(61, 195, 111, GetHueFor(3))); // Spirituality + Add(new VirtueGumpItem(186, 195, 109, GetHueFor(6))); // Justice + Add(new VirtueGumpItem(121, 221, 106, GetHueFor(7))); // Honesty + + if (_beholder == _beheld) { - m_Beholder = beholder; - m_Beheld = beheld; + AddButton(57, 269, 2027, 2027, 1); + AddButton(186, 269, 2071, 2071, 2); + } + } - Serial = beheld.Serial; + private int GetHueFor(int index) + { + var value = _beheld.GetVirtues()?.GetValue(index) ?? 0; - AddPage(0); - - AddImage(30, 40, 104); - - AddPage(1); - - Add(new VirtueGumpItem(61, 71, 108, GetHueFor(0))); // Humility - Add(new VirtueGumpItem(123, 46, 112, GetHueFor(4))); // Valor - Add(new VirtueGumpItem(187, 70, 107, GetHueFor(5))); // Honor - Add(new VirtueGumpItem(35, 135, 110, GetHueFor(1))); // Sacrifice - Add(new VirtueGumpItem(211, 133, 105, GetHueFor(2))); // Compassion - Add(new VirtueGumpItem(61, 195, 111, GetHueFor(3))); // Spiritulaity - Add(new VirtueGumpItem(186, 195, 109, GetHueFor(6))); // Justice - Add(new VirtueGumpItem(121, 221, 106, GetHueFor(7))); // Honesty - - if (m_Beholder == m_Beheld) - { - AddButton(57, 269, 2027, 2027, 1); - AddButton(186, 269, 2071, 2071, 2); - } + if (value < 4000) + { + return 2402; } - public static void Initialize() + if (value >= 30000) { - EventSink.VirtueGumpRequest += EventSink_VirtueGumpRequest; - EventSink.VirtueItemRequest += EventSink_VirtueItemRequest; - EventSink.VirtueMacroRequest += EventSink_VirtueMacroRequest; + value = 20000; } - public static void Register(int gumpID, OnVirtueUsed callback) + int vl = value switch { - m_Callbacks[gumpID] = callback; + < 10000 => 0, + >= 20000 when index == 5 => 2, + >= 22000 when index == 1 => 2, + >= 21000 when index != 1 => 2, + _ => 1 + }; + + return _table[index * 3 + vl]; + } + + public override void OnResponse(NetState state, RelayInfo info) + { + if (info.ButtonID == 1 && _beholder == _beheld) + { + _beholder.SendGump(new VirtueStatusGump(_beholder)); } + } - private static void EventSink_VirtueItemRequest(Mobile beholder, Mobile beheld, int gumpID) + private class VirtueGumpItem : GumpImage + { + public VirtueGumpItem(int x, int y, int gumpID, int hue) : base(x, y, gumpID, hue, "VirtueGumpItem") { - if (beholder != beheld) - { - return; - } - - beholder.CloseGump(); - - if (beholder.Kills >= 5) - { - beholder.SendLocalizedMessage(1049609); // Murderers cannot invoke this virtue. - return; - } - - if (m_Callbacks.TryGetValue(gumpID, out var callback)) - { - callback(beholder); - } - else - { - beholder.SendLocalizedMessage(1052066); // That virtue is not active yet. - } - } - - private static void EventSink_VirtueMacroRequest(Mobile beholder, int virtue) - { - var virtueID = virtue switch - { - 0 => 107, // Honor - 1 => 110, // Sacrifice - 2 => 112, // Valor; - _ => 0 - }; - - EventSink_VirtueItemRequest(beholder, beholder, virtueID); - } - - private static void EventSink_VirtueGumpRequest(Mobile beholder, Mobile beheld) - { - if (beholder == beheld && beholder.Kills >= 5) - { - beholder.SendLocalizedMessage(1049609); // Murderers cannot invoke this virtue. - } - else if (beholder.Map == beheld.Map && beholder.InRange(beheld, 12)) - { - beholder.CloseGump(); - beholder.SendGump(new VirtueGump(beholder, beheld)); - } - } - - private int GetHueFor(int index) - { - if (m_Beheld.Virtues.GetValue(index) == 0) - { - return 2402; - } - - var value = m_Beheld.Virtues.GetValue(index); - - if (value < 4000) - { - return 2402; - } - - if (value >= 30000) - { - value = 20000; // Sanity - } - - int vl; - - if (value < 10000) - { - vl = 0; - } - else if (value >= 20000 && index == 5) - { - vl = 2; - } - else if (value >= 21000 && index != 1) - { - vl = 2; - } - else if (value >= 22000 && index == 1) - { - vl = 2; - } - else - { - vl = 1; - } - - return m_Table[index * 3 + vl]; - } - - public override void OnResponse(NetState state, RelayInfo info) - { - if (info.ButtonID == 1 && m_Beholder == m_Beheld) - { - m_Beholder.SendGump(new VirtueStatusGump(m_Beholder)); - } - } - - private class VirtueGumpItem : GumpImage - { - public VirtueGumpItem(int x, int y, int gumpID, int hue) : base(x, y, gumpID, hue, "VirtueGumpItem") - { - } } } } diff --git a/Projects/UOContent/Engines/Virtues/VirtueHelper.cs b/Projects/UOContent/Engines/Virtues/VirtueHelper.cs deleted file mode 100644 index 85fe4a349..000000000 --- a/Projects/UOContent/Engines/Virtues/VirtueHelper.cs +++ /dev/null @@ -1,232 +0,0 @@ -using System; -using Server.Mobiles; - -namespace Server -{ - public enum VirtueLevel - { - None, - Seeker, - Follower, - Knight - } - - public enum VirtueName - { - Humility, - Sacrifice, - Compassion, - Spirituality, - Valor, - Honor, - Justice, - Honesty - } - - public static class VirtueHelper - { - public static bool HasAny(Mobile from, VirtueName virtue) => from.Virtues.GetValue((int)virtue) > 0; - - public static bool IsHighestPath(Mobile from, VirtueName virtue) => - from.Virtues.GetValue((int)virtue) >= GetMaxAmount(virtue); - - public static VirtueLevel GetLevel(Mobile from, VirtueName virtue) - { - var v = from.Virtues.GetValue((int)virtue); - int vl; - - if (v < 4000) - { - vl = 0; - } - else if (v >= GetMaxAmount(virtue)) - { - vl = 3; - } - else - { - vl = (v + 9999) / 10000; - } - - return (VirtueLevel)vl; - } - - public static string GetName(this VirtueName virtue) => - virtue switch - { - VirtueName.Humility => "Humility", - VirtueName.Sacrifice => "Sacrifice", - VirtueName.Compassion => "Compassion", - VirtueName.Spirituality => "Spirituality", - VirtueName.Valor => "Valor", - VirtueName.Honor => "Honor", - VirtueName.Justice => "Justice", - VirtueName.Honesty => "Honesty", - _ => "" - }; - - public static int GetMaxAmount(VirtueName virtue) => - virtue switch - { - VirtueName.Honor => 20000, - VirtueName.Sacrifice => 22000, - _ => 21000 - }; - - public static int GetGainedLocalizedMessage(VirtueName virtue) => - virtue switch - { - VirtueName.Sacrifice => 1054160, // You have gained in sacrifice. - VirtueName.Compassion => 1053002, // You have gained in compassion. - VirtueName.Spirituality => 1155832, // You have gained in Spirituality. - VirtueName.Valor => 1054030, // You have gained in Valor! - VirtueName.Honor => 1063225, // You have gained in Honor. - VirtueName.Justice => 1049363, // You have gained in Justice. - VirtueName.Humility => 1052070, // You have gained in Humility. - _ => 0 - }; - - public static int GetGainedAPathLocalizedMessage(VirtueName virtue) => - virtue switch - { - VirtueName.Sacrifice => 1052008, // You have gained a path in Sacrifice! - VirtueName.Spirituality => 1155833, // You have gained a path in Spirituality! - VirtueName.Valor => 1054032, // You have gained a path in Valor! - VirtueName.Honor => 1063226, // You have gained a path in Honor! - VirtueName.Justice => 1049367, // You have gained a path in Justice! - VirtueName.Humility => 1155811, // You have gained a path in Humility! - _ => 0 - }; - - public static int GetHightestPathLocalizedMessage(VirtueName virtue) => - virtue switch - { - VirtueName.Compassion => 1053003, // You have achieved the highest path of compassion and can no longer gain any further. - VirtueName.Valor => 1054031, // You have achieved the highest path in Valor and can no longer gain any further. - VirtueName.Honesty => 1153771, // You have achieved the highest path in Honesty and can no longer gain any further. - _ => 0 - }; - - public static bool Award(Mobile from, VirtueName virtue, int amount, ref bool gainedPath) - { - var current = from.Virtues.GetValue((int)virtue); - - var maxAmount = GetMaxAmount(virtue); - - if (current >= maxAmount) - { - return false; - } - - if (current + amount >= maxAmount) - { - amount = maxAmount - current; - } - - var oldLevel = GetLevel(from, virtue); - - from.Virtues.SetValue((int)virtue, current + amount); - - gainedPath = GetLevel(from, virtue) != oldLevel; - - return true; - } - - public static bool Atrophy(Mobile from, VirtueName virtue) => Atrophy(from, virtue, 1); - - public static bool Atrophy(Mobile from, VirtueName virtue, int amount) - { - var current = from.Virtues.GetValue((int)virtue); - - if (current - amount >= 0) - { - from.Virtues.SetValue((int)virtue, current - amount); - } - else - { - from.Virtues.SetValue((int)virtue, 0); - } - - return current > 0; - } - - public static bool IsSeeker(Mobile from, VirtueName virtue) => GetLevel(from, virtue) >= VirtueLevel.Seeker; - - public static bool IsFollower(Mobile from, VirtueName virtue) => GetLevel(from, virtue) >= VirtueLevel.Follower; - - public static bool IsKnight(Mobile from, VirtueName virtue) => GetLevel(from, virtue) >= VirtueLevel.Knight; - - public static void AwardVirtue(PlayerMobile pm, VirtueName virtue, int amount) - { - if (virtue == VirtueName.Compassion) - { - if (pm.CompassionGains > 0 && Core.Now > pm.NextCompassionDay) - { - pm.NextCompassionDay = DateTime.MinValue; - pm.CompassionGains = 0; - } - - if (pm.CompassionGains >= 5) - { - pm.SendLocalizedMessage(1053004); // You must wait about a day before you can gain in compassion again. - return; - } - } - - var gainedPath = false; - var virtueName = virtue.GetName(); - - if (Award(pm, virtue, amount, ref gainedPath)) - { - if (gainedPath) - { - var gainedPathMessage = GetGainedAPathLocalizedMessage(virtue); - if (gainedPathMessage != 0) - { - pm.SendLocalizedMessage(gainedPathMessage); - } - else - { - pm.SendMessage($"You have gained a path in {virtueName}!"); - } - } - else - { - var gainMessage = GetGainedLocalizedMessage(virtue); - if (gainMessage != 0) - { - pm.SendLocalizedMessage(gainMessage); - } - else - { - pm.SendMessage($"You have gained in {virtueName}."); - } - } - - if (virtue == VirtueName.Compassion) - { - pm.NextCompassionDay = Core.Now + TimeSpan.FromDays(1.0); - - if (++pm.CompassionGains >= 5) - { - // You must wait about a day before you can gain in compassion again. - pm.SendLocalizedMessage(1053004); - } - } - } - else - { - var highestPathMessage = GetHightestPathLocalizedMessage(virtue); - if (highestPathMessage != 0) - { - pm.SendLocalizedMessage(highestPathMessage); - } - else - { - pm.SendMessage($"You have achieved the highest path in {virtueName} and can no longer gain any further."); - } - - } - } - } -} diff --git a/Projects/UOContent/Engines/Virtues/VirtueInfoGump.cs b/Projects/UOContent/Engines/Virtues/VirtueInfoGump.cs index eb4af7f2d..47f6a1269 100644 --- a/Projects/UOContent/Engines/Virtues/VirtueInfoGump.cs +++ b/Projects/UOContent/Engines/Virtues/VirtueInfoGump.cs @@ -1,137 +1,132 @@ using Server.Gumps; +using Server.Mobiles; using Server.Network; -namespace Server +namespace Server.Engines.Virtues; + +public class VirtueInfoGump : Gump { - public class VirtueInfoGump : Gump + private readonly PlayerMobile _beholder; + private readonly int _desc; + private readonly string _site; + private readonly VirtueName _virtue; + + public VirtueInfoGump(PlayerMobile beholder, VirtueName virtue, int description, string webPage = null) : base(0, 0) { - private readonly Mobile m_Beholder; - private readonly int m_Desc; - private readonly string m_Page; - private readonly VirtueName m_Virtue; + _beholder = beholder; + _virtue = virtue; + _desc = description; + _site = webPage; - public VirtueInfoGump(Mobile beholder, VirtueName virtue, int description, string webPage = null) : base(0, 0) + var value = beholder.GetVirtues()?.GetValue((int)virtue) ?? 0; + + AddPage(0); + + AddImage(30, 40, 2080); + AddImage(47, 77, 2081); + AddImage(47, 147, 2081); + AddImage(47, 217, 2081); + AddImage(47, 267, 2083); + AddImage(70, 213, 2091); + + AddPage(1); + + var maxValue = VirtueSystem.GetMaxAmount(_virtue); + + int valueDesc; + int dots; + + if (value < 4000) { - m_Beholder = beholder; - m_Virtue = virtue; - m_Desc = description; - m_Page = webPage; - - var value = beholder.Virtues.GetValue((int)virtue); - - AddPage(0); - - AddImage(30, 40, 2080); - AddImage(47, 77, 2081); - AddImage(47, 147, 2081); - AddImage(47, 217, 2081); - AddImage(47, 267, 2083); - AddImage(70, 213, 2091); - - AddPage(1); - - var maxValue = VirtueHelper.GetMaxAmount(m_Virtue); - - int valueDesc; - int dots; - - if (value < 4000) - { - dots = value / 400; - } - else if (value < 10000) - { - dots = (value - 4000) / 600; - } - else if (value < maxValue) - { - dots = (value - 10000) / ((maxValue - 10000) / 10); - } - else - { - dots = 10; - } - - for (var i = 0; i < 10; ++i) - { - AddImage(95 + i * 17, 50, i < dots ? 2362 : 2360); - } - - if (value < 1) - { - valueDesc = 1052044; // You have not started on the path of this Virtue. - } - else if (value < 400) - { - valueDesc = 1052045; // You have barely begun your journey through the path of this Virtue. - } - else if (value < 2000) - { - valueDesc = 1052046; // You have progressed in this Virtue, but still have much to do. - } - else if (value < 3600) - { - valueDesc = 1052047; // Your journey through the path of this Virtue is going well. - } - else if (value < 4000) - { - valueDesc = 1052048; // You feel very close to achieving your next path in this Virtue. - } - else if (dots < 1) - { - valueDesc = 1052049; // You have achieved a path in this Virtue. - } - else if (dots < 9) - { - valueDesc = 1052047; // Your journey through the path of this Virtue is going well. - } - else if (dots < 10) - { - valueDesc = 1052048; // You feel very close to achieving your next path in this Virtue. - } - else - { - valueDesc = 1052050; // You have achieved the highest path in this Virtue. - } - - AddHtmlLocalized(157, 73, 200, 40, 1051000 + (int)virtue); - AddHtmlLocalized(75, 95, 220, 140, description); - AddHtmlLocalized(70, 224, 229, 60, valueDesc); - - AddButton(65, 277, 1209, 1209, 1); - - AddButton(280, 43, 4014, 4014, 2); - - AddHtmlLocalized( - 83, - 275, - 400, - 40, - webPage == null ? 1052055 : 1052052 - ); // This virtue is not yet defined. OR -click to learn more (opens webpage) + dots = value / 400; + } + else if (value < 10000) + { + dots = (value - 4000) / 600; + } + else if (value < maxValue) + { + dots = (value - 10000) / ((maxValue - 10000) / 10); + } + else + { + dots = 10; } - public override void OnResponse(NetState state, RelayInfo info) + for (var i = 0; i < 10; ++i) { - switch (info.ButtonID) - { - case 1: - { - m_Beholder.SendGump(new VirtueInfoGump(m_Beholder, m_Virtue, m_Desc, m_Page)); + AddImage(95 + i * 17, 50, i < dots ? 2362 : 2360); + } - if (m_Page != null) - { - state.SendLaunchBrowser(m_Page); // No message about web browser starting on OSI - } + if (value < 1) + { + valueDesc = 1052044; // You have not started on the path of this Virtue. + } + else if (value < 400) + { + valueDesc = 1052045; // You have barely begun your journey through the path of this Virtue. + } + else if (value < 2000) + { + valueDesc = 1052046; // You have progressed in this Virtue, but still have much to do. + } + else if (value < 3600) + { + valueDesc = 1052047; // Your journey through the path of this Virtue is going well. + } + else if (value < 4000) + { + valueDesc = 1052048; // You feel very close to achieving your next path in this Virtue. + } + else if (dots < 1) + { + valueDesc = 1052049; // You have achieved a path in this Virtue. + } + else if (dots < 9) + { + valueDesc = 1052047; // Your journey through the path of this Virtue is going well. + } + else if (dots < 10) + { + valueDesc = 1052048; // You feel very close to achieving your next path in this Virtue. + } + else + { + valueDesc = 1052050; // You have achieved the highest path in this Virtue. + } - break; - } - case 2: + AddHtmlLocalized(157, 73, 200, 40, 1051000 + (int)virtue); + AddHtmlLocalized(75, 95, 220, 140, description); + AddHtmlLocalized(70, 224, 229, 60, valueDesc); + + AddButton(65, 277, 1209, 1209, 1); + + AddButton(280, 43, 4014, 4014, 2); + + // This virtue is not yet defined. OR -click to learn more (opens webpage) + AddHtmlLocalized(83, 275, 400, 40, webPage == null ? 1052055 : 1052052); + } + + public override void OnResponse(NetState state, RelayInfo info) + { + switch (info.ButtonID) + { + case 1: + { + _beholder.SendGump(new VirtueInfoGump(_beholder, _virtue, _desc, _site)); + + if (_site != null) { - m_Beholder.SendGump(new VirtueStatusGump(m_Beholder)); - break; + state.SendLaunchBrowser(_site); // No message about web browser starting on OSI } - } + + break; + } + case 2: + { + _beholder.SendGump(new VirtueStatusGump(_beholder)); + break; + } } } } diff --git a/Projects/UOContent/Engines/Virtues/VirtueStatusGump.cs b/Projects/UOContent/Engines/Virtues/VirtueStatusGump.cs index 2375fa8a8..15dcea67c 100644 --- a/Projects/UOContent/Engines/Virtues/VirtueStatusGump.cs +++ b/Projects/UOContent/Engines/Virtues/VirtueStatusGump.cs @@ -1,137 +1,87 @@ using Server.Gumps; +using Server.Mobiles; using Server.Network; -namespace Server +namespace Server.Engines.Virtues; + +public class VirtueStatusGump : Gump { - public class VirtueStatusGump : Gump + private readonly PlayerMobile _beholder; + + public VirtueStatusGump(PlayerMobile beholder) : base(0, 0) { - private readonly Mobile m_Beholder; + _beholder = beholder; - public VirtueStatusGump(Mobile beholder) : base(0, 0) + AddPage(0); + + AddImage(30, 40, 2080); + AddImage(47, 77, 2081); + AddImage(47, 147, 2081); + AddImage(47, 217, 2081); + AddImage(47, 267, 2083); + AddImage(70, 213, 2091); + + AddPage(1); + + AddHtmlLocalized(140, 73, 200, 20, 1077972); // The Virtues + + AddHtmlLocalized(80, 100, 100, 40, 1051000); // Humility + AddHtmlLocalized(80, 129, 100, 40, 1051001); // Sacrifice + AddHtmlLocalized(80, 159, 100, 40, 1051002); // Compassion + AddHtmlLocalized(80, 189, 100, 40, 1051003); // Spirituality + AddHtmlLocalized(200, 100, 200, 40, 1051004); // Valor + AddHtmlLocalized(200, 129, 200, 40, 1051005); // Honor + AddHtmlLocalized(200, 159, 200, 40, 1051006); // Justice + AddHtmlLocalized(200, 189, 200, 40, 1051007); // Honesty + + AddHtmlLocalized(75, 224, 220, 60, 1052062); // Click on a blue gem to view your status in that virtue. + + AddButton(60, 100, 1210, 1210, 1); // Humility + AddButton(60, 129, 1210, 1210, 2); // Sacrifice + AddButton(60, 159, 1210, 1210, 3); // Compassion + AddButton(60, 189, 1210, 1210, 4); // Spirituality + AddButton(180, 100, 1210, 1210, 5); // Valor + AddButton(180, 129, 1210, 1210, 6); // Honor + AddButton(180, 159, 1210, 1210, 7); // Justice + AddButton(180, 189, 1210, 1210, 8); // Honesty + + AddButton(280, 43, 4014, 4014, 9); + } + + private static int GetVirtueDescription(VirtueName virtue) => + virtue switch { - m_Beholder = beholder; + VirtueName.Humility => 1052051, // Humility is perceiving one's place in the world, not according to one's own accomplishments, but according to the intrinsic value of all individuals. One gains Humility through Humility Hunts. + VirtueName.Sacrifice => 1052053, // Sacrifice is the courage to give of oneself in the name of love. One gains in Sacrifice by giving away their fame to certain monsters to save them from their eternal torment. + VirtueName.Compassion => 1053000, // Compassion is the nonjudgmental empathy for one's fellow creatures. You gain in Compassion by escorting NPCs and prisoners safely to their destinations. + VirtueName.Spirituality => 1052056, // Spirituality is the concern with one's inner being and how one deals with truth, love, and courage. One gains Spirituality by healing others. + VirtueName.Valor => 1054033, // Valor is the courage to take actions in support of one's convictions. You gain in Valor by slaying creatures spawned in regions controlled by Champions of Evil. + VirtueName.Honor => 1052058, // Honor is the courage to stand for truth, against any odds. Honor is gained by entering Honorable combat with thy foes. + VirtueName.Justice => 1052059, // Justice is the devotion to truth, tempered by love. Justice is gained by making those who would murder innocents pay for their crimes. + VirtueName.Honesty => 1052060, // Honesty is the scrupulous respect for truth, the willingness to never deceive oneself or another. + _ => 0 + }; - AddPage(0); - - AddImage(30, 40, 2080); - AddImage(47, 77, 2081); - AddImage(47, 147, 2081); - AddImage(47, 217, 2081); - AddImage(47, 267, 2083); - AddImage(70, 213, 2091); - - AddPage(1); - - AddHtml(140, 73, 200, 20, "The Virtues"); - - AddHtmlLocalized(80, 100, 100, 40, 1051000); // Humility - AddHtmlLocalized(80, 129, 100, 40, 1051001); // Sacrifice - AddHtmlLocalized(80, 159, 100, 40, 1051002); // Compassion - AddHtmlLocalized(80, 189, 100, 40, 1051003); // Spirituality - AddHtmlLocalized(200, 100, 200, 40, 1051004); // Valor - AddHtmlLocalized(200, 129, 200, 40, 1051005); // Honor - AddHtmlLocalized(200, 159, 200, 40, 1051006); // Justice - AddHtmlLocalized(200, 189, 200, 40, 1051007); // Honesty - - AddHtmlLocalized(75, 224, 220, 60, 1052062); // Click on a blue gem to view your status in that virtue. - - AddButton(60, 100, 1210, 1210, 1); - AddButton(60, 129, 1210, 1210, 2); - AddButton(60, 159, 1210, 1210, 3); - AddButton(60, 189, 1210, 1210, 4); - AddButton(180, 100, 1210, 1210, 5); - AddButton(180, 129, 1210, 1210, 6); - AddButton(180, 159, 1210, 1210, 7); - AddButton(180, 189, 1210, 1210, 8); - - AddButton(280, 43, 4014, 4014, 9); + public override void OnResponse(NetState state, RelayInfo info) + { + if (info.ButtonID == 9) + { + _beholder.SendGump(new VirtueGump(_beholder, _beholder)); + return; } - public override void OnResponse(NetState state, RelayInfo info) + if (info.ButtonID is < 1 or > (int)VirtueName.Honesty) { - switch (info.ButtonID) - { - case 1: - { - m_Beholder.SendGump(new VirtueInfoGump(m_Beholder, VirtueName.Humility, 1052051)); - break; - } - case 2: - { - m_Beholder.SendGump( - new VirtueInfoGump( - m_Beholder, - VirtueName.Sacrifice, - 1052053, - @"http://update.uo.com/design_389.html" - ) - ); - break; - } - case 3: - { - m_Beholder.SendGump( - new VirtueInfoGump( - m_Beholder, - VirtueName.Compassion, - 1053000, - @"http://update.uo.com/design_412.html" - ) - ); - break; - } - case 4: - { - m_Beholder.SendGump(new VirtueInfoGump(m_Beholder, VirtueName.Spirituality, 1052056)); - break; - } - case 5: - { - m_Beholder.SendGump( - new VirtueInfoGump( - m_Beholder, - VirtueName.Valor, - 1054033, - @"http://update.uo.com/design_427.html" - ) - ); - break; - } - case 6: - { - m_Beholder.SendGump( - new VirtueInfoGump( - m_Beholder, - VirtueName.Honor, - 1052058, - @"http://guide.uo.com/virtues_2.html" - ) - ); - break; - } - case 7: - { - m_Beholder.SendGump( - new VirtueInfoGump( - m_Beholder, - VirtueName.Justice, - 1052059, - @"http://update.uo.com/design_413.html" - ) - ); - break; - } - case 8: - { - m_Beholder.SendGump(new VirtueInfoGump(m_Beholder, VirtueName.Honesty, 1052060)); - break; - } - case 9: - { - m_Beholder.SendGump(new VirtueGump(m_Beholder, m_Beholder)); - break; - } - } + return; } + + var virtue = (VirtueName)(info.ButtonID - 1); + + _beholder.SendGump(new VirtueInfoGump( + _beholder, + virtue, + GetVirtueDescription(virtue), + @$"https://uo.com/wiki/ultima-online-wiki/gameplay/the-virtues/#{virtue.GetLowerCaseName()}" + )); } } diff --git a/Projects/UOContent/Engines/Virtues/VirtueSystem.cs b/Projects/UOContent/Engines/Virtues/VirtueSystem.cs new file mode 100644 index 000000000..ebc7d57a3 --- /dev/null +++ b/Projects/UOContent/Engines/Virtues/VirtueSystem.cs @@ -0,0 +1,375 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using Server.Collections; +using Server.Mobiles; + +namespace Server.Engines.Virtues; + +public enum VirtueLevel +{ + None, + Seeker, + Follower, + Knight +} + +[Flags] +public enum VirtueName +{ + Humility, + Sacrifice, + Compassion, + Spirituality, + Valor, + Honor, + Justice, + Honesty +} + +public static class VirtueSystem +{ + private static readonly Dictionary _playerVirtues = new(); + + private static readonly Timer _virtueTimer = new VirtueTimer(); + + private static void FixVirtue(Mobile m, int[] virtueValues) + { + if (m is not PlayerMobile pm) + { + return; + } + + var virtues = pm.Virtues; + for (var i = 0; i < virtueValues.Length; i++) + { + var val = virtueValues[i]; + if (val > 0) + { + virtues.SetValue(i, val); + } + } + } + + public static void Configure() + { + GenericPersistence.Register("Virtues", Serialize, Deserialize); + } + + public static void Initialize() + { + var migrations = Mobile.VirtueMigrations; + if (migrations?.Count > 0) + { + foreach (var (m, values) in migrations) + { + FixVirtue(m, values); + } + } + + _virtueTimer.Start(); + } + + private static void Serialize(IGenericWriter writer) + { + writer.WriteEncodedInt(0); // version + + writer.WriteEncodedInt(_playerVirtues.Count); + foreach (var (pm, virtues) in _playerVirtues) + { + writer.Write(pm); + virtues.Serialize(writer); + } + } + + private static void Deserialize(IGenericReader reader) + { + reader.ReadEncodedInt(); // version + + var contextCount = reader.ReadEncodedInt(); + for (var i = 0; i < contextCount; i++) + { + var player = reader.ReadEntity(); + var virtues = new VirtueContext(); + virtues.Deserialize(reader); + + if (virtues.IsUsed()) + { + _playerVirtues.Add(player, virtues); + } + } + } + + public static VirtueContext GetVirtues(this PlayerMobile from) => + _playerVirtues.TryGetValue(from, out var context) ? context : null; + + public static VirtueContext GetOrCreateVirtues(this PlayerMobile from) + { + ref VirtueContext context = ref CollectionsMarshal.GetValueRefOrAddDefault(_playerVirtues, from, out bool exists); + if (!exists) + { + context = new VirtueContext(); + } + + return context; + } + + public static bool IsHighestPath(PlayerMobile from, VirtueName virtue) => + from.GetVirtues()?.GetValue((int)virtue) >= GetMaxAmount(virtue); + + public static VirtueLevel GetLevel(Mobile from, VirtueName virtue) + { + var v = (from as PlayerMobile)?.GetVirtues()?.GetValue((int)virtue) ?? 0; + int vl; + + if (v < 4000) + { + vl = 0; + } + else if (v >= GetMaxAmount(virtue)) + { + vl = 3; + } + else + { + vl = (v + 9999) / 10000; + } + + return (VirtueLevel)vl; + } + + public static string GetName(this VirtueName virtue) => + virtue switch + { + VirtueName.Humility => "Humility", + VirtueName.Sacrifice => "Sacrifice", + VirtueName.Compassion => "Compassion", + VirtueName.Spirituality => "Spirituality", + VirtueName.Valor => "Valor", + VirtueName.Honor => "Honor", + VirtueName.Justice => "Justice", + VirtueName.Honesty => "Honesty", + _ => "" + }; + + public static string GetLowerCaseName(this VirtueName virtue) => + virtue switch + { + VirtueName.Humility => "humility", + VirtueName.Sacrifice => "sacrifice", + VirtueName.Compassion => "compassion", + VirtueName.Spirituality => "spirituality", + VirtueName.Valor => "valor", + VirtueName.Honor => "honor", + VirtueName.Justice => "justice", + VirtueName.Honesty => "honesty", + _ => "" + }; + + public static int GetMaxAmount(VirtueName virtue) => + virtue switch + { + VirtueName.Honor => 20000, + VirtueName.Sacrifice => 22000, + _ => 21000 + }; + + public static int GetGainedLocalizedMessage(VirtueName virtue) => + virtue switch + { + VirtueName.Sacrifice => 1054160, // You have gained in sacrifice. + VirtueName.Compassion => 1053002, // You have gained in compassion. + VirtueName.Spirituality => 1155832, // You have gained in Spirituality. + VirtueName.Valor => 1054030, // You have gained in Valor! + VirtueName.Honor => 1063225, // You have gained in Honor. + VirtueName.Justice => 1049363, // You have gained in Justice. + VirtueName.Humility => 1052070, // You have gained in Humility. + _ => 0 + }; + + public static int GetGainedAPathLocalizedMessage(VirtueName virtue) => + virtue switch + { + VirtueName.Sacrifice => 1052008, // You have gained a path in Sacrifice! + VirtueName.Spirituality => 1155833, // "You have gained a path in Spirituality!" (Why are there quotes?) + VirtueName.Valor => 1054032, // You have gained a path in Valor! + VirtueName.Honor => 1063226, // You have gained a path in Honor! + VirtueName.Justice => 1049367, // You have gained a path in Justice! + VirtueName.Humility => 1155811, // You have gained a path in Humility! + _ => 0 + }; + + public static int GetHightestPathLocalizedMessage(VirtueName virtue) => + virtue switch + { + VirtueName.Compassion => 1053003, // You have achieved the highest path of compassion and can no longer gain any further. + VirtueName.Spirituality => 1155831, // You cannot gain more Spirituality. + VirtueName.Valor => 1054031, // You have achieved the highest path in Valor and can no longer gain any further. + VirtueName.Honor => 1063228, // You cannot gain more Honor. + VirtueName.Justice => 1049534, // You cannot gain more Justice. + VirtueName.Humility => 1155808, // You cannot gain more Humility. + VirtueName.Honesty => 1153771, // You have achieved the highest path in Honesty and can no longer gain any further. + _ => 1052050, // You have achieved the highest path in this virtue. + }; + + public static bool Award(PlayerMobile from, VirtueName virtue, int amount, ref bool gainedPath) + { + var virtues = from.Virtues; + + var current = virtues.GetValue((int)virtue); + + var maxAmount = GetMaxAmount(virtue); + + if (current >= maxAmount) + { + return false; + } + + if (current + amount >= maxAmount) + { + amount = maxAmount - current; + } + + var oldLevel = GetLevel(from, virtue); + + virtues.SetValue((int)virtue, current + amount); + + gainedPath = GetLevel(from, virtue) != oldLevel; + + return true; + } + + public static bool Atrophy(PlayerMobile from, VirtueName virtue, int amount = 1) + { + var virtues = from.GetVirtues(); + if (virtues == null) + { + return false; + } + + var current = virtues.GetValue((int)virtue); + + if (current - amount >= 0) + { + virtues.SetValue((int)virtue, current - amount); + } + else + { + virtues.SetValue((int)virtue, 0); + } + + return current > 0; + } + + public static bool IsSeeker(PlayerMobile from, VirtueName virtue) => GetLevel(from, virtue) >= VirtueLevel.Seeker; + + public static bool IsFollower(PlayerMobile from, VirtueName virtue) => GetLevel(from, virtue) >= VirtueLevel.Follower; + + public static bool IsKnight(PlayerMobile from, VirtueName virtue) => GetLevel(from, virtue) >= VirtueLevel.Knight; + + public static void AwardVirtue(PlayerMobile pm, VirtueName virtue, int amount) + { + var virtues = pm.GetOrCreateVirtues(); + if (virtue == VirtueName.Compassion) + { + if (virtues.CompassionGains > 0 && Core.Now > virtues.NextCompassionDay) + { + virtues.NextCompassionDay = DateTime.MinValue; + virtues.CompassionGains = 0; + } + + if (virtues.CompassionGains >= 5) + { + pm.SendLocalizedMessage(1053004); // You must wait about a day before you can gain in compassion again. + return; + } + } + + var gainedPath = false; + var virtueName = virtue.GetName(); + + if (Award(pm, virtue, amount, ref gainedPath)) + { + if (gainedPath) + { + var gainedPathMessage = GetGainedAPathLocalizedMessage(virtue); + if (gainedPathMessage != 0) + { + pm.SendLocalizedMessage(gainedPathMessage); + } + else + { + pm.SendMessage($"You have gained a path in {virtueName}!"); + } + } + else + { + var gainMessage = GetGainedLocalizedMessage(virtue); + if (gainMessage != 0) + { + pm.SendLocalizedMessage(gainMessage); + } + else + { + pm.SendMessage($"You have gained in {virtueName}."); + } + } + + if (virtue == VirtueName.Compassion) + { + 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 + { + pm.SendLocalizedMessage(GetHightestPathLocalizedMessage(virtue)); + } + } + + public static void CheckAtrophies(this PlayerMobile pm) + { + SacrificeVirtue.CheckAtrophy(pm); + JusticeVirtue.CheckAtrophy(pm); + CompassionVirtue.CheckAtrophy(pm); + ValorVirtue.CheckAtrophy(pm); + } + + private class VirtueTimer : Timer + { + public VirtueTimer() : base(TimeSpan.FromMinutes(5.0), TimeSpan.FromMinutes(5.0)) + { + } + + protected override void OnTick() + { + if (_playerVirtues.Count == 0) + { + return; + } + + using var queue = PooledRefQueue.Create(); + + // This is not particularly efficient. If it gets too slow, then use a different architecture. + foreach (var (player, virtues) in _playerVirtues) + { + CheckAtrophies(player); + + if (!virtues.IsUsed()) + { + queue.Enqueue(player); + } + } + + while (queue.Count > 0) + { + _playerVirtues.Remove((PlayerMobile)queue.Dequeue()); + } + } + } +} diff --git a/Projects/UOContent/Gumps/HonorSelf.cs b/Projects/UOContent/Gumps/HonorSelf.cs index e76fa9172..9a60b2440 100644 --- a/Projects/UOContent/Gumps/HonorSelf.cs +++ b/Projects/UOContent/Gumps/HonorSelf.cs @@ -1,3 +1,4 @@ +using Server.Engines.Virtues; using Server.Mobiles; using Server.Network; diff --git a/Projects/UOContent/Gumps/ResurrectGump.cs b/Projects/UOContent/Gumps/ResurrectGump.cs index 1a5cecb71..86267020e 100644 --- a/Projects/UOContent/Gumps/ResurrectGump.cs +++ b/Projects/UOContent/Gumps/ResurrectGump.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using Server.Engines.Virtues; using Server.Misc; using Server.Mobiles; using Server.Network; @@ -171,7 +172,7 @@ namespace Server.Gumps if (m_Healer != null && from != m_Healer) { - var level = VirtueHelper.GetLevel(m_Healer, VirtueName.Compassion); + var level = VirtueSystem.GetLevel(m_Healer, VirtueName.Compassion); from.Hits = level switch { @@ -186,7 +187,7 @@ namespace Server.Gumps if (m_FromSacrifice && player != null) { - player.AvailableResurrects -= 1; + player.Virtues.AvailableResurrects -= 1; var pack = player.Backpack; var corpse = player.Corpse; diff --git a/Projects/UOContent/Items/Special/Solen Items/BallOfSummoning.cs b/Projects/UOContent/Items/Special/Solen Items/BallOfSummoning.cs index 22bb66795..3e367c040 100644 --- a/Projects/UOContent/Items/Special/Solen Items/BallOfSummoning.cs +++ b/Projects/UOContent/Items/Special/Solen Items/BallOfSummoning.cs @@ -4,7 +4,6 @@ using ModernUO.Serialization; using Server.ContextMenus; using Server.Engines.ConPVP; using Server.Mobiles; -using Server.Network; using Server.Regions; using Server.Spells; using Server.Spells.Ninjitsu; diff --git a/Projects/UOContent/Items/Weapons/BaseWeapon.cs b/Projects/UOContent/Items/Weapons/BaseWeapon.cs index 3b6315ade..044915d7f 100644 --- a/Projects/UOContent/Items/Weapons/BaseWeapon.cs +++ b/Projects/UOContent/Items/Weapons/BaseWeapon.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using Server.Collections; using Server.Engines.Craft; +using Server.Engines.Virtues; using Server.Ethics; using Server.Factions; using Server.Mobiles; @@ -1723,7 +1724,7 @@ namespace Server.Items if (attacker is PlayerMobile pmAttacker && !(Core.ML && defender is PlayerMobile)) { - if (pmAttacker.HonorActive && pmAttacker.InRange(defender, 1)) + if (pmAttacker.GetVirtues()?.HonorActive == true && pmAttacker.InRange(defender, 1)) { percentageBonus += 25; } diff --git a/Projects/UOContent/Migrations/Server.Engines.Virtues.VirtueContext.v0.json b/Projects/UOContent/Migrations/Server.Engines.Virtues.VirtueContext.v0.json new file mode 100644 index 000000000..f6555574b --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Engines.Virtues.VirtueContext.v0.json @@ -0,0 +1,119 @@ +{ + "version": 0, + "type": "Server.Engines.Virtues.VirtueContext", + "properties": [ + { + "name": "LastSacrificeGain", + "type": "System.DateTime", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "DeltaTime" + ] + }, + { + "name": "LastSacrificeLoss", + "type": "System.DateTime", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "DeltaTime" + ] + }, + { + "name": "AvailableResurrects", + "type": "int", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + }, + { + "name": "LastJusticeLoss", + "type": "System.DateTime", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "DeltaTime" + ] + }, + { + "name": "LastCompassionLoss", + "type": "System.DateTime", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "DeltaTime" + ] + }, + { + "name": "NextCompassionDay", + "type": "System.DateTime", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "DeltaTime" + ] + }, + { + "name": "CompassionGains", + "type": "int", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + }, + { + "name": "LastValorLoss", + "type": "System.DateTime", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "DeltaTime" + ] + }, + { + "name": "LastHonorUse", + "type": "System.DateTime", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "DeltaTime" + ] + }, + { + "name": "HonorActive", + "type": "bool", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + }, + { + "name": "JusticeProtection", + "type": "Server.Mobiles.PlayerMobile", + "usesSaveFlag": true, + "rule": "SerializableInterfaceMigrationRule" + }, + { + "name": "JusticeStatus", + "type": "Server.Engines.Virtues.JusticeProtectorStatus", + "usesSaveFlag": true, + "rule": "EnumMigrationRule" + }, + { + "name": "Values", + "type": "int[]", + "usesSaveFlag": true, + "rule": "ArrayMigrationRule", + "ruleArguments": [ + "int", + "PrimitiveTypeMigrationRule", + "" + ] + } + ] +} \ No newline at end of file diff --git a/Projects/UOContent/Misc/Poison.cs b/Projects/UOContent/Misc/Poison.cs index 57135718d..e9d11fc72 100644 --- a/Projects/UOContent/Misc/Poison.cs +++ b/Projects/UOContent/Misc/Poison.cs @@ -1,4 +1,5 @@ using System; +using Server.Engines.Virtues; using Server.Items; using Server.Mobiles; using Server.Spells; @@ -142,10 +143,7 @@ namespace Server From?.DoHarmful(m_Mobile, true); - if (m_Mobile is IHonorTarget honorTarget) - { - honorTarget.ReceivedHonorContext?.OnTargetPoisoned(); - } + (m_Mobile as IHonorTarget)?.ReceivedHonorContext?.OnTargetPoisoned(); AOS.Damage(m_Mobile, From, damage, 0, 0, 0, 100, 0); diff --git a/Projects/UOContent/Mobiles/AI/BaseAI.cs b/Projects/UOContent/Mobiles/AI/BaseAI.cs index c8b61fb73..0cae96dc3 100644 --- a/Projects/UOContent/Mobiles/AI/BaseAI.cs +++ b/Projects/UOContent/Mobiles/AI/BaseAI.cs @@ -4,6 +4,7 @@ using Server.ContextMenus; using Server.Engines.Quests; using Server.Engines.Quests.Necro; using Server.Engines.Spawners; +using Server.Engines.Virtues; using Server.Factions; using Server.Gumps; using Server.Items; @@ -2697,7 +2698,7 @@ public abstract class BaseAI } // Ignore players with activated honor - if (pm?.HonorActive == true && m_Mobile.Combatant != m) + if (m_Mobile.Combatant != m && pm?.GetVirtues()?.HonorActive == true) { continue; } diff --git a/Projects/UOContent/Mobiles/BaseCreature.cs b/Projects/UOContent/Mobiles/BaseCreature.cs index d2c75f505..4dc4c9303 100644 --- a/Projects/UOContent/Mobiles/BaseCreature.cs +++ b/Projects/UOContent/Mobiles/BaseCreature.cs @@ -8,6 +8,7 @@ using Server.Engines.MLQuests; using Server.Engines.Quests.Doom; using Server.Engines.Quests.Haven; using Server.Engines.Spawners; +using Server.Engines.Virtues; using Server.Ethics; using Server.Factions; using Server.Items; @@ -1309,7 +1310,7 @@ namespace Server.Mobiles return false; } - if (m is PlayerMobile mobile && mobile.HonorActive) + if ((m as PlayerMobile)?.GetVirtues()?.HonorActive == true) { return false; } diff --git a/Projects/UOContent/Mobiles/PlayerMobile.cs b/Projects/UOContent/Mobiles/PlayerMobile.cs index d370d277b..b684c180f 100644 --- a/Projects/UOContent/Mobiles/PlayerMobile.cs +++ b/Projects/UOContent/Mobiles/PlayerMobile.cs @@ -12,6 +12,7 @@ using Server.Engines.MLQuests.Gumps; using Server.Engines.PartySystem; using Server.Engines.PlayerMurderSystem; using Server.Engines.Quests; +using Server.Engines.Virtues; using Server.Ethics; using Server.Factions; using Server.Guilds; @@ -204,8 +205,6 @@ namespace Server.Mobiles BOBFilter = new BOBFilter(); m_GameTime = TimeSpan.Zero; - - JusticeProtectors = new List(); m_GuildRank = RankDefinition.Lowest; ChampionTitles = new ChampionTitleInfo(); @@ -672,33 +671,6 @@ namespace Server.Mobiles public bool WaitingForEnemy { get; set; } - public DateTime LastSacrificeGain { get; set; } - - public DateTime LastSacrificeLoss { get; set; } - - [CommandProperty(AccessLevel.GameMaster)] - public int AvailableResurrects { get; set; } - - public DateTime LastJusticeLoss { get; set; } - - public List JusticeProtectors { get; set; } - - public DateTime LastCompassionLoss { get; set; } - - [CommandProperty(AccessLevel.GameMaster)] - public DateTime NextCompassionDay { get; set; } - - [CommandProperty(AccessLevel.GameMaster)] - public int CompassionGains { get; set; } - - public DateTime LastValorLoss { get; set; } - - public DateTime LastHonorLoss { get; set; } - - public DateTime LastHonorUse { get; set; } - - public bool HonorActive { get; set; } - public HonorContext SentHonorContext { get; set; } [CommandProperty(AccessLevel.GameMaster)] @@ -744,6 +716,9 @@ namespace Server.Mobiles [CommandProperty(AccessLevel.GameMaster)] public int KnownRecipes => m_AcquiredRecipes?.Count ?? 0; + [CommandProperty(AccessLevel.Counselor, canModify: true)] + public VirtueContext Virtues => this.GetOrCreateVirtues(); + public HonorContext ReceivedHonorContext { get; set; } public QuestArrow QuestArrow @@ -1924,7 +1899,7 @@ namespace Server.Mobiles } } - if (JusticeProtectors.Count > 0) + if (JusticeVirtue.IsProtected(this)) { list.Add(new CallbackEntry(6157, CancelProtection)); } @@ -1940,12 +1915,8 @@ namespace Server.Mobiles if (ns?.ExtendedStatus == true) { - list.Add( - new CallbackEntry( - RefuseTrades ? 1154112 : 1154113, - ToggleTrades - ) - ); // Allow Trades / Refuse Trades + // Allow Trades / Refuse Trades + list.Add(new CallbackEntry(RefuseTrades ? 1154112 : 1154113, ToggleTrades)); } } } @@ -1990,23 +1961,16 @@ namespace Server.Mobiles private void CancelProtection() { - for (var i = 0; i < JusticeProtectors.Count; ++i) + if (JusticeVirtue.CancelProtection(this, out var prot)) { - var prot = JusticeProtectors[i]; - var args = $"{Name}\t{prot.Name}"; - prot.SendLocalizedMessage( - 1049371, - args - ); // The protective relationship between ~1_PLAYER1~ and ~2_PLAYER2~ has been ended. - SendLocalizedMessage( - 1049371, - args - ); // The protective relationship between ~1_PLAYER1~ and ~2_PLAYER2~ has been ended. - } + // The protective relationship between ~1_PLAYER1~ and ~2_PLAYER2~ has been ended. + prot.SendLocalizedMessage(1049371, args); - JusticeProtectors.Clear(); + // The protective relationship between ~1_PLAYER1~ and ~2_PLAYER2~ has been ended. + SendLocalizedMessage(1049371, args); + } } private void ToggleTrades() @@ -2497,10 +2461,8 @@ namespace Server.Mobiles if (Banker.Withdraw(this, cost)) { item.PaidInsurance = true; - SendLocalizedMessage( - 1060398, - cost.ToString() - ); // ~1_AMOUNT~ gold has been withdrawn from your bank box. + // ~1_AMOUNT~ gold has been withdrawn from your bank box. + SendLocalizedMessage(1060398, cost.ToString()); } else { @@ -2516,9 +2478,9 @@ namespace Server.Mobiles item.Insured = false; } - if (m_InsuranceAward != null && Banker.Deposit(m_InsuranceAward, 300) && m_InsuranceAward is PlayerMobile pm) + if (m_InsuranceAward is PlayerMobile insurancePm && Banker.Deposit(m_InsuranceAward, 300)) { - pm.m_InsuranceBonus += 300; + insurancePm.m_InsuranceBonus += 300; } return true; @@ -2628,7 +2590,7 @@ namespace Server.Mobiles m = bc.GetMaster(); } - if (m != this && m is PlayerMobile) + if (m != this && m is PlayerMobile pm) { var gainedPath = false; @@ -2638,7 +2600,7 @@ namespace Server.Mobiles pointsToGain *= 5; pointsToGain += (int)Math.Pow(Skills.Total / 250.0, 2); - if (VirtueHelper.Award(m, VirtueName.Justice, pointsToGain, ref gainedPath)) + if (VirtueSystem.Award(pm, VirtueName.Justice, pointsToGain, ref gainedPath)) { if (gainedPath) { @@ -2657,15 +2619,10 @@ namespace Server.Mobiles } } - if (m_InsuranceAward is PlayerMobile pm) + if (m_InsuranceAward is PlayerMobile insurancePm && insurancePm.m_InsuranceBonus > 0) { - if (pm.m_InsuranceBonus > 0) - { - pm.SendLocalizedMessage( - 1060397, - pm.m_InsuranceBonus.ToString() - ); // ~1_AMOUNT~ gold has been deposited into your bank box. - } + // ~1_AMOUNT~ gold has been deposited into your bank box. + insurancePm.SendLocalizedMessage(1060397, insurancePm.m_InsuranceBonus.ToString()); } var killer = FindMostRecentDamager(true); @@ -2894,8 +2851,11 @@ namespace Server.Mobiles base.Deserialize(reader); var version = reader.ReadInt(); + VirtueContext virtues = version < 32 ? Virtues : null; + switch (version) { + case 32: // Removes virtue properties case 31: // Removed Short/Long Term Elapse case 30: { @@ -2960,7 +2920,10 @@ namespace Server.Mobiles } case 24: { - LastHonorLoss = reader.ReadDeltaTime(); + if (version < 32) + { + reader.ReadDeltaTime(); // LastHonorLoss - Not even used + } goto case 23; } case 23: @@ -2970,7 +2933,11 @@ namespace Server.Mobiles } case 22: { - LastValorLoss = reader.ReadDateTime(); + if (version < 32) + { + virtues.LastValorLoss = reader.ReadDateTime(); + } + goto case 21; } case 21: @@ -3044,16 +3011,21 @@ namespace Server.Mobiles } case 15: { - LastCompassionLoss = reader.ReadDeltaTime(); + if (version < 32) + { + virtues.LastCompassionLoss = reader.ReadDeltaTime(); + } goto case 14; } case 14: { - CompassionGains = reader.ReadEncodedInt(); - - if (CompassionGains > 0) + if (version < 32) { - NextCompassionDay = reader.ReadDeltaTime(); + virtues.CompassionGains = reader.ReadEncodedInt(); + if (virtues.CompassionGains > 0) + { + virtues.NextCompassionDay = reader.ReadDeltaTime(); + } } goto case 13; @@ -3126,15 +3098,31 @@ namespace Server.Mobiles } case 4: { - LastJusticeLoss = reader.ReadDeltaTime(); - JusticeProtectors = reader.ReadEntityList(); + if (version < 32) + { + virtues.LastJusticeLoss = reader.ReadDeltaTime(); + var protectors = reader.ReadEntityList(); // Always a list of 0, or 1 + if (protectors.Count > 0) + { + var protector = protectors[0]; + if (protector != null) + { + JusticeVirtue.AddProtection(protector, this); + } + } + } + goto case 3; } case 3: { - LastSacrificeGain = reader.ReadDeltaTime(); - LastSacrificeLoss = reader.ReadDeltaTime(); - AvailableResurrects = reader.ReadInt(); + if (version < 32) + { + virtues.LastSacrificeGain = reader.ReadDeltaTime(); + virtues.LastSacrificeLoss = reader.ReadDeltaTime(); + virtues.AvailableResurrects = reader.ReadInt(); + } + goto case 2; } case 2: @@ -3167,7 +3155,6 @@ namespace Server.Mobiles } PermaFlags ??= new List(); - JusticeProtectors ??= new List(); BOBFilter ??= new BOBFilter(); // Default to member if going from older version to new version (only time it should be null) @@ -3209,7 +3196,7 @@ namespace Server.Mobiles { base.Serialize(writer); - writer.Write(31); // version + writer.Write(32); // version if (Stabled == null) { @@ -3264,11 +3251,8 @@ namespace Server.Mobiles } } - writer.WriteDeltaTime(LastHonorLoss); - ChampionTitleInfo.Serialize(writer, ChampionTitles); - writer.Write(LastValorLoss); writer.WriteEncodedInt(ToTItemsTurnedIn); writer.Write(ToTTotalMonsterFame); // This ain't going to be a small #. @@ -3301,15 +3285,6 @@ namespace Server.Mobiles writer.WriteEncodedInt(Profession); - writer.WriteDeltaTime(LastCompassionLoss); - - writer.WriteEncodedInt(CompassionGains); - - if (CompassionGains > 0) - { - writer.WriteDeltaTime(NextCompassionDay); - } - BOBFilter.Serialize(writer); var useMods = m_HairModID != -1 || m_BeardModID != -1; @@ -3337,14 +3312,6 @@ namespace Server.Mobiles writer.Write(NextSmithBulkOrder); - writer.WriteDeltaTime(LastJusticeLoss); - JusticeProtectors.Tidy(); - writer.Write(JusticeProtectors); - - writer.WriteDeltaTime(LastSacrificeGain); - writer.WriteDeltaTime(LastSacrificeLoss); - writer.Write(AvailableResurrects); - writer.Write((int)Flags); writer.Write(GameTime); @@ -3356,27 +3323,13 @@ namespace Server.Mobiles public override void AfterSerialize() { base.AfterSerialize(); - CheckAtrophies(); } - public bool ShouldAtrophy() - { - var sacrifice = SacrificeVirtue.ShouldAtrophy(this); - var justice = JusticeVirtue.ShouldAtrophy(this); - var compassion = CompassionVirtue.ShouldAtrophy(this); - var valor = ValorVirtue.ShouldAtrophy(this); - var titles = ChampionTitleInfo.ShouldAtrophy(this); - - return sacrifice || justice || compassion || valor || titles; - } + public bool ShouldAtrophy() => ChampionTitleInfo.ShouldAtrophy(this); public void CheckAtrophies() { - SacrificeVirtue.CheckAtrophy(this); - JusticeVirtue.CheckAtrophy(this); - CompassionVirtue.CheckAtrophy(this); - ValorVirtue.CheckAtrophy(this); ChampionTitleInfo.CheckAtrophy(this); } diff --git a/Projects/UOContent/Mobiles/Special/BaseChampion.cs b/Projects/UOContent/Mobiles/Special/BaseChampion.cs index 92359bb84..e93565f07 100644 --- a/Projects/UOContent/Mobiles/Special/BaseChampion.cs +++ b/Projects/UOContent/Mobiles/Special/BaseChampion.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using Server.Engines.CannedEvil; +using Server.Engines.Virtues; using Server.Items; namespace Server.Mobiles @@ -111,16 +112,16 @@ namespace Server.Mobiles { var m = toGive[i]; - if (m is not PlayerMobile) + if (m is not PlayerMobile pm) { continue; } var gainedPath = false; - var pointsToGain = 800; + const int pointsToGain = 800; - if (VirtueHelper.Award(m, VirtueName.Valor, pointsToGain, ref gainedPath)) + if (VirtueSystem.Award(pm, VirtueName.Valor, pointsToGain, ref gainedPath)) { if (gainedPath) { @@ -178,41 +179,39 @@ namespace Server.Mobiles return; } - for (var j = 0; j < pm.JusticeProtectors.Count; ++j) + var prot = JusticeVirtue.GetProtector(pm); + + if (prot == null || prot.Map != pm.Map || prot.Kills >= 5 || prot.Criminal || + !JusticeVirtue.CheckMapRegion(pm, prot)) { - var prot = pm.JusticeProtectors[j]; + return; + } - if (prot.Map != pm.Map || prot.Kills >= 5 || prot.Criminal || !JusticeVirtue.CheckMapRegion(pm, prot)) + var chance = VirtueSystem.GetLevel(prot, VirtueName.Justice) switch + { + VirtueLevel.Seeker => 60, + VirtueLevel.Follower => 80, + VirtueLevel.Knight => 100, + _ => 0 + }; + + if (chance > 0 && chance > Utility.Random(100)) + { + var powerScroll = new PowerScroll(ps.Skill, ps.Value); + + prot.SendLocalizedMessage(1049368); // You have been rewarded for your dedication to Justice! + + if (!Core.SE || prot.Alive) { - continue; + prot.AddToBackpack(powerScroll); } - - var chance = VirtueHelper.GetLevel(prot, VirtueName.Justice) switch + else if (prot.Corpse?.Deleted == false) { - VirtueLevel.Seeker => 60, - VirtueLevel.Follower => 80, - VirtueLevel.Knight => 100, - _ => 0 - }; - - if (chance > Utility.Random(100)) + prot.Corpse.DropItem(powerScroll); + } + else { - var powerScroll = new PowerScroll(ps.Skill, ps.Value); - - prot.SendLocalizedMessage(1049368); // You have been rewarded for your dedication to Justice! - - if (!Core.SE || prot.Alive) - { - prot.AddToBackpack(powerScroll); - } - else if (prot.Corpse?.Deleted == false) - { - prot.Corpse.DropItem(powerScroll); - } - else - { - prot.AddToBackpack(powerScroll); - } + prot.AddToBackpack(powerScroll); } } } diff --git a/Projects/UOContent/Mobiles/Special/Harrower.cs b/Projects/UOContent/Mobiles/Special/Harrower.cs index 547fa6822..bb5bfbf9c 100644 --- a/Projects/UOContent/Mobiles/Special/Harrower.cs +++ b/Projects/UOContent/Mobiles/Special/Harrower.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using Server.Engines.Virtues; using Server.Items; using Server.Spells; @@ -283,32 +284,31 @@ namespace Server.Mobiles m.SendLocalizedMessage(1049524); // You have received a scroll of power! m.AddToBackpack(new StatCapScroll(225 + level)); - if (m is PlayerMobile pm) + if (m is not PlayerMobile pm) { - for (var j = 0; j < pm.JusticeProtectors.Count; ++j) - { - var prot = pm.JusticeProtectors[j]; + continue; + } - if (prot.Map != pm.Map || prot.Kills >= 5 || prot.Criminal || - !JusticeVirtue.CheckMapRegion(pm, prot)) - { - continue; - } + var prot = JusticeVirtue.GetProtector(pm); - var chance = VirtueHelper.GetLevel(prot, VirtueName.Justice) switch - { - VirtueLevel.Seeker => 60, - VirtueLevel.Follower => 80, - VirtueLevel.Knight => 100, - _ => 0 - }; + if (prot == null || prot.Map != pm.Map || prot.Kills >= 5 || prot.Criminal || + !JusticeVirtue.CheckMapRegion(pm, prot)) + { + continue; + } - if (chance > Utility.Random(100)) - { - prot.SendLocalizedMessage(1049368); // You have been rewarded for your dedication to Justice! - prot.AddToBackpack(new StatCapScroll(225 + level)); - } - } + var chance = VirtueSystem.GetLevel(prot, VirtueName.Justice) switch + { + VirtueLevel.Seeker => 60, + VirtueLevel.Follower => 80, + VirtueLevel.Knight => 100, + _ => 0 + }; + + if (chance > 0 && chance > Utility.Random(100)) + { + prot.SendLocalizedMessage(1049368); // You have been rewarded for your dedication to Justice! + prot.AddToBackpack(new StatCapScroll(225 + level)); } } } diff --git a/Projects/UOContent/Mobiles/Townfolk/BaseEscortable.cs b/Projects/UOContent/Mobiles/Townfolk/BaseEscortable.cs index 39a8d3175..ec336af9d 100644 --- a/Projects/UOContent/Mobiles/Townfolk/BaseEscortable.cs +++ b/Projects/UOContent/Mobiles/Townfolk/BaseEscortable.cs @@ -5,6 +5,7 @@ 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.Misc; using Server.Regions; @@ -515,19 +516,19 @@ namespace Server.Mobiles if (escorter is PlayerMobile pm) { - if (pm.CompassionGains > 0 && Core.Now > pm.NextCompassionDay) + var virtues = pm.GetOrCreateVirtues(); + if (virtues.CompassionGains > 0 && Core.Now > virtues.NextCompassionDay) { - pm.NextCompassionDay = DateTime.MinValue; - pm.CompassionGains = 0; + virtues.NextCompassionDay = DateTime.MinValue; + virtues.CompassionGains = 0; } - if (pm.CompassionGains >= 5) // have already gained 5 times in one day, can gain no more + if (virtues.CompassionGains >= 5) // have already gained 5 times in one day, can gain no more { - pm.SendLocalizedMessage( - 1053004 - ); // You must wait about a day before you can gain in compassion again. + // You must wait about a day before you can gain in compassion again. + pm.SendLocalizedMessage(1053004); } - else if (VirtueHelper.Award(pm, VirtueName.Compassion, IsPrisoner ? 400 : 200, ref gainedPath)) + else if (VirtueSystem.Award(pm, VirtueName.Compassion, IsPrisoner ? 400 : 200, ref gainedPath)) { if (gainedPath) { @@ -538,22 +539,19 @@ namespace Server.Mobiles pm.SendLocalizedMessage(1053002); // You have gained in compassion. } - pm.NextCompassionDay = - Core.Now + TimeSpan.FromDays(1.0); // in one day CompassionGains gets reset to 0 - ++pm.CompassionGains; + // in one day CompassionGains gets reset to 0 + virtues.NextCompassionDay = Core.Now + TimeSpan.FromDays(1.0); - if (pm.CompassionGains >= 5) + if (++virtues.CompassionGains >= 5) { - pm.SendLocalizedMessage( - 1053004 - ); // You must wait about a day before you can gain in compassion again. + // You must wait about a day before you can gain in compassion again. + pm.SendLocalizedMessage(1053004); } } else { - pm.SendLocalizedMessage( - 1053003 - ); // You have achieved the highest path of compassion and can no longer gain any further. + // You have achieved the highest path of compassion and can no longer gain any further. + pm.SendLocalizedMessage(1053003); } } diff --git a/Projects/UOContent/Multis/Boats/BaseBoat.cs b/Projects/UOContent/Multis/Boats/BaseBoat.cs index 8f84b3990..eb8156a92 100644 --- a/Projects/UOContent/Multis/Boats/BaseBoat.cs +++ b/Projects/UOContent/Multis/Boats/BaseBoat.cs @@ -297,14 +297,6 @@ namespace Server.Multis } } - public override bool ShouldExecuteAfterSerialize => !m_Decaying && CheckDecay(); - - public override void AfterSerialize() - { - base.AfterSerialize(); - CheckDecay(); - } - public override void Serialize(IGenericWriter writer) { base.Serialize(writer); @@ -399,6 +391,8 @@ namespace Server.Multis } Boats.Add(this); + + Timer.DelayCall(() => CheckDecay()); } public void RemoveKeys(Mobile m) diff --git a/Projects/UOContent/Network/Packets/IncomingPlayerPackets.cs b/Projects/UOContent/Network/Packets/IncomingPlayerPackets.cs index cedd1a9d8..642b3eb85 100644 --- a/Projects/UOContent/Network/Packets/IncomingPlayerPackets.cs +++ b/Projects/UOContent/Network/Packets/IncomingPlayerPackets.cs @@ -16,8 +16,10 @@ using System.Diagnostics; using Microsoft.Toolkit.HighPerformance; using Server.Diagnostics; +using Server.Engines.Virtues; using Server.Exceptions; using Server.Gumps; +using Server.Mobiles; namespace Server.Network; @@ -184,7 +186,7 @@ public static class IncomingPlayerPackets { var virtueID = Utility.ToInt32(command) - 1; - EventSink.InvokeVirtueMacroRequest(from, virtueID); + VirtueGump.RequestVirtueMacro((PlayerMobile)from, virtueID); break; } @@ -460,11 +462,11 @@ public static class IncomingPlayerPackets if (buttonID == 1 && switchCount > 0) { - var beheld = World.FindMobile((Serial)reader.ReadUInt32()); + var beheld = World.FindEntity((Serial)reader.ReadUInt32()); if (beheld != null) { - EventSink.InvokeVirtueGumpRequest(state.Mobile, beheld); + VirtueGump.RequestVirtueGump((PlayerMobile)state.Mobile, beheld); } } else @@ -473,7 +475,7 @@ public static class IncomingPlayerPackets if (beheld != null) { - EventSink.InvokeVirtueItemRequest(state.Mobile, beheld, buttonID); + VirtueGump.RequestVirtueItem((PlayerMobile)state.Mobile, beheld, buttonID); } } } diff --git a/Projects/UOContent/Skills/AnimalTaming.cs b/Projects/UOContent/Skills/AnimalTaming.cs index 674062459..fd604f944 100644 --- a/Projects/UOContent/Skills/AnimalTaming.cs +++ b/Projects/UOContent/Skills/AnimalTaming.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using Server.Engines.Virtues; using Server.Factions; using Server.Mobiles; using Server.Spells; @@ -227,7 +228,7 @@ namespace Server.SkillHandlers creature.AIObject?.DoMove(creature.Direction); if (from is PlayerMobile pm && - !(pm.HonorActive || + !(pm.GetVirtues()?.HonorActive == true || TransformationSpellHelper.UnderTransformation(pm, typeof(EtherealVoyageSpell)))) { creature.Combatant = pm; @@ -237,16 +238,11 @@ namespace Server.SkillHandlers { m_BeingTamed.Add(creature); - from.LocalOverheadMessage( - MessageType.Emote, - 0x59, - 1010597 - ); // You start to tame the creature. - from.NonlocalOverheadMessage( - MessageType.Emote, - 0x59, - 1010598 - ); // *begins taming a creature.* + // You start to tame the creature. + from.LocalOverheadMessage(MessageType.Emote, 0x59, 1010597); + + // *begins taming a creature.* + from.NonlocalOverheadMessage(MessageType.Emote, 0x59, 1010598); new InternalTimer(from, creature, Utility.Random(3, 2)).Start(); diff --git a/Projects/UOContent/Skills/AntiMacroSystem.cs b/Projects/UOContent/Skills/AntiMacroSystem.cs index 736226a59..b1299868f 100644 --- a/Projects/UOContent/Skills/AntiMacroSystem.cs +++ b/Projects/UOContent/Skills/AntiMacroSystem.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.IO; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text.Json.Serialization; using Server.Collections;