From 51ecee6caa043b4c28303eab7b1639c5ef7610cf Mon Sep 17 00:00:00 2001
From: Kamron Batman <3953314+kamronbatman@users.noreply.github.com>
Date: Thu, 27 Jul 2023 21:44:06 -0700
Subject: [PATCH] fix: Overhauls champion titles & codegen champion system
(#1430)
## MAJOR CHANGE
Added a champion title system to facilitate the existing champion titles. This should make it easier to extend or create other related game content. Champion titles will be saved in a folder called _ChampionTitles_.
### Motivation
The motivation to refactor was two-folder, but mostly related to performance in two ways.
First, every player had a ChampionTitleInfo object with an array of ChamptionTitleInfo. We want to eliminate the need for this information unless a player actually uses it. This should save a considerable amount of memory.
Second, to facilitate the atrophy mechanic, the champion titles would run atrophy post-world save, adding to the time that the server is frozen. Eliminating this post-world save side effect unlocks our ability to further optimize the world save process since there are no direct side effects.
### Bugs fixed
- [X] Fixed titles getting cut off on the paperdoll
- [X] Fixed champion title not displaying overhead (OPL)
### Screenshots
---
Projects/Server/Guild.cs | 3 -
Projects/Server/IEntity.cs | 6 -
Projects/Server/Items/Item.cs | 6 -
Projects/Server/Main.cs | 2 -
Projects/Server/Mobiles/Mobile.cs | 6 -
.../Serialization/GenericPersistence.cs | 3 +-
.../Server/Serialization/ISerializable.cs | 13 -
Projects/Server/Serialization/Persistence.cs | 9 -
Projects/Server/World/World.cs | 14 +-
Projects/UOContent/Accounting/Account.cs | 6 -
.../Engines/CannedEvil/ChampionAltar.cs | 63 +-
.../Engines/CannedEvil/ChampionPlatform.cs | 119 +-
.../Engines/CannedEvil/ChampionSkull.cs | 103 +-
.../CannedEvil/ChampionSkullBrazier.cs | 266 +-
.../CannedEvil/ChampionSkullPlatform.cs | 197 +-
.../Engines/CannedEvil/ChampionSpawn.cs | 2776 ++++++++---------
.../Engines/CannedEvil/ChampionSpawnInfo.cs | 150 +
.../Engines/CannedEvil/ChampionSpawnType.cs | 134 -
.../Engines/CannedEvil/ChampionTitle.cs | 30 +
.../CannedEvil/ChampionTitleContext.cs | 379 +++
.../Engines/CannedEvil/ChampionTitleSystem.cs | 172 +
.../CannedEvil/DungeonChampionSpawn.cs | 52 +-
.../Engines/CannedEvil/HarrowerGate.cs | 78 +-
.../Engines/CannedEvil/LLChampionSpawn.cs | 54 +-
.../Engines/CannedEvil/StarRoomGate.cs | 136 +-
Projects/UOContent/Engines/Virtues/Valor.cs | 4 +-
...r.Engines.CannedEvil.ChampionAltar.v0.json | 11 +
...ngines.CannedEvil.ChampionPlatform.v0.json | 11 +
...es.CannedEvil.ChampionSkullBrazier.v1.json | 21 +
...s.CannedEvil.ChampionSkullPlatform.v0.json | 36 +
....Engines.CannedEvil.ChampionSpawn.v10.json | 186 ++
...r.Engines.CannedEvil.ChampionTitle.v0.json | 22 +
...es.CannedEvil.ChampionTitleContext.v1.json | 95 +
...es.CannedEvil.DungeonChampionSpawn.v0.json | 4 +
...gines.CannedEvil.IdolOfTheChampion.v0.json | 11 +
...Engines.CannedEvil.LLChampionSpawn.v0.json | 4 +
.../Server.Items.ChampionSkull.v2.json | 11 +
.../Server.Items.HarrowerGate.v0.json | 11 +
.../Server.Items.StarRoomGate.v1.json | 22 +
Projects/UOContent/Misc/Guild.cs | 6 -
Projects/UOContent/Misc/Titles.cs | 173 +-
.../UOContent/Mobiles/ChampionTitleInfo.cs | 339 --
Projects/UOContent/Mobiles/PlayerMobile.cs | 66 +-
.../UOContent/Mobiles/Special/Harrower.cs | 3 +-
44 files changed, 3028 insertions(+), 2785 deletions(-)
create mode 100644 Projects/UOContent/Engines/CannedEvil/ChampionSpawnInfo.cs
delete mode 100755 Projects/UOContent/Engines/CannedEvil/ChampionSpawnType.cs
create mode 100644 Projects/UOContent/Engines/CannedEvil/ChampionTitle.cs
create mode 100644 Projects/UOContent/Engines/CannedEvil/ChampionTitleContext.cs
create mode 100644 Projects/UOContent/Engines/CannedEvil/ChampionTitleSystem.cs
create mode 100644 Projects/UOContent/Migrations/Server.Engines.CannedEvil.ChampionAltar.v0.json
create mode 100644 Projects/UOContent/Migrations/Server.Engines.CannedEvil.ChampionPlatform.v0.json
create mode 100644 Projects/UOContent/Migrations/Server.Engines.CannedEvil.ChampionSkullBrazier.v1.json
create mode 100644 Projects/UOContent/Migrations/Server.Engines.CannedEvil.ChampionSkullPlatform.v0.json
create mode 100644 Projects/UOContent/Migrations/Server.Engines.CannedEvil.ChampionSpawn.v10.json
create mode 100644 Projects/UOContent/Migrations/Server.Engines.CannedEvil.ChampionTitle.v0.json
create mode 100644 Projects/UOContent/Migrations/Server.Engines.CannedEvil.ChampionTitleContext.v1.json
create mode 100644 Projects/UOContent/Migrations/Server.Engines.CannedEvil.DungeonChampionSpawn.v0.json
create mode 100644 Projects/UOContent/Migrations/Server.Engines.CannedEvil.IdolOfTheChampion.v0.json
create mode 100644 Projects/UOContent/Migrations/Server.Engines.CannedEvil.LLChampionSpawn.v0.json
create mode 100644 Projects/UOContent/Migrations/Server.Items.ChampionSkull.v2.json
create mode 100644 Projects/UOContent/Migrations/Server.Items.HarrowerGate.v0.json
create mode 100644 Projects/UOContent/Migrations/Server.Items.StarRoomGate.v1.json
delete mode 100644 Projects/UOContent/Mobiles/ChampionTitleInfo.cs
diff --git a/Projects/Server/Guild.cs b/Projects/Server/Guild.cs
index eca1d305f..2732cf473 100644
--- a/Projects/Server/Guild.cs
+++ b/Projects/Server/Guild.cs
@@ -40,9 +40,6 @@ public abstract class BaseGuild : ISerializable
public abstract GuildType Type { get; set; }
public abstract bool Disbanded { get; }
- public abstract bool ShouldExecuteAfterSerialize { get; }
- public abstract void AfterSerialize();
-
public abstract void Delete();
public bool Deleted => Disbanded;
diff --git a/Projects/Server/IEntity.cs b/Projects/Server/IEntity.cs
index f10c372ec..4825d7294 100644
--- a/Projects/Server/IEntity.cs
+++ b/Projects/Server/IEntity.cs
@@ -112,10 +112,4 @@ public class Entity : IEntity
public void Serialize(IGenericWriter writer)
{
}
-
- public bool ShouldExecuteAfterSerialize => false;
-
- public void AfterSerialize()
- {
- }
}
diff --git a/Projects/Server/Items/Item.cs b/Projects/Server/Items/Item.cs
index dfdc5e8f4..b319b65c4 100644
--- a/Projects/Server/Items/Item.cs
+++ b/Projects/Server/Items/Item.cs
@@ -1041,12 +1041,6 @@ public class Item : IHued, IComparable- , ISpawnable, IObjectPropertyListEnt
}
}
- public virtual bool ShouldExecuteAfterSerialize => false;
-
- public virtual void AfterSerialize()
- {
- }
-
public void MoveToWorld(WorldLocation worldLocation)
{
MoveToWorld(worldLocation.Location, worldLocation.Map);
diff --git a/Projects/Server/Main.cs b/Projects/Server/Main.cs
index 8b83355f8..cd60c0f6d 100644
--- a/Projects/Server/Main.cs
+++ b/Projects/Server/Main.cs
@@ -126,8 +126,6 @@ public static class Core
private static long _tickCount;
// Don't access this from other threads than the game thread.
- // Persistence accesses this via AfterSerialize or Serialize in other threads, but the value is set and won't change
- // since the game loop is frozen at that moment.
private static DateTime _now;
// For Unix Stopwatch.Frequency is normalized to 1ns
diff --git a/Projects/Server/Mobiles/Mobile.cs b/Projects/Server/Mobiles/Mobile.cs
index 1b1c12b9c..f7ae20bbe 100644
--- a/Projects/Server/Mobiles/Mobile.cs
+++ b/Projects/Server/Mobiles/Mobile.cs
@@ -2380,12 +2380,6 @@ public partial class Mobile : IHued, IComparable, ISpawnable, IObjectPro
writer.Write((byte)m_IntLock);
}
- public virtual bool ShouldExecuteAfterSerialize => false;
-
- public virtual void AfterSerialize()
- {
- }
-
public bool Deleted { get; private set; }
public virtual void Delete()
diff --git a/Projects/Server/Serialization/GenericPersistence.cs b/Projects/Server/Serialization/GenericPersistence.cs
index b567edba0..20ff8899f 100644
--- a/Projects/Server/Serialization/GenericPersistence.cs
+++ b/Projects/Server/Serialization/GenericPersistence.cs
@@ -25,7 +25,6 @@ public static class GenericPersistence
string name,
Action serializer,
Action deserializer,
- Action afterSerialize = null,
int priority = Persistence.DefaultPriority
)
{
@@ -49,6 +48,6 @@ public static class GenericPersistence
void Deserialize(string savePath, Dictionary typesDb) =>
AdhocPersistence.Deserialize(Path.Combine(savePath, name, $"{name}.bin"), deserializer);
- Persistence.Register(name, Serialize, WriteSnapshot, Deserialize, afterSerialize, priority);
+ Persistence.Register(name, Serialize, WriteSnapshot, Deserialize, priority);
}
}
diff --git a/Projects/Server/Serialization/ISerializable.cs b/Projects/Server/Serialization/ISerializable.cs
index fce6c8af2..e569d435c 100644
--- a/Projects/Server/Serialization/ISerializable.cs
+++ b/Projects/Server/Serialization/ISerializable.cs
@@ -34,12 +34,6 @@ public interface ISerializable
void Deserialize(IGenericReader reader);
void Serialize(IGenericWriter writer);
- // Determines if AfterSerialize should execute. This is checked on a worker thread.
- bool ShouldExecuteAfterSerialize { get; }
-
- // Executes after serialization if ShouldExecuteAfterSerialize is true. This is run on the game thread synchronously.
- void AfterSerialize();
-
bool Deleted { get; }
void Delete();
@@ -60,13 +54,6 @@ public interface ISerializable
{
SaveBuffer ??= new BufferWriter(true, types);
- // Queue for post serialization if this entity has it enabled
- // This will run AfterSerialize in the main game thread after the world is done saving
- if (ShouldExecuteAfterSerialize)
- {
- World.EnqueueAfterSerialization(this);
- }
-
// Clean, don't bother serializing
if (SavePosition > -1)
{
diff --git a/Projects/Server/Serialization/Persistence.cs b/Projects/Server/Serialization/Persistence.cs
index d8faf0aaf..c9aacd9cc 100644
--- a/Projects/Server/Serialization/Persistence.cs
+++ b/Projects/Server/Serialization/Persistence.cs
@@ -32,7 +32,6 @@ public static class Persistence
Action serializer,
Action snapshotWriter,
Action> deserializer,
- Action afterSerialize = null,
int priority = DefaultPriority
)
{
@@ -42,7 +41,6 @@ public static class Persistence
Name = name,
Priority = priority,
Serialize = serializer,
- AfterSerialize = afterSerialize,
WriteSnapshot = snapshotWriter,
Deserialize = deserializer
}
@@ -91,12 +89,6 @@ public static class Persistence
public static void Serialize()
{
Parallel.ForEach(_registry, entry => entry.Serialize());
-
- // Synchronously run the AfterSerialize on the main game thread
- foreach (var entry in _registry)
- {
- entry.AfterSerialize?.Invoke();
- }
}
public static void WriteSnapshot(string path, ConcurrentQueue types)
@@ -141,7 +133,6 @@ public static class Persistence
// Serializes to memory buffers and run in parallel
public Action Serialize { get; init; }
- public Action AfterSerialize { get; init; }
public Action WriteSnapshot { get; init; }
public Action> Deserialize { get; init; }
}
diff --git a/Projects/Server/World/World.cs b/Projects/Server/World/World.cs
index 6a5639fe8..8e3faa085 100644
--- a/Projects/Server/World/World.cs
+++ b/Projects/Server/World/World.cs
@@ -44,7 +44,6 @@ public static class World
private static Dictionary _pendingAdd = new();
private static Dictionary _pendingDelete = new();
private static ConcurrentQueue
- _decayQueue = new();
- private static ConcurrentQueue _afterSerializeEntities = new();
private static string _tempSavePath; // Path to the temporary folder for the save
private static bool _enableSaveStats;
@@ -166,7 +165,7 @@ public static class World
_enableSaveStats = ServerConfiguration.GetOrUpdateSetting("world.enableSaveStats", false);
// Mobiles & Items
- Persistence.Register("Mobiles & Items", SaveEntities, WriteEntities, LoadEntities, AfterSerialize, 1);
+ Persistence.Register("Mobiles & Items", SaveEntities, WriteEntities, LoadEntities, 1);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -187,9 +186,6 @@ public static class World
_decayQueue.Enqueue(item);
}
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static void EnqueueAfterSerialization(ISerializable entity) => _afterSerializeEntities.Enqueue(entity);
-
public static void Broadcast(int hue, bool ascii, string text)
{
var length = OutgoingMessagePackets.GetMaxMessageLength(text);
@@ -519,14 +515,6 @@ public static class World
*/
public static ConcurrentQueue SerializedTypes { get; } = new();
- private static void AfterSerialize()
- {
- while (_afterSerializeEntities.TryDequeue(out var entity))
- {
- entity.AfterSerialize();
- }
- }
-
private static void SaveEntities()
{
_serializationStart = DateTime.UtcNow;
diff --git a/Projects/UOContent/Accounting/Account.cs b/Projects/UOContent/Accounting/Account.cs
index b0fdfe9ee..88b6643f4 100644
--- a/Projects/UOContent/Accounting/Account.cs
+++ b/Projects/UOContent/Accounting/Account.cs
@@ -333,12 +333,6 @@ namespace Server.Accounting
}
}
- public bool ShouldExecuteAfterSerialize => false;
-
- public void AfterSerialize()
- {
- }
-
///
/// Deletes the account, all characters of the account, and all houses of those characters
///
diff --git a/Projects/UOContent/Engines/CannedEvil/ChampionAltar.cs b/Projects/UOContent/Engines/CannedEvil/ChampionAltar.cs
index 3e41ff33c..40d20ac81 100755
--- a/Projects/UOContent/Engines/CannedEvil/ChampionAltar.cs
+++ b/Projects/UOContent/Engines/CannedEvil/ChampionAltar.cs
@@ -13,56 +13,31 @@
* along with this program. If not, see . *
*************************************************************************/
+using ModernUO.Serialization;
using Server.Items;
-namespace Server.Engines.CannedEvil
+namespace Server.Engines.CannedEvil;
+
+[SerializationGenerator(0, false)]
+public partial class ChampionAltar : PentagramAddon
{
- public class ChampionAltar : PentagramAddon
+ [SerializableField(0, setter: "private")]
+ private ChampionSpawn _spawn;
+
+ public ChampionAltar(ChampionSpawn spawn) => _spawn = spawn;
+
+ public override void OnAfterDelete()
{
- public ChampionSpawn Spawn { get; private set; }
+ base.OnAfterDelete();
+ Spawn?.Delete();
+ }
- public ChampionAltar(ChampionSpawn spawn) => Spawn = spawn;
-
- public override void OnAfterDelete()
+ [AfterDeserialization(false)]
+ private void AfterDeserialization()
+ {
+ if (Spawn == null)
{
- base.OnAfterDelete();
-
- Spawn?.Delete();
- }
-
- public ChampionAltar(Serial serial) : base(serial)
- {
- }
-
- public override void Serialize(IGenericWriter writer)
- {
- base.Serialize(writer);
-
- writer.Write(0); // version
-
- writer.Write(Spawn);
- }
-
- public override void Deserialize(IGenericReader reader)
- {
- base.Deserialize(reader);
-
- var version = reader.ReadInt();
-
- switch (version)
- {
- case 0:
- {
- Spawn = reader.ReadEntity();
-
- if (Spawn == null)
- {
- Delete();
- }
-
- break;
- }
- }
+ Delete();
}
}
}
diff --git a/Projects/UOContent/Engines/CannedEvil/ChampionPlatform.cs b/Projects/UOContent/Engines/CannedEvil/ChampionPlatform.cs
index 0ef33dda8..7212e8fc7 100755
--- a/Projects/UOContent/Engines/CannedEvil/ChampionPlatform.cs
+++ b/Projects/UOContent/Engines/CannedEvil/ChampionPlatform.cs
@@ -13,95 +13,70 @@
* along with this program. If not, see . *
*************************************************************************/
+using ModernUO.Serialization;
using Server.Items;
-namespace Server.Engines.CannedEvil
+namespace Server.Engines.CannedEvil;
+
+[SerializationGenerator(0, false)]
+public partial class ChampionPlatform : BaseAddon
{
- public class ChampionPlatform : BaseAddon
+ [SerializableField(0, setter: "private")]
+ private ChampionSpawn _spawn;
+
+ public ChampionPlatform(ChampionSpawn spawn)
{
- public ChampionSpawn Spawn { get; private set; }
+ _spawn = spawn;
- public ChampionPlatform(ChampionSpawn spawn)
+ for (var x = -2; x <= 2; ++x)
{
- Spawn = spawn;
-
- for (var x = -2; x <= 2; ++x)
+ for (var y = -2; y <= 2; ++y)
{
- for (var y = -2; y <= 2; ++y)
- {
- AddComponent(0x750, x, y, -5);
- }
+ AddComponent(0x750, x, y, -5);
}
+ }
- for (var x = -1; x <= 1; ++x)
+ for (var x = -1; x <= 1; ++x)
+ {
+ for (var y = -1; y <= 1; ++y)
{
- for (var y = -1; y <= 1; ++y)
- {
- AddComponent(0x750, x, y, 0);
- }
+ AddComponent(0x750, x, y, 0);
}
-
- for (var i = -1; i <= 1; ++i)
- {
- AddComponent(0x751, i, 2, 0);
- AddComponent(0x752, 2, i, 0);
-
- AddComponent(0x753, i, -2, 0);
- AddComponent(0x754, -2, i, 0);
- }
-
- AddComponent(0x759, -2, -2, 0);
- AddComponent(0x75A, 2, 2, 0);
- AddComponent(0x75B, -2, 2, 0);
- AddComponent(0x75C, 2, -2, 0);
}
- public void AddComponent(int id, int x, int y, int z)
+ for (var i = -1; i <= 1; ++i)
{
- AddonComponent ac = new AddonComponent(id) { Hue = 0x497 };
- AddComponent(ac, x, y, z);
+ AddComponent(0x751, i, 2, 0);
+ AddComponent(0x752, 2, i, 0);
+
+ AddComponent(0x753, i, -2, 0);
+ AddComponent(0x754, -2, i, 0);
}
- public override void OnAfterDelete()
+ AddComponent(0x759, -2, -2, 0);
+ AddComponent(0x75A, 2, 2, 0);
+ AddComponent(0x75B, -2, 2, 0);
+ AddComponent(0x75C, 2, -2, 0);
+ }
+
+ public void AddComponent(int id, int x, int y, int z)
+ {
+ AddonComponent ac = new AddonComponent(id) { Hue = 0x497 };
+ AddComponent(ac, x, y, z);
+ }
+
+ public override void OnAfterDelete()
+ {
+ base.OnAfterDelete();
+ Spawn?.Delete();
+ }
+
+ [AfterDeserialization(false)]
+ private void AfterDeserialization()
+ {
+ if (Spawn == null)
{
- base.OnAfterDelete();
-
- Spawn?.Delete();
- }
-
- public ChampionPlatform(Serial serial) : base(serial)
- {
- }
-
- public override void Serialize(IGenericWriter writer)
- {
- base.Serialize(writer);
-
- writer.Write(0); // version
-
- writer.Write(Spawn);
- }
-
- public override void Deserialize(IGenericReader reader)
- {
- base.Deserialize(reader);
-
- var version = reader.ReadInt();
-
- switch (version)
- {
- case 0:
- {
- Spawn = reader.ReadEntity();
-
- if (Spawn == null)
- {
- Delete();
- }
-
- break;
- }
- }
+ Delete();
}
}
}
diff --git a/Projects/UOContent/Engines/CannedEvil/ChampionSkull.cs b/Projects/UOContent/Engines/CannedEvil/ChampionSkull.cs
index 72e634e8f..9834a40c1 100755
--- a/Projects/UOContent/Engines/CannedEvil/ChampionSkull.cs
+++ b/Projects/UOContent/Engines/CannedEvil/ChampionSkull.cs
@@ -13,86 +13,41 @@
* along with this program. If not, see . *
*************************************************************************/
+using ModernUO.Serialization;
using Server.Engines.CannedEvil;
-namespace Server.Items
+namespace Server.Items;
+
+[SerializationGenerator(2, false)]
+public partial class ChampionSkull : Item
{
- public class ChampionSkull : Item
+ [InvalidateProperties]
+ [SerializableField(0)]
+ [SerializedCommandProperty(AccessLevel.GameMaster)]
+ private ChampionSkullType _type;
+
+ public override int LabelNumber => 1049479 + (int)_type;
+
+ [Constructible]
+ public ChampionSkull(ChampionSkullType type) : base(0x1AE1)
{
- private ChampionSkullType m_Type;
+ _type = type;
+ LootType = LootType.Cursed;
- [CommandProperty(AccessLevel.GameMaster)]
- public ChampionSkullType Type
+ // TODO: All hue values
+ Hue = type switch
{
- get => m_Type;
- set
- {
- m_Type = value;
- InvalidateProperties();
- }
- }
+ ChampionSkullType.Power => 0x159,
+ ChampionSkullType.Venom => 0x172,
+ ChampionSkullType.Greed => 0x1EE,
+ ChampionSkullType.Death => 0x025,
+ ChampionSkullType.Pain => 0x035,
+ _ => Hue
+ };
+ }
- public override int LabelNumber => 1049479 + (int)m_Type;
-
- [Constructible]
- public ChampionSkull(ChampionSkullType type) : base(0x1AE1)
- {
- m_Type = type;
- LootType = LootType.Cursed;
-
- // TODO: All hue values
- Hue = type switch
- {
- ChampionSkullType.Power => 0x159,
- ChampionSkullType.Venom => 0x172,
- ChampionSkullType.Greed => 0x1EE,
- ChampionSkullType.Death => 0x025,
- ChampionSkullType.Pain => 0x035,
- _ => Hue
- };
- }
-
- public ChampionSkull(Serial serial) : base(serial)
- {
- }
-
- public override void Serialize(IGenericWriter writer)
- {
- base.Serialize(writer);
-
- writer.Write(1); // version
-
- writer.Write((int)m_Type);
- }
-
- public override void Deserialize(IGenericReader reader)
- {
- base.Deserialize(reader);
-
- var version = reader.ReadInt();
-
- switch (version)
- {
- case 1:
- case 0:
- {
- m_Type = (ChampionSkullType)reader.ReadInt();
- break;
- }
- }
-
- if (version == 0)
- {
- if (LootType != LootType.Cursed)
- {
- LootType = LootType.Cursed;
- }
-
- if (Insured)
- {
- Insured = false;
- }
- }
- }
+ private void Deserialize(IGenericReader reader, int version)
+ {
+ _type = (ChampionSkullType)reader.ReadInt();
}
}
diff --git a/Projects/UOContent/Engines/CannedEvil/ChampionSkullBrazier.cs b/Projects/UOContent/Engines/CannedEvil/ChampionSkullBrazier.cs
index ed0db3277..39b1c5fe5 100755
--- a/Projects/UOContent/Engines/CannedEvil/ChampionSkullBrazier.cs
+++ b/Projects/UOContent/Engines/CannedEvil/ChampionSkullBrazier.cs
@@ -13,194 +13,156 @@
* along with this program. If not, see . *
*************************************************************************/
+using ModernUO.Serialization;
using Server.Items;
using Server.Targeting;
using Server.Mobiles;
-namespace Server.Engines.CannedEvil
+namespace Server.Engines.CannedEvil;
+
+[SerializationGenerator(1, false)]
+public partial class ChampionSkullBrazier : AddonComponent
{
- public class ChampionSkullBrazier : AddonComponent
+ [InvalidateProperties]
+ [SerializableField(0)]
+ [SerializedCommandProperty(AccessLevel.GameMaster)]
+ private ChampionSkullType _type;
+
+ [InvalidateProperties]
+ [SerializableField(1)]
+ [SerializedCommandProperty(AccessLevel.GameMaster)]
+ private ChampionSkullPlatform _platform;
+
+ [SerializableProperty(2)]
+ [CommandProperty(AccessLevel.GameMaster)]
+ public Item Skull
{
- private ChampionSkullType m_Type;
- private Item m_Skull;
-
- [CommandProperty(AccessLevel.GameMaster)]
- public ChampionSkullPlatform Platform { get; private set; }
-
- [CommandProperty(AccessLevel.GameMaster)]
- public ChampionSkullType Type
+ get => _skull;
+ set
{
- get => m_Type;
- set
- {
- m_Type = value;
- InvalidateProperties();
- }
+ _skull = value;
+ this.MarkDirty();
+ _platform?.Validate();
+ }
+ }
+
+ public override int LabelNumber => 1049489 + (int)_type;
+
+ public ChampionSkullBrazier(ChampionSkullPlatform platform, ChampionSkullType type) : base(0x19BB)
+ {
+ Hue = 0x455;
+ Light = LightType.Circle300;
+
+ _platform = platform;
+ _type = type;
+ }
+
+ public override void OnDoubleClick(Mobile from)
+ {
+ _platform?.Validate();
+ BeginSacrifice(from);
+ }
+
+ public void BeginSacrifice(Mobile from)
+ {
+ if (Deleted)
+ {
+ return;
}
- [CommandProperty(AccessLevel.GameMaster)]
- public Item Skull
+ if (_skull is { Deleted: true })
{
- get => m_Skull;
- set
- {
- m_Skull = value;
- Platform?.Validate();
- }
+ Skull = null;
}
- public override int LabelNumber => 1049489 + (int)m_Type;
-
- public ChampionSkullBrazier(ChampionSkullPlatform platform, ChampionSkullType type) : base(0x19BB)
+ if (from.Map != Map || !from.InRange(GetWorldLocation(), 3))
{
- Hue = 0x455;
- Light = LightType.Circle300;
+ from.SendLocalizedMessage(500446); // That is too far away.
+ }
+ else if (!Harrower.CanSpawn)
+ {
+ from.SendMessage("The harrower has already been spawned.");
+ }
+ else if (_skull == null)
+ {
+ from.SendLocalizedMessage(1049485); // What would you like to sacrifice?
+ from.Target = new SacrificeTarget(this);
+ }
+ else
+ {
+ SendLocalizedMessageTo(from, 1049487); // I already have my champions awakening skull!
+ }
+ }
- Platform = platform;
- m_Type = type;
+ public void EndSacrifice(Mobile from, ChampionSkull skull)
+ {
+ if (Deleted)
+ {
+ return;
}
- public ChampionSkullBrazier(Serial serial) : base(serial)
+ if (_skull is { Deleted: true })
{
+ Skull = null;
}
- public override void OnDoubleClick(Mobile from)
+ if (from.Map != Map || !from.InRange(GetWorldLocation(), 3))
{
- Platform?.Validate();
-
- BeginSacrifice(from);
+ from.SendLocalizedMessage(500446); // That is too far away.
}
-
- public void BeginSacrifice(Mobile from)
+ else if (!Harrower.CanSpawn)
{
- if (Deleted)
- {
- return;
- }
-
- if (m_Skull is { Deleted: true })
- {
- Skull = null;
- }
-
- if (from.Map != Map || !from.InRange(GetWorldLocation(), 3))
- {
- from.SendLocalizedMessage(500446); // That is too far away.
- }
- else if (!Harrower.CanSpawn)
- {
- from.SendMessage("The harrower has already been spawned.");
- }
- else if (m_Skull == null)
- {
- from.SendLocalizedMessage(1049485); // What would you like to sacrifice?
- from.Target = new SacrificeTarget(this);
- }
- else
- {
- SendLocalizedMessageTo(from, 1049487); // I already have my champions awakening skull!
- }
+ from.SendMessage("The harrower has already been spawned.");
}
-
- public void EndSacrifice(Mobile from, ChampionSkull skull)
+ else if (skull == null)
{
- if (Deleted)
- {
- return;
- }
-
- if (m_Skull is { Deleted: true })
- {
- Skull = null;
- }
-
- if (from.Map != Map || !from.InRange(GetWorldLocation(), 3))
- {
- from.SendLocalizedMessage(500446); // That is too far away.
- }
- else if (!Harrower.CanSpawn)
- {
- from.SendMessage("The harrower has already been spawned.");
- }
- else if (skull == null)
- {
- SendLocalizedMessageTo(from, 1049488); // That is not my champions awakening skull!
- }
- else if (m_Skull != null)
- {
- SendLocalizedMessageTo(from, 1049487); // I already have my champions awakening skull!
- }
- else if (!skull.IsChildOf(from.Backpack))
- {
- from.SendLocalizedMessage(1049486); // You can only sacrifice items that are in your backpack!
- }
- else if (skull.Type == Type)
- {
- skull.Movable = false;
- skull.MoveToWorld(GetWorldTop(), Map);
-
- Skull = skull;
- }
- else
- {
- SendLocalizedMessageTo(from, 1049488); // That is not my champions awakening skull!
- }
+ SendLocalizedMessageTo(from, 1049488); // That is not my champions awakening skull!
}
-
- private class SacrificeTarget : Target
+ else if (_skull != null)
{
- private readonly ChampionSkullBrazier m_Brazier;
-
- public SacrificeTarget(ChampionSkullBrazier brazier) : base(12, false, TargetFlags.None) =>
- m_Brazier = brazier;
-
- protected override void OnTarget(Mobile from, object targeted) =>
- m_Brazier.EndSacrifice(from, targeted as ChampionSkull);
+ SendLocalizedMessageTo(from, 1049487); // I already have my champions awakening skull!
}
-
- public override void Serialize(IGenericWriter writer)
+ else if (!skull.IsChildOf(from.Backpack))
{
- base.Serialize(writer);
-
- writer.Write(0); // version
-
- writer.Write((int)m_Type);
- writer.Write(Platform);
- writer.Write(m_Skull);
+ from.SendLocalizedMessage(1049486); // You can only sacrifice items that are in your backpack!
}
-
- public override void Deserialize(IGenericReader reader)
+ else if (skull.Type == Type)
{
- base.Deserialize(reader);
+ skull.Movable = false;
+ skull.MoveToWorld(GetWorldTop(), Map);
- var version = reader.ReadInt();
+ Skull = skull;
+ }
+ else
+ {
+ SendLocalizedMessageTo(from, 1049488); // That is not my champions awakening skull!
+ }
+ }
- switch (version)
- {
- case 0:
- {
- m_Type = (ChampionSkullType)reader.ReadInt();
- Platform = reader.ReadEntity();
- m_Skull = reader.ReadEntity
- ();
+ private class SacrificeTarget : Target
+ {
+ private readonly ChampionSkullBrazier _brazier;
- if (Platform == null)
- {
- Delete();
- }
+ public SacrificeTarget(ChampionSkullBrazier brazier) : base(12, false, TargetFlags.None) =>
+ _brazier = brazier;
- break;
- }
- }
+ protected override void OnTarget(Mobile from, object targeted) =>
+ _brazier.EndSacrifice(from, targeted as ChampionSkull);
+ }
- if (Hue == 0x497)
- {
- Hue = 0x455;
- }
+ private void Deserialize(IGenericReader reader, int version)
+ {
+ _type = (ChampionSkullType)reader.ReadInt();
+ _platform = reader.ReadEntity();
+ _skull = reader.ReadEntity
- ();
+ }
- if (Light != LightType.Circle300)
- {
- Light = LightType.Circle300;
- }
+ [AfterDeserialization(false)]
+ private void AfterDeserialization()
+ {
+ if (_platform == null)
+ {
+ Delete();
}
}
}
diff --git a/Projects/UOContent/Engines/CannedEvil/ChampionSkullPlatform.cs b/Projects/UOContent/Engines/CannedEvil/ChampionSkullPlatform.cs
index c60f6747d..fb4f51c4f 100755
--- a/Projects/UOContent/Engines/CannedEvil/ChampionSkullPlatform.cs
+++ b/Projects/UOContent/Engines/CannedEvil/ChampionSkullPlatform.cs
@@ -13,127 +13,104 @@
* along with this program. If not, see . *
*************************************************************************/
+using ModernUO.Serialization;
using Server.Items;
using Server.Mobiles;
-namespace Server.Engines.CannedEvil
+namespace Server.Engines.CannedEvil;
+
+[SerializationGenerator(0, false)]
+public partial class ChampionSkullPlatform : BaseAddon
{
- public class ChampionSkullPlatform : BaseAddon
+ [SerializableField(0)]
+ private ChampionSkullBrazier _power;
+
+ [SerializableField(1)]
+ private ChampionSkullBrazier _enlightenment;
+
+ [SerializableField(2)]
+ private ChampionSkullBrazier _venom;
+
+ [SerializableField(3)]
+ private ChampionSkullBrazier _pain;
+
+ [SerializableField(4)]
+ private ChampionSkullBrazier _greed;
+
+ [SerializableField(5)]
+ private ChampionSkullBrazier _death;
+
+ [Constructible]
+ public ChampionSkullPlatform()
{
- private ChampionSkullBrazier m_Power, m_Enlightenment, m_Venom, m_Pain, m_Greed, m_Death;
+ AddComponent(new AddonComponent(0x71A), -1, -1, -1);
+ AddComponent(new AddonComponent(0x709), 0, -1, -1);
+ AddComponent(new AddonComponent(0x709), 1, -1, -1);
+ AddComponent(new AddonComponent(0x709), -1, 0, -1);
+ AddComponent(new AddonComponent(0x709), 0, 0, -1);
+ AddComponent(new AddonComponent(0x709), 1, 0, -1);
+ AddComponent(new AddonComponent(0x709), -1, 1, -1);
+ AddComponent(new AddonComponent(0x709), 0, 1, -1);
+ AddComponent(new AddonComponent(0x71B), 1, 1, -1);
- [Constructible]
- public ChampionSkullPlatform()
+ AddComponent(new AddonComponent(0x50F), 0, -1, 4);
+ AddComponent(_power = new ChampionSkullBrazier(this, ChampionSkullType.Power), 0, -1, 5);
+
+ AddComponent(new AddonComponent(0x50F), 1, -1, 4);
+ AddComponent(_enlightenment = new ChampionSkullBrazier(this, ChampionSkullType.Enlightenment), 1, -1, 5);
+
+ AddComponent(new AddonComponent(0x50F), -1, 0, 4);
+ AddComponent(_venom = new ChampionSkullBrazier(this, ChampionSkullType.Venom), -1, 0, 5);
+
+ AddComponent(new AddonComponent(0x50F), 1, 0, 4);
+ AddComponent(_pain = new ChampionSkullBrazier(this, ChampionSkullType.Pain), 1, 0, 5);
+
+ AddComponent(new AddonComponent(0x50F), -1, 1, 4);
+ AddComponent(_greed = new ChampionSkullBrazier(this, ChampionSkullType.Greed), -1, 1, 5);
+
+ AddComponent(new AddonComponent(0x50F), 0, 1, 4);
+ AddComponent(_death = new ChampionSkullBrazier(this, ChampionSkullType.Death), 0, 1, 5);
+
+ AddonComponent comp = new LocalizedAddonComponent(0x20D2, 1049495) { Hue = 0x482 };
+ AddComponent(comp, 0, 0, 5);
+
+ comp = new LocalizedAddonComponent(0x0BCF, 1049496) { Hue = 0x482 };
+ AddComponent(comp, 0, 2, -7);
+
+ comp = new LocalizedAddonComponent(0x0BD0, 1049497) { Hue = 0x482 };
+ AddComponent(comp, 2, 0, -7);
+ }
+
+ public void Validate()
+ {
+ if (Validate(_power) && Validate(_enlightenment) && Validate(_venom) && Validate(_pain) &&
+ Validate(_greed) && Validate(_death))
{
- AddComponent(new AddonComponent(0x71A), -1, -1, -1);
- AddComponent(new AddonComponent(0x709), 0, -1, -1);
- AddComponent(new AddonComponent(0x709), 1, -1, -1);
- AddComponent(new AddonComponent(0x709), -1, 0, -1);
- AddComponent(new AddonComponent(0x709), 0, 0, -1);
- AddComponent(new AddonComponent(0x709), 1, 0, -1);
- AddComponent(new AddonComponent(0x709), -1, 1, -1);
- AddComponent(new AddonComponent(0x709), 0, 1, -1);
- AddComponent(new AddonComponent(0x71B), 1, 1, -1);
+ Mobile harrower = Harrower.Spawn(new Point3D(X, Y, Z + 6), Map);
- AddComponent(new AddonComponent(0x50F), 0, -1, 4);
- AddComponent(m_Power = new ChampionSkullBrazier(this, ChampionSkullType.Power), 0, -1, 5);
-
- AddComponent(new AddonComponent(0x50F), 1, -1, 4);
- AddComponent(m_Enlightenment = new ChampionSkullBrazier(this, ChampionSkullType.Enlightenment), 1, -1, 5);
-
- AddComponent(new AddonComponent(0x50F), -1, 0, 4);
- AddComponent(m_Venom = new ChampionSkullBrazier(this, ChampionSkullType.Venom), -1, 0, 5);
-
- AddComponent(new AddonComponent(0x50F), 1, 0, 4);
- AddComponent(m_Pain = new ChampionSkullBrazier(this, ChampionSkullType.Pain), 1, 0, 5);
-
- AddComponent(new AddonComponent(0x50F), -1, 1, 4);
- AddComponent(m_Greed = new ChampionSkullBrazier(this, ChampionSkullType.Greed), -1, 1, 5);
-
- AddComponent(new AddonComponent(0x50F), 0, 1, 4);
- AddComponent(m_Death = new ChampionSkullBrazier(this, ChampionSkullType.Death), 0, 1, 5);
-
- AddonComponent comp = new LocalizedAddonComponent(0x20D2, 1049495) { Hue = 0x482 };
- AddComponent(comp, 0, 0, 5);
-
- comp = new LocalizedAddonComponent(0x0BCF, 1049496) { Hue = 0x482 };
- AddComponent(comp, 0, 2, -7);
-
- comp = new LocalizedAddonComponent(0x0BD0, 1049497) { Hue = 0x482 };
- AddComponent(comp, 2, 0, -7);
- }
-
- public void Validate()
- {
- if (Validate(m_Power) && Validate(m_Enlightenment) && Validate(m_Venom) && Validate(m_Pain) &&
- Validate(m_Greed) && Validate(m_Death))
+ if (harrower == null)
{
- Mobile harrower = Harrower.Spawn(new Point3D(X, Y, Z + 6), Map);
-
- if (harrower == null)
- {
- return;
- }
-
- Clear(m_Power);
- Clear(m_Enlightenment);
- Clear(m_Venom);
- Clear(m_Pain);
- Clear(m_Greed);
- Clear(m_Death);
+ return;
}
- }
- public void Clear(ChampionSkullBrazier brazier)
- {
- if (brazier != null)
- {
- Effects.SendBoltEffect(brazier);
-
- brazier.Skull?.Delete();
- }
- }
-
- public bool Validate(ChampionSkullBrazier brazier) => brazier is { Skull: { Deleted: false } };
-
- public ChampionSkullPlatform(Serial serial) : base(serial)
- {
- }
-
- public override void Serialize(IGenericWriter writer)
- {
- base.Serialize(writer);
-
- writer.Write(0); // version
-
- writer.Write(m_Power);
- writer.Write(m_Enlightenment);
- writer.Write(m_Venom);
- writer.Write(m_Pain);
- writer.Write(m_Greed);
- writer.Write(m_Death);
- }
-
- public override void Deserialize(IGenericReader reader)
- {
- base.Deserialize(reader);
-
- var version = reader.ReadInt();
-
- switch (version)
- {
- case 0:
- {
- m_Power = reader.ReadEntity();
- m_Enlightenment = reader.ReadEntity();
- m_Venom = reader.ReadEntity();
- m_Pain = reader.ReadEntity();
- m_Greed = reader.ReadEntity();
- m_Death = reader.ReadEntity();
-
- break;
- }
- }
+ Clear(_power);
+ Clear(_enlightenment);
+ Clear(_venom);
+ Clear(_pain);
+ Clear(_greed);
+ Clear(_death);
}
}
+
+ public static void Clear(ChampionSkullBrazier brazier)
+ {
+ if (brazier != null)
+ {
+ Effects.SendBoltEffect(brazier);
+
+ brazier.Skull?.Delete();
+ }
+ }
+
+ public static bool Validate(ChampionSkullBrazier brazier) => brazier is { Skull: { Deleted: false } };
}
diff --git a/Projects/UOContent/Engines/CannedEvil/ChampionSpawn.cs b/Projects/UOContent/Engines/CannedEvil/ChampionSpawn.cs
index 36f830c8c..9e95df3b7 100755
--- a/Projects/UOContent/Engines/CannedEvil/ChampionSpawn.cs
+++ b/Projects/UOContent/Engines/CannedEvil/ChampionSpawn.cs
@@ -16,6 +16,8 @@
using System;
using System.Net;
using System.Collections.Generic;
+using System.Runtime.InteropServices;
+using ModernUO.Serialization;
using Server.Engines.Virtues;
using Server.Gumps;
using Server.Items;
@@ -24,1621 +26,1497 @@ using Server.Regions;
using Server.Logging;
using Server.Utilities;
-namespace Server.Engines.CannedEvil
+namespace Server.Engines.CannedEvil;
+
+[SerializationGenerator(10, false)]
+public partial class ChampionSpawn : Item
{
- public class ChampionSpawn : Item
+ private static readonly ILogger logger = LogFactory.GetLogger(typeof(ChampionSpawn));
+
+ [SerializableField(1)]
+ [SerializedCommandProperty(AccessLevel.GameMaster)]
+ private bool _activatedByProximity;
+
+ [DeltaDateTime]
+ [SerializableField(2)]
+ [SerializedCommandProperty(AccessLevel.GameMaster)]
+ private DateTime _nextProximityTime;
+
+ [SerializableField(4)]
+ [SerializedCommandProperty(AccessLevel.GameMaster)]
+ private bool _activatedByValor;
+
+ [SerializableField(5)]
+ private Dictionary_damageEntries;
+
+ [SerializableField(6)]
+ [SerializedCommandProperty(AccessLevel.GameMaster)]
+ private bool _confinedRoaming;
+
+ [SerializableField(7)]
+ [SerializedCommandProperty(AccessLevel.GameMaster)]
+ private IdolOfTheChampion _idol;
+
+ [SerializableField(8)]
+ [SerializedCommandProperty(AccessLevel.GameMaster)]
+ private bool _hasBeenAdvanced;
+
+ [SerializableField(10)]
+ [SerializedCommandProperty(AccessLevel.GameMaster)]
+ private bool _randomizeType;
+
+ [InvalidateProperties]
+ [SerializableField(13)]
+ [SerializedCommandProperty(AccessLevel.GameMaster)]
+ private ChampionSpawnType _type;
+
+ [SerializableField(14)]
+ [SerializedCommandProperty(AccessLevel.GameMaster)]
+ private List _creatures;
+
+ [SerializableField(15)]
+ [SerializedCommandProperty(AccessLevel.GameMaster)]
+ private List
- _redSkulls;
+
+ [SerializableField(16)]
+ [SerializedCommandProperty(AccessLevel.GameMaster)]
+ private List
- _whiteSkulls;
+
+ [SerializableField(17)]
+ [SerializedCommandProperty(AccessLevel.GameMaster)]
+ private ChampionPlatform _platform;
+
+ [SerializableField(18)]
+ [SerializedCommandProperty(AccessLevel.GameMaster)]
+ private ChampionAltar _altar;
+
+ [SerializableField(19)]
+ [SerializedCommandProperty(AccessLevel.GameMaster)]
+ private TimeSpan _expireDelay;
+
+ [DeltaDateTime]
+ [SerializableField(20)]
+ [SerializedCommandProperty(AccessLevel.GameMaster)]
+ private DateTime _expireTime;
+
+ [SerializableField(21)]
+ [SerializedCommandProperty(AccessLevel.GameMaster)]
+ private Mobile _champion;
+
+ [SerializableField(22)]
+ [SerializedCommandProperty(AccessLevel.GameMaster)]
+ private TimeSpan _restartDelay;
+
+ [DeltaDateTime]
+ [SerializableField(23, setter: "private")]
+ [SerializedCommandProperty(AccessLevel.GameMaster)]
+ private DateTime _restartTime;
+
+ private ChampionSpawnRegion m_Region;
+
+ //Goes back each level, below level 0 and it goes off!
+
+ private TimerExecutionToken _timerToken;
+ private TimerExecutionToken _restartTimerToken;
+
+ public virtual string BroadcastMessage => "The Champion has sensed your presence! Beware its wrath!";
+ public virtual bool ProximitySpawn => false;
+ public virtual bool AlwaysActive => false;
+
+ public override TimeSpan DecayTime => TimeSpan.FromSeconds(180.0);
+
+ public virtual bool HasStarRoomGate => true;
+
+ [CommandProperty(AccessLevel.GameMaster)]
+ public Point3D EjectLocation { get; set; }
+
+ [CommandProperty(AccessLevel.GameMaster)]
+ public Map EjectMap { get; set; }
+
+ public override int LabelNumber => 1041030; // Evil in a Can: Don't delete me!
+
+ [Constructible]
+ public ChampionSpawn() : base(0xBD2)
{
- private static readonly ILogger logger = LogFactory.GetLogger(typeof(ChampionSpawn));
- private bool m_Active;
- private ChampionSpawnType m_Type;
- private List m_Creatures;
- private List
- m_RedSkulls;
- private List
- m_WhiteSkulls;
- private ChampionPlatform m_Platform;
- private ChampionAltar m_Altar;
- private int m_Kills;
- private int m_MaxLevel;
- private int m_Level;
+ Movable = false;
+ Visible = false;
- //private int m_SpawnRange;
- private Rectangle2D m_SpawnArea;
- private ChampionSpawnRegion m_Region;
+ _creatures = new List();
+ _redSkulls = new List
- ();
+ _whiteSkulls = new List
- ();
- //Goes back each level, below level 0 and it goes off!
+ _platform = new ChampionPlatform(this);
+ _altar = new ChampionAltar(this);
+ _idol = new IdolOfTheChampion(this);
- private TimerExecutionToken _timerToken;
+ ExpireDelay = TimeSpan.FromMinutes(30.0);
+ RestartDelay = TimeSpan.FromMinutes(30.0);
+ _damageEntries = new Dictionary();
- private IdolOfTheChampion m_Idol;
- private TimerExecutionToken _restartTimerToken;
+ Timer.StartTimer(TimeSpan.Zero, SetInitialSpawnArea);
+ }
- public virtual string BroadcastMessage => "The Champion has sensed your presence! Beware its wrath!";
- public virtual bool ProximitySpawn => false;
- public virtual bool CanAdvanceByValor => true;
- public virtual bool CanActivateByValor => true;
- public virtual bool AlwaysActive => false;
+ public void SetInitialSpawnArea()
+ {
+ //Previous default used to be 24;
+ SpawnArea = new Rectangle2D(new Point2D(X - 24, Y - 24), new Point2D(X + 24, Y + 24));
+ }
- public override TimeSpan DecayTime => TimeSpan.FromSeconds(180.0);
+ public virtual ChampionSpawnRegion GetRegion() => new(this);
- public virtual bool HasStarRoomGate => true;
+ public void UpdateRegion()
+ {
+ m_Region?.Unregister();
- public Dictionary DamageEntries { get; private set; }
-
- [CommandProperty(AccessLevel.GameMaster)]
- public bool ConfinedRoaming { get; set; }
-
- [CommandProperty(AccessLevel.GameMaster)]
- public bool HasBeenAdvanced { get; set; }
-
- [CommandProperty(AccessLevel.GameMaster)]
- public Point3D EjectLocation { get; set; }
-
- [CommandProperty(AccessLevel.GameMaster)]
- public Map EjectMap { get; set; }
-
- public override int LabelNumber => 1041030; // Evil in a Can: Don't delete me!
-
- [Constructible]
- public ChampionSpawn() : base(0xBD2)
+ if (!Deleted && Map != Map.Internal)
{
- Movable = false;
- Visible = false;
-
- m_Creatures = new List();
- m_RedSkulls = new List
- ();
- m_WhiteSkulls = new List
- ();
-
- m_Platform = new ChampionPlatform(this);
- m_Altar = new ChampionAltar(this);
- m_Idol = new IdolOfTheChampion(this);
-
- ExpireDelay = TimeSpan.FromMinutes(30.0);
- RestartDelay = TimeSpan.FromMinutes(30.0);
- DamageEntries = new Dictionary();
-
- Timer.StartTimer(TimeSpan.Zero, SetInitialSpawnArea);
+ m_Region = GetRegion();
+ m_Region.Register();
}
+ }
- public void SetInitialSpawnArea()
+ [SerializableProperty(0)]
+ [CommandProperty(AccessLevel.GameMaster)]
+ public int Level
+ {
+ get => _level;
+ set
{
- //Previous default used to be 24;
- SpawnArea = new Rectangle2D(new Point2D(X - 24, Y - 24), new Point2D(X + 24, Y + 24));
+ for (int i = _redSkulls.Count - 1; i >= value; --i)
+ {
+ _redSkulls[i].Delete();
+ _redSkulls.RemoveAt(i);
+ }
+
+ for (int i = _redSkulls.Count; i < Math.Min(value, 16); ++i)
+ {
+ Item skull = new Item(0x1854) { Hue = 0x26, Movable = false, Light = LightType.Circle150 };
+ skull.MoveToWorld(GetRedSkullLocation(i), Map);
+ _redSkulls.Add(skull);
+ }
+
+ _level = value;
+ this.MarkDirty();
+ InvalidateProperties();
}
+ }
- public virtual ChampionSpawnRegion GetRegion() => new(this);
+ [SerializableProperty(3)]
+ [CommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)]
+ public int MaxLevel
+ {
+ get => _maxLevel;
+ set => _maxLevel = Math.Clamp(value, 0, 18);
+ }
- public void UpdateRegion()
+ [SerializableProperty(9)]
+ [CommandProperty(AccessLevel.GameMaster)]
+ public Rectangle2D SpawnArea
+ {
+ get => _spawnArea;
+ set
{
- m_Region?.Unregister();
-
- if (!Deleted && Map != Map.Internal)
- {
- m_Region = GetRegion();
- m_Region.Register();
- }
+ _spawnArea = value;
+ this.MarkDirty();
+ InvalidateProperties();
+ UpdateRegion();
}
+ }
- [CommandProperty(AccessLevel.GameMaster)]
- public bool RandomizeType { get; set; }
-
- [CommandProperty(AccessLevel.GameMaster)]
- public int Kills
+ [SerializableProperty(11)]
+ [CommandProperty(AccessLevel.GameMaster)]
+ public int Kills
+ {
+ get => _kills;
+ set
{
- get => m_Kills;
- set
+ _kills = value;
+ this.MarkDirty();
+
+ double n = _kills / (double)MaxKills;
+ int p = (int)(n * 100);
+
+ if (p < 90)
{
- m_Kills = value;
-
- double n = m_Kills / (double)MaxKills;
- int p = (int)(n * 100);
-
- if (p < 90)
- {
- SetWhiteSkullCount(p / 20);
- }
-
- InvalidateProperties();
+ SetWhiteSkullCount(p / 20);
}
- }
- [CommandProperty(AccessLevel.GameMaster)]
- public Rectangle2D SpawnArea
+ InvalidateProperties();
+ }
+ }
+
+ [SerializableProperty(12)]
+ [CommandProperty(AccessLevel.GameMaster)]
+ public bool Active
+ {
+ get => _active;
+ set
{
- get => m_SpawnArea;
- set
+ this.MarkDirty();
+
+ if (value)
{
- m_SpawnArea = value;
- InvalidateProperties();
- UpdateRegion();
- }
- }
-
- [CommandProperty(AccessLevel.GameMaster)]
- public TimeSpan RestartDelay { get; set; }
-
- [CommandProperty(AccessLevel.GameMaster)]
- public DateTime RestartTime { get; private set; }
-
- [CommandProperty(AccessLevel.GameMaster)]
- public TimeSpan ExpireDelay { get; set; }
-
- [CommandProperty(AccessLevel.GameMaster)]
- public DateTime ExpireTime { get; set; }
-
- [CommandProperty(AccessLevel.GameMaster)]
- public ChampionSpawnType Type
- {
- get => m_Type;
- set
- {
- m_Type = value;
- InvalidateProperties();
- }
- }
-
- [CommandProperty(AccessLevel.GameMaster)]
- public bool Active
- {
- get => m_Active;
- set
- {
- if (value)
- {
- Start();
- }
- else
- {
- Stop();
- }
-
- InvalidateProperties();
- }
- }
-
- [CommandProperty(AccessLevel.GameMaster)]
- public bool ReadyToActivate { get; set; }
-
- [CommandProperty(AccessLevel.GameMaster)]
- public bool ActivatedByValor { get; set; }
-
- [CommandProperty(AccessLevel.GameMaster)]
- public bool ActivatedByProximity { get; set; }
-
- [CommandProperty(AccessLevel.GameMaster)]
- public DateTime NextProximityTime { get; set; }
-
- [CommandProperty(AccessLevel.GameMaster)]
- public Mobile Champion { get; set; }
-
- [CommandProperty(AccessLevel.GameMaster)]
- public int Level
- {
- get => m_Level;
- set
- {
- for (int i = m_RedSkulls.Count - 1; i >= value; --i)
- {
- m_RedSkulls[i].Delete();
- m_RedSkulls.RemoveAt(i);
- }
-
- for (int i = m_RedSkulls.Count; i < Math.Min(value, 16); ++i)
- {
- Item skull = new Item(0x1854) { Hue = 0x26, Movable = false, Light = LightType.Circle150 };
- skull.MoveToWorld(GetRedSkullLocation(i), Map);
- m_RedSkulls.Add(skull);
- }
-
- m_Level = value;
-
- InvalidateProperties();
- }
- }
-
- [CommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)]
- public int MaxLevel{ get => m_MaxLevel;
- set => m_MaxLevel = Math.Max(Math.Min(value, 18), 0);
- }
-
- public bool IsChampionSpawn(Mobile m) => m_Creatures.Contains(m);
-
- [CommandProperty(AccessLevel.GameMaster)]
- public virtual int MaxKills
- {
- get
- {
- return Level switch
- {
- >= 16 => 16,
- >= 12 => 32,
- >= 8 => 64,
- >= 4 => 128,
- _ => 256
- };
- }
- }
-
- public void SetWhiteSkullCount(int val)
- {
- for (int i = m_WhiteSkulls.Count - 1; i >= val; --i)
- {
- m_WhiteSkulls[i].Delete();
- m_WhiteSkulls.RemoveAt(i);
- }
-
- for (int i = m_WhiteSkulls.Count; i < val; ++i)
- {
- Item skull = new Item(0x1854);
-
- skull.Movable = false;
- skull.Light = LightType.Circle150;
-
- skull.MoveToWorld(GetWhiteSkullLocation(i), Map);
-
- m_WhiteSkulls.Add(skull);
-
- Effects.PlaySound(skull.Location, skull.Map, 0x29);
- Effects.SendLocationEffect(new Point3D(skull.X + 1, skull.Y + 1, skull.Z), skull.Map, 0x3728, 10);
- }
- }
-
- public void Start()
- {
- if (m_Active || Deleted)
- {
- return;
- }
-
- if (RandomizeType)
- {
- Type = Utility.Random(5) switch
- {
- 0 => ChampionSpawnType.VerminHorde,
- 1 => ChampionSpawnType.UnholyTerror,
- 2 => ChampionSpawnType.ColdBlood,
- 3 => ChampionSpawnType.Abyss,
- 4 => ChampionSpawnType.Arachnid,
- _ => Type
- };
- }
-
- m_Active = true;
- ReadyToActivate = false;
- HasBeenAdvanced = false;
- m_MaxLevel = 16 + Utility.Random(3);
-
- _timerToken.Cancel();
- Timer.StartTimer(TimeSpan.FromSeconds(1.0), TimeSpan.FromSeconds(1.0), OnSlice, out _timerToken);
-
- _restartTimerToken.Cancel();
-
- if (m_Altar != null)
- {
- m_Altar.Hue = Champion != null ? 0x26 : 0;
- }
-
- if (m_Platform != null)
- {
- m_Platform.Hue = 0x452;
- }
-
- ExpireTime = Core.Now + ExpireDelay;
- }
-
- public void Stop()
- {
- if (!m_Active || Deleted)
- {
- return;
- }
-
- m_Active = false;
- ActivatedByValor = false;
- HasBeenAdvanced = false;
- m_MaxLevel = 0;
-
- _timerToken.Cancel();
- _restartTimerToken.Cancel();
-
- if (m_Altar != null)
- {
- m_Altar.Hue = 0;
- }
-
- if (m_Platform != null)
- {
- m_Platform.Hue = 0x497;
- }
-
- if (AlwaysActive)
- {
- BeginRestart(RestartDelay);
- }
- else if (ActivatedByProximity)
- {
- ActivatedByProximity = false;
- NextProximityTime = Core.Now + TimeSpan.FromHours(6.0);
- }
-
- Timer.StartTimer(TimeSpan.FromMinutes(10.0), ExpireCreatures);
- }
-
- public void BeginRestart(TimeSpan ts)
- {
- RestartTime = Core.Now + ts;
- _restartTimerToken.Cancel();
- Timer.StartTimer(ts, EndRestart, out _restartTimerToken);
- }
-
- public void EndRestart()
- {
- HasBeenAdvanced = false;
- ReadyToActivate = true;
- }
-
- private ScrollofTranscendence CreateRandomTramSoT()
- {
- int level = Utility.Random(5) + 1;
- return ScrollofTranscendence.CreateRandom(level, level);
- }
-
- private ScrollofTranscendence CreateRandomFelSoT()
- {
- int level = Utility.Random(5) + 1;
- return ScrollofTranscendence.CreateRandom(level, level);
- }
-
- private static PowerScroll CreateRandomFelPS() => PowerScroll.CreateRandomNoCraft(5, 5);
-
- public static void GiveScrollOfTranscendenceFelTo(Mobile killer, ScrollofTranscendence SoTF)
- {
- if (SoTF == null || killer == null) //sanity
- {
- return;
- }
-
- killer.SendLocalizedMessage(1094936); // You have received a Scroll of Transcendence!
-
- if (killer.Alive)
- {
- killer.AddToBackpack(SoTF);
- }
- else if (killer.Corpse is { Deleted: false })
- {
- killer.Corpse.DropItem(SoTF);
+ Start();
}
else
{
- killer.AddToBackpack(SoTF);
+ Stop();
}
- // Justice reward
- var pm = (PlayerMobile)killer;
- var prot = JusticeVirtue.GetProtector(pm);
- if (prot == null || prot.Map != killer.Map || prot.Kills >= 5 || prot.Criminal ||
- !JusticeVirtue.CheckMapRegion(killer, prot))
- {
- return;
- }
+ InvalidateProperties();
+ }
+ }
- var chance = VirtueSystem.GetLevel(prot, VirtueName.Justice) switch
+ [CommandProperty(AccessLevel.GameMaster)]
+ public bool ReadyToActivate { get; set; }
+
+ public bool IsChampionSpawn(Mobile m) => _creatures.Contains(m);
+
+ [CommandProperty(AccessLevel.GameMaster)]
+ public virtual int MaxKills
+ {
+ get
+ {
+ return Level switch
{
- VirtueLevel.Seeker => 60,
- VirtueLevel.Follower => 80,
- VirtueLevel.Knight => 100,
- _ => 0
+ >= 16 => 16,
+ >= 12 => 32,
+ >= 8 => 64,
+ >= 4 => 128,
+ _ => 256
+ };
+ }
+ }
+
+ public void SetWhiteSkullCount(int val)
+ {
+ for (int i = _whiteSkulls.Count - 1; i >= val; --i)
+ {
+ _whiteSkulls[i].Delete();
+ _whiteSkulls.RemoveAt(i);
+ }
+
+ for (int i = _whiteSkulls.Count; i < val; ++i)
+ {
+ Item skull = new Item(0x1854)
+ {
+ Movable = false,
+ Light = LightType.Circle150
};
- 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);
- }
+ skull.MoveToWorld(GetWhiteSkullLocation(i), Map);
+
+ _whiteSkulls.Add(skull);
+
+ Effects.PlaySound(skull.Location, skull.Map, 0x29);
+ Effects.SendLocationEffect(new Point3D(skull.X + 1, skull.Y + 1, skull.Z), skull.Map, 0x3728, 10);
}
- public static void GivePowerScrollFelTo (Mobile killer, PowerScroll PS)
+ this.MarkDirty();
+ }
+
+ public void Start()
+ {
+ if (_active || Deleted)
{
- if (PS == null || killer == null) //sanity
- {
- return;
- }
+ return;
+ }
- killer.SendLocalizedMessage(1049524); // You have received a scroll of power!
-
- if (killer.Alive)
+ if (RandomizeType)
+ {
+ Type = Utility.Random(5) switch
{
- killer.AddToBackpack(PS);
- }
- else if (killer.Corpse is { Deleted: false })
- {
- killer.Corpse.DropItem(PS);
- }
- else
- {
- killer.AddToBackpack(PS);
- }
-
- // Justice reward
- var pm = (PlayerMobile)killer;
- var prot = JusticeVirtue.GetProtector(pm);
- if (prot == null || prot.Map != killer.Map || prot.Kills >= 5 || prot.Criminal ||
- !JusticeVirtue.CheckMapRegion(killer, prot))
- {
- return;
- }
-
- var chance = VirtueSystem.GetLevel(prot, VirtueName.Justice) switch
- {
- VirtueLevel.Seeker => 60,
- VirtueLevel.Follower => 80,
- VirtueLevel.Knight => 100,
- _ => 0
+ 0 => ChampionSpawnType.VerminHorde,
+ 1 => ChampionSpawnType.UnholyTerror,
+ 2 => ChampionSpawnType.ColdBlood,
+ 3 => ChampionSpawnType.Abyss,
+ 4 => ChampionSpawnType.Arachnid,
+ _ => Type
};
-
- if (chance > 0 && chance > Utility.Random(100))
- {
- prot.SendLocalizedMessage(1049368); // You have been rewarded for your dedication to Justice!
- prot.AddToBackpack(CreateRandomFelPS());
- }
}
- public void OnSlice()
+ Active = true;
+ ReadyToActivate = false;
+ HasBeenAdvanced = false;
+ MaxLevel = 16 + Utility.Random(3);
+
+ _timerToken.Cancel();
+ Timer.StartTimer(TimeSpan.FromSeconds(1.0), TimeSpan.FromSeconds(1.0), OnSlice, out _timerToken);
+
+ _restartTimerToken.Cancel();
+
+ if (_altar != null)
{
- if (!m_Active || Deleted)
- {
- return;
- }
+ _altar.Hue = Champion != null ? 0x26 : 0;
+ }
- if (Champion != null)
+ if (_platform != null)
+ {
+ _platform.Hue = 0x452;
+ }
+
+ ExpireTime = Core.Now + ExpireDelay;
+ }
+
+ public void Stop()
+ {
+ if (!_active || Deleted)
+ {
+ return;
+ }
+
+ Active = false;
+ ActivatedByValor = false;
+ HasBeenAdvanced = false;
+ MaxLevel = 0;
+
+ _timerToken.Cancel();
+ _restartTimerToken.Cancel();
+
+ if (_altar != null)
+ {
+ _altar.Hue = 0;
+ }
+
+ if (_platform != null)
+ {
+ _platform.Hue = 0x497;
+ }
+
+ if (AlwaysActive)
+ {
+ BeginRestart(RestartDelay);
+ }
+ else if (ActivatedByProximity)
+ {
+ ActivatedByProximity = false;
+ NextProximityTime = Core.Now + TimeSpan.FromHours(6.0);
+ }
+
+ Timer.StartTimer(TimeSpan.FromMinutes(10.0), ExpireCreatures);
+ }
+
+ public void BeginRestart(TimeSpan ts)
+ {
+ RestartTime = Core.Now + ts;
+ _restartTimerToken.Cancel();
+ Timer.StartTimer(ts, EndRestart, out _restartTimerToken);
+ }
+
+ public void EndRestart()
+ {
+ HasBeenAdvanced = false;
+ ReadyToActivate = true;
+ }
+
+ private ScrollofTranscendence CreateRandomTramSoT()
+ {
+ int level = Utility.Random(5) + 1;
+ return ScrollofTranscendence.CreateRandom(level, level);
+ }
+
+ private ScrollofTranscendence CreateRandomFelSoT()
+ {
+ int level = Utility.Random(5) + 1;
+ return ScrollofTranscendence.CreateRandom(level, level);
+ }
+
+ private static PowerScroll CreateRandomFelPS() => PowerScroll.CreateRandomNoCraft(5, 5);
+
+ public static void GiveScrollOfTranscendenceFelTo(Mobile killer, ScrollofTranscendence SoTF)
+ {
+ if (SoTF == null || killer == null) //sanity
+ {
+ return;
+ }
+
+ killer.SendLocalizedMessage(1094936); // You have received a Scroll of Transcendence!
+
+ if (killer.Alive)
+ {
+ killer.AddToBackpack(SoTF);
+ }
+ else if (killer.Corpse is { Deleted: false })
+ {
+ killer.Corpse.DropItem(SoTF);
+ }
+ else
+ {
+ killer.AddToBackpack(SoTF);
+ }
+
+ // Justice reward
+ var pm = (PlayerMobile)killer;
+ var prot = JusticeVirtue.GetProtector(pm);
+ if (prot == null || prot.Map != killer.Map || prot.Kills >= 5 || prot.Criminal ||
+ !JusticeVirtue.CheckMapRegion(killer, prot))
+ {
+ return;
+ }
+
+ 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!
+ ScrollofTranscendence SoTFduplicate = new ScrollofTranscendence (SoTF.Skill, SoTF.Value);
+ prot.AddToBackpack(SoTFduplicate);
+ }
+ }
+
+ public static void GivePowerScrollFelTo (Mobile killer, PowerScroll PS)
+ {
+ if (PS == null || killer == null) //sanity
+ {
+ return;
+ }
+
+ killer.SendLocalizedMessage(1049524); // You have received a scroll of power!
+
+ if (killer.Alive)
+ {
+ killer.AddToBackpack(PS);
+ }
+ else if (killer.Corpse is { Deleted: false })
+ {
+ killer.Corpse.DropItem(PS);
+ }
+ else
+ {
+ killer.AddToBackpack(PS);
+ }
+
+ // Justice reward
+ var pm = (PlayerMobile)killer;
+ var prot = JusticeVirtue.GetProtector(pm);
+ if (prot == null || prot.Map != killer.Map || prot.Kills >= 5 || prot.Criminal ||
+ !JusticeVirtue.CheckMapRegion(killer, prot))
+ {
+ return;
+ }
+
+ 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(CreateRandomFelPS());
+ }
+ }
+
+ public void OnSlice()
+ {
+ if (!_active || Deleted)
+ {
+ return;
+ }
+
+ if (Champion != null)
+ {
+ if (Champion.Deleted)
{
- if (Champion.Deleted)
+ RegisterDamageTo(Champion);
+
+ if (Core.ML)
{
- RegisterDamageTo(Champion);
-
- if (Core.ML)
- {
- AwardArtifact((Champion as BaseChampion)?.GetArtifact());
- }
-
- DamageEntries.Clear();
-
- if (m_Platform != null)
- {
- m_Platform.Hue = 0x497;
- }
-
- if (m_Altar != null)
- {
- m_Altar.Hue = 0;
-
- if (HasStarRoomGate && (!Core.ML || Map == Map.Felucca))
- {
- new StarRoomGate(m_Altar.Location, m_Altar.Map, true);
- }
- }
-
- Champion = null;
- Stop();
+ AwardArtifact((Champion as BaseChampion)?.GetArtifact());
}
- }
- else
- {
- int kills = m_Kills;
- for (var i = 0; i < m_Creatures.Count; ++i)
+ this.Clear(_damageEntries);
+
+ if (_platform != null)
{
- Mobile m = m_Creatures[i];
+ _platform.Hue = 0x497;
+ }
- if (m.Deleted)
+ if (_altar != null)
+ {
+ _altar.Hue = 0;
+
+ if (HasStarRoomGate && (!Core.ML || Map == Map.Felucca))
{
- if (m.Corpse is { Deleted: false })
+ new StarRoomGate(_altar.Location, _altar.Map, true);
+ }
+ }
+
+ Champion = null;
+ Stop();
+ }
+ }
+ else
+ {
+ int kills = _kills;
+
+ for (var i = 0; i < _creatures.Count; ++i)
+ {
+ Mobile m = _creatures[i];
+
+ if (m.Deleted)
+ {
+ if (m.Corpse is { Deleted: false })
+ {
+ ((Corpse)m.Corpse).BeginDecay(TimeSpan.FromMinutes(1));
+ }
+
+ this.RemoveAt(_creatures, i);
+ --i;
+ ++_kills;
+
+ Mobile killer = m.FindMostRecentDamager(false);
+
+ RegisterDamageTo(m);
+
+ if (killer is BaseCreature creature)
+ {
+ killer = creature.GetMaster();
+ }
+
+ if (killer is PlayerMobile pm)
+ {
+ if (Core.ML)
{
- ((Corpse)m.Corpse).BeginDecay(TimeSpan.FromMinutes(1));
- }
-
- m_Creatures.RemoveAt(i);
- --i;
- ++m_Kills;
-
- Mobile killer = m.FindMostRecentDamager(false);
-
- RegisterDamageTo(m);
-
- if (killer is BaseCreature creature)
- {
- killer = creature.GetMaster();
- }
-
- if (killer is PlayerMobile pm)
- {
- if (Core.ML)
+ if (Map == Map.Felucca)
{
- if (Map == Map.Felucca)
+ // 1 in 1000 you get either a scroll of transcendence or a powerscroll
+ var random = Utility.Random(2000);
+ if (random == 0)
{
- // 1 in 1000 you get either a scroll of transcendence or a powerscroll
- var random = Utility.Random(2000);
- if (random == 0)
- {
- GiveScrollOfTranscendenceFelTo(pm, CreateRandomFelSoT());
- }
- else if (random == 1)
- {
- GivePowerScrollFelTo(pm, CreateRandomFelPS());
- }
+ GiveScrollOfTranscendenceFelTo(pm, CreateRandomFelSoT());
}
-
- if ((Map == Map.Ilshenar || Map == Map.Tokuno) && Utility.Random(10000) < 15)
+ else if (random == 1)
{
- pm.SendLocalizedMessage(1094936); // You have received a Scroll of Transcendence!
- pm.AddToBackpack(CreateRandomTramSoT());
+ GivePowerScrollFelTo(pm, CreateRandomFelPS());
}
}
- int mobSubLevel = GetSubLevelfor(m) + 1;
-
- if (mobSubLevel >= 0)
+ if ((Map == Map.Ilshenar || Map == Map.Tokuno) && Utility.Random(10000) < 15)
{
- bool gainedPath = false;
+ pm.SendLocalizedMessage(1094936); // You have received a Scroll of Transcendence!
+ pm.AddToBackpack(CreateRandomTramSoT());
+ }
+ }
- int pointsToGain = mobSubLevel * 40;
+ int mobSubLevel = GetSubLevelfor(m) + 1;
- if (VirtueSystem.Award(pm, VirtueName.Valor, pointsToGain, ref gainedPath))
+ if (mobSubLevel >= 0)
+ {
+ bool gainedPath = false;
+
+ int pointsToGain = mobSubLevel * 40;
+
+ if (VirtueSystem.Award(pm, VirtueName.Valor, pointsToGain, ref gainedPath))
+ {
+ if (gainedPath)
{
- if (gainedPath)
- {
- pm.SendLocalizedMessage(1054032); // You have gained a path in Valor!
- }
- else
- {
- pm.SendLocalizedMessage(1054030); // You have gained in Valor!
- }
-
- // No delay on Valor gains
+ pm.SendLocalizedMessage(1054032); // You have gained a path in Valor!
+ }
+ else
+ {
+ pm.SendLocalizedMessage(1054030); // You have gained in Valor!
}
- pm.ChampionTitles.Award(m_Type, mobSubLevel);
+ // No delay on Valor gains
}
+
+ pm.ChampionTitles.Award(_type, mobSubLevel);
}
}
}
-
- // Only really needed once.
- if (m_Kills > kills)
- {
- InvalidateProperties();
- }
-
- double n = m_Kills / (double)MaxKills;
- int p = (int)(n * 100);
-
- if (p >= 99)
- {
- AdvanceLevel();
- }
- else if (p > 0)
- {
- SetWhiteSkullCount(p / 20);
- }
-
- if (Core.Now >= ExpireTime)
- {
- Expire();
- }
-
- Respawn();
}
- }
- public void AdvanceLevel()
- {
- ExpireTime = Core.Now + ExpireDelay;
-
- if (Level < m_MaxLevel)
+ // Only really needed once.
+ if (_kills > kills)
{
- m_Kills = 0;
- ++Level;
InvalidateProperties();
- SetWhiteSkullCount(0);
+ }
- if (m_Altar != null)
- {
- Effects.PlaySound(m_Altar.Location, m_Altar.Map, 0x29);
- Effects.SendLocationEffect(new Point3D(m_Altar.X + 1, m_Altar.Y + 1, m_Altar.Z), m_Altar.Map, 0x3728, 10);
- }
- }
- else
+ double n = _kills / (double)MaxKills;
+ int p = (int)(n * 100);
+
+ if (p >= 99)
{
- SpawnChampion();
+ AdvanceLevel();
}
+ else if (p > 0)
+ {
+ SetWhiteSkullCount(p / 20);
+ }
+
+ if (Core.Now >= ExpireTime)
+ {
+ Expire();
+ }
+
+ Respawn();
}
+ }
- public void SpawnChampion()
+ public void AdvanceLevel()
+ {
+ ExpireTime = Core.Now + ExpireDelay;
+
+ if (Level < _maxLevel)
{
- if (m_Altar != null)
- {
- m_Altar.Hue = 0x26;
- }
-
- if (m_Platform != null)
- {
- m_Platform.Hue = 0x452;
- }
-
- m_Kills = 0;
- Level = 0;
+ _kills = 0;
+ ++Level;
InvalidateProperties();
SetWhiteSkullCount(0);
- try
+ if (_altar != null)
{
- Champion = ChampionSpawnInfo.GetInfo(m_Type).Champion.CreateInstance();
- }
- catch (Exception e)
- {
- logger.Error(
- e,
- "Failed to spawn champion \"{MobileType}\" at {Location} ({Map}).",
- m_Type,
- Location,
- Map
- );
+ Effects.PlaySound(_altar.Location, _altar.Map, 0x29);
+ Effects.SendLocationEffect(new Point3D(_altar.X + 1, _altar.Y + 1, _altar.Z), _altar.Map, 0x3728, 10);
}
+ }
+ else
+ {
+ SpawnChampion();
+ }
+ }
- if (Champion != null)
- {
- Champion.MoveToWorld(new Point3D(X, Y, Z - 15), Map);
+ public void SpawnChampion()
+ {
+ if (_altar != null)
+ {
+ _altar.Hue = 0x26;
+ }
- if (Champion is BaseCreature bc)
+ if (_platform != null)
+ {
+ _platform.Hue = 0x452;
+ }
+
+ _kills = 0;
+ Level = 0;
+ InvalidateProperties();
+ SetWhiteSkullCount(0);
+
+ try
+ {
+ Champion = ChampionSpawnInfo.GetInfo(_type).Champion.CreateInstance();
+ }
+ catch (Exception e)
+ {
+ logger.Error(
+ e,
+ "Failed to spawn champion \"{MobileType}\" at {Location} ({Map}).",
+ _type,
+ Location,
+ Map
+ );
+ }
+
+ if (Champion != null)
+ {
+ Champion.MoveToWorld(new Point3D(X, Y, Z - 15), Map);
+
+ if (Champion is BaseCreature bc)
+ {
+ if (ConfinedRoaming)
{
- if (ConfinedRoaming)
- {
- bc.Home = Location;
- bc.HomeMap = Map;
- bc.RangeHome = Math.Min(m_SpawnArea.Width / 2, m_SpawnArea.Height / 2);
- }
- else
- {
- bc.Home = bc.Location;
- bc.HomeMap = bc.Map;
-
- Point2D xWall1 = new Point2D(m_SpawnArea.X, bc.Y);
- Point2D xWall2 = new Point2D(m_SpawnArea.X + m_SpawnArea.Width, bc.Y);
- Point2D yWall1 = new Point2D(bc.X, m_SpawnArea.Y);
- Point2D yWall2 = new Point2D(bc.X, m_SpawnArea.Y + m_SpawnArea.Height);
-
- double minXDist = Math.Min(bc.GetDistanceToSqrt(xWall1), bc.GetDistanceToSqrt(xWall2));
- double minYDist = Math.Min(bc.GetDistanceToSqrt(yWall1), bc.GetDistanceToSqrt(yWall2));
-
- bc.RangeHome = (int)Math.Min(minXDist, minYDist);
- }
+ bc.Home = Location;
+ bc.HomeMap = Map;
+ bc.RangeHome = Math.Min(_spawnArea.Width / 2, _spawnArea.Height / 2);
}
else
{
- throw new Exception("Champion Spawn is not inherited from BaseCreature");
+ bc.Home = bc.Location;
+ bc.HomeMap = bc.Map;
+
+ Point2D xWall1 = new Point2D(_spawnArea.X, bc.Y);
+ Point2D xWall2 = new Point2D(_spawnArea.X + _spawnArea.Width, bc.Y);
+ Point2D yWall1 = new Point2D(bc.X, _spawnArea.Y);
+ Point2D yWall2 = new Point2D(bc.X, _spawnArea.Y + _spawnArea.Height);
+
+ double minXDist = Math.Min(bc.GetDistanceToSqrt(xWall1), bc.GetDistanceToSqrt(xWall2));
+ double minYDist = Math.Min(bc.GetDistanceToSqrt(yWall1), bc.GetDistanceToSqrt(yWall2));
+
+ bc.RangeHome = (int)Math.Min(minXDist, minYDist);
}
}
- }
-
- public virtual int MaxSpawn => 250 - GetSubLevel() * 40;
-
- public void Respawn()
- {
- if (!m_Active || Deleted || Champion != null)
- {
- return;
- }
-
- while (m_Creatures.Count < MaxSpawn)
- {
- Mobile m = Spawn();
-
- if (m == null)
- {
- return;
- }
-
- Point3D loc = GetSpawnLocation();
-
- // Allow creatures to turn into Paragons at Ilshenar champions.
- m.OnBeforeSpawn(loc, Map);
-
- m_Creatures.Add(m);
- m.MoveToWorld(loc, Map);
-
- if (m is BaseCreature bc)
- {
- bc.Tamable = false;
-
- if (ConfinedRoaming)
- {
- bc.Home = Location;
- bc.HomeMap = Map;
- bc.RangeHome = Math.Min(m_SpawnArea.Width / 2, m_SpawnArea.Height / 2);
- }
- else
- {
- bc.Home = bc.Location;
- bc.HomeMap = bc.Map;
-
- Point2D xWall1 = new Point2D(m_SpawnArea.X, bc.Y);
- Point2D xWall2 = new Point2D(m_SpawnArea.X + m_SpawnArea.Width, bc.Y);
- Point2D yWall1 = new Point2D(bc.X, m_SpawnArea.Y);
- Point2D yWall2 = new Point2D(bc.X, m_SpawnArea.Y + m_SpawnArea.Height);
-
- double minXDist = Math.Min(bc.GetDistanceToSqrt(xWall1), bc.GetDistanceToSqrt(xWall2));
- double minYDist = Math.Min(bc.GetDistanceToSqrt(yWall1), bc.GetDistanceToSqrt(yWall2));
-
- bc.RangeHome = (int)Math.Min(minXDist, minYDist);
- }
- }
- }
- }
-
- public Point3D GetSpawnLocation()
- {
- Map map = Map;
-
- if (map == null)
- {
- return Location;
- }
-
- // Try 20 times to find a spawnable location.
- for (int i = 0; i < 20; i++)
- {
- int x = Utility.Random(m_SpawnArea.X, m_SpawnArea.Width);
- int y = Utility.Random(m_SpawnArea.Y, m_SpawnArea.Height);
-
- int z = Map.GetAverageZ(x, y);
-
- if (Map.CanSpawnMobile(new Point2D(x, y), z))
- {
- return new Point3D(x, y, z);
- }
- }
-
- return Location;
- }
-
- public int Level1 => 4;
- public int Level2 => 8;
- public int Level3 => 12;
-
- public int GetSubLevel()
- {
- int level = Level;
-
- return level <= Level1 ? 0 : level <= Level2 ? 1 : level <= Level3 ? 2 : 3;
- }
-
- public int GetSubLevelfor (Mobile m)
- {
- Type[][] types = ChampionSpawnInfo.GetInfo(m_Type).SpawnTypes;
- Type t = m.GetType();
-
- for (int i = 0; i < types.GetLength(0); i++)
- {
- Type[] individualTypes = types[i];
-
- for (int j = 0; j < individualTypes.Length; j++)
- {
- if (t == individualTypes[j])
- {
- return i;
- }
- }
- }
-
- return -1;
- }
-
- public Mobile Spawn()
- {
- Type[][] types = ChampionSpawnInfo.GetInfo(m_Type).SpawnTypes;
-
- int v = GetSubLevel();
-
- if (v >= 0 && v < types.Length)
- {
- return Spawn(types[v]);
- }
-
- return null;
- }
-
- public Mobile Spawn(params Type[] types)
- {
- var type = types[Utility.Random(types.Length)];
- try
- {
- return type.CreateInstance();
- }
- catch (Exception e)
- {
- logger.Error(
- e,
- "Failed to spawn minion \"{Type}\" for champion {ChampionSpawnType} at {Location} ({Map}).",
- type,
- m_Type,
- Location,
- Map
- );
- return null;
- }
- }
-
- public void Expire()
- {
- m_Kills = 0;
-
- if (m_WhiteSkulls.Count == 0)
- {
- // They didn't even get 20%, go back a level
-
- if (Level > 0)
- {
- --Level;
- }
-
- if (!AlwaysActive && Level == 0)
- {
- Stop();
- }
-
- InvalidateProperties();
- }
else
{
- SetWhiteSkullCount(0);
- }
-
- ExpireTime = Core.Now + ExpireDelay;
- }
-
- public Point3D GetRedSkullLocation(int index)
- {
- int x, y;
-
- if (index < 5)
- {
- x = index - 2;
- y = -2;
- }
- else if (index < 9)
- {
- x = 2;
- y = index - 6;
- }
- else if (index < 13)
- {
- x = 10 - index;
- y = 2;
- }
- else
- {
- x = -2;
- y = 14 - index;
- }
-
- return new Point3D(X + x, Y + y, Z - 15);
- }
-
- public Point3D GetWhiteSkullLocation(int index)
- {
- int x, y;
-
- switch(index)
- {
- default: x = -1; y = -1; break;
- case 1: x = 1; y = -1; break;
- case 2: x = 1; y = 1; break;
- case 3: x = -1; y = 1; break;
- }
-
- return new Point3D(X + x, Y + y, Z - 15);
- }
-
- public override void AddNameProperty(IPropertyList list)
- {
- list.Add("champion spawn");
- }
-
- public override void GetProperties(IPropertyList list)
- {
- base.GetProperties(list);
-
- if (m_Active)
- {
- list.Add(1060742); // active
- list.Add(1060658, $"{"Type"}\t{m_Type}"); // ~1_val~: ~2_val~
- list.Add(1060659, $"{"Level"}\t{Level}"); // ~1_val~: ~2_val~
- var killRatio = 100.0 * ((double)m_Kills / MaxKills);
- list.Add(1060660, $"{"Kills"}\t{m_Kills} of {MaxKills} ({killRatio:F1}%)"); // ~1_val~: ~2_val~
- }
- else
- {
- list.Add(1060743); // inactive
+ throw new Exception("Champion Spawn is not inherited from BaseCreature");
}
}
+ }
- public override void OnSingleClick(Mobile from)
+ public virtual int MaxSpawn => 250 - GetSubLevel() * 40;
+
+ public void Respawn()
+ {
+ if (!_active || Deleted || Champion != null)
{
- if (m_Active)
- {
- LabelTo(from, $"{m_Type} (Active; Level: {Level}; Kills: {m_Kills}/{MaxKills})");
- }
- else
- {
- LabelTo(from, $"{m_Type} (Inactive)");
- }
+ return;
}
- public override void OnDoubleClick(Mobile from)
+ while (_creatures.Count < MaxSpawn)
{
- from.SendGump(new PropertiesGump(from, this));
- }
+ Mobile m = Spawn();
- public override void OnLocationChange(Point3D oldLoc)
- {
- if (Deleted)
- {
- return;
- }
-
- if (m_Platform != null)
- {
- m_Platform.Location = new Point3D(X, Y, Z - 20);
- }
-
- if (m_Altar != null)
- {
- m_Altar.Location = new Point3D(X, Y, Z - 15);
- }
-
- if (m_Idol != null)
- {
- m_Idol.Location = new Point3D(X, Y, Z - 15);
- }
-
- if (m_RedSkulls != null)
- {
- for (var i = 0; i < Math.Min(m_RedSkulls.Count, 16); ++i)
- {
- m_RedSkulls[i].Location = GetRedSkullLocation(i);
- }
- }
-
- if (m_WhiteSkulls != null)
- {
- for (int i = 0; i < m_WhiteSkulls.Count; ++i)
- {
- m_WhiteSkulls[i].Location = GetWhiteSkullLocation(i);
- }
- }
-
- m_SpawnArea.X += Location.X - oldLoc.X;
- m_SpawnArea.Y += Location.Y - oldLoc.Y;
-
- UpdateRegion();
- }
-
- public override void OnMapChange()
- {
- if (Deleted)
- {
- return;
- }
-
- if (m_Platform != null)
- {
- m_Platform.Map = Map;
- }
-
- if (m_Altar != null)
- {
- m_Altar.Map = Map;
- }
-
- if (m_Idol != null)
- {
- m_Idol.Map = Map;
- }
-
- if (m_RedSkulls != null)
- {
- for (int i = 0; i < m_RedSkulls.Count; ++i)
- {
- m_RedSkulls[i].Map = Map;
- }
- }
-
- if (m_WhiteSkulls != null)
- {
- for (int i = 0; i < m_WhiteSkulls.Count; ++i)
- {
- m_WhiteSkulls[i].Map = Map;
- }
- }
-
- UpdateRegion();
- }
-
- public override void OnAfterDelete()
- {
- base.OnAfterDelete();
-
- m_Platform?.Delete();
-
- m_Altar?.Delete();
-
- m_Idol?.Delete();
-
- if (m_RedSkulls != null)
- {
- for (int i = 0; i < m_RedSkulls.Count; ++i)
- {
- m_RedSkulls[i].Delete();
- }
-
- m_RedSkulls.Clear();
- }
-
- if (m_WhiteSkulls != null)
- {
- for (int i = 0; i < m_WhiteSkulls.Count; ++i)
- {
- m_WhiteSkulls[i].Delete();
- }
-
- m_WhiteSkulls.Clear();
- }
-
- DeleteCreatures();
-
- if (Champion is { Player: false })
- {
- Champion.Delete();
- }
-
- Stop();
-
- UpdateRegion();
- }
-
- public void ExpireCreatures()
- {
- if (!m_Active && !ReadyToActivate && !AlwaysActive)
- {
- DeleteCreatures();
- }
- }
-
- public void DeleteCreatures()
- {
- if (m_Creatures != null)
- {
- for (int i = 0; i < m_Creatures.Count; ++i)
- {
- Mobile mob = m_Creatures[i];
-
- if (!mob.Player)
- {
- mob.Delete();
- }
- }
-
- m_Creatures.Clear();
- }
- }
-
- public ChampionSpawn(Serial serial) : base(serial)
- {
- }
-
- public virtual void RegisterDamageTo(Mobile m)
- {
if (m == null)
{
return;
}
- foreach (DamageEntry de in m.DamageEntries)
+ Point3D loc = GetSpawnLocation();
+
+ // Allow creatures to turn into Paragons at Ilshenar champions.
+ m.OnBeforeSpawn(loc, Map);
+
+ this.Add(_creatures, m);
+ m.MoveToWorld(loc, Map);
+
+ if (m is BaseCreature bc)
{
- if (de.HasExpired)
+ bc.Tamable = false;
+
+ if (ConfinedRoaming)
{
- continue;
+ bc.Home = Location;
+ bc.HomeMap = Map;
+ bc.RangeHome = Math.Min(_spawnArea.Width / 2, _spawnArea.Height / 2);
}
-
- Mobile damager = de.Damager;
- Mobile master = damager.GetDamageMaster(m);
-
- if (master != null)
+ else
{
- damager = master;
+ bc.Home = bc.Location;
+ bc.HomeMap = bc.Map;
+
+ Point2D xWall1 = new Point2D(_spawnArea.X, bc.Y);
+ Point2D xWall2 = new Point2D(_spawnArea.X + _spawnArea.Width, bc.Y);
+ Point2D yWall1 = new Point2D(bc.X, _spawnArea.Y);
+ Point2D yWall2 = new Point2D(bc.X, _spawnArea.Y + _spawnArea.Height);
+
+ double minXDist = Math.Min(bc.GetDistanceToSqrt(xWall1), bc.GetDistanceToSqrt(xWall2));
+ double minYDist = Math.Min(bc.GetDistanceToSqrt(yWall1), bc.GetDistanceToSqrt(yWall2));
+
+ bc.RangeHome = (int)Math.Min(minXDist, minYDist);
}
-
- RegisterDamage(damager, de.DamageGiven);
}
}
-
- public void RegisterDamage(Mobile from, int amount)
- {
- if (from?.Player != true)
- {
- return;
- }
-
- if (DamageEntries.ContainsKey(from))
- {
- DamageEntries[from] += amount;
- }
- else
- {
- DamageEntries.Add(from, amount);
- }
- }
-
- public virtual void AwardArtifact(Item artifact)
- {
- if (artifact == null)
- {
- return;
- }
-
- if (DamageEntries.Count > 0)
- {
- int totalDamage = 0;
-
- Dictionary validEntries = new Dictionary();
-
- foreach (var (key, value) in DamageEntries)
- {
- if (IsEligible(key, artifact))
- {
- validEntries.Add(key, value);
- totalDamage += value;
- }
- }
-
- bool artifactGiven = false;
-
- do
- {
- int randomDamage = Utility.RandomMinMax(1, totalDamage);
-
- int checkDamage = 0;
-
- foreach (var (key, value) in validEntries)
- {
- checkDamage += value;
-
- if (checkDamage > randomDamage)
- {
- if (GiveArtifact(key, artifact))
- {
- artifactGiven = true;
- }
- else
- {
- validEntries.Remove(key);
- }
-
- break;
- }
- }
-
- if (validEntries.Count == 0) //EVERYONE has a full backpack?!@
- {
- artifact.Delete();
- break;
- }
- }
- while (!artifactGiven);
- }
- else
- {
- artifact.Delete();
- }
- }
-
- public bool GiveArtifact(Mobile to, Item artifact)
- {
- if (to == null || artifact == null)
- {
- return false;
- }
-
- Container pack = to.Backpack;
-
- if (pack?.TryDropItem(to, artifact, false) != true)
- {
- return false;
- }
-
- to.SendLocalizedMessage(1062317); // For your valor in combating the fallen beast, a special artifact has been bestowed on you.
- return true;
- }
-
- public bool IsEligible(Mobile m, Item Artifact) =>
- m.Player && m.Alive && m.Region != null && m.Region == m_Region &&
- m.Backpack?.CheckHold(m, Artifact, false) == true;
-
- public override void Serialize(IGenericWriter writer)
- {
- base.Serialize(writer);
-
- writer.Write(9); // version
-
- writer.Write(m_Level);
-
- writer.Write(ActivatedByProximity);
- writer.WriteDeltaTime(NextProximityTime);
-
- writer.Write(m_MaxLevel); //This can change, based on how you use the champion spawn
-
- writer.Write(ActivatedByValor);
-
- writer.Write(DamageEntries.Count);
- foreach (KeyValuePair kvp in DamageEntries)
- {
- writer.Write(kvp.Key);
- writer.Write(kvp.Value);
- }
-
- writer.Write(ConfinedRoaming);
- writer.Write(m_Idol);
- writer.Write(HasBeenAdvanced);
- writer.Write(m_SpawnArea);
-
- writer.Write(RandomizeType);
-
- writer.Write(m_Kills);
-
- writer.Write(m_Active);
- writer.Write((int)m_Type);
- writer.Write(m_Creatures);
- writer.Write(m_RedSkulls);
- writer.Write(m_WhiteSkulls);
- writer.Write(m_Platform);
- writer.Write(m_Altar);
- writer.Write(ExpireDelay);
- writer.WriteDeltaTime(ExpireTime);
- writer.Write(Champion);
- writer.Write(RestartDelay);
-
- if (_restartTimerToken.Running)
- {
- writer.Write(true);
- writer.WriteDeltaTime(RestartTime);
- }
- else
- {
- writer.Write(false);
- }
- }
-
- public override void Deserialize(IGenericReader reader)
- {
- base.Deserialize(reader);
-
- DamageEntries = new Dictionary();
-
- int version = reader.ReadInt();
-
- switch(version)
- {
- case 9:
- {
- m_Level = reader.ReadInt();
-
- goto case 8;
- }
- case 8:
- {
- ActivatedByProximity = reader.ReadBool();
- NextProximityTime = reader.ReadDeltaTime();
- goto case 7;
- }
- case 7:
- {
- m_MaxLevel = reader.ReadInt();
- goto case 6;
- }
- case 6:
- {
- if (version < 7)
- {
- m_MaxLevel = 16 + Utility.Random(3); //full levels
- }
-
- ActivatedByValor = reader.ReadBool();
- goto case 5;
- }
- case 5:
- {
- int entries = reader.ReadInt();
- Mobile m;
- int damage;
- for (int i = 0; i < entries; ++i)
- {
- m = reader.ReadEntity();
- damage = reader.ReadInt();
- if (m != null)
- {
- DamageEntries.Add(m, damage);
- }
- }
- goto case 4;
- }
- case 4:
- {
- ConfinedRoaming = reader.ReadBool();
- m_Idol = reader.ReadEntity();
- HasBeenAdvanced = reader.ReadBool();
-
- goto case 3;
- }
- case 3:
- {
- m_SpawnArea = reader.ReadRect2D();
-
- goto case 2;
- }
- case 2:
- {
- RandomizeType = reader.ReadBool();
-
- goto case 1;
- }
- case 1:
- {
- if (version < 3)
- {
- int oldRange = reader.ReadInt();
-
- m_SpawnArea = new Rectangle2D(new Point2D(X - oldRange, Y - oldRange), new Point2D(X + oldRange, Y + oldRange));
- }
-
- m_Kills = reader.ReadInt();
-
- goto case 0;
- }
- case 0:
- {
- if (version < 1)
- {
- m_SpawnArea = new Rectangle2D(new Point2D(X - 24, Y - 24), new Point2D(X + 24, Y + 24)); //Default was 24
- }
-
- bool active = reader.ReadBool();
- m_Type = (ChampionSpawnType)reader.ReadInt();
- m_Creatures = reader.ReadEntityList();
- m_RedSkulls = reader.ReadEntityList
- ();
- m_WhiteSkulls = reader.ReadEntityList
- ();
- m_Platform = reader.ReadEntity();
- m_Altar = reader.ReadEntity();
- ExpireDelay = reader.ReadTimeSpan();
- ExpireTime = reader.ReadDeltaTime();
- Champion = reader.ReadEntity();
- RestartDelay = reader.ReadTimeSpan();
-
- if (reader.ReadBool())
- {
- RestartTime = reader.ReadDeltaTime();
- BeginRestart(RestartTime - Core.Now);
- }
-
- if (version < 4)
- {
- m_Idol = new IdolOfTheChampion(this);
- m_Idol.MoveToWorld(new Point3D(X, Y, Z - 15), Map);
- }
-
- if (m_Platform == null || m_Altar == null || m_Idol == null)
- {
- Delete();
- }
- else if (active)
- {
- Start();
- }
- else if (AlwaysActive)
- {
- ReadyToActivate = true;
- }
-
- break;
- }
- }
-
- Timer.StartTimer(TimeSpan.Zero, UpdateRegion);
- }
}
- public class ChampionSpawnRegion : BaseRegion
+ public Point3D GetSpawnLocation()
{
- public ChampionSpawn Spawn { get; }
+ Map map = Map;
- public ChampionSpawnRegion(ChampionSpawn spawn) :
- base(null, spawn.Map, Find(spawn.Location, spawn.Map), spawn.SpawnArea) => Spawn = spawn;
-
- public override bool AllowHousing(Mobile from, Point3D p) => false;
-
- public bool CanSpawn() => Spawn.EjectLocation != new Point3D(0, 0, 0) && Spawn.EjectMap != null;
-
- public override void AlterLightLevel(Mobile m, ref int global, ref int personal)
+ if (map == null)
{
- base.AlterLightLevel(m, ref global, ref personal);
- global = Math.Max(global, 1 + Spawn.Level); //This is a guesstimate. TODO: Verify & get exact values // OSI testing: at 2 red skulls, light = 0x3 ; 1 red = 0x3.; 3 = 8; 9 = 0xD 8 = 0xD 12 = 0x12 10 = 0xD
+ return Location;
}
- public override void OnEnter(Mobile m)
+ // Try 20 times to find a spawnable location.
+ for (int i = 0; i < 20; i++)
{
- if (m.Player && m.AccessLevel == AccessLevel.Player && !Spawn.Active)
+ int x = Utility.Random(_spawnArea.X, _spawnArea.Width);
+ int y = Utility.Random(_spawnArea.Y, _spawnArea.Height);
+
+ int z = Map.GetAverageZ(x, y);
+
+ if (Map.CanSpawnMobile(new Point2D(x, y), z))
{
- Region parent = Parent ?? this;
+ return new Point3D(x, y, z);
+ }
+ }
- if (Spawn.ReadyToActivate)
+ return Location;
+ }
+
+ public int Level1 => 4;
+ public int Level2 => 8;
+ public int Level3 => 12;
+
+ public int GetSubLevel()
+ {
+ int level = Level;
+
+ return level <= Level1 ? 0 : level <= Level2 ? 1 : level <= Level3 ? 2 : 3;
+ }
+
+ public int GetSubLevelfor (Mobile m)
+ {
+ Type[][] types = ChampionSpawnInfo.GetInfo(_type).SpawnTypes;
+ Type t = m.GetType();
+
+ for (int i = 0; i < types.GetLength(0); i++)
+ {
+ Type[] individualTypes = types[i];
+
+ for (int j = 0; j < individualTypes.Length; j++)
+ {
+ if (t == individualTypes[j])
{
- Spawn.Start();
- }
- else if (Spawn.ProximitySpawn && !Spawn.ActivatedByProximity && Core.Now >= Spawn.NextProximityTime)
- {
- List players = parent.GetPlayers();
- List addresses = new List();
- for (var i = 0; i < players.Count; i++)
- {
- if (players[i].AccessLevel == AccessLevel.Player && players[i].NetState != null &&
- !addresses.Contains(players[i].NetState.Address) && !((PlayerMobile)players[i]).Young)
- {
- addresses.Add(players[i].NetState.Address);
- }
- }
-
- if (addresses.Count >= 15)
- {
- foreach (Mobile player in players)
- {
- player.SendMessage(0x20, Spawn.BroadcastMessage);
- }
-
- Spawn.ActivatedByProximity = true;
- Spawn.BeginRestart(TimeSpan.FromMinutes(5.0));
- }
+ return i;
}
}
}
- public override bool OnMoveInto(Mobile m, Direction d, Point3D newLocation, Point3D oldLocation)
+ return -1;
+ }
+
+ public Mobile Spawn()
+ {
+ Type[][] types = ChampionSpawnInfo.GetInfo(_type).SpawnTypes;
+
+ int v = GetSubLevel();
+
+ if (v >= 0 && v < types.Length)
{
- if (base.OnMoveInto(m, d, newLocation, oldLocation))
+ return Spawn(types[v]);
+ }
+
+ return null;
+ }
+
+ public Mobile Spawn(params Type[] types)
+ {
+ var type = types[Utility.Random(types.Length)];
+ try
+ {
+ return type.CreateInstance();
+ }
+ catch (Exception e)
+ {
+ logger.Error(
+ e,
+ "Failed to spawn minion \"{Type}\" for champion {ChampionSpawnType} at {Location} ({Map}).",
+ type,
+ _type,
+ Location,
+ Map
+ );
+ return null;
+ }
+ }
+
+ public void Expire()
+ {
+ _kills = 0;
+
+ if (_whiteSkulls.Count == 0)
+ {
+ // They didn't even get 20%, go back a level
+
+ if (Level > 0)
{
- if (m.Player)
+ --Level;
+ }
+
+ if (!AlwaysActive && Level == 0)
+ {
+ Stop();
+ }
+
+ InvalidateProperties();
+ }
+ else
+ {
+ SetWhiteSkullCount(0);
+ }
+
+ ExpireTime = Core.Now + ExpireDelay;
+ }
+
+ public Point3D GetRedSkullLocation(int index)
+ {
+ int x, y;
+
+ if (index < 5)
+ {
+ x = index - 2;
+ y = -2;
+ }
+ else if (index < 9)
+ {
+ x = 2;
+ y = index - 6;
+ }
+ else if (index < 13)
+ {
+ x = 10 - index;
+ y = 2;
+ }
+ else
+ {
+ x = -2;
+ y = 14 - index;
+ }
+
+ return new Point3D(X + x, Y + y, Z - 15);
+ }
+
+ public Point3D GetWhiteSkullLocation(int index)
+ {
+ int x, y;
+
+ switch(index)
+ {
+ default: x = -1; y = -1; break;
+ case 1: x = 1; y = -1; break;
+ case 2: x = 1; y = 1; break;
+ case 3: x = -1; y = 1; break;
+ }
+
+ return new Point3D(X + x, Y + y, Z - 15);
+ }
+
+ public override void AddNameProperty(IPropertyList list)
+ {
+ list.Add("champion spawn");
+ }
+
+ public override void GetProperties(IPropertyList list)
+ {
+ base.GetProperties(list);
+
+ if (_active)
+ {
+ list.Add(1060742); // active
+ list.Add(1060658, $"{"Type"}\t{_type}"); // ~1_val~: ~2_val~
+ list.Add(1060659, $"{"Level"}\t{Level}"); // ~1_val~: ~2_val~
+ var killRatio = 100.0 * ((double)_kills / MaxKills);
+ list.Add(1060660, $"{"Kills"}\t{_kills} of {MaxKills} ({killRatio:F1}%)"); // ~1_val~: ~2_val~
+ }
+ else
+ {
+ list.Add(1060743); // inactive
+ }
+ }
+
+ public override void OnSingleClick(Mobile from)
+ {
+ if (_active)
+ {
+ LabelTo(from, $"{_type} (Active; Level: {Level}; Kills: {_kills}/{MaxKills})");
+ }
+ else
+ {
+ LabelTo(from, $"{_type} (Inactive)");
+ }
+ }
+
+ public override void OnDoubleClick(Mobile from)
+ {
+ from.SendGump(new PropertiesGump(from, this));
+ }
+
+ public override void OnLocationChange(Point3D oldLoc)
+ {
+ if (Deleted)
+ {
+ return;
+ }
+
+ if (_platform != null)
+ {
+ _platform.Location = new Point3D(X, Y, Z - 20);
+ }
+
+ if (_altar != null)
+ {
+ _altar.Location = new Point3D(X, Y, Z - 15);
+ }
+
+ if (_idol != null)
+ {
+ _idol.Location = new Point3D(X, Y, Z - 15);
+ }
+
+ if (_redSkulls != null)
+ {
+ for (var i = 0; i < Math.Min(_redSkulls.Count, 16); ++i)
+ {
+ _redSkulls[i].Location = GetRedSkullLocation(i);
+ }
+ }
+
+ if (_whiteSkulls != null)
+ {
+ for (int i = 0; i < _whiteSkulls.Count; ++i)
+ {
+ _whiteSkulls[i].Location = GetWhiteSkullLocation(i);
+ }
+ }
+
+ _spawnArea.X += Location.X - oldLoc.X;
+ _spawnArea.Y += Location.Y - oldLoc.Y;
+
+ UpdateRegion();
+ }
+
+ public override void OnMapChange()
+ {
+ if (Deleted)
+ {
+ return;
+ }
+
+ if (_platform != null)
+ {
+ _platform.Map = Map;
+ }
+
+ if (_altar != null)
+ {
+ _altar.Map = Map;
+ }
+
+ if (_idol != null)
+ {
+ _idol.Map = Map;
+ }
+
+ if (_redSkulls != null)
+ {
+ for (int i = 0; i < _redSkulls.Count; ++i)
+ {
+ _redSkulls[i].Map = Map;
+ }
+ }
+
+ if (_whiteSkulls != null)
+ {
+ for (int i = 0; i < _whiteSkulls.Count; ++i)
+ {
+ _whiteSkulls[i].Map = Map;
+ }
+ }
+
+ UpdateRegion();
+ }
+
+ public override void OnAfterDelete()
+ {
+ base.OnAfterDelete();
+
+ _platform?.Delete();
+
+ _altar?.Delete();
+
+ _idol?.Delete();
+
+ if (_redSkulls != null)
+ {
+ for (int i = 0; i < _redSkulls.Count; ++i)
+ {
+ _redSkulls[i].Delete();
+ }
+
+ this.Clear(_redSkulls);
+ }
+
+ if (_whiteSkulls != null)
+ {
+ for (int i = 0; i < _whiteSkulls.Count; ++i)
+ {
+ _whiteSkulls[i].Delete();
+ }
+
+ this.Clear(_whiteSkulls);
+ }
+
+ DeleteCreatures();
+
+ if (Champion is { Player: false })
+ {
+ Champion.Delete();
+ }
+
+ Stop();
+
+ UpdateRegion();
+ }
+
+ public void ExpireCreatures()
+ {
+ if (!_active && !ReadyToActivate && !AlwaysActive)
+ {
+ DeleteCreatures();
+ }
+ }
+
+ public void DeleteCreatures()
+ {
+ if (_creatures != null)
+ {
+ for (int i = 0; i < _creatures.Count; ++i)
+ {
+ Mobile mob = _creatures[i];
+
+ if (!mob.Player)
{
- if (((PlayerMobile)m).Young)
+ mob.Delete();
+ }
+ }
+
+ this.Clear(_creatures);
+ }
+ }
+
+ public virtual void RegisterDamageTo(Mobile m)
+ {
+ if (m == null)
+ {
+ return;
+ }
+
+ foreach (DamageEntry de in m.DamageEntries)
+ {
+ if (de.HasExpired)
+ {
+ continue;
+ }
+
+ Mobile damager = de.Damager;
+ Mobile master = damager.GetDamageMaster(m);
+
+ if (master != null)
+ {
+ damager = master;
+ }
+
+ RegisterDamage(damager, de.DamageGiven);
+ }
+ }
+
+ public void RegisterDamage(Mobile from, int amount)
+ {
+ if (from?.Player != true)
+ {
+ return;
+ }
+
+ ref var value = ref CollectionsMarshal.GetValueRefOrAddDefault(_damageEntries, from, out _);
+ value += amount;
+
+ this.MarkDirty();
+ }
+
+ public virtual void AwardArtifact(Item artifact)
+ {
+ if (artifact == null)
+ {
+ return;
+ }
+
+ if (DamageEntries.Count <= 0)
+ {
+ artifact.Delete();
+ return;
+ }
+
+ int totalDamage = 0;
+
+ Dictionary validEntries = new Dictionary();
+
+ foreach (var (key, value) in DamageEntries)
+ {
+ if (IsEligible(key, artifact))
+ {
+ validEntries.Add(key, value);
+ totalDamage += value;
+ }
+ }
+
+ bool artifactGiven = false;
+
+ do
+ {
+ int randomDamage = Utility.RandomMinMax(1, totalDamage);
+
+ int checkDamage = 0;
+
+ foreach (var (key, value) in validEntries)
+ {
+ checkDamage += value;
+
+ if (checkDamage > randomDamage)
+ {
+ if (GiveArtifact(key, artifact))
{
- m.SendMessage("You decide against going here because of the danger.");
- }
- else if (!m.Alive)
- {
- m.SendMessage("A magical force prevents ghosts from entering this region.");
+ artifactGiven = true;
}
else
{
- return true;
+ validEntries.Remove(key);
}
+
+ break;
+ }
+ }
+
+ if (validEntries.Count == 0) // EVERYONE has a full backpack?!@
+ {
+ artifact.Delete();
+ break;
+ }
+ } while (!artifactGiven);
+ }
+
+ public static bool GiveArtifact(Mobile to, Item artifact)
+ {
+ if (to == null || artifact == null)
+ {
+ return false;
+ }
+
+ Container pack = to.Backpack;
+
+ if (pack?.TryDropItem(to, artifact, false) != true)
+ {
+ return false;
+ }
+
+ to.SendLocalizedMessage(1062317); // For your valor in combating the fallen beast, a special artifact has been bestowed on you.
+ return true;
+ }
+
+ public bool IsEligible(Mobile m, Item Artifact) =>
+ m.Player && m.Alive && m.Region != null && m.Region == m_Region &&
+ m.Backpack?.CheckHold(m, Artifact, false) == true;
+
+ private void Deserialize(IGenericReader reader, int version)
+ {
+ _damageEntries = new Dictionary();
+
+ _level = reader.ReadInt();
+ _activatedByProximity = reader.ReadBool();
+ _nextProximityTime = reader.ReadDeltaTime();
+ _maxLevel = reader.ReadInt();
+ _activatedByValor = reader.ReadBool();
+
+ int entries = reader.ReadInt();
+ for (int i = 0; i < entries; ++i)
+ {
+ var m = reader.ReadEntity();
+ var damage = reader.ReadInt();
+ if (m != null)
+ {
+ _damageEntries.Add(m, damage);
+ }
+ }
+
+ _confinedRoaming = reader.ReadBool();
+ _idol = reader.ReadEntity();
+ _hasBeenAdvanced = reader.ReadBool();
+ _spawnArea = reader.ReadRect2D();
+ _randomizeType = reader.ReadBool();
+ _kills = reader.ReadInt();
+
+ _active = reader.ReadBool();
+ _type = (ChampionSpawnType)reader.ReadInt();
+ _creatures = reader.ReadEntityList();
+ _redSkulls = reader.ReadEntityList
- ();
+ _whiteSkulls = reader.ReadEntityList
- ();
+ _platform = reader.ReadEntity();
+ _altar = reader.ReadEntity();
+ _expireDelay = reader.ReadTimeSpan();
+ _expireTime = reader.ReadDeltaTime();
+ _champion = reader.ReadEntity();
+ _restartDelay = reader.ReadTimeSpan();
+
+ if (reader.ReadBool())
+ {
+ _restartTime = reader.ReadDeltaTime();
+ }
+ }
+
+ [AfterDeserialization(false)]
+ private void AfterDeserialization()
+ {
+ if (_restartTime < Core.Now)
+ {
+ BeginRestart(_restartTime - Core.Now);
+ }
+
+ if (_platform == null || _altar == null || _idol == null)
+ {
+ Delete();
+ }
+ else if (_active)
+ {
+ _active = false; // Set back to false so we can properly start
+ Start();
+ }
+ else if (AlwaysActive)
+ {
+ ReadyToActivate = true;
+ }
+
+ Timer.StartTimer(TimeSpan.Zero, UpdateRegion);
+ }
+}
+
+public class ChampionSpawnRegion : BaseRegion
+{
+ public ChampionSpawn Spawn { get; }
+
+ public ChampionSpawnRegion(ChampionSpawn spawn) :
+ base(null, spawn.Map, Find(spawn.Location, spawn.Map), spawn.SpawnArea) => Spawn = spawn;
+
+ public override bool AllowHousing(Mobile from, Point3D p) => false;
+
+ public bool CanSpawn() => Spawn.EjectLocation != new Point3D(0, 0, 0) && Spawn.EjectMap != null;
+
+ public override void AlterLightLevel(Mobile m, ref int global, ref int personal)
+ {
+ base.AlterLightLevel(m, ref global, ref personal);
+ global = Math.Max(global, 1 + Spawn.Level); //This is a guesstimate. TODO: Verify & get exact values // OSI testing: at 2 red skulls, light = 0x3 ; 1 red = 0x3.; 3 = 8; 9 = 0xD 8 = 0xD 12 = 0x12 10 = 0xD
+ }
+
+ public override void OnEnter(Mobile m)
+ {
+ if (m.Player && m.AccessLevel == AccessLevel.Player && !Spawn.Active)
+ {
+ Region parent = Parent ?? this;
+
+ if (Spawn.ReadyToActivate)
+ {
+ Spawn.Start();
+ }
+ else if (Spawn.ProximitySpawn && !Spawn.ActivatedByProximity && Core.Now >= Spawn.NextProximityTime)
+ {
+ List players = parent.GetPlayers();
+ List addresses = new List();
+ for (var i = 0; i < players.Count; i++)
+ {
+ if (players[i].AccessLevel == AccessLevel.Player && players[i].NetState != null &&
+ !addresses.Contains(players[i].NetState.Address) && !((PlayerMobile)players[i]).Young)
+ {
+ addresses.Add(players[i].NetState.Address);
+ }
+ }
+
+ if (addresses.Count >= 15)
+ {
+ foreach (Mobile player in players)
+ {
+ player.SendMessage(0x20, Spawn.BroadcastMessage);
+ }
+
+ Spawn.ActivatedByProximity = true;
+ Spawn.BeginRestart(TimeSpan.FromMinutes(5.0));
+ }
+ }
+ }
+ }
+
+ public override bool OnMoveInto(Mobile m, Direction d, Point3D newLocation, Point3D oldLocation)
+ {
+ if (base.OnMoveInto(m, d, newLocation, oldLocation))
+ {
+ if (m.Player)
+ {
+ if (((PlayerMobile)m).Young)
+ {
+ m.SendMessage("You decide against going here because of the danger.");
+ }
+ else if (!m.Alive)
+ {
+ m.SendMessage("A magical force prevents ghosts from entering this region.");
}
else
{
return true;
}
}
+ else
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public override bool OnBeforeDeath(Mobile m)
+ {
+ if (Parent?.OnBeforeDeath(m) == false)
+ {
return false;
}
- public override bool OnBeforeDeath(Mobile m)
+ if (m.Player) //Give them 5 minutes to resurrect, then they are booted.
{
- if (Parent?.OnBeforeDeath(m) == false)
- {
- return false;
- }
-
- if (m.Player) //Give them 5 minutes to resurrect, then they are booted.
- {
- m.SendMessage("A magical force encompasses you, attempting to force you out of the area.");
- new EjectTimer(m, this).Start();
- }
-
- return true;
+ m.SendMessage("A magical force encompasses you, attempting to force you out of the area.");
+ new EjectTimer(m, this).Start();
}
- private class EjectTimer : Timer
+ return true;
+ }
+
+ private class EjectTimer : Timer
+ {
+ private readonly Mobile m_From;
+ private readonly ChampionSpawnRegion m_Region;
+
+ public EjectTimer(Mobile from, ChampionSpawnRegion region) : base(TimeSpan.FromMinutes(5.0))
{
- private readonly Mobile m_From;
- private readonly ChampionSpawnRegion m_Region;
+ m_From = from;
+ m_Region = region;
+ }
- public EjectTimer(Mobile from, ChampionSpawnRegion region) : base(TimeSpan.FromMinutes(5.0))
+ protected override void OnTick()
+ {
+ //See if they are dead, or logged out!
+ if (m_Region.Spawn != null && m_Region.CanSpawn() && !m_From.Alive)
{
- m_From = from;
- m_Region = region;
- }
-
- protected override void OnTick()
- {
- //See if they are dead, or logged out!
- if (m_Region.Spawn != null && m_Region.CanSpawn() && !m_From.Alive)
+ if (m_From.NetState != null)
{
- if (m_From.NetState != null)
+ if (m_From.Region.IsPartOf(m_Region))
{
- if (m_From.Region.IsPartOf(m_Region))
- {
- m_From.MoveToWorld(m_Region.Spawn.EjectLocation, m_Region.Spawn.EjectMap);
- m_From.SendMessage("A magical force forces you out of the area.");
- }
- }
- else if (Find(m_From.LogoutLocation, m_From.LogoutMap).IsPartOf(m_Region))
- {
- m_From.LogoutLocation = m_Region.Spawn.EjectLocation;
- m_From.LogoutMap = m_Region.Spawn.EjectMap;
+ m_From.MoveToWorld(m_Region.Spawn.EjectLocation, m_Region.Spawn.EjectMap);
+ m_From.SendMessage("A magical force forces you out of the area.");
}
}
+ else if (Find(m_From.LogoutLocation, m_From.LogoutMap).IsPartOf(m_Region))
+ {
+ m_From.LogoutLocation = m_Region.Spawn.EjectLocation;
+ m_From.LogoutMap = m_Region.Spawn.EjectMap;
+ }
}
}
}
+}
- public class IdolOfTheChampion : Item
+[SerializationGenerator(0, false)]
+public partial class IdolOfTheChampion : Item
+{
+ [SerializableField(0)]
+ private ChampionSpawn _spawn;
+
+ public override string DefaultName => "Idol of the Champion";
+
+ public IdolOfTheChampion(ChampionSpawn spawn): base(0x1F18)
{
- public ChampionSpawn Spawn { get; private set; }
+ _spawn = spawn;
+ Movable = false;
+ }
- public override string DefaultName => "Idol of the Champion";
+ public override void OnAfterDelete()
+ {
+ base.OnAfterDelete();
+ _spawn?.Delete();
+ }
- public IdolOfTheChampion(ChampionSpawn spawn): base(0x1F18)
+ [AfterDeserialization(false)]
+ private void AfterDeserialization()
+ {
+ if (_spawn == null)
{
- Spawn = spawn;
- Movable = false;
- }
-
- public override void OnAfterDelete()
- {
- base.OnAfterDelete();
-
- Spawn?.Delete();
- }
-
- public IdolOfTheChampion(Serial serial) : base(serial)
- {
- }
-
- public override void Serialize(IGenericWriter writer)
- {
- base.Serialize(writer);
-
- writer.Write(0); // version
-
- writer.Write(Spawn);
- }
-
- public override void Deserialize(IGenericReader reader)
- {
- base.Deserialize(reader);
-
- int version = reader.ReadInt();
-
- switch (version)
- {
- case 0:
- {
- Spawn = reader.ReadEntity();
-
- if (Spawn == null)
- {
- Delete();
- }
-
- break;
- }
- }
+ Delete();
}
}
}
diff --git a/Projects/UOContent/Engines/CannedEvil/ChampionSpawnInfo.cs b/Projects/UOContent/Engines/CannedEvil/ChampionSpawnInfo.cs
new file mode 100644
index 000000000..0754076d9
--- /dev/null
+++ b/Projects/UOContent/Engines/CannedEvil/ChampionSpawnInfo.cs
@@ -0,0 +1,150 @@
+using System;
+using Server.Mobiles;
+
+namespace Server.Engines.CannedEvil;
+
+public enum ChampionSpawnType
+{
+ Abyss,
+ Arachnid,
+ ColdBlood,
+ ForestLord,
+ VerminHorde,
+ UnholyTerror,
+ SleepingDragon,
+ Glade,
+ Corrupt,
+ // Unliving,
+ // Pit,
+ // DragonTurtle,
+ // Khaldun
+}
+
+public class ChampionSpawnInfo
+{
+ public ChampionSpawnType Type { get; }
+
+ public Type Champion { get; }
+
+ public Type[][] SpawnTypes { get; }
+
+ public TextDefinition[] LevelNames { get; }
+
+ public ChampionSpawnInfo(ChampionSpawnType type, Type champion, TextDefinition[] levelNames, Type[][] spawnTypes)
+ {
+ Type = type;
+ Champion = champion;
+ LevelNames = levelNames;
+ SpawnTypes = spawnTypes;
+ }
+
+ // After updating this table, make sure to update ChampionTitleContext.
+ public static ChampionSpawnInfo[] Table { get; } = {
+ new(ChampionSpawnType.Abyss, typeof(Semidar), new TextDefinition[]{ 1113118, 1113107, 1113096 }, new[]
+ {
+ new[]{ typeof(GreaterMongbat), typeof(Imp) }, // Level 1
+ new[]{ typeof(Gargoyle), typeof(Harpy) }, // Level 2
+ new[]{ typeof(FireGargoyle), typeof(StoneGargoyle) }, // Level 3
+ new[]{ typeof(Daemon), typeof(Succubus) } // Level 4
+ }),
+ new(ChampionSpawnType.Arachnid, typeof(Mephitis), new TextDefinition[]{ 1113117, 1113106, 1113095 }, new[]
+ {
+ new[]{ typeof(Scorpion), typeof(GiantSpider) }, // Level 1
+ new[]{ typeof(TerathanDrone), typeof(TerathanWarrior) }, // Level 2
+ new[]{ typeof(DreadSpider), typeof(TerathanMatriarch) }, // Level 3
+ new[]{ typeof(PoisonElemental), typeof(TerathanAvenger) } // Level 4
+ }),
+ new(ChampionSpawnType.ColdBlood, typeof(Rikktor), new TextDefinition[]{ 1113115, 1113104, 1113093 }, new[]
+ {
+ new[]{ typeof(Lizardman), typeof(GiantSerpent) }, // Level 1
+ new[]{ typeof(LavaLizard), typeof(OphidianWarrior) }, // Level 2
+ new[]{ typeof(Drake), typeof(OphidianArchmage) }, // Level 3
+ new[]{ typeof(Dragon), typeof(OphidianKnight) } // Level 4
+ }),
+ new(ChampionSpawnType.ForestLord, typeof(LordOaks), new TextDefinition[]{ 1113116, 1113105, 1113094 }, new[]
+ {
+ new[]{ typeof(Pixie), typeof(ShadowWisp) }, // Level 1
+ new[]{ typeof(Kirin), typeof(Wisp) }, // Level 2
+ new[]{ typeof(Centaur), typeof(Unicorn) }, // Level 3
+ new[]{ typeof(EtherealWarrior), typeof(SerpentineDragon) } // Level 4
+ }),
+ new(ChampionSpawnType.VerminHorde, typeof(Barracoon), new TextDefinition[]{ 1113119, 1113108, 1113097 }, new[]
+ {
+ new[]{ typeof(GiantRat), typeof(Slime) }, // Level 1
+ new[]{ typeof(DireWolf), typeof(Ratman) }, // Level 2
+ new[]{ typeof(HellHound), typeof(RatmanMage) }, // Level 3
+ new[]{ typeof(RatmanArcher), typeof(SilverSerpent) } // Level 4
+ }),
+ new(ChampionSpawnType.UnholyTerror, typeof(Neira), new TextDefinition[]{ 1113120, 1113109, 1113098 }, new[]
+ {
+ Core.AOS ? // Level 1
+ new[]{ typeof(Bogle), typeof(Ghoul), typeof(Shade), typeof(Spectre), typeof(Wraith) }
+ : new[]{ typeof(Ghoul), typeof(Shade), typeof(Spectre), typeof(Wraith) },
+
+ new[]{ typeof(BoneMagi), typeof(Mummy), typeof(SkeletalMage) }, // Level 2
+ new[]{ typeof(BoneKnight), typeof(Lich), typeof(SkeletalKnight) }, // Level 3
+ new[]{ typeof(LichLord), typeof(RottingCorpse) } // Level 4
+ }),
+ new(ChampionSpawnType.SleepingDragon, typeof(Serado), new TextDefinition[]{ 1113121, 1113110, 1113099 }, new[]
+ {
+ new[]{ typeof(DeathwatchBeetleHatchling), typeof(Lizardman) }, // Level 1
+ new[]{ typeof(DeathwatchBeetle), typeof(Kappa) }, // Level 2
+ new[]{ typeof(LesserHiryu), typeof(RevenantLion) }, // Level 3
+ new[]{ typeof(Hiryu), typeof(Oni) } // Level 4
+ }),
+ new(ChampionSpawnType.Glade, typeof(Twaulo), new TextDefinition[]{ 1113123, 1113112, 1113101 }, new[]
+ {
+ new[]{ typeof(Pixie), typeof(ShadowWisp) }, // Level 1
+ new[]{ typeof(Centaur), typeof(MLDryad) }, // Level 2
+ new[]{ typeof(Satyr), typeof(CuSidhe) }, // Level 3
+ new[]{ typeof(FeralTreefellow), typeof(RagingGrizzlyBear) } // Level 4
+ }),
+ new(ChampionSpawnType.Corrupt, typeof(Ilhenir), new TextDefinition[]{ 1113122, 1113111, 1113100 }, new[]
+ {
+ new[]{ typeof(PlagueSpawn), typeof(Bogling) }, // Level 1
+ new[]{ typeof(PlagueBeast), typeof(BogThing) }, // Level 2
+ new[]{ typeof(PlagueBeastLord), typeof(InterredGrizzle) }, // Level 3
+ new[]{ typeof(FetidEssence), typeof(PestilentBandage) } // Level 4
+ }),
+ // new(ChampionSpawnType.Unliving, typeof(PrimevalLich), new TextDefinition[]{ 1113124, 1113113, 1113102 }, new[]
+ // {
+ // new[]{ typeof(GoreFiend), typeof(VampireBat) },
+ // new[]{ typeof(FleshGolem), typeof(DarkWisp) },
+ // new[]{ typeof(UndeadGargoyle), typeof(Wight) },
+ // new[]{ typeof(SkeletalDrake), typeof(DreamWraith) }
+ // }),
+ // new(ChampionSpawnType.Pit, typeof(AbyssalInfernal), new TextDefinition[]{ 1113125, 1113114, 1113103 }, new[]
+ // {
+ // new[]{ typeof(HordeMinion), typeof(ChaosDaemon) },
+ // new[]{ typeof(StoneHarpy), typeof(ArcaneDaemon) },
+ // new[]{ typeof(PitFiend), typeof(Moloch) },
+ // new[]{ typeof(ArchDaemon), typeof(AbyssalAbomination) }
+ // }),
+ // new(ChampionSpawnType.Valley, typeof(DragonTurtle), new TextDefinition[]{ "Explorer", "Huntsman", "Msafiri" }, new[]
+ // {
+ // new[]{ typeof(MyrmidexDrone), typeof(MyrmidexLarvae) },
+ // new[]{ typeof(SilverbackGorilla), typeof(WildTiger) },
+ // new[]{ typeof(GreaterPhoenix ), typeof(Infernus) },
+ // new[]{ typeof(Dimetrosaur), typeof(Allosaurus) }
+ // }),
+ // new(ChampionSpawnType.Khaldun, typeof(KhalAnkur), new TextDefinition[]{ "Banisher", "Enforcer", "Eradicator" }, new[]
+ // {
+ // new[]{ typeof(SkelementalKnight), typeof(KhaldunBlood) },
+ // new[]{ typeof(SkelementalMage), typeof(Viscera) },
+ // new[]{ typeof(CultistAmbusher), typeof(ShadowFiend) },
+ // new[]{ typeof(KhalAnkurWarriors) }
+ // })
+ };
+
+ public static ChampionSpawnInfo GetInfo(ChampionSpawnType type)
+ {
+ var v = (int)type;
+
+ if (v < 0 || v >= Table.Length)
+ {
+ v = 0;
+ }
+
+ return Table[v];
+ }
+}
diff --git a/Projects/UOContent/Engines/CannedEvil/ChampionSpawnType.cs b/Projects/UOContent/Engines/CannedEvil/ChampionSpawnType.cs
deleted file mode 100755
index 546fc46ca..000000000
--- a/Projects/UOContent/Engines/CannedEvil/ChampionSpawnType.cs
+++ /dev/null
@@ -1,134 +0,0 @@
-/*************************************************************************
- * ModernUO *
- * Copyright 2019-2022 - ModernUO Development Team *
- * Email: hi@modernuo.com *
- * File: ChampionSpawnType.cs *
- * *
- * This program is free software: you can redistribute it and/or modify *
- * it under the terms of the GNU General Public License as published by *
- * the Free Software Foundation, either version 3 of the License, or *
- * (at your option) any later version. *
- * *
- * You should have received a copy of the GNU General Public License *
- * along with this program. If not, see . *
- *************************************************************************/
-
-using System;
-using Server.Mobiles;
-
-namespace Server.Engines.CannedEvil
-{
- public enum ChampionSpawnType
- {
- Abyss,
- Arachnid,
- ColdBlood,
- ForestLord,
- VerminHorde,
- UnholyTerror,
- SleepingDragon,
- Glade,
- Pestilence,
- Graveyard
- }
-
- public class ChampionSpawnInfo
- {
- public string Name { get; }
-
- public Type Champion { get; }
-
- public Type[][] SpawnTypes { get; }
-
- public string[] LevelNames { get; }
-
- public ChampionSpawnInfo(string name, Type champion, string[] levelNames, Type[][] spawnTypes)
- {
- Name = name;
- Champion = champion;
- LevelNames = levelNames;
- SpawnTypes = spawnTypes;
- }
-
- public static ChampionSpawnInfo[] Table { get; } = {
- new("Abyss", typeof(Semidar), new[]{ "Foe", "Assassin", "Conqueror" }, new[]
- {
- new[]{ typeof(GreaterMongbat), typeof(Imp) }, // Level 1
- new[]{ typeof(Gargoyle), typeof(Harpy) }, // Level 2
- new[]{ typeof(FireGargoyle), typeof(StoneGargoyle) }, // Level 3
- new[]{ typeof(Daemon), typeof(Succubus) } // Level 4
- }),
- new("Arachnid", typeof(Mephitis), new[]{ "Bane", "Killer", "Vanquisher" }, new[]
- {
- new[]{ typeof(Scorpion), typeof(GiantSpider) }, // Level 1
- new[]{ typeof(TerathanDrone), typeof(TerathanWarrior) }, // Level 2
- new[]{ typeof(DreadSpider), typeof(TerathanMatriarch) }, // Level 3
- new[]{ typeof(PoisonElemental), typeof(TerathanAvenger) } // Level 4
- }),
- new("Cold Blood", typeof(Rikktor), new[]{ "Blight", "Slayer", "Destroyer" }, new[]
- {
- new[]{ typeof(Lizardman), typeof(GiantSerpent) }, // Level 1
- new[]{ typeof(LavaLizard), typeof(OphidianWarrior) }, // Level 2
- new[]{ typeof(Drake), typeof(OphidianArchmage) }, // Level 3
- new[]{ typeof(Dragon), typeof(OphidianKnight) } // Level 4
- }),
- new("Forest Lord", typeof(LordOaks), new[]{ "Enemy", "Curse", "Slaughterer" }, new[]
- {
- new[]{ typeof(Pixie), typeof(ShadowWisp) }, // Level 1
- new[]{ typeof(Kirin), typeof(Wisp) }, // Level 2
- new[]{ typeof(Centaur), typeof(Unicorn) }, // Level 3
- new[]{ typeof(EtherealWarrior), typeof(SerpentineDragon) } // Level 4
- }),
- new("Vermin Horde", typeof(Barracoon), new[]{ "Adversary", "Subjugator", "Eradicator" }, new[]
- {
- new[]{ typeof(GiantRat), typeof(Slime) }, // Level 1
- new[]{ typeof(DireWolf), typeof(Ratman) }, // Level 2
- new[]{ typeof(HellHound), typeof(RatmanMage) }, // Level 3
- new[]{ typeof(RatmanArcher), typeof(SilverSerpent) } // Level 4
- }),
- new("Unholy Terror", typeof(Neira), new[]{ "Scourge", "Punisher", "Nemesis" }, new[]
- {
- Core.AOS ? // Level 1
- new[]{ typeof(Bogle), typeof(Ghoul), typeof(Shade), typeof(Spectre), typeof(Wraith) }
- : new[]{ typeof(Ghoul), typeof(Shade), typeof(Spectre), typeof(Wraith) },
-
- new[]{ typeof(BoneMagi), typeof(Mummy), typeof(SkeletalMage) }, // Level 2
- new[]{ typeof(BoneKnight), typeof(Lich), typeof(SkeletalKnight) }, // Level 3
- new[]{ typeof(LichLord), typeof(RottingCorpse) } // Level 4
- }),
- new("Sleeping Dragon", typeof(Serado), new[]{ "Rival", "Challenger", "Antagonist" } , new[]
- {
- new[]{ typeof(DeathwatchBeetleHatchling), typeof(Lizardman) }, // Level 1
- new[]{ typeof(DeathwatchBeetle), typeof(Kappa) }, // Level 2
- new[]{ typeof(LesserHiryu), typeof(RevenantLion) }, // Level 3
- new[]{ typeof(Hiryu), typeof(Oni) } // Level 4
- }),
- new("Glade", typeof(Twaulo), new[]{ "Banisher", "Enforcer", "Eradicator" } , new[]
- {
- new[]{ typeof(Pixie), typeof(ShadowWisp) }, // Level 1
- new[]{ typeof(Centaur), typeof(MLDryad) }, // Level 2
- new[]{ typeof(Satyr), typeof(CuSidhe) }, // Level 3
- new[]{ typeof(FeralTreefellow), typeof(RagingGrizzlyBear) } // Level 4
- }),
- new("The Corrupt", typeof(Ilhenir), new[]{ "Cleanser", "Expunger", "Depurator" } , new[]
- {
- new[]{ typeof(PlagueSpawn), typeof(Bogling) }, // Level 1
- new[]{ typeof(PlagueBeast), typeof(BogThing) }, // Level 2
- new[]{ typeof(PlagueBeastLord), typeof(InterredGrizzle) }, // Level 3
- new[]{ typeof(FetidEssence), typeof(PestilentBandage) } // Level 4
- }),
- };
-
- public static ChampionSpawnInfo GetInfo(ChampionSpawnType type)
- {
- var v = (int)type;
-
- if (v < 0 || v >= Table.Length)
- {
- v = 0;
- }
-
- return Table[v];
- }
- }
-}
diff --git a/Projects/UOContent/Engines/CannedEvil/ChampionTitle.cs b/Projects/UOContent/Engines/CannedEvil/ChampionTitle.cs
new file mode 100644
index 000000000..c1970a449
--- /dev/null
+++ b/Projects/UOContent/Engines/CannedEvil/ChampionTitle.cs
@@ -0,0 +1,30 @@
+using System;
+using ModernUO.Serialization;
+
+namespace Server.Engines.CannedEvil;
+
+[SerializationGenerator(0)]
+public partial class ChampionTitle
+{
+ [EncodedInt]
+ [SerializableField(0)]
+ private int _value;
+
+ // TODO: Change to delta time
+ [SerializableField(1)]
+ private DateTime _lastDecay;
+
+ public bool Atrophy(int value)
+ {
+ var before = Value;
+
+ Value -= Math.Min(value, before);
+
+ if (before != Value)
+ {
+ LastDecay = Core.Now;
+ }
+
+ return Value > 0;
+ }
+}
diff --git a/Projects/UOContent/Engines/CannedEvil/ChampionTitleContext.cs b/Projects/UOContent/Engines/CannedEvil/ChampionTitleContext.cs
new file mode 100644
index 000000000..27dd32213
--- /dev/null
+++ b/Projects/UOContent/Engines/CannedEvil/ChampionTitleContext.cs
@@ -0,0 +1,379 @@
+using System;
+using System.Runtime.CompilerServices;
+using ModernUO.Serialization;
+using Server.Mobiles;
+
+namespace Server.Engines.CannedEvil;
+
+[PropertyObject]
+[SerializationGenerator(1)]
+public partial class ChampionTitleContext
+{
+ private const int LossAmount = 90;
+ private static readonly TimeSpan LossDelay = TimeSpan.FromDays(1.0);
+
+ [SerializableField(0)]
+ [SerializedCommandProperty(AccessLevel.GameMaster)]
+ private int _harrower;
+
+ private PlayerMobile _player;
+
+ public PlayerMobile Player => _player;
+
+ public ChampionTitleContext(PlayerMobile player) => _player = player;
+
+ // Visible to PublicMobile for migrations
+ internal void Deserialize(IGenericReader reader, int version)
+ {
+ _harrower = reader.ReadEncodedInt();
+
+ var length = reader.ReadEncodedInt();
+
+ if (length == 0)
+ {
+ return;
+ }
+
+ // If the list/enum for ChampionSpawnInfo changes, then this has to change for migrations
+ for (var i = 0; i < length; i++)
+ {
+ var type = (ChampionSpawnType)i;
+ ref var title = ref GetTitleValueRef(type);
+
+ if (Unsafe.IsNullRef(ref title))
+ {
+ throw new NotImplementedException($"Cannot find ChampionSpawnType value {type}.");
+ }
+
+ title = new ChampionTitle();
+ title.Deserialize(reader);
+ }
+ }
+
+ [SerializableField(1)]
+ private ChampionTitle _abyss;
+
+ [SerializableFieldSaveFlag(1)]
+ private bool ShouldSerializeAbyss() => _abyss != null;
+
+ [CommandProperty(AccessLevel.GameMaster)]
+ public int AbyssValue
+ {
+ get => GetTitle(ChampionSpawnType.Abyss)?.Value ?? 0;
+ set => SetValue(ChampionSpawnType.Abyss, value);
+ }
+
+ [CommandProperty(AccessLevel.GameMaster)]
+ public DateTime AbyssLastDecay
+ {
+ get => GetTitle(ChampionSpawnType.Abyss)?.LastDecay ?? DateTime.MinValue;
+ set => SetLastDecay(ChampionSpawnType.Abyss, value);
+ }
+
+ [SerializableField(2)]
+ private ChampionTitle _arachnid;
+
+ [SerializableFieldSaveFlag(2)]
+ private bool ShouldSerializeArachnid() => _arachnid != null;
+
+ [CommandProperty(AccessLevel.GameMaster)]
+ public int ArachnidValue
+ {
+ get => GetTitle(ChampionSpawnType.Arachnid)?.Value ?? 0;
+ set => SetValue(ChampionSpawnType.Arachnid, value);
+ }
+
+ [CommandProperty(AccessLevel.GameMaster)]
+ public DateTime ArachnidLastDecay
+ {
+ get => GetTitle(ChampionSpawnType.Arachnid)?.LastDecay ?? DateTime.MinValue;
+ set => SetLastDecay(ChampionSpawnType.Arachnid, value);
+ }
+
+ [SerializableField(3)]
+ private ChampionTitle _coldBlood;
+
+ [SerializableFieldSaveFlag(3)]
+ private bool ShouldSerializeColdBlood() => _coldBlood != null;
+
+ [CommandProperty(AccessLevel.GameMaster)]
+ public int ColdBloodValue
+ {
+ get => GetTitle(ChampionSpawnType.ColdBlood)?.Value ?? 0;
+ set => SetValue(ChampionSpawnType.ColdBlood, value);
+ }
+
+ [CommandProperty(AccessLevel.GameMaster)]
+ public DateTime ColdBloodLastDecay
+ {
+ get => GetTitle(ChampionSpawnType.ColdBlood)?.LastDecay ?? DateTime.MinValue;
+ set => SetLastDecay(ChampionSpawnType.ColdBlood, value);
+ }
+
+ [SerializableField(4)]
+ private ChampionTitle _forestLord;
+
+ [SerializableFieldSaveFlag(4)]
+ private bool ShouldSerializeForestLord() => _forestLord != null;
+
+ [CommandProperty(AccessLevel.GameMaster)]
+ public int ForestLordValue
+ {
+ get => GetTitle(ChampionSpawnType.ForestLord)?.Value ?? 0;
+ set => SetValue(ChampionSpawnType.ForestLord, value);
+ }
+
+ [CommandProperty(AccessLevel.GameMaster)]
+ public DateTime ForestLordLastDecay
+ {
+ get => GetTitle(ChampionSpawnType.ForestLord)?.LastDecay ?? DateTime.MinValue;
+ set => SetLastDecay(ChampionSpawnType.ForestLord, value);
+ }
+
+ [SerializableField(5)]
+ private ChampionTitle _verminHorde;
+
+ [SerializableFieldSaveFlag(5)]
+ private bool ShouldSerializeVerminHorde() => _verminHorde != null;
+
+ [CommandProperty(AccessLevel.GameMaster)]
+ public int VerminHordeValue
+ {
+ get => GetTitle(ChampionSpawnType.VerminHorde)?.Value ?? 0;
+ set => SetValue(ChampionSpawnType.VerminHorde, value);
+ }
+
+ [CommandProperty(AccessLevel.GameMaster)]
+ public DateTime VerminHordeLastDecay
+ {
+ get => GetTitle(ChampionSpawnType.VerminHorde)?.LastDecay ?? DateTime.MinValue;
+ set => SetLastDecay(ChampionSpawnType.VerminHorde, value);
+ }
+
+ [SerializableField(6)]
+ private ChampionTitle _unholyTerror;
+
+ [SerializableFieldSaveFlag(6)]
+ private bool ShouldSerializeUnholyTerror() => _unholyTerror != null;
+
+ [CommandProperty(AccessLevel.GameMaster)]
+ public int UnholyTerrorValue
+ {
+ get => GetTitle(ChampionSpawnType.UnholyTerror)?.Value ?? 0;
+ set => SetValue(ChampionSpawnType.UnholyTerror, value);
+ }
+
+ [CommandProperty(AccessLevel.GameMaster)]
+ public DateTime UnholyTerrorLastDecay
+ {
+ get => GetTitle(ChampionSpawnType.UnholyTerror)?.LastDecay ?? DateTime.MinValue;
+ set => SetLastDecay(ChampionSpawnType.UnholyTerror, value);
+ }
+
+ [SerializableField(7)]
+ private ChampionTitle _sleepingDragon;
+
+ [SerializableFieldSaveFlag(7)]
+ private bool ShouldSerializeSleepingDragon() => _sleepingDragon != null;
+
+ [CommandProperty(AccessLevel.GameMaster)]
+ public int SleepingDragonValue
+ {
+ get => GetTitle(ChampionSpawnType.SleepingDragon)?.Value ?? 0;
+ set => SetValue(ChampionSpawnType.SleepingDragon, value);
+ }
+
+ [CommandProperty(AccessLevel.GameMaster)]
+ public DateTime SleepingDragonLastDecay
+ {
+ get => GetTitle(ChampionSpawnType.SleepingDragon)?.LastDecay ?? DateTime.MinValue;
+ set => SetLastDecay(ChampionSpawnType.SleepingDragon, value);
+ }
+
+ [SerializableField(8)]
+ private ChampionTitle _corrupt;
+
+ [SerializableFieldSaveFlag(8)]
+ private bool ShouldSerializeCorrupt() => _corrupt != null;
+
+ [CommandProperty(AccessLevel.GameMaster)]
+ public int CorruptValue
+ {
+ get => GetTitle(ChampionSpawnType.Corrupt)?.Value ?? 0;
+ set => SetValue(ChampionSpawnType.Corrupt, value);
+ }
+
+ [CommandProperty(AccessLevel.GameMaster)]
+ public DateTime CorruptLastDecay
+ {
+ get => GetTitle(ChampionSpawnType.Corrupt)?.LastDecay ?? DateTime.MinValue;
+ set => SetLastDecay(ChampionSpawnType.Corrupt, value);
+ }
+
+ [SerializableField(9)]
+ private ChampionTitle _glade;
+
+ [SerializableFieldSaveFlag(9)]
+ private bool ShouldSerializeGlade() => _glade != null;
+
+ [CommandProperty(AccessLevel.GameMaster)]
+ public int GladeValue
+ {
+ get => GetTitle(ChampionSpawnType.Glade)?.Value ?? 0;
+ set => SetValue(ChampionSpawnType.Glade, value);
+ }
+
+ [CommandProperty(AccessLevel.GameMaster)]
+ public DateTime GladeLastDecay
+ {
+ get => GetTitle(ChampionSpawnType.Glade)?.LastDecay ?? DateTime.MinValue;
+ set => SetLastDecay(ChampionSpawnType.Glade, value);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public ChampionTitle GetTitle(ChampionSpawnType type) => GetTitleValueRef(type);
+
+ // Get a pointer to the title so we can manipulate it internally such as nullifying it.
+ // Using this technique instead of an array decouples the context from changes to ChampionSpawnInfo.Table indexes
+ private ref ChampionTitle GetTitleValueRef(ChampionSpawnType type)
+ {
+ switch (type)
+ {
+ case ChampionSpawnType.Abyss:
+ {
+ return ref _abyss;
+ }
+ case ChampionSpawnType.Arachnid:
+ {
+ return ref _arachnid;
+ }
+ case ChampionSpawnType.ColdBlood:
+ {
+ return ref _coldBlood;
+ }
+ case ChampionSpawnType.ForestLord:
+ {
+ return ref _forestLord;
+ }
+ case ChampionSpawnType.VerminHorde:
+ {
+ return ref _verminHorde;
+ }
+ case ChampionSpawnType.UnholyTerror:
+ {
+ return ref _unholyTerror;
+ }
+ case ChampionSpawnType.SleepingDragon:
+ {
+ return ref _sleepingDragon;
+ }
+ case ChampionSpawnType.Glade:
+ {
+ return ref _glade;
+ }
+ case ChampionSpawnType.Corrupt:
+ {
+ return ref _corrupt;
+ }
+ default:
+ {
+ return ref Unsafe.NullRef();
+ }
+ }
+ }
+
+ public ChampionTitle TryGetOrCreateTitle(ChampionSpawnType type)
+ {
+ ref var title = ref GetTitleValueRef(type);
+ if (Unsafe.IsNullRef(ref title))
+ {
+ return null;
+ }
+
+ return title ??= new ChampionTitle();
+ }
+
+ public void SetValue(ChampionSpawnType type, int value)
+ {
+ ref var title = ref GetTitleValueRef(type);
+ if (Unsafe.IsNullRef(ref title))
+ {
+ return;
+ }
+
+ if (title != null)
+ {
+ if (value <= 0)
+ {
+ title = null;
+
+ _player.InvalidateProperties();
+ return;
+ }
+ }
+ else
+ {
+ title = new ChampionTitle();
+ }
+
+ title.Value = value;
+ title.LastDecay = Core.Now;
+ _player.InvalidateProperties();
+ }
+
+ public void SetLastDecay(ChampionSpawnType type, DateTime value)
+ {
+ var title = TryGetOrCreateTitle(type);
+ if (title != null)
+ {
+ title.LastDecay = value;
+ }
+ }
+
+ public void Award(ChampionSpawnType type, int value)
+ {
+ var title = TryGetOrCreateTitle(type);
+ if (title == null)
+ {
+ return;
+ }
+
+ title.Value += value;
+ if (title.LastDecay == DateTime.MinValue)
+ {
+ title.LastDecay = Core.Now;
+ }
+ }
+
+ public bool CheckAtrophy()
+ {
+ var valuesUsed = _harrower > 0;
+
+ for (var i = 0; i < ChampionSpawnInfo.Table.Length; i++)
+ {
+ ref var title = ref GetTitleValueRef(ChampionSpawnInfo.Table[i].Type);
+ if (Unsafe.IsNullRef(ref title))
+ {
+ continue;
+ }
+
+ var decay = title.LastDecay;
+ if (decay > DateTime.MinValue && decay + LossDelay < Core.Now)
+ {
+ // Use bitwise-or to force-execute the Atrophy function
+ var isUsed = title.Atrophy(LossAmount);
+
+ if (!isUsed)
+ {
+ title = null;
+ }
+
+ valuesUsed |= isUsed;
+ }
+ }
+
+ return valuesUsed;
+ }
+
+ public override string ToString() => "...";
+}
diff --git a/Projects/UOContent/Engines/CannedEvil/ChampionTitleSystem.cs b/Projects/UOContent/Engines/CannedEvil/ChampionTitleSystem.cs
new file mode 100644
index 000000000..bbb8f92bc
--- /dev/null
+++ b/Projects/UOContent/Engines/CannedEvil/ChampionTitleSystem.cs
@@ -0,0 +1,172 @@
+using System;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+using Server.Collections;
+using Server.Mobiles;
+
+namespace Server.Engines.CannedEvil;
+
+public static class ChampionTitleSystem
+{
+ // All of the players with murders
+ private static readonly Dictionary _championTitleContexts = new();
+
+ private static readonly Timer _championTitleTimer = new ChampionTitleTimer();
+
+ public static void Configure()
+ {
+ GenericPersistence.Register("ChampionTitles", Serialize, Deserialize);
+ }
+
+ public static void Initialize()
+ {
+ EventSink.PlayerDeleted += OnPlayerDeleted;
+
+ _championTitleTimer.Start();
+ }
+
+ private static void OnPlayerDeleted(Mobile m)
+ {
+ if (m is PlayerMobile pm)
+ {
+ _championTitleContexts.Remove(pm);
+ }
+ }
+
+ private static void Deserialize(IGenericReader reader)
+ {
+ var version = reader.ReadEncodedInt();
+
+ var count = reader.ReadEncodedInt();
+ for (var i = 0; i < count; ++i)
+ {
+ var context = new ChampionTitleContext(reader.ReadEntity());
+ context.Deserialize(reader);
+
+ _championTitleContexts.Add(context.Player, context);
+ }
+ }
+
+ private static void Serialize(IGenericWriter writer)
+ {
+ writer.WriteEncodedInt(0); // version
+
+ writer.WriteEncodedInt(_championTitleContexts.Count);
+ foreach (var (m, context) in _championTitleContexts)
+ {
+ writer.Write(m);
+ context.Serialize(writer);
+ }
+ }
+
+ public static bool GetChampionTitleContext(this PlayerMobile player, out ChampionTitleContext context) =>
+ _championTitleContexts.TryGetValue(player, out context);
+
+ public static ChampionTitleContext GetOrCreateChampionTitleContext(this PlayerMobile player)
+ {
+ ref var context = ref CollectionsMarshal.GetValueRefOrAddDefault(_championTitleContexts, player, out var exists);
+ if (!exists)
+ {
+ context = new ChampionTitleContext(player);
+ }
+
+ return context;
+ }
+
+ // Called when killing a harrower. Will give a minimum of 1 point.
+ public static void AwardHarrowerTitle(PlayerMobile pm)
+ {
+ var context = pm.GetOrCreateChampionTitleContext();
+
+ var count = 1;
+ for (var i = 0; i < ChampionSpawnInfo.Table.Length; i++)
+ {
+ var title = context.GetTitle(ChampionSpawnInfo.Table[i].Type);
+ if (title?.Value > 900)
+ {
+ count++;
+ }
+ }
+
+ context.Harrower = Math.Max(count, context.Harrower); // Harrower titles never decay.
+ }
+
+ public static int GetChampionTitleLabel(this PlayerMobile player)
+ {
+ if (!player.GetChampionTitleContext(out var context))
+ {
+ return 0;
+ }
+
+ if (context.Harrower > 0)
+ {
+ return 1113082 + Math.Min(context.Harrower, 10);
+ }
+
+ var highestValue = 0;
+ var highestType = 0;
+
+ for (var i = 0; i < ChampionSpawnInfo.Table.Length; i++)
+ {
+ var t = context.GetTitle(ChampionSpawnInfo.Table[i].Type);
+ if (t == null)
+ {
+ continue;
+ }
+
+ var v = t.Value;
+
+ if (v > highestValue)
+ {
+ highestValue = v;
+ highestType = i;
+ }
+ }
+
+ var offset = highestValue switch
+ {
+ > 800 => 3,
+ > 300 => highestValue / 300,
+ _ => 0
+ };
+
+ if (offset > 0)
+ {
+ var champInfo = ChampionSpawnInfo.Table[highestType];
+ var championLevelName = champInfo.LevelNames[Math.Min(offset, champInfo.LevelNames.Length) - 1];
+ return championLevelName.Number;
+ }
+
+ return 0;
+ }
+
+ private class ChampionTitleTimer : Timer
+ {
+ public ChampionTitleTimer() : base(TimeSpan.FromMinutes(5.0), TimeSpan.FromMinutes(5.0))
+ {
+ }
+
+ protected override void OnTick()
+ {
+ if (_championTitleContexts.Count == 0)
+ {
+ return;
+ }
+
+ using var queue = PooledRefQueue.Create();
+
+ foreach (var context in _championTitleContexts.Values)
+ {
+ if (!context.CheckAtrophy())
+ {
+ queue.Enqueue(context.Player);
+ }
+ }
+
+ while (queue.Count > 0)
+ {
+ _championTitleContexts.Remove((PlayerMobile)queue.Dequeue());
+ }
+ }
+ }
+}
diff --git a/Projects/UOContent/Engines/CannedEvil/DungeonChampionSpawn.cs b/Projects/UOContent/Engines/CannedEvil/DungeonChampionSpawn.cs
index f1bd4a0fe..eeff89c3c 100644
--- a/Projects/UOContent/Engines/CannedEvil/DungeonChampionSpawn.cs
+++ b/Projects/UOContent/Engines/CannedEvil/DungeonChampionSpawn.cs
@@ -13,42 +13,30 @@
* along with this program. If not, see . *
*************************************************************************/
-namespace Server.Engines.CannedEvil
+using ModernUO.Serialization;
+
+namespace Server.Engines.CannedEvil;
+
+[SerializationGenerator(0)]
+public partial class DungeonChampionSpawn : ChampionSpawn
{
- public class DungeonChampionSpawn : ChampionSpawn
+ [Constructible]
+ public DungeonChampionSpawn()
{
- [Constructible]
- public DungeonChampionSpawn()
- {
- CannedEvilTimer.AddSpawn(this);
- }
+ CannedEvilTimer.AddSpawn(this);
+ }
- public DungeonChampionSpawn(Serial serial) : base(serial)
- {
- CannedEvilTimer.AddSpawn(this);
- }
+ public DungeonChampionSpawn(Serial serial)
+ {
+ CannedEvilTimer.AddSpawn(this);
+ }
- public override bool ProximitySpawn => true;
- public override bool AlwaysActive => false;
+ public override bool ProximitySpawn => true;
+ public override bool AlwaysActive => false;
- public override void Serialize(IGenericWriter writer)
- {
- base.Serialize(writer);
-
- writer.WriteEncodedInt(0); //version
- }
-
- public override void Deserialize(IGenericReader reader)
- {
- base.Deserialize(reader);
-
- int version = reader.ReadEncodedInt();
- }
-
- public override void OnAfterDelete()
- {
- base.OnAfterDelete();
- CannedEvilTimer.RemoveSpawn(this);
- }
+ public override void OnAfterDelete()
+ {
+ base.OnAfterDelete();
+ CannedEvilTimer.RemoveSpawn(this);
}
}
diff --git a/Projects/UOContent/Engines/CannedEvil/HarrowerGate.cs b/Projects/UOContent/Engines/CannedEvil/HarrowerGate.cs
index aa11225df..fc862cab8 100644
--- a/Projects/UOContent/Engines/CannedEvil/HarrowerGate.cs
+++ b/Projects/UOContent/Engines/CannedEvil/HarrowerGate.cs
@@ -1,60 +1,32 @@
-namespace Server.Items
+using ModernUO.Serialization;
+
+namespace Server.Items;
+
+[SerializationGenerator(0, false)]
+public partial class HarrowerGate : Moongate
{
- public class HarrowerGate : Moongate
+ [SerializableField(0)]
+ private Mobile _harrower;
+
+ public HarrowerGate(Mobile harrower, Point3D loc, Map map, Point3D targLoc, Map targMap) : base(targLoc, targMap)
{
- private Mobile m_Harrower;
+ _harrower = harrower;
- public HarrowerGate(Mobile harrower, Point3D loc, Map map, Point3D targLoc, Map targMap) : base(targLoc, targMap)
+ Dispellable = false;
+ ItemID = 0x1FD4;
+ Light = LightType.Circle300;
+
+ MoveToWorld(loc, map);
+ }
+
+ public override int LabelNumber => 1049498; // dark moongate
+
+ [AfterDeserialization(false)]
+ private void AfterDeserialization()
+ {
+ if (_harrower == null)
{
- m_Harrower = harrower;
-
- Dispellable = false;
- ItemID = 0x1FD4;
- Light = LightType.Circle300;
-
- MoveToWorld(loc, map);
- }
-
- public HarrowerGate(Serial serial) : base(serial)
- {
- }
-
- public override int LabelNumber => 1049498; // dark moongate
-
- public override void Serialize(IGenericWriter writer)
- {
- base.Serialize(writer);
-
- writer.Write(0); // version
-
- writer.Write(m_Harrower);
- }
-
- public override void Deserialize(IGenericReader reader)
- {
- base.Deserialize(reader);
-
- var version = reader.ReadInt();
-
- switch (version)
- {
- case 0:
- {
- m_Harrower = reader.ReadEntity();
-
- if (m_Harrower == null)
- {
- Delete();
- }
-
- break;
- }
- }
-
- if (Light != LightType.Circle300)
- {
- Light = LightType.Circle300;
- }
+ Delete();
}
}
}
diff --git a/Projects/UOContent/Engines/CannedEvil/LLChampionSpawn.cs b/Projects/UOContent/Engines/CannedEvil/LLChampionSpawn.cs
index 6ce233709..51d7fe462 100644
--- a/Projects/UOContent/Engines/CannedEvil/LLChampionSpawn.cs
+++ b/Projects/UOContent/Engines/CannedEvil/LLChampionSpawn.cs
@@ -13,43 +13,31 @@
* along with this program. If not, see . *
*************************************************************************/
-namespace Server.Engines.CannedEvil
+using ModernUO.Serialization;
+
+namespace Server.Engines.CannedEvil;
+
+[SerializationGenerator(0)]
+public partial class LLChampionSpawn : ChampionSpawn
{
- public class LLChampionSpawn : ChampionSpawn
+ public override bool HasStarRoomGate => false;
+
+ [Constructible]
+ public LLChampionSpawn()
{
- public override bool HasStarRoomGate => false;
+ CannedEvilTimer.AddSpawn(this);
+ }
- [Constructible]
- public LLChampionSpawn()
- {
- CannedEvilTimer.AddSpawn(this);
- }
+ public LLChampionSpawn(Serial serial)
+ {
+ CannedEvilTimer.AddSpawn(this);
+ }
- public LLChampionSpawn(Serial serial) : base(serial)
- {
- }
+ public override bool AlwaysActive => false;
- public override bool AlwaysActive => false;
-
- public override void Serialize(IGenericWriter writer)
- {
- base.Serialize(writer);
-
- writer.WriteEncodedInt(0); //version
- }
-
- public override void Deserialize(IGenericReader reader)
- {
- base.Deserialize(reader);
-
- int version = reader.ReadEncodedInt();
- CannedEvilTimer.AddSpawn(this);
- }
-
- public override void OnAfterDelete()
- {
- base.OnAfterDelete();
- CannedEvilTimer.RemoveSpawn(this);
- }
+ public override void OnAfterDelete()
+ {
+ base.OnAfterDelete();
+ CannedEvilTimer.RemoveSpawn(this);
}
}
diff --git a/Projects/UOContent/Engines/CannedEvil/StarRoomGate.cs b/Projects/UOContent/Engines/CannedEvil/StarRoomGate.cs
index 0eb74b62f..a5426e7a5 100644
--- a/Projects/UOContent/Engines/CannedEvil/StarRoomGate.cs
+++ b/Projects/UOContent/Engines/CannedEvil/StarRoomGate.cs
@@ -1,98 +1,68 @@
using System;
+using ModernUO.Serialization;
-namespace Server.Items
+namespace Server.Items;
+
+[SerializationGenerator(1, false)]
+public partial class StarRoomGate : Moongate
{
- public class StarRoomGate : Moongate
+ private static TimeSpan GateDuration = TimeSpan.FromMinutes(2.0);
+
+ [SerializableField(0)]
+ private bool _decays;
+
+ [DeltaDateTime]
+ [SerializableField(1)]
+ private DateTime _decayTime;
+
+ private Timer _timer;
+
+ [Constructible]
+ public StarRoomGate(Point3D loc, Map map, bool decays) : this(decays)
{
- private bool m_Decays;
- private DateTime m_DecayTime;
- private Timer m_Timer;
+ MoveToWorld(loc, map);
+ Effects.PlaySound(loc, map, 0x20E);
+ }
- [Constructible]
- public StarRoomGate(Point3D loc, Map map, bool decays) : this(decays)
+ [Constructible]
+ public StarRoomGate(bool decays = false) : base(new Point3D(5143, 1774, 0), Map.Felucca)
+ {
+ Dispellable = false;
+ ItemID = 0x1FD4;
+
+ if (decays)
{
- MoveToWorld(loc, map);
- Effects.PlaySound(loc, map, 0x20E);
+ _decays = true;
+ _decayTime = Core.Now + GateDuration;
+
+ _timer = Timer.DelayCall(GateDuration, Delete);
}
+ }
- [Constructible]
- public StarRoomGate(bool decays = false) : base(new Point3D(5143, 1774, 0), Map.Felucca)
+ public override int LabelNumber => 1049498; // dark moongate
+
+ public override void OnAfterDelete()
+ {
+ _timer?.Stop();
+ base.OnAfterDelete();
+ }
+
+ private void Deserialize(IGenericReader reader, int version)
+ {
+ _decays = reader.ReadBool();
+
+ if (_decays)
{
- Dispellable = false;
- ItemID = 0x1FD4;
-
- if (decays)
- {
- m_Decays = true;
- m_DecayTime = Core.Now + TimeSpan.FromMinutes(2.0);
-
- m_Timer = new InternalTimer(this, m_DecayTime);
- m_Timer.Start();
- }
+ _decayTime = reader.ReadDeltaTime();
}
+ }
- public StarRoomGate(Serial serial) : base(serial)
+ [AfterDeserialization]
+ private void AfterDeserialization()
+ {
+ if (_decays)
{
- }
-
- public override int LabelNumber => 1049498; // dark moongate
-
- public override void OnAfterDelete()
- {
- m_Timer?.Stop();
-
- base.OnAfterDelete();
- }
-
- public override void Serialize(IGenericWriter writer)
- {
- base.Serialize(writer);
-
- writer.Write(0); // version
-
- writer.Write(m_Decays);
-
- if (m_Decays)
- {
- writer.WriteDeltaTime(m_DecayTime);
- }
- }
-
- public override void Deserialize(IGenericReader reader)
- {
- base.Deserialize(reader);
-
- var version = reader.ReadInt();
-
- switch (version)
- {
- case 0:
- {
- m_Decays = reader.ReadBool();
-
- if (m_Decays)
- {
- m_DecayTime = reader.ReadDeltaTime();
-
- m_Timer = new InternalTimer(this, m_DecayTime);
- m_Timer.Start();
- }
-
- break;
- }
- }
- }
-
- private class InternalTimer : Timer
- {
- private readonly Item m_Item;
-
- public InternalTimer(Item item, DateTime end) : base(end - Core.Now) => m_Item = item;
-
- protected override void OnTick()
- {
- m_Item.Delete();
- }
+ _timer = Timer.DelayCall(_decayTime - Core.Now, Delete);
}
}
}
diff --git a/Projects/UOContent/Engines/Virtues/Valor.cs b/Projects/UOContent/Engines/Virtues/Valor.cs
index e8a2e520b..4c9d58b10 100644
--- a/Projects/UOContent/Engines/Virtues/Valor.cs
+++ b/Projects/UOContent/Engines/Virtues/Valor.cs
@@ -58,9 +58,9 @@ public static class ValorVirtue
}
else if (idol.Spawn.Active)
{
- if (idol.Spawn.Champion != null) // TODO: Message?
+ if (idol.Spawn.Champion != null)
{
- return;
+ from.SendLocalizedMessage(1112470); // You may not use Valor on this Champion Idol. The Champion has already spawned.
}
int needed, consumed;
diff --git a/Projects/UOContent/Migrations/Server.Engines.CannedEvil.ChampionAltar.v0.json b/Projects/UOContent/Migrations/Server.Engines.CannedEvil.ChampionAltar.v0.json
new file mode 100644
index 000000000..5954cfce7
--- /dev/null
+++ b/Projects/UOContent/Migrations/Server.Engines.CannedEvil.ChampionAltar.v0.json
@@ -0,0 +1,11 @@
+{
+ "version": 0,
+ "type": "Server.Engines.CannedEvil.ChampionAltar",
+ "properties": [
+ {
+ "name": "Spawn",
+ "type": "Server.Engines.CannedEvil.ChampionSpawn",
+ "rule": "SerializableInterfaceMigrationRule"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/Projects/UOContent/Migrations/Server.Engines.CannedEvil.ChampionPlatform.v0.json b/Projects/UOContent/Migrations/Server.Engines.CannedEvil.ChampionPlatform.v0.json
new file mode 100644
index 000000000..92931e758
--- /dev/null
+++ b/Projects/UOContent/Migrations/Server.Engines.CannedEvil.ChampionPlatform.v0.json
@@ -0,0 +1,11 @@
+{
+ "version": 0,
+ "type": "Server.Engines.CannedEvil.ChampionPlatform",
+ "properties": [
+ {
+ "name": "Spawn",
+ "type": "Server.Engines.CannedEvil.ChampionSpawn",
+ "rule": "SerializableInterfaceMigrationRule"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/Projects/UOContent/Migrations/Server.Engines.CannedEvil.ChampionSkullBrazier.v1.json b/Projects/UOContent/Migrations/Server.Engines.CannedEvil.ChampionSkullBrazier.v1.json
new file mode 100644
index 000000000..b3e997c01
--- /dev/null
+++ b/Projects/UOContent/Migrations/Server.Engines.CannedEvil.ChampionSkullBrazier.v1.json
@@ -0,0 +1,21 @@
+{
+ "version": 1,
+ "type": "Server.Engines.CannedEvil.ChampionSkullBrazier",
+ "properties": [
+ {
+ "name": "Type",
+ "type": "Server.Engines.CannedEvil.ChampionSkullType",
+ "rule": "EnumMigrationRule"
+ },
+ {
+ "name": "Platform",
+ "type": "Server.Engines.CannedEvil.ChampionSkullPlatform",
+ "rule": "SerializableInterfaceMigrationRule"
+ },
+ {
+ "name": "Skull",
+ "type": "Server.Item",
+ "rule": "SerializableInterfaceMigrationRule"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/Projects/UOContent/Migrations/Server.Engines.CannedEvil.ChampionSkullPlatform.v0.json b/Projects/UOContent/Migrations/Server.Engines.CannedEvil.ChampionSkullPlatform.v0.json
new file mode 100644
index 000000000..78176295c
--- /dev/null
+++ b/Projects/UOContent/Migrations/Server.Engines.CannedEvil.ChampionSkullPlatform.v0.json
@@ -0,0 +1,36 @@
+{
+ "version": 0,
+ "type": "Server.Engines.CannedEvil.ChampionSkullPlatform",
+ "properties": [
+ {
+ "name": "Power",
+ "type": "Server.Engines.CannedEvil.ChampionSkullBrazier",
+ "rule": "SerializableInterfaceMigrationRule"
+ },
+ {
+ "name": "Enlightenment",
+ "type": "Server.Engines.CannedEvil.ChampionSkullBrazier",
+ "rule": "SerializableInterfaceMigrationRule"
+ },
+ {
+ "name": "Venom",
+ "type": "Server.Engines.CannedEvil.ChampionSkullBrazier",
+ "rule": "SerializableInterfaceMigrationRule"
+ },
+ {
+ "name": "Pain",
+ "type": "Server.Engines.CannedEvil.ChampionSkullBrazier",
+ "rule": "SerializableInterfaceMigrationRule"
+ },
+ {
+ "name": "Greed",
+ "type": "Server.Engines.CannedEvil.ChampionSkullBrazier",
+ "rule": "SerializableInterfaceMigrationRule"
+ },
+ {
+ "name": "Death",
+ "type": "Server.Engines.CannedEvil.ChampionSkullBrazier",
+ "rule": "SerializableInterfaceMigrationRule"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/Projects/UOContent/Migrations/Server.Engines.CannedEvil.ChampionSpawn.v10.json b/Projects/UOContent/Migrations/Server.Engines.CannedEvil.ChampionSpawn.v10.json
new file mode 100644
index 000000000..b04ef31d0
--- /dev/null
+++ b/Projects/UOContent/Migrations/Server.Engines.CannedEvil.ChampionSpawn.v10.json
@@ -0,0 +1,186 @@
+{
+ "version": 10,
+ "type": "Server.Engines.CannedEvil.ChampionSpawn",
+ "properties": [
+ {
+ "name": "Level",
+ "type": "int",
+ "rule": "PrimitiveTypeMigrationRule",
+ "ruleArguments": [
+ ""
+ ]
+ },
+ {
+ "name": "ActivatedByProximity",
+ "type": "bool",
+ "rule": "PrimitiveTypeMigrationRule",
+ "ruleArguments": [
+ ""
+ ]
+ },
+ {
+ "name": "NextProximityTime",
+ "type": "System.DateTime",
+ "rule": "PrimitiveTypeMigrationRule",
+ "ruleArguments": [
+ "DeltaTime"
+ ]
+ },
+ {
+ "name": "MaxLevel",
+ "type": "int",
+ "rule": "PrimitiveTypeMigrationRule",
+ "ruleArguments": [
+ ""
+ ]
+ },
+ {
+ "name": "ActivatedByValor",
+ "type": "bool",
+ "rule": "PrimitiveTypeMigrationRule",
+ "ruleArguments": [
+ ""
+ ]
+ },
+ {
+ "name": "DamageEntries",
+ "type": "System.Collections.Generic.Dictionary\u003CServer.Mobile, int\u003E",
+ "rule": "DictionaryMigrationRule",
+ "ruleArguments": [
+ "Server.Mobile",
+ "SerializableInterfaceMigrationRule",
+ "0",
+ "int",
+ "PrimitiveTypeMigrationRule",
+ "1",
+ ""
+ ]
+ },
+ {
+ "name": "ConfinedRoaming",
+ "type": "bool",
+ "rule": "PrimitiveTypeMigrationRule",
+ "ruleArguments": [
+ ""
+ ]
+ },
+ {
+ "name": "Idol",
+ "type": "Server.Engines.CannedEvil.IdolOfTheChampion",
+ "rule": "SerializableInterfaceMigrationRule"
+ },
+ {
+ "name": "HasBeenAdvanced",
+ "type": "bool",
+ "rule": "PrimitiveTypeMigrationRule",
+ "ruleArguments": [
+ ""
+ ]
+ },
+ {
+ "name": "SpawnArea",
+ "type": "Server.Rectangle2D",
+ "rule": "PrimitiveUOTypeMigrationRule",
+ "ruleArguments": [
+ "Rect2D"
+ ]
+ },
+ {
+ "name": "RandomizeType",
+ "type": "bool",
+ "rule": "PrimitiveTypeMigrationRule",
+ "ruleArguments": [
+ ""
+ ]
+ },
+ {
+ "name": "Kills",
+ "type": "int",
+ "rule": "PrimitiveTypeMigrationRule",
+ "ruleArguments": [
+ ""
+ ]
+ },
+ {
+ "name": "Active",
+ "type": "bool",
+ "rule": "PrimitiveTypeMigrationRule",
+ "ruleArguments": [
+ ""
+ ]
+ },
+ {
+ "name": "Type",
+ "type": "Server.Engines.CannedEvil.ChampionSpawnType",
+ "rule": "EnumMigrationRule"
+ },
+ {
+ "name": "Creatures",
+ "type": "System.Collections.Generic.List\u003CServer.Mobile\u003E",
+ "rule": "ListMigrationRule",
+ "ruleArguments": [
+ "Server.Mobile",
+ "SerializableInterfaceMigrationRule"
+ ]
+ },
+ {
+ "name": "RedSkulls",
+ "type": "System.Collections.Generic.List\u003CServer.Item\u003E",
+ "rule": "ListMigrationRule",
+ "ruleArguments": [
+ "Server.Item",
+ "SerializableInterfaceMigrationRule"
+ ]
+ },
+ {
+ "name": "WhiteSkulls",
+ "type": "System.Collections.Generic.List\u003CServer.Item\u003E",
+ "rule": "ListMigrationRule",
+ "ruleArguments": [
+ "Server.Item",
+ "SerializableInterfaceMigrationRule"
+ ]
+ },
+ {
+ "name": "Platform",
+ "type": "Server.Engines.CannedEvil.ChampionPlatform",
+ "rule": "SerializableInterfaceMigrationRule"
+ },
+ {
+ "name": "Altar",
+ "type": "Server.Engines.CannedEvil.ChampionAltar",
+ "rule": "SerializableInterfaceMigrationRule"
+ },
+ {
+ "name": "ExpireDelay",
+ "type": "System.TimeSpan",
+ "rule": "PrimitiveTypeMigrationRule"
+ },
+ {
+ "name": "ExpireTime",
+ "type": "System.DateTime",
+ "rule": "PrimitiveTypeMigrationRule",
+ "ruleArguments": [
+ "DeltaTime"
+ ]
+ },
+ {
+ "name": "Champion",
+ "type": "Server.Mobile",
+ "rule": "SerializableInterfaceMigrationRule"
+ },
+ {
+ "name": "RestartDelay",
+ "type": "System.TimeSpan",
+ "rule": "PrimitiveTypeMigrationRule"
+ },
+ {
+ "name": "RestartTime",
+ "type": "System.DateTime",
+ "rule": "PrimitiveTypeMigrationRule",
+ "ruleArguments": [
+ "DeltaTime"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/Projects/UOContent/Migrations/Server.Engines.CannedEvil.ChampionTitle.v0.json b/Projects/UOContent/Migrations/Server.Engines.CannedEvil.ChampionTitle.v0.json
new file mode 100644
index 000000000..93699d32d
--- /dev/null
+++ b/Projects/UOContent/Migrations/Server.Engines.CannedEvil.ChampionTitle.v0.json
@@ -0,0 +1,22 @@
+{
+ "version": 0,
+ "type": "Server.Engines.CannedEvil.ChampionTitle",
+ "properties": [
+ {
+ "name": "Value",
+ "type": "int",
+ "rule": "PrimitiveTypeMigrationRule",
+ "ruleArguments": [
+ "EncodedInt"
+ ]
+ },
+ {
+ "name": "LastDecay",
+ "type": "System.DateTime",
+ "rule": "PrimitiveTypeMigrationRule",
+ "ruleArguments": [
+ ""
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/Projects/UOContent/Migrations/Server.Engines.CannedEvil.ChampionTitleContext.v1.json b/Projects/UOContent/Migrations/Server.Engines.CannedEvil.ChampionTitleContext.v1.json
new file mode 100644
index 000000000..af6658d06
--- /dev/null
+++ b/Projects/UOContent/Migrations/Server.Engines.CannedEvil.ChampionTitleContext.v1.json
@@ -0,0 +1,95 @@
+{
+ "version": 1,
+ "type": "Server.Engines.CannedEvil.ChampionTitleContext",
+ "properties": [
+ {
+ "name": "Harrower",
+ "type": "int",
+ "rule": "PrimitiveTypeMigrationRule",
+ "ruleArguments": [
+ ""
+ ]
+ },
+ {
+ "name": "Abyss",
+ "type": "Server.Engines.CannedEvil.ChampionTitle",
+ "usesSaveFlag": true,
+ "rule": "RawSerializableMigrationRule",
+ "ruleArguments": [
+ ""
+ ]
+ },
+ {
+ "name": "Arachnid",
+ "type": "Server.Engines.CannedEvil.ChampionTitle",
+ "usesSaveFlag": true,
+ "rule": "RawSerializableMigrationRule",
+ "ruleArguments": [
+ ""
+ ]
+ },
+ {
+ "name": "ColdBlood",
+ "type": "Server.Engines.CannedEvil.ChampionTitle",
+ "usesSaveFlag": true,
+ "rule": "RawSerializableMigrationRule",
+ "ruleArguments": [
+ ""
+ ]
+ },
+ {
+ "name": "ForestLord",
+ "type": "Server.Engines.CannedEvil.ChampionTitle",
+ "usesSaveFlag": true,
+ "rule": "RawSerializableMigrationRule",
+ "ruleArguments": [
+ ""
+ ]
+ },
+ {
+ "name": "VerminHorde",
+ "type": "Server.Engines.CannedEvil.ChampionTitle",
+ "usesSaveFlag": true,
+ "rule": "RawSerializableMigrationRule",
+ "ruleArguments": [
+ ""
+ ]
+ },
+ {
+ "name": "UnholyTerror",
+ "type": "Server.Engines.CannedEvil.ChampionTitle",
+ "usesSaveFlag": true,
+ "rule": "RawSerializableMigrationRule",
+ "ruleArguments": [
+ ""
+ ]
+ },
+ {
+ "name": "SleepingDragon",
+ "type": "Server.Engines.CannedEvil.ChampionTitle",
+ "usesSaveFlag": true,
+ "rule": "RawSerializableMigrationRule",
+ "ruleArguments": [
+ ""
+ ]
+ },
+ {
+ "name": "Corrupt",
+ "type": "Server.Engines.CannedEvil.ChampionTitle",
+ "usesSaveFlag": true,
+ "rule": "RawSerializableMigrationRule",
+ "ruleArguments": [
+ ""
+ ]
+ },
+ {
+ "name": "Glade",
+ "type": "Server.Engines.CannedEvil.ChampionTitle",
+ "usesSaveFlag": true,
+ "rule": "RawSerializableMigrationRule",
+ "ruleArguments": [
+ ""
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/Projects/UOContent/Migrations/Server.Engines.CannedEvil.DungeonChampionSpawn.v0.json b/Projects/UOContent/Migrations/Server.Engines.CannedEvil.DungeonChampionSpawn.v0.json
new file mode 100644
index 000000000..a175e33dc
--- /dev/null
+++ b/Projects/UOContent/Migrations/Server.Engines.CannedEvil.DungeonChampionSpawn.v0.json
@@ -0,0 +1,4 @@
+{
+ "version": 0,
+ "type": "Server.Engines.CannedEvil.DungeonChampionSpawn"
+}
\ No newline at end of file
diff --git a/Projects/UOContent/Migrations/Server.Engines.CannedEvil.IdolOfTheChampion.v0.json b/Projects/UOContent/Migrations/Server.Engines.CannedEvil.IdolOfTheChampion.v0.json
new file mode 100644
index 000000000..d4f713358
--- /dev/null
+++ b/Projects/UOContent/Migrations/Server.Engines.CannedEvil.IdolOfTheChampion.v0.json
@@ -0,0 +1,11 @@
+{
+ "version": 0,
+ "type": "Server.Engines.CannedEvil.IdolOfTheChampion",
+ "properties": [
+ {
+ "name": "Spawn",
+ "type": "Server.Engines.CannedEvil.ChampionSpawn",
+ "rule": "SerializableInterfaceMigrationRule"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/Projects/UOContent/Migrations/Server.Engines.CannedEvil.LLChampionSpawn.v0.json b/Projects/UOContent/Migrations/Server.Engines.CannedEvil.LLChampionSpawn.v0.json
new file mode 100644
index 000000000..75993331f
--- /dev/null
+++ b/Projects/UOContent/Migrations/Server.Engines.CannedEvil.LLChampionSpawn.v0.json
@@ -0,0 +1,4 @@
+{
+ "version": 0,
+ "type": "Server.Engines.CannedEvil.LLChampionSpawn"
+}
\ No newline at end of file
diff --git a/Projects/UOContent/Migrations/Server.Items.ChampionSkull.v2.json b/Projects/UOContent/Migrations/Server.Items.ChampionSkull.v2.json
new file mode 100644
index 000000000..48cf1246f
--- /dev/null
+++ b/Projects/UOContent/Migrations/Server.Items.ChampionSkull.v2.json
@@ -0,0 +1,11 @@
+{
+ "version": 2,
+ "type": "Server.Items.ChampionSkull",
+ "properties": [
+ {
+ "name": "Type",
+ "type": "Server.Engines.CannedEvil.ChampionSkullType",
+ "rule": "EnumMigrationRule"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/Projects/UOContent/Migrations/Server.Items.HarrowerGate.v0.json b/Projects/UOContent/Migrations/Server.Items.HarrowerGate.v0.json
new file mode 100644
index 000000000..6f5d139b9
--- /dev/null
+++ b/Projects/UOContent/Migrations/Server.Items.HarrowerGate.v0.json
@@ -0,0 +1,11 @@
+{
+ "version": 0,
+ "type": "Server.Items.HarrowerGate",
+ "properties": [
+ {
+ "name": "Harrower",
+ "type": "Server.Mobile",
+ "rule": "SerializableInterfaceMigrationRule"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/Projects/UOContent/Migrations/Server.Items.StarRoomGate.v1.json b/Projects/UOContent/Migrations/Server.Items.StarRoomGate.v1.json
new file mode 100644
index 000000000..544069802
--- /dev/null
+++ b/Projects/UOContent/Migrations/Server.Items.StarRoomGate.v1.json
@@ -0,0 +1,22 @@
+{
+ "version": 1,
+ "type": "Server.Items.StarRoomGate",
+ "properties": [
+ {
+ "name": "Decays",
+ "type": "bool",
+ "rule": "PrimitiveTypeMigrationRule",
+ "ruleArguments": [
+ ""
+ ]
+ },
+ {
+ "name": "DecayTime",
+ "type": "System.DateTime",
+ "rule": "PrimitiveTypeMigrationRule",
+ "ruleArguments": [
+ "DeltaTime"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/Projects/UOContent/Misc/Guild.cs b/Projects/UOContent/Misc/Guild.cs
index c8c39d931..f9e30c56b 100644
--- a/Projects/UOContent/Misc/Guild.cs
+++ b/Projects/UOContent/Misc/Guild.cs
@@ -1207,12 +1207,6 @@ namespace Server.Guilds
writer.Write(Website);
}
- public override bool ShouldExecuteAfterSerialize => false;
-
- public override void AfterSerialize()
- {
- }
-
public override void Delete()
{
World.RemoveGuild(this);
diff --git a/Projects/UOContent/Misc/Titles.cs b/Projects/UOContent/Misc/Titles.cs
index 98ada37ee..7b293356e 100644
--- a/Projects/UOContent/Misc/Titles.cs
+++ b/Projects/UOContent/Misc/Titles.cs
@@ -1,5 +1,6 @@
using System;
using System.Text;
+using Server.Buffers;
using Server.Engines.CannedEvil;
using Server.Mobiles;
@@ -13,12 +14,6 @@ namespace Server.Misc
public const int MinKarma = -15000;
public const int MaxKarma = 15000;
- public static string[] HarrowerTitles =
- {
- "Spite", "Opponent", "Hunter", "Venom", "Executioner",
- "Annihilator", "Champion", "Assailant", "Purifier", "Nullifier"
- };
-
private static readonly string[,] m_Levels =
{
{ "Neophyte", "Neophyte", "Neophyte" },
@@ -39,85 +34,85 @@ namespace Server.Misc
1249,
new[]
{
- new KarmaEntry(-10000, "The Outcast {0}"),
- new KarmaEntry(-5000, "The Despicable {0}"),
- new KarmaEntry(-2500, "The Scoundrel {0}"),
- new KarmaEntry(-1250, "The Unsavory {0}"),
- new KarmaEntry(-625, "The Rude {0}"),
- new KarmaEntry(624, "{0}"),
- new KarmaEntry(1249, "The Fair {0}"),
- new KarmaEntry(2499, "The Kind {0}"),
- new KarmaEntry(4999, "The Good {0}"),
- new KarmaEntry(9999, "The Honest {0}"),
- new KarmaEntry(10000, "The Trustworthy {0}")
+ new KarmaEntry(-10000, "The Outcast "),
+ new KarmaEntry(-5000, "The Despicable "),
+ new KarmaEntry(-2500, "The Scoundrel "),
+ new KarmaEntry(-1250, "The Unsavory "),
+ new KarmaEntry(-625, "The Rude "),
+ new KarmaEntry(624, ""),
+ new KarmaEntry(1249, "The Fair "),
+ new KarmaEntry(2499, "The Kind "),
+ new KarmaEntry(4999, "The Good "),
+ new KarmaEntry(9999, "The Honest "),
+ new KarmaEntry(10000, "The Trustworthy ")
}
),
new(
2499,
new[]
{
- new KarmaEntry(-10000, "The Wretched {0}"),
- new KarmaEntry(-5000, "The Dastardly {0}"),
- new KarmaEntry(-2500, "The Malicious {0}"),
- new KarmaEntry(-1250, "The Dishonorable {0}"),
- new KarmaEntry(-625, "The Disreputable {0}"),
- new KarmaEntry(624, "The Notable {0}"),
- new KarmaEntry(1249, "The Upstanding {0}"),
- new KarmaEntry(2499, "The Respectable {0}"),
- new KarmaEntry(4999, "The Honorable {0}"),
- new KarmaEntry(9999, "The Commendable {0}"),
- new KarmaEntry(10000, "The Estimable {0}")
+ new KarmaEntry(-10000, "The Wretched "),
+ new KarmaEntry(-5000, "The Dastardly "),
+ new KarmaEntry(-2500, "The Malicious "),
+ new KarmaEntry(-1250, "The Dishonorable "),
+ new KarmaEntry(-625, "The Disreputable "),
+ new KarmaEntry(624, "The Notable "),
+ new KarmaEntry(1249, "The Upstanding "),
+ new KarmaEntry(2499, "The Respectable "),
+ new KarmaEntry(4999, "The Honorable "),
+ new KarmaEntry(9999, "The Commendable "),
+ new KarmaEntry(10000, "The Estimable ")
}
),
new(
4999,
new[]
{
- new KarmaEntry(-10000, "The Nefarious {0}"),
- new KarmaEntry(-5000, "The Wicked {0}"),
- new KarmaEntry(-2500, "The Vile {0}"),
- new KarmaEntry(-1250, "The Ignoble {0}"),
- new KarmaEntry(-625, "The Notorious {0}"),
- new KarmaEntry(624, "The Prominent {0}"),
- new KarmaEntry(1249, "The Reputable {0}"),
- new KarmaEntry(2499, "The Proper {0}"),
- new KarmaEntry(4999, "The Admirable {0}"),
- new KarmaEntry(9999, "The Famed {0}"),
- new KarmaEntry(10000, "The Great {0}")
+ new KarmaEntry(-10000, "The Nefarious "),
+ new KarmaEntry(-5000, "The Wicked "),
+ new KarmaEntry(-2500, "The Vile "),
+ new KarmaEntry(-1250, "The Ignoble "),
+ new KarmaEntry(-625, "The Notorious "),
+ new KarmaEntry(624, "The Prominent "),
+ new KarmaEntry(1249, "The Reputable "),
+ new KarmaEntry(2499, "The Proper "),
+ new KarmaEntry(4999, "The Admirable "),
+ new KarmaEntry(9999, "The Famed "),
+ new KarmaEntry(10000, "The Great ")
}
),
new(
9999,
new[]
{
- new KarmaEntry(-10000, "The Dread {0}"),
- new KarmaEntry(-5000, "The Evil {0}"),
- new KarmaEntry(-2500, "The Villainous {0}"),
- new KarmaEntry(-1250, "The Sinister {0}"),
- new KarmaEntry(-625, "The Infamous {0}"),
- new KarmaEntry(624, "The Renowned {0}"),
- new KarmaEntry(1249, "The Distinguished {0}"),
- new KarmaEntry(2499, "The Eminent {0}"),
- new KarmaEntry(4999, "The Noble {0}"),
- new KarmaEntry(9999, "The Illustrious {0}"),
- new KarmaEntry(10000, "The Glorious {0}")
+ new KarmaEntry(-10000, "The Dread "),
+ new KarmaEntry(-5000, "The Evil "),
+ new KarmaEntry(-2500, "The Villainous "),
+ new KarmaEntry(-1250, "The Sinister "),
+ new KarmaEntry(-625, "The Infamous "),
+ new KarmaEntry(624, "The Renowned "),
+ new KarmaEntry(1249, "The Distinguished "),
+ new KarmaEntry(2499, "The Eminent "),
+ new KarmaEntry(4999, "The Noble "),
+ new KarmaEntry(9999, "The Illustrious "),
+ new KarmaEntry(10000, "The Glorious ")
}
),
new(
10000,
new[]
{
- new KarmaEntry(-10000, "The Dread {1} {0}"),
- new KarmaEntry(-5000, "The Evil {1} {0}"),
- new KarmaEntry(-2500, "The Dark {1} {0}"),
- new KarmaEntry(-1250, "The Sinister {1} {0}"),
- new KarmaEntry(-625, "The Dishonored {1} {0}"),
- new KarmaEntry(624, "{1} {0}"),
- new KarmaEntry(1249, "The Distinguished {1} {0}"),
- new KarmaEntry(2499, "The Eminent {1} {0}"),
- new KarmaEntry(4999, "The Noble {1} {0}"),
- new KarmaEntry(9999, "The Illustrious {1} {0}"),
- new KarmaEntry(10000, "The Glorious {1} {0}")
+ new KarmaEntry(-10000, "The Dread "),
+ new KarmaEntry(-5000, "The Evil "),
+ new KarmaEntry(-2500, "The Dark "),
+ new KarmaEntry(-1250, "The Sinister "),
+ new KarmaEntry(-625, "The Dishonored "),
+ new KarmaEntry(624, ""),
+ new KarmaEntry(1249, "The Distinguished "),
+ new KarmaEntry(2499, "The Eminent "),
+ new KarmaEntry(4999, "The Noble "),
+ new KarmaEntry(9999, "The Illustrious "),
+ new KarmaEntry(10000, "The Glorious ")
}
)
};
@@ -274,7 +269,7 @@ namespace Server.Misc
public static string ComputeTitle(Mobile beholder, Mobile beheld)
{
- var title = new StringBuilder();
+ using var title = ValueStringBuilder.Create();
var fame = beheld.Fame;
var karma = beheld.Karma;
@@ -297,7 +292,21 @@ namespace Server.Misc
if (karma <= ke.m_Karma || j == karmaEntries.Length - 1)
{
- title.AppendFormat(ke.m_Title, beheld.Name, beheld.Female ? "Lady" : "Lord");
+ if (karma >= 10000)
+ {
+ if (beheld.Female)
+ {
+ title.Append($"{ke.m_Title}Lady {beheld.Name}");
+ }
+ else
+ {
+ title.Append($"{ke.m_Title}Lord {beheld.Name}");
+ }
+ }
+ else
+ {
+ title.Append($"{ke.m_Title}{beheld.Name}");
+ }
break;
}
}
@@ -313,40 +322,14 @@ namespace Server.Misc
if (beheld is PlayerMobile mobile && mobile.DisplayChampionTitle)
{
- var info = mobile.ChampionTitles;
-
- if (info.Harrower > 0)
+ var titleLabel = mobile.GetChampionTitleLabel();
+ if (titleLabel > 0)
{
- title.Append($": {HarrowerTitles[Math.Min(HarrowerTitles.Length, info.Harrower) - 1]} of Evil");
- }
- else
- {
- int highestValue = 0, highestType = 0;
- for (var i = 0; i < ChampionSpawnInfo.Table.Length; i++)
- {
- var v = info.GetValue(i);
+ // Should this be translated to the receivers language? Prefix titles aren't?
+ title.Append($": {Localization.GetText(titleLabel)}");
- if (v > highestValue)
- {
- highestValue = v;
- highestType = i;
- }
- }
-
- var offset = highestValue switch
- {
- > 800 => 3,
- > 300 => highestValue / 300,
- _ => 0
- };
-
- if (offset > 0)
- {
- var champInfo = ChampionSpawnInfo.GetInfo((ChampionSpawnType)highestType);
- title.Append(
- $": {champInfo.LevelNames[Math.Min(offset, champInfo.LevelNames.Length) - 1]} of the {champInfo.Name}"
- );
- }
+ // Do not display the skills title
+ return title.ToString();
}
}
diff --git a/Projects/UOContent/Mobiles/ChampionTitleInfo.cs b/Projects/UOContent/Mobiles/ChampionTitleInfo.cs
deleted file mode 100644
index d7dd2f829..000000000
--- a/Projects/UOContent/Mobiles/ChampionTitleInfo.cs
+++ /dev/null
@@ -1,339 +0,0 @@
-using System;
-using System.Runtime.CompilerServices;
-using Server.Engines.CannedEvil;
-
-namespace Server.Mobiles
-{
- [PropertyObject]
- public class ChampionTitleInfo
- {
- private const int LossAmount = 90;
- private static TimeSpan LossDelay = TimeSpan.FromDays(1.0);
-
- private TitleInfo[] m_Values;
-
- public ChampionTitleInfo()
- {
- }
-
- public ChampionTitleInfo(IGenericReader reader)
- {
- var version = reader.ReadEncodedInt();
-
- switch (version)
- {
- case 0:
- {
- Harrower = reader.ReadEncodedInt();
-
- var length = reader.ReadEncodedInt();
-
- if (length == 0)
- {
- break;
- }
-
- m_Values = new TitleInfo[length];
-
- for (var i = 0; i < length; i++)
- {
- m_Values[i] = new TitleInfo(reader);
- }
-
- if (m_Values.Length != ChampionSpawnInfo.Table.Length)
- {
- var oldValues = m_Values;
- m_Values = new TitleInfo[ChampionSpawnInfo.Table.Length];
-
- for (var i = 0; i < m_Values.Length && i < oldValues.Length; i++)
- {
- m_Values[i] = oldValues[i];
- }
- }
-
- break;
- }
- }
- }
-
- [CommandProperty(AccessLevel.GameMaster)]
- public int Pestilence
- {
- get => GetValue(ChampionSpawnType.Pestilence);
- set => SetValue(ChampionSpawnType.Pestilence, value);
- }
-
- [CommandProperty(AccessLevel.GameMaster)]
- public int Abyss
- {
- get => GetValue(ChampionSpawnType.Abyss);
- set => SetValue(ChampionSpawnType.Abyss, value);
- }
-
- [CommandProperty(AccessLevel.GameMaster)]
- public int Arachnid
- {
- get => GetValue(ChampionSpawnType.Arachnid);
- set => SetValue(ChampionSpawnType.Arachnid, value);
- }
-
- [CommandProperty(AccessLevel.GameMaster)]
- public int ColdBlood
- {
- get => GetValue(ChampionSpawnType.ColdBlood);
- set => SetValue(ChampionSpawnType.ColdBlood, value);
- }
-
- [CommandProperty(AccessLevel.GameMaster)]
- public int ForestLord
- {
- get => GetValue(ChampionSpawnType.ForestLord);
- set => SetValue(ChampionSpawnType.ForestLord, value);
- }
-
- [CommandProperty(AccessLevel.GameMaster)]
- public int SleepingDragon
- {
- get => GetValue(ChampionSpawnType.SleepingDragon);
- set => SetValue(ChampionSpawnType.SleepingDragon, value);
- }
-
- [CommandProperty(AccessLevel.GameMaster)]
- public int UnholyTerror
- {
- get => GetValue(ChampionSpawnType.UnholyTerror);
- set => SetValue(ChampionSpawnType.UnholyTerror, value);
- }
-
- [CommandProperty(AccessLevel.GameMaster)]
- public int VerminHorde
- {
- get => GetValue(ChampionSpawnType.VerminHorde);
- set => SetValue(ChampionSpawnType.VerminHorde, value);
- }
-
- [CommandProperty(AccessLevel.GameMaster)]
- public int Harrower { get; set; }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public int GetValue(ChampionSpawnType type) => GetValue((int)type);
-
- public int GetValue(int index)
- {
- if (index < 0 || index >= m_Values?.Length)
- {
- return 0;
- }
-
- return m_Values?[index]?.Value ?? 0;
- }
-
- public DateTime GetLastDecay(int index)
- {
- if (index < 0 || index >= m_Values?.Length)
- {
- return DateTime.MinValue;
- }
-
- return m_Values?[index]?.LastDecay ?? DateTime.MinValue;
- }
-
- public void SetValue(ChampionSpawnType type, int value)
- {
- var index = (int)type;
- if (index < 0 || index >= ChampionSpawnInfo.Table.Length)
- {
- return;
- }
-
- m_Values ??= new TitleInfo[ChampionSpawnInfo.Table.Length];
- var title = m_Values[index];
- if (title == null)
- {
- if (value > 0)
- {
- m_Values[index] = new TitleInfo(value, Core.Now);
- }
- return;
- }
-
- title.Value = Math.Max(value, 0);
- title.LastDecay = title.Value == 0 ? DateTime.MinValue : Core.Now;
- }
-
- public void Award(ChampionSpawnType type, int value)
- {
- var index = (int)type;
- if (value <= 0 || index < 0 || index >= ChampionSpawnInfo.Table.Length)
- {
- return;
- }
-
- m_Values ??= new TitleInfo[ChampionSpawnInfo.Table.Length];
- var title = m_Values[index];
- if (title == null)
- {
- m_Values[index] = new TitleInfo(value, Core.Now);
- return;
- }
-
- title.Value += value;
- if (title.LastDecay == DateTime.MinValue)
- {
- title.LastDecay = Core.Now;
- }
- }
-
- public void Atrophy(int index, int value)
- {
- if (value <= 0 || index < 0 || index >= ChampionSpawnInfo.Table.Length)
- {
- return;
- }
-
- var title = m_Values?[index];
- if (title == null)
- {
- return;
- }
-
- var before = title.Value;
-
- title.Value -= Math.Min(value, title.Value);
-
- if (before != title.Value)
- {
- title.LastDecay = Core.Now;
- }
- }
-
- public override string ToString() => "...";
-
- public static void Serialize(IGenericWriter writer, ChampionTitleInfo titles)
- {
- writer.WriteEncodedInt(0); // version
-
- writer.WriteEncodedInt(titles.Harrower);
-
- var length = titles.m_Values?.Length ?? 0;
- writer.WriteEncodedInt(length);
-
- for (var i = 0; i < length; i++)
- {
- titles.m_Values![i] ??= new TitleInfo();
-
- TitleInfo.Serialize(writer, titles.m_Values[i]);
- }
- }
-
- public static bool ShouldAtrophy(PlayerMobile pm)
- {
- var t = pm.ChampionTitles;
- if (t?.m_Values == null)
- {
- return false;
- }
-
- for (var i = 0; i < t.m_Values.Length; i++)
- {
- var decay = t.GetLastDecay(i);
- if (decay > DateTime.MinValue && decay + LossDelay < Core.Now)
- {
- return true;
- }
- }
-
- return false;
- }
-
- public static void CheckAtrophy(PlayerMobile pm)
- {
- var t = pm.ChampionTitles;
- if (t?.m_Values == null)
- {
- return;
- }
-
- for (var i = 0; i < t.m_Values.Length; i++)
- {
- var decay = t.GetLastDecay(i);
- if (decay > DateTime.MinValue && decay + LossDelay < Core.Now)
- {
- t.Atrophy(i, LossAmount);
- }
- }
- }
-
- // Called when killing a harrower. Will give a minimum of 1 point.
- public static void AwardHarrowerTitle(PlayerMobile pm)
- {
- var t = pm.ChampionTitles;
- if (t == null)
- {
- return;
- }
-
- if (t.m_Values == null)
- {
- if (t.Harrower == 0)
- {
- t.Harrower = 1;
- }
- return;
- }
-
- t.m_Values ??= new TitleInfo[ChampionSpawnInfo.Table.Length];
-
- var count = 1;
- for (var i = 0; i < t.m_Values.Length; i++)
- {
- if (t.m_Values[i].Value > 900)
- {
- count++;
- }
- }
-
- t.Harrower = Math.Max(count, t.Harrower); // Harrower titles never decay.
- }
-
- private class TitleInfo
- {
- public TitleInfo()
- {
- }
-
- public TitleInfo(int value, DateTime lastDecay)
- {
- Value = value;
- LastDecay = lastDecay;
- }
-
- public TitleInfo(IGenericReader reader)
- {
- var version = reader.ReadEncodedInt();
-
- switch (version)
- {
- case 0:
- {
- Value = reader.ReadEncodedInt();
- LastDecay = reader.ReadDateTime();
- break;
- }
- }
- }
-
- public int Value { get; set; }
-
- public DateTime LastDecay { get; set; }
-
- public static void Serialize(IGenericWriter writer, TitleInfo info)
- {
- writer.WriteEncodedInt(0); // version
-
- writer.WriteEncodedInt(info.Value);
- writer.Write(info.LastDecay);
- }
- }
- }
-}
diff --git a/Projects/UOContent/Mobiles/PlayerMobile.cs b/Projects/UOContent/Mobiles/PlayerMobile.cs
index fcb93e317..636e8d34b 100644
--- a/Projects/UOContent/Mobiles/PlayerMobile.cs
+++ b/Projects/UOContent/Mobiles/PlayerMobile.cs
@@ -4,6 +4,7 @@ using Server.Accounting;
using Server.Collections;
using Server.ContextMenus;
using Server.Engines.BulkOrders;
+using Server.Engines.CannedEvil;
using Server.Engines.ConPVP;
using Server.Engines.Craft;
using Server.Engines.Help;
@@ -206,8 +207,6 @@ namespace Server.Mobiles
m_GameTime = TimeSpan.Zero;
m_GuildRank = RankDefinition.Lowest;
-
- ChampionTitles = new ChampionTitleInfo();
}
public PlayerMobile(Serial s) : base(s)
@@ -693,8 +692,8 @@ namespace Server.Mobiles
set => SetFlag(PlayerFlag.DisplayChampionTitle, value);
}
- [CommandProperty(AccessLevel.GameMaster)]
- public ChampionTitleInfo ChampionTitles { get; private set; }
+ [CommandProperty(AccessLevel.GameMaster, canModify: true)]
+ public ChampionTitleContext ChampionTitles => this.GetOrCreateChampionTitleContext();
[CommandProperty(AccessLevel.GameMaster)]
public int ShortTermMurders
@@ -1859,21 +1858,13 @@ namespace Server.Mobiles
{
if (AutoRenewInsurance)
{
- list.Add(
- new CallbackEntry(
- 6202,
- CancelRenewInventoryInsurance
- )
- ); // Cancel Renewing Inventory Insurance
+ // Cancel Renewing Inventory Insurance
+ list.Add(new CallbackEntry(6202, CancelRenewInventoryInsurance));
}
else
{
- list.Add(
- new CallbackEntry(
- 6200,
- AutoRenewInventoryInsurance
- )
- ); // Auto Renew Inventory Insurance
+ // Auto Renew Inventory Insurance
+ list.Add(new CallbackEntry(6200, AutoRenewInventoryInsurance));
}
}
}
@@ -2855,6 +2846,7 @@ namespace Server.Mobiles
switch (version)
{
+ case 33: // Removes champion title
case 32: // Removes virtue properties
case 31: // Removed Short/Long Term Elapse
case 30:
@@ -2928,7 +2920,11 @@ namespace Server.Mobiles
}
case 23:
{
- ChampionTitles = new ChampionTitleInfo(reader);
+ if (version < 33)
+ {
+ ChampionTitles.Deserialize(reader);
+ }
+
goto case 22;
}
case 22:
@@ -3165,8 +3161,6 @@ namespace Server.Mobiles
LastOnline = ((Account)Account).LastLogin;
}
- ChampionTitles ??= new ChampionTitleInfo();
-
if (AccessLevel > AccessLevel.Player)
{
IgnoreMobiles = true;
@@ -3184,8 +3178,6 @@ namespace Server.Mobiles
}
}
- CheckAtrophies();
-
if (Hidden) // Hiding is the only buff where it has an effect that's serialized.
{
AddBuff(new BuffInfo(BuffIcon.HidingAndOrStealth, 1075655));
@@ -3196,7 +3188,7 @@ namespace Server.Mobiles
{
base.Serialize(writer);
- writer.Write(32); // version
+ writer.Write(33); // version
if (Stabled == null)
{
@@ -3251,8 +3243,6 @@ namespace Server.Mobiles
}
}
- ChampionTitleInfo.Serialize(writer, ChampionTitles);
-
writer.WriteEncodedInt(ToTItemsTurnedIn);
writer.Write(ToTTotalMonsterFame); // This ain't going to be a small #.
@@ -3317,22 +3307,6 @@ namespace Server.Mobiles
writer.Write(GameTime);
}
- // Do we need to run an after serialize?
- public override bool ShouldExecuteAfterSerialize => ShouldAtrophy();
-
- public override void AfterSerialize()
- {
- base.AfterSerialize();
- CheckAtrophies();
- }
-
- public bool ShouldAtrophy() => ChampionTitleInfo.ShouldAtrophy(this);
-
- public void CheckAtrophies()
- {
- ChampionTitleInfo.CheckAtrophy(this);
- }
-
public override bool CanSee(Mobile m)
{
if (m is CharacterStatue statue)
@@ -3439,6 +3413,17 @@ namespace Server.Mobiles
}
}
+ // TODO: Add the Titles Menu for later Eras:
+ // https://uo.com/wiki/ultima-online-wiki/player/skill-titles-order/
+ if (DisplayChampionTitle)
+ {
+ var titleLabel = this.GetChampionTitleLabel();
+ if (titleLabel > 0)
+ {
+ list.Add(titleLabel);
+ }
+ }
+
if (Core.ML && AllFollowers != null)
{
foreach (var follower in AllFollowers)
@@ -4457,6 +4442,7 @@ namespace Server.Mobiles
}
DisplayChampionTitle = !DisplayChampionTitle;
+ InvalidateProperties();
}
public virtual bool HasRecipe(Recipe r) => r != null && HasRecipe(r.ID);
diff --git a/Projects/UOContent/Mobiles/Special/Harrower.cs b/Projects/UOContent/Mobiles/Special/Harrower.cs
index bb5bfbf9c..0f3fa3345 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.CannedEvil;
using Server.Engines.Virtues;
using Server.Items;
using Server.Spells;
@@ -325,7 +326,7 @@ namespace Server.Mobiles
if (ds.m_HasRight && ds.m_Mobile is PlayerMobile mobile)
{
- ChampionTitleInfo.AwardHarrowerTitle(mobile);
+ ChampionTitleSystem.AwardHarrowerTitle(mobile);
}
}