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
<img width="216" alt="image" src="https://github.com/modernuo/ModernUO/assets/3953314/8916f895-8d68-4fb0-892e-108a0c43be90">
This commit is contained in:
Kamron Batman 2023-07-27 21:44:06 -07:00 • committed by GitHub
parent 4dddc8d3f1
commit 51ecee6caa
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
44 changed files with 3028 additions and 2785 deletions

View file

@ -13,56 +13,31 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
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<ChampionSpawn>();
if (Spawn == null)
{
Delete();
}
break;
}
}
Delete();
}
}
}

View file

@ -13,95 +13,70 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
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<ChampionSpawn>();
if (Spawn == null)
{
Delete();
}
break;
}
}
Delete();
}
}
}

View file

@ -13,86 +13,41 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
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();
}
}

View file

@ -13,194 +13,156 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
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<ChampionSkullPlatform>();
m_Skull = reader.ReadEntity<Item>();
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<ChampionSkullPlatform>();
_skull = reader.ReadEntity<Item>();
}
if (Light != LightType.Circle300)
{
Light = LightType.Circle300;
}
[AfterDeserialization(false)]
private void AfterDeserialization()
{
if (_platform == null)
{
Delete();
}
}
}

View file

@ -13,127 +13,104 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
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<ChampionSkullBrazier>();
m_Enlightenment = reader.ReadEntity<ChampionSkullBrazier>();
m_Venom = reader.ReadEntity<ChampionSkullBrazier>();
m_Pain = reader.ReadEntity<ChampionSkullBrazier>();
m_Greed = reader.ReadEntity<ChampionSkullBrazier>();
m_Death = reader.ReadEntity<ChampionSkullBrazier>();
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 } };
}

File diff suppressed because it is too large Load diff

View file

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

View file

@ -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 <http://www.gnu.org/licenses/>. *
*************************************************************************/
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];
}
}
}

View file

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

View file

@ -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<ChampionTitle>();
}
}
}
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() => "...";
}

View file

@ -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<PlayerMobile, ChampionTitleContext> _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<PlayerMobile>());
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<Mobile>.Create();
foreach (var context in _championTitleContexts.Values)
{
if (!context.CheckAtrophy())
{
queue.Enqueue(context.Player);
}
}
while (queue.Count > 0)
{
_championTitleContexts.Remove((PlayerMobile)queue.Dequeue());
}
}
}
}

View file

@ -13,42 +13,30 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
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);
}
}

View file

@ -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<Mobile>();
if (m_Harrower == null)
{
Delete();
}
break;
}
}
if (Light != LightType.Circle300)
{
Light = LightType.Circle300;
}
Delete();
}
}
}

View file

@ -13,43 +13,31 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
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);
}
}

View file

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

View file

@ -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;