From 7819830628ddc7ad8ab72b25897232e9c90e298d Mon Sep 17 00:00:00 2001 From: Kamron Batman <3953314+kamronbatman@users.noreply.github.com> Date: Sun, 9 May 2021 00:20:43 -0700 Subject: [PATCH] feat(champs): Ports Casiopia champions (#584) --- .../Engines/CannedEvil/CannedEvilTimer.cs | 107 ++ .../Engines/CannedEvil/ChampionAltar.cs | 35 +- .../Engines/CannedEvil/ChampionCommands.cs | 83 ++ .../Engines/CannedEvil/ChampionPlatform.cs | 40 +- .../Engines/CannedEvil/ChampionSkull.cs | 42 +- .../CannedEvil/ChampionSkullBrazier.cs | 72 +- .../CannedEvil/ChampionSkullPlatform.cs | 34 +- .../Engines/CannedEvil/ChampionSkullType.cs | 18 +- .../Engines/CannedEvil/ChampionSpawn.cs | 1115 ++++++++++------- .../Engines/CannedEvil/ChampionSpawnType.cs | 222 ++-- .../CannedEvil/DungeonChampionSpawn.cs | 54 + .../Engines/CannedEvil/GenChampEntry.cs | 49 + .../UOContent/Engines/CannedEvil/GenChamps.cs | 116 ++ .../Engines/CannedEvil/LLChampionSpawn.cs | 55 + .../Engines/CannedEvil/RestartTimer.cs | 20 - .../Engines/CannedEvil/SliceTimer.cs | 20 - Projects/UOContent/Items/Food/Cooking.cs | 4 +- .../Items/Weapons/Staves/ShepherdsCrook.cs | 2 +- .../UOContent/Spells/Necromancy/Exorcism.cs | 4 +- 19 files changed, 1402 insertions(+), 690 deletions(-) create mode 100644 Projects/UOContent/Engines/CannedEvil/CannedEvilTimer.cs mode change 100644 => 100755 Projects/UOContent/Engines/CannedEvil/ChampionAltar.cs create mode 100644 Projects/UOContent/Engines/CannedEvil/ChampionCommands.cs mode change 100644 => 100755 Projects/UOContent/Engines/CannedEvil/ChampionPlatform.cs mode change 100644 => 100755 Projects/UOContent/Engines/CannedEvil/ChampionSkull.cs mode change 100644 => 100755 Projects/UOContent/Engines/CannedEvil/ChampionSkullBrazier.cs mode change 100644 => 100755 Projects/UOContent/Engines/CannedEvil/ChampionSkullPlatform.cs mode change 100644 => 100755 Projects/UOContent/Engines/CannedEvil/ChampionSkullType.cs mode change 100644 => 100755 Projects/UOContent/Engines/CannedEvil/ChampionSpawn.cs mode change 100644 => 100755 Projects/UOContent/Engines/CannedEvil/ChampionSpawnType.cs create mode 100644 Projects/UOContent/Engines/CannedEvil/DungeonChampionSpawn.cs create mode 100644 Projects/UOContent/Engines/CannedEvil/GenChampEntry.cs create mode 100644 Projects/UOContent/Engines/CannedEvil/GenChamps.cs create mode 100644 Projects/UOContent/Engines/CannedEvil/LLChampionSpawn.cs delete mode 100644 Projects/UOContent/Engines/CannedEvil/RestartTimer.cs delete mode 100644 Projects/UOContent/Engines/CannedEvil/SliceTimer.cs diff --git a/Projects/UOContent/Engines/CannedEvil/CannedEvilTimer.cs b/Projects/UOContent/Engines/CannedEvil/CannedEvilTimer.cs new file mode 100644 index 000000000..a708b5969 --- /dev/null +++ b/Projects/UOContent/Engines/CannedEvil/CannedEvilTimer.cs @@ -0,0 +1,107 @@ +/************************************************************************* + * ModernUO * + * Copyright 2019-2021 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: CannedEvilTimer.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 System.Collections.Generic; +using Server.Misc; + +namespace Server.Engines.CannedEvil +{ + public class CannedEvilTimer : Timer + { + public static void Initialize() + { + // TODO: Needs configuration + Instance = new CannedEvilTimer(); + Instance.Start(); + Instance.OnTick(); + } + + private static readonly HashSet _dungeonSpawns = new(); + private static readonly HashSet _lostLandsSpawns = new(); + private static DateTime _sliceTime; + + public static CannedEvilTimer Instance { get; private set; } + + public static void AddSpawn(DungeonChampionSpawn spawn) + { + _dungeonSpawns.Add(spawn); + Instance?.OnSlice(_dungeonSpawns, false); + } + + public static void AddSpawn(LLChampionSpawn spawn) + { + _lostLandsSpawns.Add(spawn); + Instance?.OnSlice(_lostLandsSpawns, false); + } + + public static void RemoveSpawn(DungeonChampionSpawn spawn) + { + _dungeonSpawns.Remove(spawn); + Instance?.OnSlice(_dungeonSpawns, false); + } + + public static void RemoveSpawn(LLChampionSpawn spawn) + { + _lostLandsSpawns.Remove(spawn); + Instance?.OnSlice(_lostLandsSpawns, false); + } + + public CannedEvilTimer() : base(TimeSpan.Zero, TimeSpan.FromMinutes(1.0)) + { + Priority = TimerPriority.OneMinute; + _sliceTime = Core.Now; + } + + public void OnSlice(ICollection list, bool rotate = true) where T : ChampionSpawn + { + if (list.Count > 0) + { + List valid = new List(); + + foreach (T spawn in list) + { + if (spawn.AlwaysActive && !spawn.Active) + { + spawn.ReadyToActivate = true; + } + else if (rotate && (!spawn.Active || spawn.Kills == 0 && spawn.Level == 0)) + { + spawn.Active = false; + spawn.ReadyToActivate = false; + + valid.Add(spawn); + } + } + + if (valid.Count > 0) + { + valid[Utility.Random(valid.Count)].ReadyToActivate = true; + } + } + } + + protected override void OnTick() + { + if (!AutoRestart.Restarting && Core.Now >= _sliceTime) + { + OnSlice(_dungeonSpawns); + OnSlice(_lostLandsSpawns); + + _sliceTime = Core.Now.Date + TimeSpan.FromDays(1.0); + } + } + } +} diff --git a/Projects/UOContent/Engines/CannedEvil/ChampionAltar.cs b/Projects/UOContent/Engines/CannedEvil/ChampionAltar.cs old mode 100644 new mode 100755 index afc5c9cb0..4094d4fd2 --- a/Projects/UOContent/Engines/CannedEvil/ChampionAltar.cs +++ b/Projects/UOContent/Engines/CannedEvil/ChampionAltar.cs @@ -1,22 +1,37 @@ +/************************************************************************* + * ModernUO * + * Copyright 2019-2021 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: ChampionAltar.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 Server.Items; namespace Server.Engines.CannedEvil { public class ChampionAltar : PentagramAddon { - private ChampionSpawn m_Spawn; + public ChampionSpawn Spawn { get; private set; } - public ChampionAltar(ChampionSpawn spawn) => m_Spawn = spawn; - - public ChampionAltar(Serial serial) : base(serial) - { - } + public ChampionAltar(ChampionSpawn spawn) => Spawn = spawn; public override void OnAfterDelete() { base.OnAfterDelete(); - m_Spawn?.Delete(); + Spawn?.Delete(); + } + + public ChampionAltar(Serial serial) : base(serial) + { } public override void Serialize(IGenericWriter writer) @@ -25,7 +40,7 @@ namespace Server.Engines.CannedEvil writer.Write(0); // version - writer.Write(m_Spawn); + writer.Write(Spawn); } public override void Deserialize(IGenericReader reader) @@ -38,9 +53,9 @@ namespace Server.Engines.CannedEvil { case 0: { - m_Spawn = reader.ReadEntity(); + Spawn = reader.ReadEntity(); - if (m_Spawn == null) + if (Spawn == null) { Delete(); } diff --git a/Projects/UOContent/Engines/CannedEvil/ChampionCommands.cs b/Projects/UOContent/Engines/CannedEvil/ChampionCommands.cs new file mode 100644 index 000000000..a3a3d7e59 --- /dev/null +++ b/Projects/UOContent/Engines/CannedEvil/ChampionCommands.cs @@ -0,0 +1,83 @@ +/************************************************************************* + * ModernUO * + * Copyright 2019-2021 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: ChampionCommands.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 Server.Targeting; + +namespace Server.Engines.CannedEvil +{ + public static class ChampionCommands + { + public static void Initialize() + { + CommandSystem.Register("ClearChampByTarget", AccessLevel.Administrator, KillByTarget_OnCommand); + CommandSystem.Register("ClearChampByRegion", AccessLevel.Administrator, KillByRegion_OnCommand); + } + + [Usage("ClearChampByTarget")] + [Description("Kills all minions of a champion spawn.")] + private static void KillByTarget_OnCommand(CommandEventArgs e) + { + e.Mobile.Target = new KillTarget(); + e.Mobile.SendMessage("Which champion spawn would you like to clear?"); + } + + private class KillTarget : Target + { + public KillTarget() : base(15, false, TargetFlags.None) + { + } + + protected override void OnTarget(Mobile from, object targ) + { + if (from == null || from.AccessLevel < AccessLevel.Administrator) + { + return; + } + + ChampionSpawn spawn = targ switch + { + ChampionSpawn championSpawn => championSpawn, + IdolOfTheChampion champion => champion.Spawn, + ChampionAltar altar => altar.Spawn, + ChampionPlatform platform => platform.Spawn, + _ => null + }; + + if (spawn == null) + { + from.SendMessage("That is not a valid target. Please target the champion, altar, platform, or idol."); + } + + spawn?.DeleteCreatures(); + spawn?.Champion?.Delete(); + } + } + + [Usage("ClearChampByRegion")] + [Description("Kills all minions of a champion spawn.")] + private static void KillByRegion_OnCommand(CommandEventArgs e) + { + if (e.Mobile.Region is ChampionSpawnRegion { Spawn: { } } region) + { + region.Spawn.DeleteCreatures(); + region.Spawn.Champion?.Delete(); + } + else + { + e.Mobile.SendMessage("You are not in a champion spawn region."); + } + } + } +} diff --git a/Projects/UOContent/Engines/CannedEvil/ChampionPlatform.cs b/Projects/UOContent/Engines/CannedEvil/ChampionPlatform.cs old mode 100644 new mode 100755 index 690c2926d..c68bd16bc --- a/Projects/UOContent/Engines/CannedEvil/ChampionPlatform.cs +++ b/Projects/UOContent/Engines/CannedEvil/ChampionPlatform.cs @@ -1,14 +1,29 @@ +/************************************************************************* + * ModernUO * + * Copyright 2019-2021 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: ChampionPlatform.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 Server.Items; namespace Server.Engines.CannedEvil { public class ChampionPlatform : BaseAddon { - private ChampionSpawn m_Spawn; + public ChampionSpawn Spawn { get; private set; } public ChampionPlatform(ChampionSpawn spawn) { - m_Spawn = spawn; + Spawn = spawn; for (var x = -2; x <= 2; ++x) { @@ -41,16 +56,9 @@ namespace Server.Engines.CannedEvil AddComponent(0x75C, 2, -2, 0); } - public ChampionPlatform(Serial serial) : base(serial) - { - } - public void AddComponent(int id, int x, int y, int z) { - var ac = new AddonComponent(id); - - ac.Hue = 0x497; - + AddonComponent ac = new AddonComponent(id) { Hue = 0x497 }; AddComponent(ac, x, y, z); } @@ -58,7 +66,11 @@ namespace Server.Engines.CannedEvil { base.OnAfterDelete(); - m_Spawn?.Delete(); + Spawn?.Delete(); + } + + public ChampionPlatform(Serial serial) : base(serial) + { } public override void Serialize(IGenericWriter writer) @@ -67,7 +79,7 @@ namespace Server.Engines.CannedEvil writer.Write(0); // version - writer.Write(m_Spawn); + writer.Write(Spawn); } public override void Deserialize(IGenericReader reader) @@ -80,9 +92,9 @@ namespace Server.Engines.CannedEvil { case 0: { - m_Spawn = reader.ReadEntity(); + Spawn = reader.ReadEntity(); - if (m_Spawn == null) + if (Spawn == null) { Delete(); } diff --git a/Projects/UOContent/Engines/CannedEvil/ChampionSkull.cs b/Projects/UOContent/Engines/CannedEvil/ChampionSkull.cs old mode 100644 new mode 100755 index 6d4159c29..37fa7d0db --- a/Projects/UOContent/Engines/CannedEvil/ChampionSkull.cs +++ b/Projects/UOContent/Engines/CannedEvil/ChampionSkull.cs @@ -1,3 +1,18 @@ +/************************************************************************* + * ModernUO * + * Copyright 2019-2021 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: ChampionSkull.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 Server.Engines.CannedEvil; namespace Server.Items @@ -6,6 +21,19 @@ namespace Server.Items { private ChampionSkullType m_Type; + [CommandProperty(AccessLevel.GameMaster)] + public ChampionSkullType Type + { + get => m_Type; + set + { + m_Type = value; + InvalidateProperties(); + } + } + + public override int LabelNumber => 1049479 + (int)m_Type; + [Constructible] public ChampionSkull(ChampionSkullType type) : base(0x1AE1) { @@ -28,19 +56,6 @@ namespace Server.Items { } - [CommandProperty(AccessLevel.GameMaster)] - public ChampionSkullType Type - { - get => m_Type; - set - { - m_Type = value; - InvalidateProperties(); - } - } - - public override int LabelNumber => 1049479 + (int)m_Type; - public override void Serialize(IGenericWriter writer) { base.Serialize(writer); @@ -62,7 +77,6 @@ namespace Server.Items case 0: { m_Type = (ChampionSkullType)reader.ReadInt(); - break; } } diff --git a/Projects/UOContent/Engines/CannedEvil/ChampionSkullBrazier.cs b/Projects/UOContent/Engines/CannedEvil/ChampionSkullBrazier.cs old mode 100644 new mode 100755 index ca96884c2..70b6fcef2 --- a/Projects/UOContent/Engines/CannedEvil/ChampionSkullBrazier.cs +++ b/Projects/UOContent/Engines/CannedEvil/ChampionSkullBrazier.cs @@ -1,26 +1,28 @@ +/************************************************************************* + * ModernUO * + * Copyright 2019-2021 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: ChampionSkullBrazier.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 Server.Items; -using Server.Mobiles; using Server.Targeting; +using Server.Mobiles; namespace Server.Engines.CannedEvil { public class ChampionSkullBrazier : AddonComponent { - private Item m_Skull; private ChampionSkullType m_Type; - - public ChampionSkullBrazier(ChampionSkullPlatform platform, ChampionSkullType type) : base(0x19BB) - { - Hue = 0x455; - Light = LightType.Circle300; - - Platform = platform; - m_Type = type; - } - - public ChampionSkullBrazier(Serial serial) : base(serial) - { - } + private Item m_Skull; [CommandProperty(AccessLevel.GameMaster)] public ChampionSkullPlatform Platform { get; private set; } @@ -49,6 +51,19 @@ namespace Server.Engines.CannedEvil public override int LabelNumber => 1049489 + (int)m_Type; + public ChampionSkullBrazier(ChampionSkullPlatform platform, ChampionSkullType type) : base(0x19BB) + { + Hue = 0x455; + Light = LightType.Circle300; + + Platform = platform; + m_Type = type; + } + + public ChampionSkullBrazier(Serial serial) : base(serial) + { + } + public override void OnDoubleClick(Mobile from) { Platform?.Validate(); @@ -63,7 +78,7 @@ namespace Server.Engines.CannedEvil return; } - if (m_Skull?.Deleted == true) + if (m_Skull is { Deleted: true }) { Skull = null; } @@ -94,7 +109,7 @@ namespace Server.Engines.CannedEvil return; } - if (m_Skull?.Deleted == true) + if (m_Skull is { Deleted: true }) { Skull = null; } @@ -135,6 +150,17 @@ namespace Server.Engines.CannedEvil } } + private class SacrificeTarget : Target + { + 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); + } + public override void Serialize(IGenericWriter writer) { base.Serialize(writer); @@ -179,17 +205,5 @@ namespace Server.Engines.CannedEvil Light = LightType.Circle300; } } - - private class SacrificeTarget : Target - { - 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); - } - } } } diff --git a/Projects/UOContent/Engines/CannedEvil/ChampionSkullPlatform.cs b/Projects/UOContent/Engines/CannedEvil/ChampionSkullPlatform.cs old mode 100644 new mode 100755 index 4c0902dfe..c6a3b2814 --- a/Projects/UOContent/Engines/CannedEvil/ChampionSkullPlatform.cs +++ b/Projects/UOContent/Engines/CannedEvil/ChampionSkullPlatform.cs @@ -1,3 +1,18 @@ +/************************************************************************* + * ModernUO * + * Copyright 2019-2021 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: ChampionSkullPlatform.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 Server.Items; using Server.Mobiles; @@ -38,23 +53,16 @@ namespace Server.Engines.CannedEvil AddComponent(new AddonComponent(0x50F), 0, 1, 4); AddComponent(m_Death = new ChampionSkullBrazier(this, ChampionSkullType.Death), 0, 1, 5); - AddonComponent comp = new LocalizedAddonComponent(0x20D2, 1049495); - comp.Hue = 0x482; + AddonComponent comp = new LocalizedAddonComponent(0x20D2, 1049495) { Hue = 0x482 }; AddComponent(comp, 0, 0, 5); - comp = new LocalizedAddonComponent(0x0BCF, 1049496); - comp.Hue = 0x482; + comp = new LocalizedAddonComponent(0x0BCF, 1049496) { Hue = 0x482 }; AddComponent(comp, 0, 2, -7); - comp = new LocalizedAddonComponent(0x0BD0, 1049497); - comp.Hue = 0x482; + comp = new LocalizedAddonComponent(0x0BD0, 1049497) { Hue = 0x482 }; AddComponent(comp, 2, 0, -7); } - public ChampionSkullPlatform(Serial serial) : base(serial) - { - } - public void Validate() { if (Validate(m_Power) && Validate(m_Enlightenment) && Validate(m_Venom) && Validate(m_Pain) && @@ -86,7 +94,11 @@ namespace Server.Engines.CannedEvil } } - public bool Validate(ChampionSkullBrazier brazier) => brazier?.Skull?.Deleted == false; + public bool Validate(ChampionSkullBrazier brazier) => brazier is { Skull: { Deleted: false } }; + + public ChampionSkullPlatform(Serial serial) : base(serial) + { + } public override void Serialize(IGenericWriter writer) { diff --git a/Projects/UOContent/Engines/CannedEvil/ChampionSkullType.cs b/Projects/UOContent/Engines/CannedEvil/ChampionSkullType.cs old mode 100644 new mode 100755 index e48f02292..fe6f48b0b --- a/Projects/UOContent/Engines/CannedEvil/ChampionSkullType.cs +++ b/Projects/UOContent/Engines/CannedEvil/ChampionSkullType.cs @@ -1,3 +1,18 @@ +/************************************************************************* + * ModernUO * + * Copyright 2019-2021 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: ChampionSkullType.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 . * + *************************************************************************/ + namespace Server.Engines.CannedEvil { public enum ChampionSkullType @@ -7,6 +22,7 @@ namespace Server.Engines.CannedEvil Venom, Pain, Greed, - Death + Death, + None } } diff --git a/Projects/UOContent/Engines/CannedEvil/ChampionSpawn.cs b/Projects/UOContent/Engines/CannedEvil/ChampionSpawn.cs old mode 100644 new mode 100755 index 6923057bf..96f21ecdf --- a/Projects/UOContent/Engines/CannedEvil/ChampionSpawn.cs +++ b/Projects/UOContent/Engines/CannedEvil/ChampionSpawn.cs @@ -1,38 +1,78 @@ +/************************************************************************* + * ModernUO * + * Copyright 2019-2021 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: ChampionSpawn.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 System.Net; using System.Collections.Generic; using Server.Gumps; using Server.Items; using Server.Mobiles; using Server.Regions; -using Server.Utilities; namespace Server.Engines.CannedEvil { public class ChampionSpawn : Item { - private const int Level1 = 4; // First spawn level from 0-4 red skulls - private const int Level2 = 8; // Second spawn level from 5-8 red skulls - private const int Level3 = 12; // Third spawn level from 9-12 red skulls - private bool m_Active; - private ChampionAltar m_Altar; + private ChampionSpawnType m_Type; private List m_Creatures; - - private Dictionary m_DamageEntries; - - private IdolOfTheChampion m_Idol; - private int m_Kills; - private ChampionPlatform m_Platform; 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; + + //private int m_SpawnRange; + private Rectangle2D m_SpawnArea; private ChampionSpawnRegion m_Region; - // private int m_SpawnRange; - private Rectangle2D m_SpawnArea; - private int m_SPawnSzMod; + //Goes back each level, below level 0 and it goes off! - private Timer m_Timer, m_RestartTimer; - private ChampionSpawnType m_Type; - private List m_WhiteSkulls; + private Timer m_Timer; + + private IdolOfTheChampion m_Idol; + + 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 override TimeSpan DecayTime => TimeSpan.FromSeconds(180.0); + + public virtual bool HasStarRoomGate => true; + + public Dictionary DamageEntries { get; private set; } + + public Timer RestartTimer { get; 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) @@ -48,31 +88,32 @@ namespace Server.Engines.CannedEvil m_Altar = new ChampionAltar(this); m_Idol = new IdolOfTheChampion(this); - ExpireDelay = TimeSpan.FromMinutes(10.0); - RestartDelay = TimeSpan.FromMinutes(10.0); + ExpireDelay = TimeSpan.FromMinutes(30.0); + RestartDelay = TimeSpan.FromMinutes(30.0); + DamageEntries = new Dictionary(); - m_DamageEntries = new Dictionary(); - - Timer.DelayCall(SetInitialSpawnArea); + Timer.DelayCall(TimeSpan.Zero, SetInitialSpawnArea); } - public ChampionSpawn(Serial serial) : base(serial) + public void SetInitialSpawnArea() { + //Previous default used to be 24; + SpawnArea = new Rectangle2D(new Point2D(X - 24, Y - 24), new Point2D(X + 24, Y + 24)); } - [CommandProperty(AccessLevel.GameMaster)] - public int SpawnSzMod + public virtual ChampionSpawnRegion GetRegion() => new(this); + + public void UpdateRegion() { - get => m_SPawnSzMod < 1 || m_SPawnSzMod > 12 ? 12 : m_SPawnSzMod; - set => m_SPawnSzMod = value < 1 || value > 12 ? 12 : value; + m_Region?.Unregister(); + + if (!Deleted && Map != Map.Internal) + { + m_Region = GetRegion(); + m_Region.Register(); + } } - [CommandProperty(AccessLevel.GameMaster)] - public bool ConfinedRoaming { get; set; } - - [CommandProperty(AccessLevel.GameMaster)] - public bool HasBeenAdvanced { get; set; } - [CommandProperty(AccessLevel.GameMaster)] public bool RandomizeType { get; set; } @@ -83,6 +124,15 @@ namespace Server.Engines.CannedEvil set { m_Kills = value; + + double n = m_Kills / (double)MaxKills; + int p = (int)(n * 100); + + if (p < 90) + { + SetWhiteSkullCount(p / 20); + } + InvalidateProperties(); } } @@ -141,83 +191,80 @@ namespace Server.Engines.CannedEvil } } + [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_RedSkulls.Count; + get => m_Level; set { - for (var i = m_RedSkulls.Count - 1; i >= value; --i) + for (int i = m_RedSkulls.Count - 1; i >= value; --i) { m_RedSkulls[i].Delete(); m_RedSkulls.RemoveAt(i); } - for (var i = m_RedSkulls.Count; i < value; ++i) + for (int i = m_RedSkulls.Count; i < Math.Min(value, 16); ++i) { - var skull = new Item(0x1854); - - skull.Hue = 0x26; - skull.Movable = false; - skull.Light = LightType.Circle150; - + 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(); } } - public int MaxKills => m_SPawnSzMod * (250 / 12) - Level * m_SPawnSzMod; - - 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 void UpdateRegion() - { - m_Region?.Unregister(); - - if (!Deleted && Map != Map.Internal) - { - m_Region = new ChampionSpawnRegion(this); - m_Region.Register(); - } - - /* - if (m_Region == null) - { - m_Region = new ChampionSpawnRegion( this ); - } - else - { - m_Region.Unregister(); - //Why doesn't Region allow me to set it's map/Area myself? >< - m_Region = new ChampionSpawnRegion( this ); - } - */ + [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 (var i = m_WhiteSkulls.Count - 1; i >= val; --i) + for (int i = m_WhiteSkulls.Count - 1; i >= val; --i) { m_WhiteSkulls[i].Delete(); m_WhiteSkulls.RemoveAt(i); } - for (var i = m_WhiteSkulls.Count; i < val; ++i) + for (int i = m_WhiteSkulls.Count; i < val; ++i) { - var skull = new Item(0x1854); + Item skull = new Item(0x1854); skull.Movable = false; skull.Light = LightType.Circle150; @@ -238,77 +285,6 @@ namespace Server.Engines.CannedEvil return; } - m_Active = true; - HasBeenAdvanced = false; - - m_Timer?.Stop(); - - m_Timer = new SliceTimer(this); - m_Timer.Start(); - - m_RestartTimer?.Stop(); - - m_RestartTimer = null; - - if (m_Altar != null) - { - if (Champion != null) - { - m_Altar.Hue = 0x26; - } - else - { - m_Altar.Hue = 0; - } - } - - if (m_Platform != null) - { - m_Platform.Hue = 0x452; - } - } - - public void Stop() - { - if (!m_Active || Deleted) - { - return; - } - - m_Active = false; - HasBeenAdvanced = false; - - m_Timer?.Stop(); - - m_Timer = null; - - m_RestartTimer?.Stop(); - - m_RestartTimer = null; - - if (m_Altar != null) - { - m_Altar.Hue = 0; - } - - if (m_Platform != null) - { - m_Platform.Hue = 0x497; - } - } - - public void BeginRestart(TimeSpan ts) - { - m_RestartTimer?.Stop(); - - RestartTime = Core.Now + ts; - - m_RestartTimer = new RestartTimer(this, ts); - m_RestartTimer.Start(); - } - - public void EndRestart() - { if (RandomizeType) { Type = Utility.Random(5) switch @@ -322,63 +298,136 @@ namespace Server.Engines.CannedEvil }; } + m_Active = true; + ReadyToActivate = false; HasBeenAdvanced = false; + m_MaxLevel = 16 + Utility.Random(3); - Start(); - } + m_Timer?.Stop(); - private ScrollofTranscendence CreateRandomSoT(bool felucca) - { - var level = Utility.RandomMinMax(1, 5); + m_Timer = Timer.DelayCall(TimeSpan.FromSeconds(1.0), TimeSpan.FromSeconds(1.0), OnSlice); + m_Timer.Start(); - if (felucca) + RestartTimer?.Stop(); + RestartTimer = null; + + if (m_Altar != null) { - level += 5; + m_Altar.Hue = Champion != null ? 0x26 : 0; } - return ScrollofTranscendence.CreateRandom(level, level); + if (m_Platform != null) + { + m_Platform.Hue = 0x452; + } + + ExpireTime = Core.Now + ExpireDelay; } - public static void GiveScrollTo(Mobile killer, SpecialScroll scroll) + public void Stop() { - if (scroll == null || killer == null) // sanity + if (!m_Active || Deleted) { return; } - if (scroll is ScrollofTranscendence) + m_Active = false; + ActivatedByValor = false; + HasBeenAdvanced = false; + m_MaxLevel = 0; + + m_Timer?.Stop(); + + m_Timer = null; + + RestartTimer?.Stop(); + RestartTimer = null; + + if (m_Altar != null) { - killer.SendLocalizedMessage(1094936); // You have received a Scroll of Transcendence! + m_Altar.Hue = 0; } - else + + if (m_Platform != null) { - killer.SendLocalizedMessage(1049524); // You have received a scroll of power! + m_Platform.Hue = 0x497; } + if (AlwaysActive) + { + BeginRestart(RestartDelay); + } + else if (ActivatedByProximity) + { + ActivatedByProximity = false; + NextProximityTime = Core.Now + TimeSpan.FromHours(6.0); + } + + Timer.DelayCall(TimeSpan.FromMinutes(10.0), ExpireCreatures); + } + + public void BeginRestart(TimeSpan ts) + { + RestartTimer?.Stop(); + + RestartTime = Core.Now + ts; + + RestartTimer = Timer.DelayCall(ts, EndRestart); + RestartTimer.Start(); + } + + 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(scroll); + killer.AddToBackpack(SoTF); } else { - if (killer.Corpse.Deleted == false) + if (killer.Corpse is { Deleted: false }) { - killer.Corpse.DropItem(scroll); + killer.Corpse.DropItem(SoTF); } else { - killer.AddToBackpack(scroll); + killer.AddToBackpack(SoTF); } } + // Justice reward var pm = (PlayerMobile)killer; for (var j = 0; j < pm.JusticeProtectors.Count; ++j) { - var prot = pm.JusticeProtectors[j]; - - if (prot.Map != killer.Map || prot.Kills >= 5 || prot.Criminal || - !JusticeVirtue.CheckMapRegion(killer, prot)) + Mobile prot = pm.JusticeProtectors[j]; + if (prot.Map != killer.Map || prot.Kills >= 5 || prot.Criminal || !JusticeVirtue.CheckMapRegion(killer, prot)) { continue; } @@ -393,22 +442,58 @@ namespace Server.Engines.CannedEvil if (chance > Utility.Random(100)) { - try - { - var scrollDupe = scroll.GetType().CreateEntityInstance(); + prot.SendLocalizedMessage(1049368); // You have been rewarded for your dedication to Justice! + ScrollofTranscendence SoTFduplicate = new ScrollofTranscendence (SoTF.Skill, SoTF.Value); + prot.AddToBackpack(SoTFduplicate); + } + } + } - if (scrollDupe != null) - { - prot.SendLocalizedMessage(1049368); // You have been rewarded for your dedication to Justice! - scrollDupe.Skill = scroll.Skill; - scrollDupe.Value = scroll.Value; - prot.AddToBackpack(scrollDupe); - } - } - catch - { - // ignored - } + 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; + for (var j = 0; j < pm.JusticeProtectors.Count; ++j) + { + Mobile prot = pm.JusticeProtectors[j]; + if (prot.Map != killer.Map || prot.Kills >= 5 || prot.Criminal || !JusticeVirtue.CheckMapRegion(killer, prot)) + { + continue; + } + + var chance = VirtueHelper.GetLevel(prot, VirtueName.Justice) switch + { + VirtueLevel.Seeker => 60, + VirtueLevel.Follower => 80, + VirtueLevel.Knight => 100, + _ => 0 + }; + + if (chance > Utility.Random(100)) + { + prot.SendLocalizedMessage(1049368); // You have been rewarded for your dedication to Justice! + //PowerScroll PSduplicate = new PowerScroll (PS.Skill, PS.Value); + prot.AddToBackpack(CreateRandomFelPS()); } } } @@ -426,12 +511,10 @@ namespace Server.Engines.CannedEvil { RegisterDamageTo(Champion); - if (Champion is BaseChampion champion) - { - AwardArtifact(champion.GetArtifact()); - } + //if (m_Champion is BaseChampion) + // AwardArtifact(((BaseChampion)m_Champion).GetArtifact()); - m_DamageEntries.Clear(); + DamageEntries.Clear(); if (m_Platform != null) { @@ -442,7 +525,7 @@ namespace Server.Engines.CannedEvil { m_Altar.Hue = 0; - if (!Core.ML || Map == Map.Felucca) + if (HasStarRoomGate && (!Core.ML || Map == Map.Felucca)) { new StarRoomGate(m_Altar.Location, m_Altar.Map, true); } @@ -450,21 +533,19 @@ namespace Server.Engines.CannedEvil Champion = null; Stop(); - - BeginRestart(RestartDelay); } } else { - var kills = m_Kills; + int kills = m_Kills; for (var i = 0; i < m_Creatures.Count; ++i) { - var m = m_Creatures[i]; + Mobile m = m_Creatures[i]; if (m.Deleted) { - if (m.Corpse?.Deleted == false) + if (m.Corpse is { Deleted: false }) { ((Corpse)m.Corpse).BeginDecay(TimeSpan.FromMinutes(1)); } @@ -473,13 +554,13 @@ namespace Server.Engines.CannedEvil --i; ++m_Kills; - var killer = m.FindMostRecentDamager(false); + Mobile killer = m.FindMostRecentDamager(false); RegisterDamageTo(m); - if (killer is BaseCreature bc) + if (killer is BaseCreature creature) { - killer = bc.GetMaster(); + killer = creature.GetMaster(); } if (killer is PlayerMobile pm) @@ -490,39 +571,39 @@ namespace Server.Engines.CannedEvil { if (Utility.RandomDouble() < 0.001) { - double random = Utility.Random(49); + double random = Utility.Random (49); if (random <= 24) { - var SoTF = CreateRandomSoT(true); - GiveScrollTo(pm, SoTF); + ScrollofTranscendence SoTF = CreateRandomFelSoT(); + GiveScrollOfTranscendenceFelTo (pm, SoTF); } else { - var PS = PowerScroll.CreateRandomNoCraft(5, 5); - GiveScrollTo(pm, PS); + PowerScroll PS = CreateRandomFelPS(); + GivePowerScrollFelTo (pm, PS); } } } - if (Map == Map.Ilshenar || Map == Map.Tokuno || Map == Map.Malas) + if (Map == Map.Ilshenar || Map == Map.Tokuno) { if (Utility.RandomDouble() < 0.0015) { pm.SendLocalizedMessage(1094936); // You have received a Scroll of Transcendence! - var SoTT = CreateRandomSoT(false); + ScrollofTranscendence SoTT = CreateRandomTramSoT(); pm.AddToBackpack(SoTT); } } } - var mobSubLevel = GetSubLevelFor(m) + 1; + int mobSubLevel = GetSubLevelfor (m) + 1; if (mobSubLevel >= 0) { - var gainedPath = false; + bool gainedPath = false; - var pointsToGain = mobSubLevel * 40; + int pointsToGain = mobSubLevel * 40; if (VirtueHelper.Award(pm, VirtueName.Valor, pointsToGain, ref gainedPath)) { @@ -535,10 +616,10 @@ namespace Server.Engines.CannedEvil m.SendLocalizedMessage(1054030); // You have gained in Valor! } - // No delay on Valor gains + //No delay on Valor gains } - var info = pm.ChampionTitles; + ChampionTitleInfo info = pm.ChampionTitles; info.Award(m_Type, mobSubLevel); } @@ -552,10 +633,10 @@ namespace Server.Engines.CannedEvil InvalidateProperties(); } - var n = m_Kills / (double)MaxKills; - var p = (int)(n * 100); + double n = m_Kills / (double)MaxKills; + int p = (int)(n * 100); - if (p >= 90) + if (p >= 99) { AdvanceLevel(); } @@ -577,7 +658,7 @@ namespace Server.Engines.CannedEvil { ExpireTime = Core.Now + ExpireDelay; - if (Level < 16) + if (Level < m_MaxLevel) { m_Kills = 0; ++Level; @@ -587,12 +668,7 @@ namespace Server.Engines.CannedEvil 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 - ); + Effects.SendLocationEffect(new Point3D(m_Altar.X + 1, m_Altar.Y + 1, m_Altar.Z), m_Altar.Map, 0x3728, 10); } } else @@ -620,16 +696,48 @@ namespace Server.Engines.CannedEvil try { - Champion = ChampionSpawnInfo.GetInfo(m_Type).Champion.CreateInstance(); - } - catch - { - // ignored + Champion = Activator.CreateInstance(ChampionSpawnInfo.GetInfo(m_Type).Champion) as Mobile; } + catch (Exception e) + { Console.WriteLine($"Exception creating champion {m_Type}: {e}"); } - Champion?.MoveToWorld(new Point3D(X, Y, Z - 15), Map); + if (Champion != null) + { + Champion.MoveToWorld(new Point3D(X, Y, Z - 15), Map); + + if (Champion is BaseCreature bc) + { + 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); + } + } + else + { + throw new Exception("Champion Spawn is not inherited from BaseCreature"); + } + } } + public virtual int MaxSpawn => 250 - GetSubLevel() * 40; + public void Respawn() { if (!m_Active || Deleted || Champion != null) @@ -637,16 +745,16 @@ namespace Server.Engines.CannedEvil return; } - while (m_Creatures.Count < m_SPawnSzMod * (200 / 12) - GetSubLevel() * m_SPawnSzMod * (40 / 12)) + while (m_Creatures.Count < MaxSpawn) { - var m = Spawn(); + Mobile m = Spawn(); if (m == null) { return; } - var loc = GetSpawnLocation(); + Point3D loc = GetSpawnLocation(); // Allow creatures to turn into Paragons at Ilshenar champions. m.OnBeforeSpawn(loc, Map); @@ -658,26 +766,24 @@ namespace Server.Engines.CannedEvil { bc.Tamable = false; - if (!ConfinedRoaming) + if (ConfinedRoaming) { bc.Home = Location; - bc.RangeHome = - (int)(Math.Sqrt( - m_SpawnArea.Width * m_SpawnArea.Width + - m_SpawnArea.Height * m_SpawnArea.Height - ) / 2); + bc.HomeMap = Map; + bc.RangeHome = Math.Min(m_SpawnArea.Width / 2, m_SpawnArea.Height / 2); } else { bc.Home = bc.Location; + bc.HomeMap = bc.Map; - var xWall1 = new Point2D(m_SpawnArea.X, bc.Y); - var xWall2 = new Point2D(m_SpawnArea.X + m_SpawnArea.Width, bc.Y); - var yWall1 = new Point2D(bc.X, m_SpawnArea.Y); - var yWall2 = new Point2D(bc.X, m_SpawnArea.Y + m_SpawnArea.Height); + 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); - var minXDist = Math.Min(bc.GetDistanceToSqrt(xWall1), bc.GetDistanceToSqrt(xWall2)); - var minYDist = Math.Min(bc.GetDistanceToSqrt(yWall1), bc.GetDistanceToSqrt(yWall2)); + 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); } @@ -687,7 +793,7 @@ namespace Server.Engines.CannedEvil public Point3D GetSpawnLocation() { - var map = Map; + Map map = Map; if (map == null) { @@ -695,65 +801,43 @@ namespace Server.Engines.CannedEvil } // Try 20 times to find a spawnable location. - for (var i = 0; i < 20; i++) + for (int i = 0; i < 20; i++) { - /* - int x = Location.X + (Utility.Random( (m_SpawnRange * 2) + 1 ) - m_SpawnRange); - int y = Location.Y + (Utility.Random( (m_SpawnRange * 2) + 1 ) - m_SpawnRange); - */ + int x = Utility.Random(m_SpawnArea.X, m_SpawnArea.Width); + int y = Utility.Random(m_SpawnArea.Y, m_SpawnArea.Height); - var x = Utility.Random(m_SpawnArea.X, m_SpawnArea.Width); - var y = Utility.Random(m_SpawnArea.Y, m_SpawnArea.Height); - - var z = Map.GetAverageZ(x, y); + int z = Map.GetAverageZ(x, y); if (Map.CanSpawnMobile(new Point2D(x, y), z)) { return new Point3D(x, y, z); } - - /* try @ platform Z if map z fails */ - if (Map.CanSpawnMobile(new Point2D(x, y), m_Platform.Location.Z)) - { - return new Point3D(x, y, m_Platform.Location.Z); - } } return Location; } + public int Level1 => 4; + public int Level2 => 8; + public int Level3 => 12; + public int GetSubLevel() { - var level = Level; + int level = Level; - if (level <= Level1) - { - return 0; - } - - if (level <= Level2) - { - return 1; - } - - if (level <= Level3) - { - return 2; - } - - return 3; + return level <= Level1 ? 0 : level <= Level2 ? 1 : level <= Level3 ? 2 : 3; } - public int GetSubLevelFor(Mobile m) + public int GetSubLevelfor (Mobile m) { - var types = ChampionSpawnInfo.GetInfo(m_Type).SpawnTypes; - var t = m.GetType(); + Type[][] types = ChampionSpawnInfo.GetInfo(m_Type).SpawnTypes; + Type t = m.GetType(); - for (var i = 0; i < types.GetLength(0); i++) + for (int i = 0; i < types.GetLength(0); i++) { - var individualTypes = types[i]; + Type[] individualTypes = types[i]; - for (var j = 0; j < individualTypes.Length; j++) + for (int j = 0; j < individualTypes.Length; j++) { if (t == individualTypes[j]) { @@ -767,9 +851,9 @@ namespace Server.Engines.CannedEvil public Mobile Spawn() { - var types = ChampionSpawnInfo.GetInfo(m_Type).SpawnTypes; + Type[][] types = ChampionSpawnInfo.GetInfo(m_Type).SpawnTypes; - var v = GetSubLevel(); + int v = GetSubLevel(); if (v >= 0 && v < types.Length) { @@ -783,7 +867,7 @@ namespace Server.Engines.CannedEvil { try { - return types.RandomElement().CreateInstance(); + return Activator.CreateInstance(types[Utility.Random(types.Length)]) as Mobile; } catch { @@ -804,6 +888,11 @@ namespace Server.Engines.CannedEvil --Level; } + if (!AlwaysActive && Level == 0) + { + Stop(); + } + InvalidateProperties(); } else @@ -846,24 +935,12 @@ namespace Server.Engines.CannedEvil { int x, y; - switch (index) + 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; + 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); @@ -880,17 +957,11 @@ namespace Server.Engines.CannedEvil if (m_Active) { - list.Add(1060742); // active + list.Add(1060742); // active list.Add(1060658, "Type\t{0}", m_Type); // ~1_val~: ~2_val~ list.Add(1060659, "Level\t{0}", Level); // ~1_val~: ~2_val~ - - list.Add( - 1060660, // ~1_val~: ~2_val~ - "Kills\t{0} of {1} ({2:0.#}%)", - m_Kills, - MaxKills, - 100.0 * ((double)m_Kills / MaxKills) - ); + list.Add(1060660, "Kills\t{0} of {1} ({2:F1}%)", m_Kills, MaxKills, 100.0 * ((double)m_Kills / MaxKills)); // ~1_val~: ~2_val~ + //list.Add(1060661, "Spawn Range\t{0}", m_SpawnRange); // ~1_val~: ~2_val~ } else { @@ -939,7 +1010,7 @@ namespace Server.Engines.CannedEvil if (m_RedSkulls != null) { - for (var i = 0; i < m_RedSkulls.Count; ++i) + for (var i = 0; i < Math.Min(m_RedSkulls.Count, 16); ++i) { m_RedSkulls[i].Location = GetRedSkullLocation(i); } @@ -947,7 +1018,7 @@ namespace Server.Engines.CannedEvil if (m_WhiteSkulls != null) { - for (var i = 0; i < m_WhiteSkulls.Count; ++i) + for (int i = 0; i < m_WhiteSkulls.Count; ++i) { m_WhiteSkulls[i].Location = GetWhiteSkullLocation(i); } @@ -983,7 +1054,7 @@ namespace Server.Engines.CannedEvil if (m_RedSkulls != null) { - for (var i = 0; i < m_RedSkulls.Count; ++i) + for (int i = 0; i < m_RedSkulls.Count; ++i) { m_RedSkulls[i].Map = Map; } @@ -991,7 +1062,7 @@ namespace Server.Engines.CannedEvil if (m_WhiteSkulls != null) { - for (var i = 0; i < m_WhiteSkulls.Count; ++i) + for (int i = 0; i < m_WhiteSkulls.Count; ++i) { m_WhiteSkulls[i].Map = Map; } @@ -1012,7 +1083,7 @@ namespace Server.Engines.CannedEvil if (m_RedSkulls != null) { - for (var i = 0; i < m_RedSkulls.Count; ++i) + for (int i = 0; i < m_RedSkulls.Count; ++i) { m_RedSkulls[i].Delete(); } @@ -1022,7 +1093,7 @@ namespace Server.Engines.CannedEvil if (m_WhiteSkulls != null) { - for (var i = 0; i < m_WhiteSkulls.Count; ++i) + for (int i = 0; i < m_WhiteSkulls.Count; ++i) { m_WhiteSkulls[i].Delete(); } @@ -1030,22 +1101,9 @@ namespace Server.Engines.CannedEvil m_WhiteSkulls.Clear(); } - if (m_Creatures != null) - { - for (var i = 0; i < m_Creatures.Count; ++i) - { - var mob = m_Creatures[i]; + DeleteCreatures(); - if (!mob.Player) - { - mob.Delete(); - } - } - - m_Creatures.Clear(); - } - - if (Champion?.Player == false) + if (Champion is { Player: false }) { Champion.Delete(); } @@ -1055,6 +1113,36 @@ namespace Server.Engines.CannedEvil 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) @@ -1062,16 +1150,15 @@ namespace Server.Engines.CannedEvil return; } - foreach (var de in m.DamageEntries) + foreach (DamageEntry de in m.DamageEntries) { if (de.HasExpired) { continue; } - var damager = de.Damager; - - var master = damager.GetDamageMaster(m); + Mobile damager = de.Damager; + Mobile master = damager.GetDamageMaster(m); if (master != null) { @@ -1084,85 +1171,123 @@ namespace Server.Engines.CannedEvil public void RegisterDamage(Mobile from, int amount) { - if (from?.Player != true) + if (@from?.Player != true) { return; } - m_DamageEntries[from] = amount + (m_DamageEntries.TryGetValue(from, out var value) ? value : 0); + if (DamageEntries.ContainsKey(from)) + { + DamageEntries[from] += amount; + } + else + { + DamageEntries.Add(from, amount); + } } - public void AwardArtifact(Item artifact) + public virtual void AwardArtifact(Item artifact) { if (artifact == null) { return; } - var totalDamage = 0; - - var validEntries = new Dictionary(); - - foreach (var kvp in m_DamageEntries) + if (DamageEntries.Count > 0) { - if (IsEligible(kvp.Key, artifact)) + int totalDamage = 0; + + Dictionary validEntries = new Dictionary(); + + foreach (var (key, value) in DamageEntries) { - validEntries.Add(kvp.Key, kvp.Value); - totalDamage += kvp.Value; + if (IsEligible(key, artifact)) + { + validEntries.Add(key, value); + totalDamage += value; + } } - } - var randomDamage = Utility.RandomMinMax(1, totalDamage); + bool artifactGiven = false; - totalDamage = 0; - - foreach (var kvp in validEntries) - { - totalDamage += kvp.Value; - - if (totalDamage >= randomDamage) + do { - GiveArtifact(kvp.Key, artifact); - return; + 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; + } } - } - - artifact.Delete(); - } - - public void GiveArtifact(Mobile to, Item artifact) - { - if (to == null || artifact == null) - { - return; - } - - var pack = to.Backpack; - - if (pack?.TryDropItem(to, artifact, false) != true) - { - artifact.Delete(); + while (!artifactGiven); } else { - // For your valor in combating the fallen beast, a special artifact has been bestowed on you. - to.SendLocalizedMessage(1062317); + artifact.Delete(); } } - public bool IsEligible(Mobile m, Item artifact) => + 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; + m.Backpack?.CheckHold(m, Artifact, false) == true; public override void Serialize(IGenericWriter writer) { base.Serialize(writer); - writer.Write(6); // version + writer.Write(9); // version - writer.Write(m_SPawnSzMod); - writer.Write(m_DamageEntries.Count); - foreach (var kvp in m_DamageEntries) + 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); @@ -1175,16 +1300,12 @@ namespace Server.Engines.CannedEvil writer.Write(RandomizeType); - // writer.Write( m_SpawnRange ); writer.Write(m_Kills); writer.Write(m_Active); writer.Write((int)m_Type); - m_Creatures.Tidy(); writer.Write(m_Creatures); - m_RedSkulls.Tidy(); writer.Write(m_RedSkulls); - m_WhiteSkulls.Tidy(); writer.Write(m_WhiteSkulls); writer.Write(m_Platform); writer.Write(m_Altar); @@ -1193,9 +1314,9 @@ namespace Server.Engines.CannedEvil writer.Write(Champion); writer.Write(RestartDelay); - writer.Write(m_RestartTimer != null); + writer.Write(RestartTimer != null); - if (m_RestartTimer != null) + if (RestartTimer != null) { writer.WriteDeltaTime(RestartTime); } @@ -1205,33 +1326,53 @@ namespace Server.Engines.CannedEvil { base.Deserialize(reader); - m_DamageEntries = new Dictionary(); + DamageEntries = new Dictionary(); - var version = reader.ReadInt(); + int version = reader.ReadInt(); - switch (version) + 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: { - m_SPawnSzMod = reader.ReadInt(); + if (version < 7) + { + m_MaxLevel = 16 + Utility.Random(3); //full levels + } + + ActivatedByValor = reader.ReadBool(); goto case 5; } case 5: { - var entries = reader.ReadInt(); - for (var i = 0; i < entries; ++i) + int entries = reader.ReadInt(); + Mobile m; + int damage; + for (int i = 0; i < entries; ++i) { - var m = reader.ReadEntity(); - var damage = reader.ReadInt(); - - if (m == null) + m = reader.ReadEntity(); + damage = reader.ReadInt(); + if (m != null) { - continue; + DamageEntries.Add(m, damage); } - - m_DamageEntries.Add(m, damage); } - goto case 4; } case 4: @@ -1258,12 +1399,9 @@ namespace Server.Engines.CannedEvil { if (version < 3) { - var oldRange = reader.ReadInt(); + int oldRange = reader.ReadInt(); - m_SpawnArea = new Rectangle2D( - new Point2D(X - oldRange, Y - oldRange), - new Point2D(X + oldRange, Y + oldRange) - ); + m_SpawnArea = new Rectangle2D(new Point2D(X - oldRange, Y - oldRange), new Point2D(X + oldRange, Y + oldRange)); } m_Kills = reader.ReadInt(); @@ -1274,11 +1412,10 @@ namespace Server.Engines.CannedEvil { if (version < 1) { - m_SpawnArea = - new Rectangle2D(new Point2D(X - 24, Y - 24), new Point2D(X + 24, Y + 24)); // Default was 24 + m_SpawnArea = new Rectangle2D(new Point2D(X - 24, Y - 24), new Point2D(X + 24, Y + 24)); //Default was 24 } - var active = reader.ReadBool(); + bool active = reader.ReadBool(); m_Type = (ChampionSpawnType)reader.ReadInt(); m_Creatures = reader.ReadEntityList(); m_RedSkulls = reader.ReadEntityList(); @@ -1310,57 +1447,163 @@ namespace Server.Engines.CannedEvil { Start(); } + else if (AlwaysActive) + { + ReadyToActivate = true; + } break; } } - Timer.DelayCall(UpdateRegion); + Timer.DelayCall(TimeSpan.Zero, UpdateRegion); } } public class ChampionSpawnRegion : BaseRegion { - public ChampionSpawnRegion(ChampionSpawn spawn) : base( - null, - spawn.Map, - Find(spawn.Location, spawn.Map), - spawn.SpawnArea - ) => - ChampionSpawn = spawn; + public ChampionSpawn Spawn { get; } - public override bool YoungProtected => false; - - public ChampionSpawn ChampionSpawn { 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 + } - // 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 - global = Math.Max(global, 1 + ChampionSpawn.Level); + 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; + } + + 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; + } + + 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)) + { + 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.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; + } + } + } } } public class IdolOfTheChampion : Item { - public IdolOfTheChampion(ChampionSpawn spawn) : base(0x1F18) + public ChampionSpawn Spawn { get; private set; } + + public override string DefaultName => "Idol of the Champion"; + + public IdolOfTheChampion(ChampionSpawn spawn): base(0x1F18) { Spawn = spawn; Movable = false; } - public IdolOfTheChampion(Serial serial) : base(serial) - { - } - - public ChampionSpawn Spawn { get; private set; } - - public override string DefaultName => "Idol of the Champion"; - public override void OnAfterDelete() { base.OnAfterDelete(); @@ -1368,6 +1611,10 @@ namespace Server.Engines.CannedEvil Spawn?.Delete(); } + public IdolOfTheChampion(Serial serial) : base(serial) + { + } + public override void Serialize(IGenericWriter writer) { base.Serialize(writer); @@ -1381,7 +1628,7 @@ namespace Server.Engines.CannedEvil { base.Deserialize(reader); - var version = reader.ReadInt(); + int version = reader.ReadInt(); switch (version) { diff --git a/Projects/UOContent/Engines/CannedEvil/ChampionSpawnType.cs b/Projects/UOContent/Engines/CannedEvil/ChampionSpawnType.cs old mode 100644 new mode 100755 index c6d999dc0..e1c70d7b9 --- a/Projects/UOContent/Engines/CannedEvil/ChampionSpawnType.cs +++ b/Projects/UOContent/Engines/CannedEvil/ChampionSpawnType.cs @@ -1,3 +1,18 @@ +/************************************************************************* + * ModernUO * + * Copyright 2019-2021 - 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; @@ -13,19 +28,12 @@ namespace Server.Engines.CannedEvil UnholyTerror, SleepingDragon, Glade, - Pestilence + Pestilence, + Graveyard } public class ChampionSpawnInfo { - public ChampionSpawnInfo(string name, Type champion, string[] levelNames, Type[][] spawnTypes) - { - Name = name; - Champion = champion; - LevelNames = levelNames; - SpawnTypes = spawnTypes; - } - public string Name { get; } public Type Champion { get; } @@ -34,131 +42,81 @@ namespace Server.Engines.CannedEvil public string[] LevelNames { get; } - public static ChampionSpawnInfo[] Table { get; } = + public ChampionSpawnInfo(string name, Type champion, string[] levelNames, Type[][] spawnTypes) { - new( - "Abyss", - typeof(Semidar), - new[] { "Foe", "Assassin", "Conqueror" }, - new[] // Abyss - { - // Abyss - 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[] // Arachnid - { - // Arachnid - 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[] // Cold Blood - { - // Cold Blood - new[] { typeof(Lizardman), typeof(Snake) }, // 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[] // Forest Lord - { - // Forest Lord - 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[] // Vermin Horde - { - // Vermin Horde - 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[] // Unholy Terror - { - // Unholy Terror - Core.AOS - ? new[] - { - typeof(Bogle), typeof(Ghoul), typeof(Shade), typeof(Spectre), typeof(Wraith) - } // Level 1 (Pre-AoS) - : new[] { typeof(Ghoul), typeof(Shade), typeof(Spectre), typeof(Wraith) }, // Level 1 + Name = name; + Champion = champion; + LevelNames = levelNames; + SpawnTypes = spawnTypes; + } - 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[] - { - // Unholy Terror - new[] { typeof(DeathwatchBeetleHatchling), typeof(Lizardman) }, - new[] { typeof(DeathwatchBeetle), typeof(Kappa) }, - new[] { typeof(LesserHiryu), typeof(RevenantLion) }, - new[] { typeof(Hiryu), typeof(Oni) } - } - ), - new( - "Glade", - typeof(Twaulo), - new[] { "Banisher", "Enforcer", "Eradicator" }, - new[] - { - // Glade - new[] { typeof(Pixie), typeof(ShadowWisp) }, - new[] { typeof(Centaur), typeof(MLDryad) }, - new[] { typeof(Satyr), typeof(CuSidhe) }, - new[] { typeof(FeralTreefellow), typeof(RagingGrizzlyBear) } - } - ), - new( - "The Corrupt", - typeof(Ilhenir), - new[] { "Cleanser", "Expunger", "Depurator" }, - new[] - { - // Unholy Terror - new[] { typeof(PlagueSpawn), typeof(Bogling) }, - new[] { typeof(PlagueBeast), typeof(BogThing) }, - new[] { typeof(PlagueBeastLord), typeof(InterredGrizzle) }, - new[] { typeof(FetidEssence), typeof(PestilentBandage) } - } - ) + 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) diff --git a/Projects/UOContent/Engines/CannedEvil/DungeonChampionSpawn.cs b/Projects/UOContent/Engines/CannedEvil/DungeonChampionSpawn.cs new file mode 100644 index 000000000..20402cde1 --- /dev/null +++ b/Projects/UOContent/Engines/CannedEvil/DungeonChampionSpawn.cs @@ -0,0 +1,54 @@ +/************************************************************************* + * ModernUO * + * Copyright 2019-2021 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: DungeonChampionSpawn.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 . * + *************************************************************************/ + +namespace Server.Engines.CannedEvil +{ + public class DungeonChampionSpawn : ChampionSpawn + { + [Constructible] + public DungeonChampionSpawn() : base() + { + CannedEvilTimer.AddSpawn(this); + } + + public DungeonChampionSpawn(Serial serial) : base(serial) + { + CannedEvilTimer.AddSpawn(this); + } + + 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); + } + } +} diff --git a/Projects/UOContent/Engines/CannedEvil/GenChampEntry.cs b/Projects/UOContent/Engines/CannedEvil/GenChampEntry.cs new file mode 100644 index 000000000..ec697f2e3 --- /dev/null +++ b/Projects/UOContent/Engines/CannedEvil/GenChampEntry.cs @@ -0,0 +1,49 @@ +/************************************************************************* + * ModernUO * + * Copyright 2019-2021 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: GenChampEntry.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; + +namespace Server.Engines.CannedEvil +{ + public record ChampionEntry + { + public readonly bool m_RandomizeType; + public readonly ChampionSpawnType m_Type; + public readonly Point3D m_SignLocation; + public readonly Type m_ChampType; + public readonly Map m_Map; + public readonly Point3D m_EjectLocation; + public readonly Map m_EjectMap; + + public ChampionEntry(Type champtype, Point3D signloc, Map map, Point3D ejectloc, Map ejectmap) : + this(champtype, ChampionSpawnType.Abyss, signloc, map, ejectloc, ejectmap, true) + { + } + + public ChampionEntry( + Type champtype, ChampionSpawnType type, Point3D signloc, Map map, Point3D ejectloc, Map ejectmap, + bool randomizetype = false + ) + { + m_ChampType = champtype; + m_RandomizeType = randomizetype; + m_Type = type; + m_SignLocation = signloc; + m_Map = map; + m_EjectLocation = ejectloc; + m_EjectMap = ejectmap; + } + } +} diff --git a/Projects/UOContent/Engines/CannedEvil/GenChamps.cs b/Projects/UOContent/Engines/CannedEvil/GenChamps.cs new file mode 100644 index 000000000..073c50411 --- /dev/null +++ b/Projects/UOContent/Engines/CannedEvil/GenChamps.cs @@ -0,0 +1,116 @@ +/************************************************************************* + * ModernUO * + * Copyright 2019-2021 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: GenChamps.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 System.Collections.Generic; +using Server.Logging; + +namespace Server.Engines.CannedEvil +{ + public static class ChampionGenerator + { + private static readonly ILogger logger = LogFactory.GetLogger(typeof(ChampionGenerator)); + + public static void Initialize() + { + CommandSystem.Register("GenChamps", AccessLevel.Owner, ChampGen_OnCommand); + } + + private static readonly ChampionEntry[] LLLocations = { + new(typeof(LLChampionSpawn), new Point3D(5511, 2360, 42), Map.Felucca, new Point3D(5439, 2323, 26 ), Map.Felucca), + new(typeof(LLChampionSpawn), new Point3D(6038, 2401, 47), Map.Felucca, new Point3D(5988, 2340, 24), Map.Felucca), + new(typeof(LLChampionSpawn), new Point3D(5549, 2640, 16), Map.Felucca, new Point3D(5645, 2696, -8), Map.Felucca), + new(typeof(LLChampionSpawn), new Point3D(5636, 2916, 37), Map.Felucca, new Point3D(5721, 2949, 28), Map.Felucca), + new(typeof(LLChampionSpawn), new Point3D(6035, 2943, 50), Map.Felucca, new Point3D(6098, 2997, 17), Map.Felucca), + new(typeof(LLChampionSpawn), new Point3D(5265, 3171, 105), Map.Felucca, new Point3D(5314, 3232, 2), Map.Felucca), + new(typeof(LLChampionSpawn), new Point3D(5282, 3368, 50), Map.Felucca, new Point3D(5215, 3318, 3), Map.Felucca), + new(typeof(LLChampionSpawn), new Point3D(5207, 3637, 20), Map.Felucca, new Point3D(5263, 3687, 0), Map.Felucca), + new(typeof(LLChampionSpawn), new Point3D(5954, 3475, 25), Map.Felucca, new Point3D(6013, 3529, 0), Map.Felucca), + new(typeof(LLChampionSpawn), new Point3D(5982, 3882, 20), Map.Felucca, new Point3D(5929, 3820, -1), Map.Felucca), + new(typeof(LLChampionSpawn), new Point3D(5724, 3991, 41), Map.Felucca, new Point3D(5774, 4041, 26), Map.Felucca), + new(typeof(LLChampionSpawn), ChampionSpawnType.ForestLord, new Point3D(5559, 3757, 21), Map.Felucca, new Point3D(5513, 3878, 3), Map.Felucca), + }; + + private static readonly ChampionEntry[] DungeonLocations = { + new(typeof(DungeonChampionSpawn), ChampionSpawnType.UnholyTerror, new Point3D(5179, 709, 20), Map.Felucca, new Point3D(4111, 432, 5), Map.Felucca), + new(typeof(DungeonChampionSpawn), ChampionSpawnType.VerminHorde, new Point3D(5557, 827, 65), Map.Felucca, new Point3D(5580, 632, 30), Map.Felucca), + new(typeof(DungeonChampionSpawn), ChampionSpawnType.ColdBlood, new Point3D(5259, 837, 64), Map.Felucca, new Point3D(1176, 2637, 0), Map.Felucca), + new(typeof(DungeonChampionSpawn), ChampionSpawnType.Abyss, new Point3D(5815, 1352, 5), Map.Felucca, new Point3D(2923, 3406, 8), Map.Felucca), + new(typeof(DungeonChampionSpawn), ChampionSpawnType.Arachnid, new Point3D(5190, 1607, 20), Map.Felucca, new Point3D(5482, 3161, -54), Map.Felucca), + }; + + [Usage("GenChamps")] + [Description("Generates champions for Felucca Dungeons & Lost Lands.")] + private static void ChampGen_OnCommand(CommandEventArgs e) + { + /* + //We take the assumption that we are spawning managed champions + for (int i = CannedEvilTimer.DungeonSpawns.Count - 1; i >= 0; i--) + CannedEvilTimer.DungeonSpawns[i].Delete(); + + for (int i = CannedEvilTimer.LLSpawns.Count - 1; i >= 0; i--) + CannedEvilTimer.LLSpawns[i].Delete(); + */ + + //We assume that all champion spawns are generated here. + List spawns = new List(); + foreach (Item item in World.Items.Values) + { + if (item is ChampionSpawn spawn) + { + spawns.Add(spawn); + } + } + + for (int i = spawns.Count - 1; i >= 0; i--) + { + spawns[i].Delete(); + } + + Process(DungeonLocations); + Process(LLLocations); + //ProcessIlshenar(); + //ProcessTokuno(); + } + + private static void Process(ChampionEntry[] entries) + { + for (int i = 0; i < entries.Length; i++) + { + ChampionEntry entry = entries[i]; + + try + { + if (Activator.CreateInstance(entry.m_ChampType) is ChampionSpawn spawn) + { + spawn.RandomizeType = entry.m_RandomizeType; + spawn.Type = entry.m_Type; + spawn.MoveToWorld(entry.m_SignLocation, entry.m_Map); + spawn.EjectLocation = entry.m_EjectLocation; + spawn.EjectMap = entry.m_EjectMap; + if (spawn.AlwaysActive) + { + spawn.ReadyToActivate = true; + } + } + } + catch + { + logger.Warning($"Failed to generate champion spawn {entry.m_ChampType.FullName} at {entry.m_SignLocation} ({entry.m_Map})"); + } + } + } + } +} diff --git a/Projects/UOContent/Engines/CannedEvil/LLChampionSpawn.cs b/Projects/UOContent/Engines/CannedEvil/LLChampionSpawn.cs new file mode 100644 index 000000000..b4144bad4 --- /dev/null +++ b/Projects/UOContent/Engines/CannedEvil/LLChampionSpawn.cs @@ -0,0 +1,55 @@ +/************************************************************************* + * ModernUO * + * Copyright 2019-2021 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: LLChampionSpawn.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 . * + *************************************************************************/ + +namespace Server.Engines.CannedEvil +{ + public class LLChampionSpawn : ChampionSpawn + { + public override bool HasStarRoomGate => false; + + [Constructible] + public LLChampionSpawn() + { + CannedEvilTimer.AddSpawn(this); + } + + public LLChampionSpawn(Serial serial) : base(serial) + { + } + + 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); + } + } +} diff --git a/Projects/UOContent/Engines/CannedEvil/RestartTimer.cs b/Projects/UOContent/Engines/CannedEvil/RestartTimer.cs deleted file mode 100644 index 494066791..000000000 --- a/Projects/UOContent/Engines/CannedEvil/RestartTimer.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; - -namespace Server.Engines.CannedEvil -{ - public class RestartTimer : Timer - { - private readonly ChampionSpawn m_Spawn; - - public RestartTimer(ChampionSpawn spawn, TimeSpan delay) : base(delay) - { - m_Spawn = spawn; - Priority = TimerPriority.FiveSeconds; - } - - protected override void OnTick() - { - m_Spawn.EndRestart(); - } - } -} diff --git a/Projects/UOContent/Engines/CannedEvil/SliceTimer.cs b/Projects/UOContent/Engines/CannedEvil/SliceTimer.cs deleted file mode 100644 index 8a9abc2a1..000000000 --- a/Projects/UOContent/Engines/CannedEvil/SliceTimer.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; - -namespace Server.Engines.CannedEvil -{ - public class SliceTimer : Timer - { - private readonly ChampionSpawn m_Spawn; - - public SliceTimer(ChampionSpawn spawn) : base(TimeSpan.FromSeconds(1.0), TimeSpan.FromSeconds(1.0)) - { - m_Spawn = spawn; - Priority = TimerPriority.OneSecond; - } - - protected override void OnTick() - { - m_Spawn.OnSlice(); - } - } -} diff --git a/Projects/UOContent/Items/Food/Cooking.cs b/Projects/UOContent/Items/Food/Cooking.cs index f165e7d02..8b17e5a8f 100644 --- a/Projects/UOContent/Items/Food/Cooking.cs +++ b/Projects/UOContent/Items/Food/Cooking.cs @@ -314,14 +314,14 @@ namespace Server.Items { } - public override void Serialize( GenericWriter writer ) + public override void Serialize(IGenericWriter writer) { base.Serialize( writer ); writer.Write( (int) 0 ); // version } - public override void Deserialize( GenericReader reader ) + public override void Deserialize(IGenericReader reader) { base.Deserialize( reader ); diff --git a/Projects/UOContent/Items/Weapons/Staves/ShepherdsCrook.cs b/Projects/UOContent/Items/Weapons/Staves/ShepherdsCrook.cs index 390162a07..57f5c5b93 100644 --- a/Projects/UOContent/Items/Weapons/Staves/ShepherdsCrook.cs +++ b/Projects/UOContent/Items/Weapons/Staves/ShepherdsCrook.cs @@ -121,7 +121,7 @@ namespace Server.Items if (Region.Find(bc.Home, map) is ChampionSpawnRegion region) { - var spawn = region.ChampionSpawn; + var spawn = region.Spawn; if (spawn?.IsChampionSpawn(bc) == true) { diff --git a/Projects/UOContent/Spells/Necromancy/Exorcism.cs b/Projects/UOContent/Spells/Necromancy/Exorcism.cs index 996d74849..37602bf04 100644 --- a/Projects/UOContent/Spells/Necromancy/Exorcism.cs +++ b/Projects/UOContent/Spells/Necromancy/Exorcism.cs @@ -84,7 +84,7 @@ namespace Server.Spells.Necromancy public override void OnCast() { var r = Caster.Region.GetRegion(); - if (r == null || !Caster.InRange(r.ChampionSpawn, Range)) + if (r == null || !Caster.InRange(r.Spawn, Range)) { Caster.SendLocalizedMessage(1072111); // You are not in a valid exorcism region. } @@ -95,7 +95,7 @@ namespace Server.Spells.Necromancy if (map != null) { // Surprisingly, no sparkle type effects - foreach (var m in r.ChampionSpawn.GetMobilesInRange(Range)) + foreach (var m in r.Spawn.GetMobilesInRange(Range)) { if (IsValidTarget(m)) {