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()?.<PropertyName>` instead.
- [X] Virtues were removed from non-player Mobiles.
- [X] `pm.JusticeProtectors` was removed. Use `JusticeVirtue.GetProtector()` or `JusticeVirtue.GetProtected()` instead.

### Screenshots

<img width="221" alt="Props-1" src="https://github.com/modernuo/ModernUO/assets/3953314/5c09cb83-b8d5-44a2-b899-a7ed7cd736ac">
<img width="220" alt="props-2" src="https://github.com/modernuo/ModernUO/assets/3953314/aeade4d3-8df0-47bd-955a-a864d0946cf7">
This commit is contained in:
Kamron Batman 2023-07-19 21:43:47 -07:00 • committed by GitHub
parent 063d878276
commit f77dd91811
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
37 changed files with 2105 additions and 1839 deletions

View file

@ -78,16 +78,6 @@ public static partial class EventSink
public static event Action<Mobile> PlayerDeath;
public static void InvokePlayerDeath(Mobile m) => PlayerDeath?.Invoke(m);
public static event Action<Mobile, Mobile> VirtueGumpRequest;
public static void InvokeVirtueGumpRequest(Mobile beholder, Mobile beheld) =>
VirtueGumpRequest?.Invoke(beholder, beheld);
public static event Action<Mobile, Mobile, int> VirtueItemRequest;
public static void InvokeVirtueItemRequest(Mobile beholder, Mobile beheld, int gumpID) =>
VirtueItemRequest?.Invoke(beholder, beheld, gumpID);
public static event Action<Mobile, int> VirtueMacroRequest;
public static void InvokeVirtueMacroRequest(Mobile mobile, int virtueID) =>

View file

@ -19,7 +19,7 @@ public partial class Mobile
// Migrating murders to the murder system
public static Dictionary<Mobile, int> 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<Mobile, int[]> VirtueMigrations { get; private set; }
public static void AddToVirtueMigration(Mobile m, int[] values)
{
if (values != null)
{
VirtueMigrations ??= new Dictionary<Mobile, int[]>();
VirtueMigrations[m] = values;
}
}
}

View file

@ -426,9 +426,6 @@ public partial class Mobile : IHued, IComparable<Mobile>, ISpawnable, IObjectPro
}
}
[CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)]
public VirtueInfo Virtues { get; private set; }
public object Party { get; set; }
public List<SkillMod> SkillMods => _skillMods;
@ -2264,7 +2261,7 @@ public partial class Mobile : IHued, IComparable<Mobile>, 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<Mobile>, 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<Mobile>, 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<Mobile>, 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<Mobile>, 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<Mobile>, 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<Mobile>, ISpawnable, IObjectPro
AutoPageNotify = true;
Aggressors = new List<AggressorInfo>();
Aggressed = new List<AggressorInfo>();
Virtues = new VirtueInfo();
DamageEntries = new List<DamageEntry>();
NextSkillTime = Core.TickCount;

View file

@ -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]);
}
}
}
}
}