fix: Cleans up entity persistence. Generalizes Mobiles/Items/Guilds (#1528)
### Summary - [X] Fixed a bug where entity persistence was serialized out of order, causing world corruption. - [X] Fixed LastSerialized not being utilized properly and dangling references still becoming an issue. - [X] Added a new `GenericEntityPersistence<T>` type to encapsulate `ISerializable` serialization. - [X] Removing the custom logic and moved Items, Mobiles, Guilds, and Accounts to GenericEntityPersistence. - [X] Changed serialization to use the singleton pattern to reduce calling methods from stored variables.
This commit is contained in:
parent
e0225c6e59
commit
f2de2fbb77
34 changed files with 721 additions and 970 deletions
|
|
@ -1,139 +1,95 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Xml;
|
||||
using Server.Logging;
|
||||
|
||||
namespace Server.Accounting
|
||||
namespace Server.Accounting;
|
||||
|
||||
public class Accounts : GenericEntityPersistence<Account>
|
||||
{
|
||||
public static class Accounts
|
||||
private static readonly ILogger logger = LogFactory.GetLogger(typeof(Accounts));
|
||||
|
||||
private static readonly Dictionary<string, Account> _accountsByName = new(32, StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
public static int Count => _accountsByName.Count;
|
||||
|
||||
private static Accounts _accountsPersistence;
|
||||
|
||||
public static Serial NewAccount => _accountsPersistence.NewEntity;
|
||||
|
||||
public static void Configure()
|
||||
{
|
||||
private static readonly ILogger logger = LogFactory.GetLogger(typeof(Accounts));
|
||||
_accountsPersistence = new Accounts();
|
||||
}
|
||||
|
||||
private static readonly Dictionary<string, Account> _accountsByName = new(32, StringComparer.OrdinalIgnoreCase);
|
||||
private static Dictionary<Serial, Account> _accountsById = new(32);
|
||||
private static Serial _lastAccount;
|
||||
public Accounts() : base("Accounts", 3, 0x1, 0x7FFFFFFF)
|
||||
{
|
||||
}
|
||||
|
||||
private static void OutOfMemory(string message) => throw new OutOfMemoryException(message);
|
||||
public static IEnumerable<IAccount> GetAccounts() => _accountsByName.Values;
|
||||
|
||||
public static Serial NewAccount
|
||||
public static Account GetAccount(string username)
|
||||
{
|
||||
_accountsByName.TryGetValue(username, out var a);
|
||||
return a;
|
||||
}
|
||||
|
||||
public static void Add(Account a)
|
||||
{
|
||||
_accountsByName[a.Username] = a;
|
||||
_accountsPersistence.AddEntity(a);
|
||||
}
|
||||
|
||||
public static void Remove(Account a)
|
||||
{
|
||||
_accountsByName.Remove(a.Username);
|
||||
_accountsPersistence.RemoveEntity(a);
|
||||
}
|
||||
|
||||
public override void Deserialize(string path, Dictionary<ulong, string> typesDb)
|
||||
{
|
||||
var filePath = Path.Combine(path, "Accounts", "accounts.xml");
|
||||
|
||||
// Backward Compatibility
|
||||
if (File.Exists(filePath))
|
||||
{
|
||||
get
|
||||
{
|
||||
var last = _lastAccount;
|
||||
|
||||
for (uint i = 0; i < uint.MaxValue; i++)
|
||||
{
|
||||
last++;
|
||||
|
||||
if (FindAccount(last) == null)
|
||||
{
|
||||
return _lastAccount = last;
|
||||
}
|
||||
}
|
||||
|
||||
OutOfMemory("No serials left to allocate for accounts");
|
||||
return Serial.MinusOne;
|
||||
}
|
||||
DeserializeXml(filePath);
|
||||
return;
|
||||
}
|
||||
|
||||
public static int Count => _accountsByName.Count;
|
||||
base.Deserialize(path, typesDb);
|
||||
|
||||
public static void Configure() =>
|
||||
Persistence.Register("Accounts", Serialize, WriteSnapshot, Deserialize);
|
||||
|
||||
internal static void Serialize()
|
||||
{
|
||||
EntityPersistence.SaveEntities(
|
||||
_accountsById.Values,
|
||||
account => ((ISerializable)account).Serialize(World.SerializedTypes)
|
||||
);
|
||||
}
|
||||
|
||||
internal static void WriteSnapshot(string basePath)
|
||||
{
|
||||
IIndexInfo<Serial> indexInfo = new EntityTypeIndex("Accounts");
|
||||
EntityPersistence.WriteEntities(indexInfo, _accountsById, basePath,World.SerializedTypes, out _);
|
||||
}
|
||||
|
||||
public static IEnumerable<IAccount> GetAccounts() => _accountsByName.Values;
|
||||
|
||||
public static Account GetAccount(string username)
|
||||
{
|
||||
_accountsByName.TryGetValue(username, out var a);
|
||||
return a;
|
||||
}
|
||||
|
||||
public static void Add(Account a)
|
||||
foreach (var a in EntitiesBySerial.Values)
|
||||
{
|
||||
_accountsByName[a.Username] = a;
|
||||
_accountsById[a.Serial] = a;
|
||||
}
|
||||
|
||||
public static void Remove(Account a)
|
||||
{
|
||||
_accountsByName.Remove(a.Username);
|
||||
_accountsById.Remove(a.Serial);
|
||||
}
|
||||
|
||||
internal static void Deserialize(string path, Dictionary<ulong, string> typesDb)
|
||||
{
|
||||
var filePath = Path.Combine(path, "Accounts", "accounts.xml");
|
||||
|
||||
// Backward Compatibility
|
||||
if (File.Exists(filePath))
|
||||
{
|
||||
DeserializeXml(filePath);
|
||||
return;
|
||||
}
|
||||
|
||||
IIndexInfo<Serial> indexInfo = new EntityTypeIndex("Accounts");
|
||||
|
||||
_accountsById = EntityPersistence.LoadIndex(path, indexInfo, typesDb, out List<EntitySpan<Account>> accounts);
|
||||
|
||||
if (_accountsById.Count > 0)
|
||||
{
|
||||
_lastAccount = _accountsById.Keys.Max();
|
||||
}
|
||||
|
||||
EntityPersistence.LoadData(path, indexInfo, typesDb, accounts);
|
||||
|
||||
foreach (var a in _accountsById.Values)
|
||||
{
|
||||
_accountsByName[a.Username] = a;
|
||||
}
|
||||
}
|
||||
|
||||
private static void DeserializeXml(string filePath)
|
||||
{
|
||||
var doc = new XmlDocument();
|
||||
doc.Load(filePath);
|
||||
|
||||
var root = doc["accounts"];
|
||||
|
||||
if (root == null)
|
||||
{
|
||||
throw new FileLoadException("Unable to load xml file");
|
||||
}
|
||||
|
||||
foreach (XmlElement account in root.GetElementsByTagName("account"))
|
||||
{
|
||||
try
|
||||
{
|
||||
new Account(account);
|
||||
}
|
||||
catch
|
||||
{
|
||||
logger.Warning("Account instance load failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static IAccount FindAccount(Serial serial)
|
||||
{
|
||||
_accountsById.TryGetValue(serial, out var account);
|
||||
return account;
|
||||
}
|
||||
}
|
||||
|
||||
private static void DeserializeXml(string filePath)
|
||||
{
|
||||
var doc = new XmlDocument();
|
||||
doc.Load(filePath);
|
||||
|
||||
var root = doc["accounts"];
|
||||
|
||||
if (root == null)
|
||||
{
|
||||
throw new FileLoadException("Unable to load xml file");
|
||||
}
|
||||
|
||||
foreach (XmlElement account in root.GetElementsByTagName("account"))
|
||||
{
|
||||
try
|
||||
{
|
||||
new Account(account);
|
||||
}
|
||||
catch
|
||||
{
|
||||
logger.Warning("Account instance load failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static IAccount FindAccount(Serial serial) => _accountsPersistence.FindEntity<Account>(serial);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,21 @@
|
|||
namespace Server.Engines.BulkOrders;
|
||||
|
||||
public class BOBEntries : GenericEntitySerialization<IBOBEntry>
|
||||
public class BOBEntries : GenericEntityPersistence<IBOBEntry>
|
||||
{
|
||||
private static BOBEntries _bobEntriesPersistence;
|
||||
|
||||
public static void Configure()
|
||||
{
|
||||
Configure("BOBEntries");
|
||||
_bobEntriesPersistence = new BOBEntries();
|
||||
}
|
||||
|
||||
public BOBEntries() : base("BOBEntries", 3, 0x1, 0x7FFFFFFF)
|
||||
{
|
||||
}
|
||||
|
||||
public static Serial NewBOBEntry => _bobEntriesPersistence.NewEntity;
|
||||
|
||||
public static void Add(IBOBEntry entity) => _bobEntriesPersistence.AddEntity(entity);
|
||||
|
||||
public static void Remove(IBOBEntry entity) => _bobEntriesPersistence.AddEntity(entity);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,14 +32,14 @@ public abstract partial class BaseBOBEntry : IBOBEntry
|
|||
|
||||
public BaseBOBEntry()
|
||||
{
|
||||
Serial = BOBEntries.NewEntity;
|
||||
BOBEntries.AddEntity(this);
|
||||
Serial = BOBEntries.NewBOBEntry;
|
||||
BOBEntries.Add(this);
|
||||
}
|
||||
|
||||
public virtual void Delete()
|
||||
{
|
||||
Deleted = true;
|
||||
BOBEntries.RemoveEntity(this);
|
||||
BOBEntries.Remove(this);
|
||||
}
|
||||
|
||||
public abstract Item Reconstruct();
|
||||
|
|
|
|||
|
|
@ -245,7 +245,7 @@ namespace Server.Engines.BulkOrders
|
|||
{
|
||||
case 0:
|
||||
{
|
||||
var largeEntry = new BOBLargeEntry(BOBEntries.NewEntity);
|
||||
var largeEntry = new BOBLargeEntry(BOBEntries.NewBOBEntry);
|
||||
largeEntry.Deserialize(reader);
|
||||
|
||||
Entries.Add(largeEntry);
|
||||
|
|
@ -253,7 +253,7 @@ namespace Server.Engines.BulkOrders
|
|||
}
|
||||
case 1:
|
||||
{
|
||||
var smallEntry = new BOBSmallEntry(BOBEntries.NewEntity);
|
||||
var smallEntry = new BOBSmallEntry(BOBEntries.NewBOBEntry);
|
||||
smallEntry.Deserialize(reader);
|
||||
Entries.Add(smallEntry);
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -6,8 +6,10 @@ using Server.Mobiles;
|
|||
|
||||
namespace Server.Engines.CannedEvil;
|
||||
|
||||
public static class ChampionTitleSystem
|
||||
public class ChampionTitleSystem : GenericPersistence
|
||||
{
|
||||
private static ChampionTitleSystem _championTitlePersistence;
|
||||
|
||||
// All of the players with murders
|
||||
private static readonly Dictionary<PlayerMobile, ChampionTitleContext> _championTitleContexts = new();
|
||||
|
||||
|
|
@ -15,7 +17,7 @@ public static class ChampionTitleSystem
|
|||
|
||||
public static void Configure()
|
||||
{
|
||||
GenericPersistence.Register("ChampionTitles", Serialize, Deserialize);
|
||||
_championTitlePersistence = new ChampionTitleSystem();
|
||||
}
|
||||
|
||||
public static void Initialize()
|
||||
|
|
@ -33,7 +35,11 @@ public static class ChampionTitleSystem
|
|||
}
|
||||
}
|
||||
|
||||
private static void Deserialize(IGenericReader reader)
|
||||
public ChampionTitleSystem() : base("ChampionTitles", 10)
|
||||
{
|
||||
}
|
||||
|
||||
public override void Deserialize(IGenericReader reader)
|
||||
{
|
||||
var version = reader.ReadEncodedInt();
|
||||
|
||||
|
|
@ -47,7 +53,7 @@ public static class ChampionTitleSystem
|
|||
}
|
||||
}
|
||||
|
||||
private static void Serialize(IGenericWriter writer)
|
||||
public override void Serialize(IGenericWriter writer)
|
||||
{
|
||||
writer.WriteEncodedInt(0); // version
|
||||
|
||||
|
|
@ -59,10 +65,10 @@ public static class ChampionTitleSystem
|
|||
}
|
||||
}
|
||||
|
||||
public static bool GetChampionTitleContext(this PlayerMobile player, out ChampionTitleContext context) =>
|
||||
public static bool GetChampionTitleContext(PlayerMobile player, out ChampionTitleContext context) =>
|
||||
_championTitleContexts.TryGetValue(player, out context);
|
||||
|
||||
public static ChampionTitleContext GetOrCreateChampionTitleContext(this PlayerMobile player)
|
||||
public static ChampionTitleContext GetOrCreateChampionTitleContext(PlayerMobile player)
|
||||
{
|
||||
ref var context = ref CollectionsMarshal.GetValueRefOrAddDefault(_championTitleContexts, player, out var exists);
|
||||
if (!exists)
|
||||
|
|
@ -76,7 +82,7 @@ public static class ChampionTitleSystem
|
|||
// Called when killing a harrower. Will give a minimum of 1 point.
|
||||
public static void AwardHarrowerTitle(PlayerMobile pm)
|
||||
{
|
||||
var context = pm.GetOrCreateChampionTitleContext();
|
||||
var context = GetOrCreateChampionTitleContext(pm);
|
||||
|
||||
var count = 1;
|
||||
for (var i = 0; i < ChampionSpawnInfo.Table.Length; i++)
|
||||
|
|
@ -91,9 +97,9 @@ public static class ChampionTitleSystem
|
|||
context.Harrower = Math.Max(count, context.Harrower); // Harrower titles never decay.
|
||||
}
|
||||
|
||||
public static int GetChampionTitleLabel(this PlayerMobile player)
|
||||
public static int GetChampionTitleLabel(PlayerMobile player)
|
||||
{
|
||||
if (!player.GetChampionTitleContext(out var context))
|
||||
if (!GetChampionTitleContext(player, out var context))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,8 +2,9 @@ using System;
|
|||
|
||||
namespace Server.Factions;
|
||||
|
||||
public static class FactionSystem
|
||||
public class FactionSystem : GenericPersistence
|
||||
{
|
||||
private static FactionSystem _factionSystem;
|
||||
public static bool Enabled { get; private set; }
|
||||
|
||||
public static void Configure()
|
||||
|
|
@ -12,10 +13,14 @@ public static class FactionSystem
|
|||
|
||||
if (Enabled)
|
||||
{
|
||||
GenericPersistence.Register("Factions", Serialize, Deserialize);
|
||||
_factionSystem = new();
|
||||
}
|
||||
}
|
||||
|
||||
public FactionSystem() : base("Factions", 10)
|
||||
{
|
||||
}
|
||||
|
||||
// This does not do the actual work of removing faction stuff, only turns off the persistence.
|
||||
public static void Disable()
|
||||
{
|
||||
|
|
@ -24,7 +29,7 @@ public static class FactionSystem
|
|||
return;
|
||||
}
|
||||
|
||||
Persistence.Unregister("Factions");
|
||||
_factionSystem.Unregister();
|
||||
Enabled = false;
|
||||
ServerConfiguration.SetSetting("factions.enabled", false);
|
||||
}
|
||||
|
|
@ -37,12 +42,12 @@ public static class FactionSystem
|
|||
return;
|
||||
}
|
||||
|
||||
GenericPersistence.Register("Factions", Serialize, Deserialize);
|
||||
_factionSystem.Register();
|
||||
Enabled = true;
|
||||
ServerConfiguration.SetSetting("factions.enabled", true);
|
||||
}
|
||||
|
||||
private static void Serialize(IGenericWriter writer)
|
||||
public override void Serialize(IGenericWriter writer)
|
||||
{
|
||||
writer.WriteEncodedInt(0); // version
|
||||
|
||||
|
|
@ -59,7 +64,7 @@ public static class FactionSystem
|
|||
}
|
||||
}
|
||||
|
||||
private static void Deserialize(IGenericReader reader)
|
||||
public override void Deserialize(IGenericReader reader)
|
||||
{
|
||||
var version = reader.ReadEncodedInt();
|
||||
|
||||
|
|
|
|||
|
|
@ -8,8 +8,10 @@ using Server.Mobiles;
|
|||
|
||||
namespace Server.Engines.PlayerMurderSystem;
|
||||
|
||||
public static class PlayerMurderSystem
|
||||
public class PlayerMurderSystem : GenericPersistence
|
||||
{
|
||||
private static PlayerMurderSystem _playerMurderPersistence;
|
||||
|
||||
private static readonly ILogger logger = LogFactory.GetLogger(typeof(PlayerMurderSystem));
|
||||
|
||||
// All of the players with murders
|
||||
|
|
@ -28,10 +30,10 @@ public static class PlayerMurderSystem
|
|||
|
||||
public static void Configure()
|
||||
{
|
||||
GenericPersistence.Register("PlayerMurders", Serialize, Deserialize);
|
||||
|
||||
_shortTermMurderDuration = ServerConfiguration.GetOrUpdateSetting("murderSystem.shortTermMurderDuration", TimeSpan.FromHours(8));
|
||||
_longTermMurderDuration = ServerConfiguration.GetOrUpdateSetting("murderSystem.longTermMurderDuration", TimeSpan.FromHours(40));
|
||||
|
||||
_playerMurderPersistence = new PlayerMurderSystem();
|
||||
}
|
||||
|
||||
public static void Initialize()
|
||||
|
|
@ -49,6 +51,10 @@ public static class PlayerMurderSystem
|
|||
}
|
||||
}
|
||||
|
||||
public PlayerMurderSystem() : base("PlayerMurders", 10)
|
||||
{
|
||||
}
|
||||
|
||||
// Only used for migrations!
|
||||
public static void MigrateContext(PlayerMobile player, TimeSpan shortTerm, TimeSpan longTerm)
|
||||
{
|
||||
|
|
@ -61,7 +67,7 @@ public static class PlayerMurderSystem
|
|||
return;
|
||||
}
|
||||
|
||||
var context = player.GetOrCreateMurderContext();
|
||||
var context = GetOrCreateMurderContext(player);
|
||||
|
||||
// We make a big assumption that by the time this is called, the Mobile/PlayerMobile info is deserialized
|
||||
if (Mobile.MurderMigrations?.TryGetValue(player, out var shortTermMurders) == true)
|
||||
|
|
@ -100,7 +106,7 @@ public static class PlayerMurderSystem
|
|||
}
|
||||
}
|
||||
|
||||
private static void Deserialize(IGenericReader reader)
|
||||
public override void Deserialize(IGenericReader reader)
|
||||
{
|
||||
var version = reader.ReadEncodedInt();
|
||||
|
||||
|
|
@ -114,7 +120,7 @@ public static class PlayerMurderSystem
|
|||
}
|
||||
}
|
||||
|
||||
private static void Serialize(IGenericWriter writer)
|
||||
public override void Serialize(IGenericWriter writer)
|
||||
{
|
||||
writer.WriteEncodedInt(0); // version
|
||||
|
||||
|
|
@ -126,10 +132,10 @@ public static class PlayerMurderSystem
|
|||
}
|
||||
}
|
||||
|
||||
public static bool GetMurderContext(this PlayerMobile player, out MurderContext context) =>
|
||||
public static bool GetMurderContext(PlayerMobile player, out MurderContext context) =>
|
||||
_murderContexts.TryGetValue(player, out context);
|
||||
|
||||
public static MurderContext GetOrCreateMurderContext(this PlayerMobile player)
|
||||
public static MurderContext GetOrCreateMurderContext(PlayerMobile player)
|
||||
{
|
||||
ref var context = ref CollectionsMarshal.GetValueRefOrAddDefault(_murderContexts, player, out var exists);
|
||||
if (!exists)
|
||||
|
|
@ -142,14 +148,14 @@ public static class PlayerMurderSystem
|
|||
|
||||
public static void ManuallySetShortTermMurders(PlayerMobile player, int shortTermMurders)
|
||||
{
|
||||
var context = player.GetOrCreateMurderContext();
|
||||
var context = GetOrCreateMurderContext(player);
|
||||
context.ShortTermMurders = shortTermMurders;
|
||||
UpdateMurderContext(context);
|
||||
}
|
||||
|
||||
public static void OnPlayerMurder(PlayerMobile player)
|
||||
{
|
||||
var context = player.GetOrCreateMurderContext();
|
||||
var context = GetOrCreateMurderContext(player);
|
||||
context.ShortTermMurders++;
|
||||
player.Kills++;
|
||||
|
||||
|
|
|
|||
|
|
@ -6,10 +6,11 @@ using Server.Utilities;
|
|||
|
||||
namespace Server.Engines.Stealables;
|
||||
|
||||
public static class StealableArtifacts
|
||||
public class StealableArtifacts : GenericPersistence
|
||||
{
|
||||
private static readonly ILogger logger = LogFactory.GetLogger(typeof(StealableArtifacts));
|
||||
|
||||
private static StealableArtifacts _stealableArtifactsPersistence;
|
||||
private static bool _enabled;
|
||||
private static Type[] _typesOfEntries;
|
||||
private static StealableInstance[] _artifacts;
|
||||
|
|
@ -19,7 +20,11 @@ public static class StealableArtifacts
|
|||
|
||||
public static void Configure()
|
||||
{
|
||||
GenericPersistence.Register("StealableArtifacts", Serialize, Deserialize);
|
||||
_stealableArtifactsPersistence = new StealableArtifacts();
|
||||
}
|
||||
|
||||
public StealableArtifacts() : base("StealableArtifacts", 10)
|
||||
{
|
||||
}
|
||||
|
||||
private static void RemoveStealableArtifacts()
|
||||
|
|
@ -242,7 +247,7 @@ public static class StealableArtifacts
|
|||
}
|
||||
}
|
||||
|
||||
private static void Serialize(IGenericWriter writer)
|
||||
public override void Serialize(IGenericWriter writer)
|
||||
{
|
||||
writer.WriteEncodedInt(1); // version
|
||||
|
||||
|
|
@ -262,7 +267,7 @@ public static class StealableArtifacts
|
|||
}
|
||||
}
|
||||
|
||||
private static void Deserialize(IGenericReader reader)
|
||||
public override void Deserialize(IGenericReader reader)
|
||||
{
|
||||
var version = reader.ReadEncodedInt();
|
||||
|
||||
|
|
@ -441,7 +446,7 @@ public static class StealableArtifacts
|
|||
{
|
||||
base.Deserialize(reader);
|
||||
|
||||
StealableArtifacts.Deserialize(reader);
|
||||
_stealableArtifactsPersistence.Deserialize(reader);
|
||||
|
||||
Timer.DelayCall(Delete);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ public static class CompassionVirtue
|
|||
|
||||
public static void CheckAtrophy(PlayerMobile pm)
|
||||
{
|
||||
var virtues = pm.GetVirtues();
|
||||
var virtues = VirtueSystem.GetVirtues(pm);
|
||||
if (virtues?.Compassion > 0 && CanAtrophy(virtues))
|
||||
{
|
||||
if (VirtueSystem.Atrophy(pm, VirtueName.Compassion, LossAmount))
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ public static class HonorVirtue
|
|||
|
||||
private static void EmbraceHonor(PlayerMobile pm)
|
||||
{
|
||||
var virtues = pm.GetVirtues();
|
||||
var virtues = VirtueSystem.GetVirtues(pm);
|
||||
|
||||
if (virtues?.HonorActive == true)
|
||||
{
|
||||
|
|
@ -73,7 +73,7 @@ public static class HonorVirtue
|
|||
public static void ActivateEmbrace(PlayerMobile pm)
|
||||
{
|
||||
var duration = GetHonorDuration(pm);
|
||||
var virtues = pm.GetOrCreateVirtues();
|
||||
var virtues = VirtueSystem.GetOrCreateVirtues(pm);
|
||||
|
||||
int usedPoints = virtues.Honor switch
|
||||
{
|
||||
|
|
@ -92,7 +92,7 @@ public static class HonorVirtue
|
|||
(m) =>
|
||||
{
|
||||
// We get the virtues again, in case it was deleted/dereferenced
|
||||
var v = m.GetOrCreateVirtues();
|
||||
var v = VirtueSystem.GetOrCreateVirtues(m);
|
||||
v.HonorActive = false;
|
||||
v.LastHonorUse = Core.Now;
|
||||
m.SendLocalizedMessage(1063236); // You no longer embrace your honor
|
||||
|
|
|
|||
|
|
@ -186,7 +186,7 @@ public class HonorContext
|
|||
Source.Mana += restore;
|
||||
}
|
||||
|
||||
if (Source.GetVirtues().Honor > targetFame)
|
||||
if (VirtueSystem.GetVirtues(Source).Honor > targetFame)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,23 +32,23 @@ public class JusticeVirtue
|
|||
}
|
||||
|
||||
public static bool IsProtected(PlayerMobile pm) =>
|
||||
pm.GetVirtues() is { JusticeStatus: JusticeProtectorStatus.Protected, JusticeProtection: not null };
|
||||
VirtueSystem.GetVirtues(pm) is { JusticeStatus: JusticeProtectorStatus.Protected, JusticeProtection: not null };
|
||||
|
||||
public static PlayerMobile GetProtector(PlayerMobile pm) =>
|
||||
pm.GetVirtues() is { JusticeStatus: JusticeProtectorStatus.Protected } virtues ? virtues.JusticeProtection : null;
|
||||
VirtueSystem.GetVirtues(pm) is { JusticeStatus: JusticeProtectorStatus.Protected } virtues ? virtues.JusticeProtection : null;
|
||||
|
||||
public static PlayerMobile GetProtected(PlayerMobile pm) =>
|
||||
pm.GetVirtues() is { JusticeStatus: JusticeProtectorStatus.Protector } virtues ? virtues.JusticeProtection : null;
|
||||
VirtueSystem.GetVirtues(pm) is { JusticeStatus: JusticeProtectorStatus.Protector } virtues ? virtues.JusticeProtection : null;
|
||||
|
||||
public static void CancelProtection(PlayerMobile pm)
|
||||
{
|
||||
if (pm.GetVirtues() is { JusticeStatus: not JusticeProtectorStatus.None } virtues)
|
||||
if (VirtueSystem.GetVirtues(pm) is { JusticeStatus: not JusticeProtectorStatus.None } virtues)
|
||||
{
|
||||
var protector = virtues.JusticeProtection;
|
||||
virtues.JusticeProtection = null;
|
||||
virtues.JusticeStatus = JusticeProtectorStatus.None;
|
||||
|
||||
virtues = protector?.GetVirtues();
|
||||
virtues = VirtueSystem.GetVirtues(protector);
|
||||
if (virtues != null)
|
||||
{
|
||||
virtues.JusticeProtection = null;
|
||||
|
|
@ -59,13 +59,13 @@ public class JusticeVirtue
|
|||
|
||||
public static bool CancelProtection(PlayerMobile pm, out PlayerMobile protector)
|
||||
{
|
||||
if (pm.GetVirtues() is { JusticeStatus: not JusticeProtectorStatus.None } virtues)
|
||||
if (VirtueSystem.GetVirtues(pm) is { JusticeStatus: not JusticeProtectorStatus.None } virtues)
|
||||
{
|
||||
protector = virtues.JusticeProtection;
|
||||
virtues.JusticeProtection = null;
|
||||
virtues.JusticeStatus = JusticeProtectorStatus.None;
|
||||
|
||||
virtues = protector?.GetVirtues();
|
||||
virtues = VirtueSystem.GetVirtues(protector);
|
||||
if (virtues != null)
|
||||
{
|
||||
virtues.JusticeProtection = null;
|
||||
|
|
@ -255,7 +255,7 @@ public class JusticeVirtue
|
|||
|
||||
public static void CheckAtrophy(PlayerMobile pm)
|
||||
{
|
||||
var virtues = pm.GetVirtues();
|
||||
var virtues = VirtueSystem.GetVirtues(pm);
|
||||
if (virtues?.Justice > 0 && CanAtrophy(virtues))
|
||||
{
|
||||
if (VirtueSystem.Atrophy(pm, VirtueName.Justice, LossAmount))
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ public static class SacrificeVirtue
|
|||
|
||||
public static void CheckAtrophy(PlayerMobile pm)
|
||||
{
|
||||
var virtues = pm.GetVirtues();
|
||||
var virtues = VirtueSystem.GetVirtues(pm);
|
||||
if (virtues?.Sacrifice > 0 && CanAtrophy(virtues))
|
||||
{
|
||||
if (VirtueSystem.Atrophy(pm, VirtueName.Sacrifice, LossAmount))
|
||||
|
|
@ -67,7 +67,7 @@ public static class SacrificeVirtue
|
|||
}
|
||||
else
|
||||
{
|
||||
var virtues = from.GetVirtues();
|
||||
var virtues = VirtueSystem.GetVirtues(from);
|
||||
if (virtues?.AvailableResurrects > 0)
|
||||
{
|
||||
/*
|
||||
|
|
@ -124,7 +124,7 @@ public static class SacrificeVirtue
|
|||
}
|
||||
else
|
||||
{
|
||||
var virtues = from.GetOrCreateVirtues();
|
||||
var virtues = VirtueSystem.GetOrCreateVirtues(from);
|
||||
if (!CanGain(virtues))
|
||||
{
|
||||
from.SendLocalizedMessage(1052016); // You must wait approximately one day before sacrificing again.
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ public static class ValorVirtue
|
|||
|
||||
public static void CheckAtrophy(PlayerMobile pm)
|
||||
{
|
||||
var virtues = pm.GetVirtues();
|
||||
var virtues = VirtueSystem.GetVirtues(pm);
|
||||
if (virtues?.Valor > 0 && CanAtrophy(virtues))
|
||||
{
|
||||
if (VirtueSystem.Atrophy(pm, VirtueName.Valor, LossAmount))
|
||||
|
|
@ -90,7 +90,7 @@ public static class ValorVirtue
|
|||
}
|
||||
}
|
||||
|
||||
if (from.GetVirtues()?.GetValue((int)VirtueName.Valor) >= needed)
|
||||
if (VirtueSystem.GetVirtues(from)?.GetValue((int)VirtueName.Valor) >= needed)
|
||||
{
|
||||
VirtueSystem.Atrophy(from, VirtueName.Valor, consumed);
|
||||
// Your challenge is heard by the Champion of this region! Beware its wrath!
|
||||
|
|
|
|||
|
|
@ -113,7 +113,7 @@ public class VirtueGump : Gump
|
|||
|
||||
private int GetHueFor(int index)
|
||||
{
|
||||
var value = _beheld.GetVirtues()?.GetValue(index) ?? 0;
|
||||
var value = VirtueSystem.GetVirtues((_beheld))?.GetValue(index) ?? 0;
|
||||
|
||||
if (value < 4000)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ public class VirtueInfoGump : Gump
|
|||
_desc = description;
|
||||
_site = webPage;
|
||||
|
||||
var value = beholder.GetVirtues()?.GetValue((int)virtue) ?? 0;
|
||||
var value = VirtueSystem.GetVirtues(beholder)?.GetValue((int)virtue) ?? 0;
|
||||
|
||||
AddPage(0);
|
||||
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ public class VirtueStatusGump : Gump
|
|||
_beholder,
|
||||
virtue,
|
||||
GetVirtueDescription(virtue),
|
||||
@$"https://uo.com/wiki/ultima-online-wiki/gameplay/the-virtues/#{virtue.GetLowerCaseName()}"
|
||||
@$"https://uo.com/wiki/ultima-online-wiki/gameplay/the-virtues/#{VirtueSystem.GetLowerCaseName(virtue)}"
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,12 +28,23 @@ public enum VirtueName
|
|||
Honesty
|
||||
}
|
||||
|
||||
public static class VirtueSystem
|
||||
public class VirtueSystem : GenericPersistence
|
||||
{
|
||||
private static readonly ILogger logger = LogFactory.GetLogger(typeof(VirtueSystem));
|
||||
|
||||
private static readonly Dictionary<PlayerMobile, VirtueContext> _playerVirtues = new();
|
||||
|
||||
private static VirtueSystem _virtueSystemPersistence;
|
||||
|
||||
public static void Configure()
|
||||
{
|
||||
_virtueSystemPersistence = new VirtueSystem();
|
||||
}
|
||||
|
||||
public VirtueSystem() : base("Virtues", 10)
|
||||
{
|
||||
}
|
||||
|
||||
private static void FixVirtue(Mobile m, int[] virtueValues)
|
||||
{
|
||||
if (m is not PlayerMobile pm)
|
||||
|
|
@ -52,11 +63,6 @@ public static class VirtueSystem
|
|||
}
|
||||
}
|
||||
|
||||
public static void Configure()
|
||||
{
|
||||
GenericPersistence.Register("Virtues", Serialize, Deserialize);
|
||||
}
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
var migrations = Mobile.VirtueMigrations;
|
||||
|
|
@ -69,7 +75,7 @@ public static class VirtueSystem
|
|||
}
|
||||
}
|
||||
|
||||
private static void Serialize(IGenericWriter writer)
|
||||
public override void Serialize(IGenericWriter writer)
|
||||
{
|
||||
writer.WriteEncodedInt(0); // version
|
||||
|
||||
|
|
@ -81,7 +87,7 @@ public static class VirtueSystem
|
|||
}
|
||||
}
|
||||
|
||||
private static void Deserialize(IGenericReader reader)
|
||||
public override void Deserialize(IGenericReader reader)
|
||||
{
|
||||
reader.ReadEncodedInt(); // version
|
||||
|
||||
|
|
@ -99,10 +105,10 @@ public static class VirtueSystem
|
|||
}
|
||||
}
|
||||
|
||||
public static VirtueContext GetVirtues(this PlayerMobile from) =>
|
||||
public static VirtueContext GetVirtues(PlayerMobile from) =>
|
||||
_playerVirtues.TryGetValue(from, out var context) ? context : null;
|
||||
|
||||
public static VirtueContext GetOrCreateVirtues(this PlayerMobile from)
|
||||
public static VirtueContext GetOrCreateVirtues(PlayerMobile from)
|
||||
{
|
||||
ref VirtueContext context = ref CollectionsMarshal.GetValueRefOrAddDefault(_playerVirtues, from, out bool exists);
|
||||
if (!exists)
|
||||
|
|
@ -114,11 +120,11 @@ public static class VirtueSystem
|
|||
}
|
||||
|
||||
public static bool IsHighestPath(PlayerMobile from, VirtueName virtue) =>
|
||||
from.GetVirtues()?.GetValue((int)virtue) >= GetMaxAmount(virtue);
|
||||
GetVirtues(from)?.GetValue((int)virtue) >= GetMaxAmount(virtue);
|
||||
|
||||
public static VirtueLevel GetLevel(Mobile from, VirtueName virtue)
|
||||
{
|
||||
var v = (from as PlayerMobile)?.GetVirtues()?.GetValue((int)virtue) ?? 0;
|
||||
var v = GetVirtues(from as PlayerMobile)?.GetValue((int)virtue) ?? 0;
|
||||
int vl;
|
||||
|
||||
if (v < 4000)
|
||||
|
|
@ -137,7 +143,7 @@ public static class VirtueSystem
|
|||
return (VirtueLevel)vl;
|
||||
}
|
||||
|
||||
public static string GetName(this VirtueName virtue) =>
|
||||
public static string GetName(VirtueName virtue) =>
|
||||
virtue switch
|
||||
{
|
||||
VirtueName.Humility => "Humility",
|
||||
|
|
@ -151,7 +157,7 @@ public static class VirtueSystem
|
|||
_ => ""
|
||||
};
|
||||
|
||||
public static string GetLowerCaseName(this VirtueName virtue) =>
|
||||
public static string GetLowerCaseName(VirtueName virtue) =>
|
||||
virtue switch
|
||||
{
|
||||
VirtueName.Humility => "humility",
|
||||
|
|
@ -240,7 +246,7 @@ public static class VirtueSystem
|
|||
|
||||
public static bool Atrophy(PlayerMobile from, VirtueName virtue, int amount = 1)
|
||||
{
|
||||
var virtues = from.GetVirtues();
|
||||
var virtues = GetVirtues(from);
|
||||
if (virtues == null)
|
||||
{
|
||||
return false;
|
||||
|
|
@ -268,7 +274,7 @@ public static class VirtueSystem
|
|||
|
||||
public static void AwardVirtue(PlayerMobile pm, VirtueName virtue, int amount)
|
||||
{
|
||||
var virtues = pm.GetOrCreateVirtues();
|
||||
var virtues = GetOrCreateVirtues(pm);
|
||||
if (virtue == VirtueName.Compassion)
|
||||
{
|
||||
if (virtues.CompassionGains > 0 && Core.Now > virtues.NextCompassionDay)
|
||||
|
|
@ -285,7 +291,7 @@ public static class VirtueSystem
|
|||
}
|
||||
|
||||
var gainedPath = false;
|
||||
var virtueName = virtue.GetName();
|
||||
var virtueName = GetName(virtue);
|
||||
|
||||
if (Award(pm, virtue, amount, ref gainedPath))
|
||||
{
|
||||
|
|
@ -331,7 +337,7 @@ public static class VirtueSystem
|
|||
}
|
||||
}
|
||||
|
||||
public static void CheckAtrophies(this PlayerMobile pm)
|
||||
public static void CheckAtrophies(PlayerMobile pm)
|
||||
{
|
||||
SacrificeVirtue.CheckAtrophy(pm);
|
||||
JusticeVirtue.CheckAtrophy(pm);
|
||||
|
|
|
|||
|
|
@ -4,16 +4,21 @@ using Server.Mobiles;
|
|||
|
||||
namespace Server.Items;
|
||||
|
||||
public static class DisguisePersistence
|
||||
public class DisguisePersistence : GenericPersistence
|
||||
{
|
||||
private static DisguisePersistence _disguisePersistence;
|
||||
public static Dictionary<Mobile, Timer> Timers { get; } = new();
|
||||
|
||||
public static void Configure()
|
||||
{
|
||||
GenericPersistence.Register("Disguises", Serialize, Deserialize);
|
||||
_disguisePersistence = new DisguisePersistence();
|
||||
}
|
||||
|
||||
private static void Deserialize(IGenericReader reader)
|
||||
public DisguisePersistence() : base("Disguises", 10)
|
||||
{
|
||||
}
|
||||
|
||||
public override void Deserialize(IGenericReader reader)
|
||||
{
|
||||
var count = reader.ReadEncodedInt();
|
||||
for (var i = 0; i < count; ++i)
|
||||
|
|
@ -24,7 +29,7 @@ public static class DisguisePersistence
|
|||
}
|
||||
}
|
||||
|
||||
private static void Serialize(IGenericWriter writer)
|
||||
public override void Serialize(IGenericWriter writer)
|
||||
{
|
||||
writer.WriteEncodedInt(Timers.Count);
|
||||
foreach (var (m, timer) in Timers)
|
||||
|
|
|
|||
|
|
@ -1857,7 +1857,7 @@ public abstract partial class BaseWeapon : Item, IWeapon, IFactionItem, ICraftab
|
|||
|
||||
if (attacker is PlayerMobile pmAttacker && !(Core.ML && defender is PlayerMobile))
|
||||
{
|
||||
if (pmAttacker.GetVirtues()?.HonorActive == true && pmAttacker.InRange(defender, 1))
|
||||
if (VirtueSystem.GetVirtues(pmAttacker)?.HonorActive == true && pmAttacker.InRange(defender, 1))
|
||||
{
|
||||
percentageBonus += 25;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ public enum DFAlgorithm
|
|||
PainSpike
|
||||
}
|
||||
|
||||
public static class StaminaSystem
|
||||
public class StaminaSystem : GenericPersistence
|
||||
{
|
||||
private static readonly ILogger logger = LogFactory.GetLogger(typeof(StaminaSystem));
|
||||
|
||||
|
|
@ -44,11 +44,13 @@ public static class StaminaSystem
|
|||
AdditionalLossWhenBelow = ServerConfiguration.GetOrUpdateSetting("stamina.additionalLossWhenBelow", 0.10);
|
||||
EnableMountStamina = ServerConfiguration.GetOrUpdateSetting("stamina.enableMountStamina", true);
|
||||
UseMountStaminaOnlyWhenOverloaded = ServerConfiguration.GetSetting("stamina.useMountStaminaOnlyWhenOverloaded", Core.SA);
|
||||
|
||||
GenericPersistence.Register("StaminaSystem", Serialize, Deserialize);
|
||||
}
|
||||
|
||||
private static void Serialize(IGenericWriter writer)
|
||||
public StaminaSystem() : base("StaminaSystem", 10)
|
||||
{
|
||||
}
|
||||
|
||||
public override void Serialize(IGenericWriter writer)
|
||||
{
|
||||
writer.WriteEncodedInt(0); // version
|
||||
|
||||
|
|
@ -60,7 +62,7 @@ public static class StaminaSystem
|
|||
}
|
||||
}
|
||||
|
||||
private static void Deserialize(IGenericReader reader)
|
||||
public override void Deserialize(IGenericReader reader)
|
||||
{
|
||||
var version = reader.ReadEncodedInt();
|
||||
|
||||
|
|
|
|||
|
|
@ -321,7 +321,7 @@ namespace Server.Misc
|
|||
|
||||
if (beheld is PlayerMobile mobile && mobile.DisplayChampionTitle)
|
||||
{
|
||||
var titleLabel = mobile.GetChampionTitleLabel();
|
||||
var titleLabel = ChampionTitleSystem.GetChampionTitleLabel(mobile);
|
||||
if (titleLabel > 0)
|
||||
{
|
||||
// Should this be translated to the receivers language? Prefix titles aren't?
|
||||
|
|
|
|||
|
|
@ -2702,7 +2702,7 @@ public abstract class BaseAI
|
|||
}
|
||||
|
||||
// Ignore players with activated honor
|
||||
if (m_Mobile.Combatant != m && pm?.GetVirtues()?.HonorActive == true)
|
||||
if (m_Mobile.Combatant != m && VirtueSystem.GetVirtues(pm)?.HonorActive == true)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1288,7 +1288,7 @@ namespace Server.Mobiles
|
|||
return false;
|
||||
}
|
||||
|
||||
if ((m as PlayerMobile)?.GetVirtues()?.HonorActive == true)
|
||||
if (VirtueSystem.GetVirtues(m as PlayerMobile)?.HonorActive == true)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -697,30 +697,32 @@ namespace Server.Mobiles
|
|||
}
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster, canModify: true)]
|
||||
public ChampionTitleContext ChampionTitles => this.GetOrCreateChampionTitleContext();
|
||||
public ChampionTitleContext ChampionTitles => ChampionTitleSystem.GetOrCreateChampionTitleContext(this);
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public int ShortTermMurders
|
||||
{
|
||||
get => this.GetMurderContext(out var context) ? context.ShortTermMurders : 0;
|
||||
get => PlayerMurderSystem.GetMurderContext(this, out var context) ? context.ShortTermMurders : 0;
|
||||
set => PlayerMurderSystem.ManuallySetShortTermMurders(this, value);
|
||||
}
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public DateTime ShortTermMurderExpiration => this.GetMurderContext(out var context) && context.ShortTermMurders > 0
|
||||
? Core.Now + (context.ShortTermElapse - GameTime)
|
||||
: DateTime.MinValue;
|
||||
public DateTime ShortTermMurderExpiration
|
||||
=> PlayerMurderSystem.GetMurderContext(this, out var context) && context.ShortTermMurders > 0
|
||||
? Core.Now + (context.ShortTermElapse - GameTime)
|
||||
: DateTime.MinValue;
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public DateTime LongTermMurderExpiration => Kills > 0 && this.GetMurderContext(out var context)
|
||||
? Core.Now + (context.LongTermElapse - GameTime)
|
||||
: DateTime.MinValue;
|
||||
public DateTime LongTermMurderExpiration
|
||||
=> Kills > 0 && PlayerMurderSystem.GetMurderContext(this, out var context)
|
||||
? Core.Now + (context.LongTermElapse - GameTime)
|
||||
: DateTime.MinValue;
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public int KnownRecipes => m_AcquiredRecipes?.Count ?? 0;
|
||||
|
||||
[CommandProperty(AccessLevel.Counselor, canModify: true)]
|
||||
public VirtueContext Virtues => this.GetOrCreateVirtues();
|
||||
public VirtueContext Virtues => VirtueSystem.GetOrCreateVirtues(this);
|
||||
|
||||
public HonorContext ReceivedHonorContext { get; set; }
|
||||
|
||||
|
|
@ -1255,7 +1257,7 @@ namespace Server.Mobiles
|
|||
|
||||
if (from is PlayerMobile mobile)
|
||||
{
|
||||
mobile.CheckAtrophies();
|
||||
VirtueSystem.CheckAtrophies(mobile);
|
||||
mobile.ClaimAutoStabledPets();
|
||||
}
|
||||
}
|
||||
|
|
@ -3426,7 +3428,7 @@ namespace Server.Mobiles
|
|||
// https://uo.com/wiki/ultima-online-wiki/player/skill-titles-order/
|
||||
if (DisplayChampionTitle)
|
||||
{
|
||||
var titleLabel = this.GetChampionTitleLabel();
|
||||
var titleLabel = ChampionTitleSystem.GetChampionTitleLabel(this);
|
||||
if (titleLabel > 0)
|
||||
{
|
||||
list.Add(titleLabel);
|
||||
|
|
|
|||
|
|
@ -643,7 +643,7 @@ public partial class BaseEscortable : BaseCreature
|
|||
|
||||
if (escorter is PlayerMobile pm)
|
||||
{
|
||||
var virtues = pm.GetOrCreateVirtues();
|
||||
var virtues = VirtueSystem.GetOrCreateVirtues(pm);
|
||||
if (virtues.CompassionGains > 0 && Core.Now > virtues.NextCompassionDay)
|
||||
{
|
||||
virtues.NextCompassionDay = DateTime.MinValue;
|
||||
|
|
|
|||
|
|
@ -228,7 +228,7 @@ namespace Server.SkillHandlers
|
|||
creature.AIObject?.DoMove(creature.Direction);
|
||||
|
||||
if (from is PlayerMobile pm &&
|
||||
!(pm.GetVirtues()?.HonorActive == true ||
|
||||
!(VirtueSystem.GetVirtues(pm)?.HonorActive == true ||
|
||||
TransformationSpellHelper.UnderTransformation(pm, typeof(EtherealVoyageSpell))))
|
||||
{
|
||||
creature.Combatant = pm;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue