using Server.Gumps; using Server.Items; using Server.Mobiles; using Server.Regions; using Server.Services.Virtues; using Server.Spells.Necromancy; using System; using System.Collections.Generic; using System.Linq; namespace Server.Engines.CannedEvil { public class ChampionSpawn : Item { public static readonly int MaxStrayDistance = 250; private bool m_Active; private bool m_RandomizeType; private ChampionSpawnType m_Type; private List m_Creatures; private List m_RedSkulls; private List m_WhiteSkulls; private ChampionPlatform m_Platform; private ChampionAltar m_Altar; private int m_Kills; private Mobile m_Champion; private Rectangle2D m_SpawnArea; private ChampionSpawnRegion m_Region; private TimeSpan m_ExpireDelay; private DateTime m_ExpireTime; private TimeSpan m_RestartDelay; private DateTime m_RestartTime; private IdolOfTheChampion m_Idol; private bool m_HasBeenAdvanced; private bool m_ConfinedRoaming; private Dictionary m_DamageEntries; public List Creatures => m_Creatures; [CommandProperty(AccessLevel.GameMaster)] public string GroupName { get; set; } [CommandProperty(AccessLevel.GameMaster)] public double SpawnMod { get; set; } [CommandProperty(AccessLevel.GameMaster)] public int SpawnRadius { get; set; } [CommandProperty(AccessLevel.GameMaster)] public double KillsMod { get; set; } [CommandProperty(AccessLevel.GameMaster)] public bool AutoRestart { get; set; } [CommandProperty(AccessLevel.GameMaster)] public string SpawnName { get; set; } [CommandProperty(AccessLevel.GameMaster)] public bool ConfinedRoaming { get { return m_ConfinedRoaming; } set { m_ConfinedRoaming = value; } } [CommandProperty(AccessLevel.GameMaster)] public bool HasBeenAdvanced { get { return m_HasBeenAdvanced; } set { m_HasBeenAdvanced = value; } } public bool TimerRunning => TimerRegistry.HasTimer(_TimerID, this); public bool RestartTimerRunning => TimerRegistry.HasTimer(_RestartTimerID, this); public static readonly string _TimerID = "ChampSpawnTimer"; public static readonly string _RestartTimerID = "ChampSpawnRestartTimer"; [Constructable] public ChampionSpawn() : base(0xBD2) { Movable = false; Visible = false; m_Creatures = new List(); m_RedSkulls = new List(); m_WhiteSkulls = new List(); m_Platform = new ChampionPlatform(this); m_Altar = new ChampionAltar(this); m_Idol = new IdolOfTheChampion(this); m_ExpireDelay = TimeSpan.FromMinutes(10.0); m_RestartDelay = TimeSpan.FromMinutes(10.0); m_DamageEntries = new Dictionary(); m_RandomizeType = false; SpawnRadius = 35; SpawnMod = 1; Timer.DelayCall(TimeSpan.Zero, SetInitialSpawnArea); } public void SetInitialSpawnArea() { //Previous default used to be 24; SpawnArea = new Rectangle2D(new Point2D(X - SpawnRadius, Y - SpawnRadius), new Point2D(X + SpawnRadius, Y + SpawnRadius)); } public void UpdateRegion() { if (m_Region != null) m_Region.Unregister(); if (!Deleted && Map != Map.Internal) { m_Region = new ChampionSpawnRegion(this); m_Region.Register(); } } [CommandProperty(AccessLevel.GameMaster)] public bool RandomizeType { get { return m_RandomizeType; } set { m_RandomizeType = value; } } [CommandProperty(AccessLevel.GameMaster)] public int Kills { get { return m_Kills; } set { m_Kills = value; InvalidateProperties(); } } [CommandProperty(AccessLevel.GameMaster)] public Rectangle2D SpawnArea { get { return m_SpawnArea; } set { m_SpawnArea = value; InvalidateProperties(); UpdateRegion(); } } [CommandProperty(AccessLevel.GameMaster)] public TimeSpan RestartDelay { get { return m_RestartDelay; } set { m_RestartDelay = value; } } [CommandProperty(AccessLevel.GameMaster)] public DateTime RestartTime => m_RestartTime; [CommandProperty(AccessLevel.GameMaster)] public TimeSpan ExpireDelay { get { return m_ExpireDelay; } set { m_ExpireDelay = value; } } [CommandProperty(AccessLevel.GameMaster)] public DateTime ExpireTime { get { return m_ExpireTime; } set { m_ExpireTime = value; } } [CommandProperty(AccessLevel.GameMaster)] public ChampionSpawnType Type { get { return m_Type; } set { m_Type = value; InvalidateProperties(); } } [CommandProperty(AccessLevel.GameMaster)] public bool Active { get { return m_Active; } set { if (value) Start(); else Stop(); PrimevalLichPuzzle.Update(this); InvalidateProperties(); } } [CommandProperty(AccessLevel.GameMaster)] public Mobile Champion { get { return m_Champion; } set { m_Champion = value; } } [CommandProperty(AccessLevel.GameMaster)] public int Level { get { return m_RedSkulls.Count; } set { for (int i = m_RedSkulls.Count - 1; i >= value; --i) { m_RedSkulls[i].Delete(); m_RedSkulls.RemoveAt(i); } for (int i = m_RedSkulls.Count; i < value; ++i) { Item skull = new Item(0x1854) { Hue = 0x26, Movable = false, Light = LightType.Circle150 }; skull.MoveToWorld(GetRedSkullLocation(i), Map); m_RedSkulls.Add(skull); } InvalidateProperties(); } } [CommandProperty(AccessLevel.GameMaster)] public int StartLevel { get; private set; } private void RemoveSkulls() { if (m_WhiteSkulls != null) { for (int i = 0; i < m_WhiteSkulls.Count; ++i) m_WhiteSkulls[i].Delete(); m_WhiteSkulls.Clear(); } if (m_RedSkulls != null) { for (int i = 0; i < m_RedSkulls.Count; i++) m_RedSkulls[i].Delete(); m_RedSkulls.Clear(); } } public int MaxKills { get { int l = Level; return ChampionSystem.MaxKillsForLevel(l); } } public bool IsChampionSpawn(Mobile m) { return m_Creatures.Contains(m); } public void SetWhiteSkullCount(int val) { for (int i = m_WhiteSkulls.Count - 1; i >= val; --i) { m_WhiteSkulls[i].Delete(); m_WhiteSkulls.RemoveAt(i); } for (int i = m_WhiteSkulls.Count; i < val; ++i) { Item skull = new Item(0x1854) { Movable = false, Light = LightType.Circle150 }; skull.MoveToWorld(GetWhiteSkullLocation(i), Map); m_WhiteSkulls.Add(skull); Effects.PlaySound(skull.Location, skull.Map, 0x29); Effects.SendLocationEffect(new Point3D(skull.X + 1, skull.Y + 1, skull.Z), skull.Map, 0x3728, 10); } } public void Start(bool serverLoad = false) { if (m_Active || Deleted) return; m_Active = true; m_HasBeenAdvanced = false; TimerRegistry.Register(_TimerID, this, TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(1), false, spawner => spawner.OnSlice()); TimerRegistry.RemoveFromRegistry(_RestartTimerID, this); if (m_Altar != null) m_Altar.Hue = 0; PrimevalLichPuzzle.Update(this); if (!serverLoad) { double chance = Utility.RandomDouble(); if (chance < 0.1) Level = 4; else if (chance < 0.25) Level = 3; else if (chance < 0.5) Level = 2; else if (Utility.RandomBool()) Level = 1; StartLevel = Level; if (Level > 0 && 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); } } } public void Stop() { if (!m_Active || Deleted) return; m_Active = false; m_HasBeenAdvanced = false; if (m_Creatures != null) { for (int i = 0; i < m_Creatures.Count; ++i) m_Creatures[i].Delete(); m_Creatures.Clear(); } TimerRegistry.RemoveFromRegistry(_TimerID, this); TimerRegistry.RemoveFromRegistry(_RestartTimerID, this); if (m_Altar != null) m_Altar.Hue = 0x455; PrimevalLichPuzzle.Update(this); RemoveSkulls(); m_Kills = 0; } public void BeginRestart(TimeSpan ts) { TimerRegistry.Register(_RestartTimerID, this, ts, spawner => spawner.EndRestart()); m_RestartTime = DateTime.UtcNow + ts; } public void EndRestart() { if (RandomizeType) { switch (Utility.Random(5)) { case 0: Type = ChampionSpawnType.Abyss; break; case 1: Type = ChampionSpawnType.Arachnid; break; case 2: Type = ChampionSpawnType.ColdBlood; break; case 3: Type = ChampionSpawnType.VerminHorde; break; case 4: Type = ChampionSpawnType.UnholyTerror; break; } } m_HasBeenAdvanced = false; Start(); } #region Scroll of Transcendence public static ScrollOfTranscendence CreateRandomSoT(bool felucca) { int level = Utility.RandomMinMax(1, 5); if (felucca) level += 5; return ScrollOfTranscendence.CreateRandom(level, level); } #endregion public static void GiveScrollTo(Mobile killer, SpecialScroll scroll) { if (scroll == null || killer == null) //sanity return; if (scroll is ScrollOfTranscendence) killer.SendLocalizedMessage(1094936); // You have received a Scroll of Transcendence! else killer.SendLocalizedMessage(1049524); // You have received a scroll of power! if (killer.Alive) killer.AddToBackpack(scroll); else { if (killer.Corpse != null && !killer.Corpse.Deleted) killer.Corpse.DropItem(scroll); else killer.AddToBackpack(scroll); } // Justice reward PlayerMobile pm = (PlayerMobile)killer; for (int j = 0; j < pm.JusticeProtectors.Count; ++j) { Mobile prot = pm.JusticeProtectors[j]; if (prot.Map != killer.Map || prot.Murderer || prot.Criminal || !JusticeVirtue.CheckMapRegion(killer, prot)) continue; int chance = 0; switch (VirtueHelper.GetLevel(prot, VirtueName.Justice)) { case VirtueLevel.Seeker: chance = 60; break; case VirtueLevel.Follower: chance = 80; break; case VirtueLevel.Knight: chance = 100; break; } if (chance > Utility.Random(100)) { try { prot.SendLocalizedMessage(1049368); // You have been rewarded for your dedication to Justice! SpecialScroll scrollDupe = Activator.CreateInstance(scroll.GetType()) as SpecialScroll; if (scrollDupe != null) { scrollDupe.Skill = scroll.Skill; scrollDupe.Value = scroll.Value; prot.AddToBackpack(scrollDupe); } } catch (Exception e) { Diagnostics.ExceptionLogging.LogException(e); } } } } private DateTime _NextGhostCheck; public void OnSlice() { if (!m_Active || Deleted) return; int currentRank = Rank; if (m_Champion != null) { if (m_Champion.Deleted) { RegisterDamageTo(m_Champion); if (m_Champion is BaseChampion) AwardArtifact(((BaseChampion)m_Champion).GetArtifact()); m_DamageEntries.Clear(); if (m_Altar != null) { m_Altar.Hue = 0x455; if (Map == Map.Felucca) { new StarRoomGate(true, m_Altar.Location, m_Altar.Map); } } m_Champion = null; Stop(); if (AutoRestart) BeginRestart(m_RestartDelay); } else if (m_Champion.Alive && m_Champion.GetDistanceToSqrt(this) > MaxStrayDistance) { m_Champion.MoveToWorld(new Point3D(X, Y, Z - 15), Map); } } else { int kills = m_Kills; for (int i = 0; i < m_Creatures.Count; ++i) { Mobile m = m_Creatures[i]; if (m.Deleted) { m_Creatures.RemoveAt(i); --i; int rankOfMob = GetRankFor(m); if (rankOfMob == currentRank) ++m_Kills; Mobile killer = m.FindMostRecentDamager(false); RegisterDamageTo(m); if (killer is BaseCreature) killer = ((BaseCreature)killer).GetMaster(); if (killer is PlayerMobile) { #region Scroll of Transcendence if (Map == Map.Felucca) { if (Utility.RandomDouble() < ChampionSystem.ScrollChance) { PlayerMobile pm = (PlayerMobile)killer; if (Utility.RandomDouble() < ChampionSystem.TranscendenceChance) { ScrollOfTranscendence SoTF = CreateRandomSoT(true); GiveScrollTo(pm, SoTF); } else { PowerScroll PS = PowerScroll.CreateRandomNoCraft(5, 5); GiveScrollTo(pm, PS); } } } if (Map == Map.Ilshenar || Map == Map.Tokuno || Map == Map.Malas) { if (Utility.RandomDouble() < 0.0015) { killer.SendLocalizedMessage(1094936); // You have received a Scroll of Transcendence! ScrollOfTranscendence SoTT = CreateRandomSoT(false); killer.AddToBackpack(SoTT); } } #endregion int mobSubLevel = rankOfMob + 1; if (mobSubLevel >= 0) { bool gainedPath = false; int pointsToGain = mobSubLevel * 40; if (VirtueHelper.Award(killer, VirtueName.Valor, pointsToGain, ref gainedPath)) { if (gainedPath) killer.SendLocalizedMessage(1054032); // You have gained a path in Valor! else killer.SendLocalizedMessage(1054030); // You have gained in Valor! //No delay on Valor gains } PlayerMobile.ChampionTitleInfo info = ((PlayerMobile)killer).ChampionTitles; info.Award(m_Type, mobSubLevel); CityLoyalty.CityLoyaltySystem.OnSpawnCreatureKilled(m as BaseCreature, mobSubLevel); } } } } // Only really needed once. if (m_Kills > kills) InvalidateProperties(); double n = m_Kills / (double)MaxKills; int p = (int)(n * 100); if (p >= 90) AdvanceLevel(); else if (p > 0) SetWhiteSkullCount(p / 20); if (DateTime.UtcNow >= m_ExpireTime) Expire(); Respawn(); } if (TimerRunning && _NextGhostCheck < DateTime.UtcNow) { foreach (PlayerMobile ghost in m_Region.GetEnumeratedMobiles().OfType().Where(pm => !pm.Alive && (pm.Corpse == null || pm.Corpse.Deleted))) { Map map = ghost.Map; Point3D loc = ExorcismSpell.GetNearestShrine(ghost, ref map); if (loc != Point3D.Zero) { ghost.MoveToWorld(loc, map); } else { ghost.MoveToWorld(new Point3D(989, 520, -50), Map.Malas); } } _NextGhostCheck = DateTime.UtcNow + TimeSpan.FromMinutes(Utility.RandomMinMax(5, 8)); } } public void AdvanceLevel() { m_ExpireTime = DateTime.UtcNow + m_ExpireDelay; if (Level < 16) { m_Kills = 0; ++Level; InvalidateProperties(); SetWhiteSkullCount(0); if (m_Altar != null) { Effects.PlaySound(m_Altar.Location, m_Altar.Map, 0x29); Effects.SendLocationEffect(new Point3D(m_Altar.X + 1, m_Altar.Y + 1, m_Altar.Z), m_Altar.Map, 0x3728, 10); } } else { SpawnChampion(); } } public void SpawnChampion() { m_Kills = 0; Level = 0; StartLevel = 0; InvalidateProperties(); SetWhiteSkullCount(0); try { m_Champion = Activator.CreateInstance(ChampionSpawnInfo.GetInfo(m_Type).Champion) as Mobile; } catch (Exception e) { Diagnostics.ExceptionLogging.LogException(e); } if (m_Champion != null) { Point3D p = new Point3D(X, Y, Z - 15); m_Champion.MoveToWorld(p, Map); ((BaseCreature)m_Champion).Home = p; if (m_Champion is BaseChampion) { ((BaseChampion)m_Champion).OnChampPopped(this); } } } public void Respawn() { if (!m_Active || Deleted || m_Champion != null) return; int currentLevel = Level; int currentRank = Rank; int maxSpawn = (int)(MaxKills * 0.5d * SpawnMod); if (currentLevel >= 16) maxSpawn = Math.Min(maxSpawn, MaxKills - m_Kills); if (maxSpawn < 3) maxSpawn = 3; int spawnRadius = (int)(SpawnRadius * ChampionSystem.SpawnRadiusModForLevel(Level)); Rectangle2D spawnBounds = new Rectangle2D(new Point2D(X - spawnRadius, Y - spawnRadius), new Point2D(X + spawnRadius, Y + spawnRadius)); int mobCount = 0; foreach (Mobile m in m_Creatures) { if (GetRankFor(m) == currentRank) ++mobCount; } while (mobCount <= maxSpawn) { Mobile m = Spawn(); if (m == null) return; Point3D loc = GetSpawnLocation(spawnBounds, spawnRadius); // Allow creatures to turn into Paragons at Ilshenar champions. m.OnBeforeSpawn(loc, Map); m_Creatures.Add(m); m.MoveToWorld(loc, Map); ++mobCount; if (m is BaseCreature) { BaseCreature bc = m as BaseCreature; bc.Tamable = false; bc.IsChampionSpawn = true; if (!m_ConfinedRoaming) { bc.Home = Location; bc.RangeHome = spawnRadius; } else { bc.Home = bc.Location; Point2D xWall1 = new Point2D(spawnBounds.X, bc.Y); Point2D xWall2 = new Point2D(spawnBounds.X + spawnBounds.Width, bc.Y); Point2D yWall1 = new Point2D(bc.X, spawnBounds.Y); Point2D yWall2 = new Point2D(bc.X, spawnBounds.Y + spawnBounds.Height); double minXDist = Math.Min(bc.GetDistanceToSqrt(xWall1), bc.GetDistanceToSqrt(xWall2)); double minYDist = Math.Min(bc.GetDistanceToSqrt(yWall1), bc.GetDistanceToSqrt(yWall2)); bc.RangeHome = (int)Math.Min(minXDist, minYDist); } } } } public Point3D GetSpawnLocation() { return GetSpawnLocation(m_SpawnArea, 24); } public Point3D GetSpawnLocation(Rectangle2D rect, int range) { Map map = Map; if (map == null) return Location; // Try 20 times to find a spawnable location. for (int i = 0; i < 20; i++) { int dx = Utility.Random(range * 2); int dy = Utility.Random(range * 2); int x = rect.X + dx; int y = rect.Y + dy; 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 Rank => ChampionSystem.RankForLevel(Level); public int GetRankFor(Mobile m) { Type[][] types = ChampionSpawnInfo.GetInfo(m_Type).SpawnTypes; Type t = m.GetType(); for (int i = 0; i < types.GetLength(0); i++) { Type[] individualTypes = types[i]; for (int j = 0; j < individualTypes.Length; j++) { if (t == individualTypes[j]) return i; } } return -1; } public Mobile Spawn() { Type[][] types = ChampionSpawnInfo.GetInfo(m_Type).SpawnTypes; int v = Rank; if (v >= 0 && v < types.Length) return Spawn(types[v]); return null; } public Mobile Spawn(params Type[] types) { try { return Activator.CreateInstance(types[Utility.Random(types.Length)]) as Mobile; } catch (Exception e) { Diagnostics.ExceptionLogging.LogException(e); return null; } } public void Expire() { m_Kills = 0; if (m_WhiteSkulls.Count == 0) { // They didn't even get 20%, go back a level if (Level > StartLevel) --Level; InvalidateProperties(); } else { SetWhiteSkullCount(0); } m_ExpireTime = DateTime.UtcNow + m_ExpireDelay; } public Point3D GetRedSkullLocation(int index) { int x, y; if (index < 5) { x = index - 2; y = -2; } else if (index < 9) { x = 2; y = index - 6; } else if (index < 13) { x = 10 - index; y = 2; } else { x = -2; y = 14 - index; } return new Point3D(X + x, Y + y, Z - 15); } public Point3D GetWhiteSkullLocation(int index) { int x, y; switch (index) { default: case 0: x = -1; y = -1; break; case 1: x = 1; y = -1; break; case 2: x = 1; y = 1; break; case 3: x = -1; y = 1; break; } return new Point3D(X + x, Y + y, Z - 15); } public override void AddNameProperty(ObjectPropertyList list) { list.Add("champion spawn"); } public override void GetProperties(ObjectPropertyList list) { base.GetProperties(list); if (m_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, "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 { list.Add(1060743); // inactive } } public override void OnDoubleClick(Mobile from) { from.SendGump(new PropertiesGump(from, this)); } public override void OnLocationChange(Point3D oldLoc) { if (Deleted) return; if (m_Platform != null) m_Platform.Location = new Point3D(X, Y, Z - 20); if (m_Altar != null) m_Altar.Location = new Point3D(X, Y, Z - 15); if (m_Idol != null) m_Idol.Location = new Point3D(X, Y, Z - 15); if (m_RedSkulls != null) { for (int i = 0; i < m_RedSkulls.Count; ++i) m_RedSkulls[i].Location = GetRedSkullLocation(i); } if (m_WhiteSkulls != null) { for (int i = 0; i < m_WhiteSkulls.Count; ++i) m_WhiteSkulls[i].Location = GetWhiteSkullLocation(i); } m_SpawnArea.X += Location.X - oldLoc.X; m_SpawnArea.Y += Location.Y - oldLoc.Y; UpdateRegion(); } public override void OnMapChange() { if (Deleted) return; if (m_Platform != null) m_Platform.Map = Map; if (m_Altar != null) m_Altar.Map = Map; if (m_Idol != null) m_Idol.Map = Map; if (m_RedSkulls != null) { for (int i = 0; i < m_RedSkulls.Count; ++i) m_RedSkulls[i].Map = Map; } if (m_WhiteSkulls != null) { for (int i = 0; i < m_WhiteSkulls.Count; ++i) m_WhiteSkulls[i].Map = Map; } UpdateRegion(); } public override void OnAfterDelete() { base.OnAfterDelete(); if (m_Platform != null) m_Platform.Delete(); if (m_Altar != null) m_Altar.Delete(); if (m_Idol != null) m_Idol.Delete(); RemoveSkulls(); 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(); } if (m_Champion != null && !m_Champion.Player) m_Champion.Delete(); Stop(); UpdateRegion(); } public ChampionSpawn(Serial serial) : base(serial) { } public virtual void RegisterDamageTo(Mobile m) { if (m == null) return; foreach (DamageEntry de in m.DamageEntries) { if (de.HasExpired) continue; Mobile damager = de.Damager; Mobile master = damager.GetDamageMaster(m); if (master != null) damager = master; RegisterDamage(damager, de.DamageGiven); } } public void RegisterDamage(Mobile from, int amount) { if (from == null || !from.Player) return; if (m_DamageEntries.ContainsKey(from)) m_DamageEntries[from] += amount; else m_DamageEntries.Add(from, amount); } public void AwardArtifact(Item artifact) { if (artifact == null) return; int totalDamage = 0; Dictionary validEntries = new Dictionary(); foreach (KeyValuePair kvp in m_DamageEntries) { if (IsEligible(kvp.Key, artifact)) { validEntries.Add(kvp.Key, kvp.Value); totalDamage += kvp.Value; } } int randomDamage = Utility.RandomMinMax(1, totalDamage); totalDamage = 0; foreach (KeyValuePair kvp in validEntries) { totalDamage += kvp.Value; if (totalDamage >= randomDamage) { GiveArtifact(kvp.Key, artifact); return; } } artifact.Delete(); } public void GiveArtifact(Mobile to, Item artifact) { if (to == null || artifact == null) return; to.PlaySound(0x5B4); Container pack = to.Backpack; if (pack == null || !pack.TryDropItem(to, artifact, false)) artifact.Delete(); else to.SendLocalizedMessage(1062317); // For your valor in combating the fallen beast, a special artifact has been bestowed on you. } public bool IsEligible(Mobile m, Item Artifact) { return m.Player && m.Alive && m.Region != null && m.Region == m_Region && m.Backpack != null && m.Backpack.CheckHold(m, Artifact, false); } public override void Serialize(GenericWriter writer) { base.Serialize(writer); writer.Write(8); // version writer.Write(StartLevel); writer.Write(KillsMod); writer.Write(GroupName); writer.Write(SpawnName); writer.Write(AutoRestart); writer.Write(SpawnMod); writer.Write(SpawnRadius); writer.Write(m_DamageEntries.Count); foreach (KeyValuePair kvp in m_DamageEntries) { writer.Write(kvp.Key); writer.Write(kvp.Value); } writer.Write(m_ConfinedRoaming); writer.WriteItem(m_Idol); writer.Write(m_HasBeenAdvanced); writer.Write(m_SpawnArea); writer.Write(m_RandomizeType); writer.Write(m_Kills); writer.Write(m_Active); writer.Write((int)m_Type); writer.Write(m_Creatures, true); writer.Write(m_RedSkulls, true); writer.Write(m_WhiteSkulls, true); writer.WriteItem(m_Platform); writer.WriteItem(m_Altar); writer.Write(m_ExpireDelay); writer.WriteDeltaTime(m_ExpireTime); writer.Write(m_Champion); writer.Write(m_RestartDelay); var restarting = RestartTimerRunning; writer.Write(restarting); if (restarting) { writer.WriteDeltaTime(m_RestartTime); } } public override void Deserialize(GenericReader reader) { base.Deserialize(reader); m_DamageEntries = new Dictionary(); int version = reader.ReadInt(); switch (version) { case 8: StartLevel = reader.ReadInt(); goto case 7; case 7: KillsMod = reader.ReadDouble(); GroupName = reader.ReadString(); goto case 6; case 6: SpawnName = reader.ReadString(); AutoRestart = reader.ReadBool(); SpawnMod = reader.ReadDouble(); SpawnRadius = reader.ReadInt(); goto case 5; case 5: { int entries = reader.ReadInt(); Mobile m; int damage; for (int i = 0; i < entries; ++i) { m = reader.ReadMobile(); damage = reader.ReadInt(); if (m == null) continue; m_DamageEntries.Add(m, damage); } goto case 4; } case 4: { m_ConfinedRoaming = reader.ReadBool(); m_Idol = reader.ReadItem(); m_HasBeenAdvanced = reader.ReadBool(); goto case 3; } case 3: { m_SpawnArea = reader.ReadRect2D(); goto case 2; } case 2: { m_RandomizeType = reader.ReadBool(); goto case 1; } case 1: { m_Kills = reader.ReadInt(); goto case 0; } case 0: { bool active = reader.ReadBool(); m_Type = (ChampionSpawnType)reader.ReadInt(); m_Creatures = reader.ReadStrongMobileList(); m_RedSkulls = reader.ReadStrongItemList(); m_WhiteSkulls = reader.ReadStrongItemList(); m_Platform = reader.ReadItem(); m_Altar = reader.ReadItem(); m_ExpireDelay = reader.ReadTimeSpan(); m_ExpireTime = reader.ReadDeltaTime(); m_Champion = reader.ReadMobile(); m_RestartDelay = reader.ReadTimeSpan(); if (reader.ReadBool()) { m_RestartTime = reader.ReadDeltaTime(); BeginRestart(m_RestartTime - DateTime.UtcNow); } if (m_Platform == null || m_Altar == null || m_Idol == null) Delete(); else if (active) Start(true); break; } } foreach (BaseCreature bc in m_Creatures.OfType()) { bc.IsChampionSpawn = true; } Timer.DelayCall(TimeSpan.Zero, UpdateRegion); } public void SendGump(Mobile mob) { mob.SendGump(new ChampionSpawnInfoGump(this)); } private class ChampionSpawnInfoGump : Gump { private class Damager { public Mobile Mobile; public int Damage; public Damager(Mobile mob, int dmg) { Mobile = mob; Damage = dmg; } } private const int gBoarder = 20; private const int gRowHeight = 25; private const int gFontHue = 0; private static readonly int[] gWidths = { 20, 160, 160, 20 }; private static readonly int[] gTab; private static readonly int gWidth; static ChampionSpawnInfoGump() { gWidth = gWidths.Sum(); int tab = 0; gTab = new int[gWidths.Length]; for (int i = 0; i < gWidths.Length; ++i) { gTab[i] = tab; tab += gWidths[i]; } } private readonly ChampionSpawn m_Spawn; public ChampionSpawnInfoGump(ChampionSpawn spawn) : base(40, 40) { m_Spawn = spawn; AddBackground(0, 0, gWidth, gBoarder * 2 + gRowHeight * (8 + spawn.m_DamageEntries.Count), 0x13BE); int top = gBoarder; AddLabel(gBoarder, top, gFontHue, "Champion Spawn Info Gump"); top += gRowHeight; AddLabel(gTab[1], top, gFontHue, "Kills"); AddLabel(gTab[2], top, gFontHue, spawn.Kills.ToString()); top += gRowHeight; AddLabel(gTab[1], top, gFontHue, "Max Kills"); AddLabel(gTab[2], top, gFontHue, spawn.MaxKills.ToString()); top += gRowHeight; AddLabel(gTab[1], top, gFontHue, "Level"); AddLabel(gTab[2], top, gFontHue, spawn.Level.ToString()); top += gRowHeight; AddLabel(gTab[1], top, gFontHue, "Rank"); AddLabel(gTab[2], top, gFontHue, spawn.Rank.ToString()); top += gRowHeight; AddLabel(gTab[1], top, gFontHue, "Active"); AddLabel(gTab[2], top, gFontHue, spawn.Active.ToString()); top += gRowHeight; AddLabel(gTab[1], top, gFontHue, "Auto Restart"); AddLabel(gTab[2], top, gFontHue, spawn.AutoRestart.ToString()); top += gRowHeight; List damagers = new List(); foreach (Mobile mob in spawn.m_DamageEntries.Keys) { damagers.Add(new Damager(mob, spawn.m_DamageEntries[mob])); } damagers = damagers.OrderByDescending(x => x.Damage).ToList(); foreach (Damager damager in damagers) { AddLabelCropped(gTab[1], top, 100, gRowHeight, gFontHue, damager.Mobile.RawName); AddLabelCropped(gTab[2], top, 80, gRowHeight, gFontHue, damager.Damage.ToString()); top += gRowHeight; } AddButton(gWidth - (gBoarder + 30), top, 0xFA5, 0xFA7, 1, GumpButtonType.Reply, 0); AddLabel(gWidth - (gBoarder + 100), top, gFontHue, "Refresh"); } public override void OnResponse(Network.NetState sender, RelayInfo info) { switch (info.ButtonID) { case 1: m_Spawn.SendGump(sender.Mobile); break; } } } } public class ChampionSpawnRegion : BaseRegion { public static void Initialize() { EventSink.Logout += OnLogout; EventSink.Login += OnLogin; } public override bool YoungProtected => false; private readonly ChampionSpawn m_Spawn; public ChampionSpawn ChampionSpawn => m_Spawn; public ChampionSpawnRegion(ChampionSpawn spawn) : base(null, spawn.Map, Find(spawn.Location, spawn.Map), spawn.SpawnArea) { m_Spawn = spawn; } public override bool AllowHousing(Mobile from, Point3D p) { return false; } public override void AlterLightLevel(Mobile m, ref int global, ref int personal) { base.AlterLightLevel(m, ref global, ref personal); global = Math.Max(global, 1 + m_Spawn.Level); //This is a guesstimate. TODO: Verify & get exact values // OSI testing: at 2 red skulls, light = 0x3 ; 1 red = 0x3.; 3 = 8; 9 = 0xD 8 = 0xD 12 = 0x12 10 = 0xD } public override bool OnMoveInto(Mobile m, Direction d, Point3D newLocation, Point3D oldLocation) { if (m is PlayerMobile && !m.Alive && (m.Corpse == null || m.Corpse.Deleted) && Map == Map.Felucca) { return false; } return base.OnMoveInto(m, d, newLocation, oldLocation); } public static void OnLogout(LogoutEventArgs e) { Mobile m = e.Mobile; if (m is PlayerMobile && m.Region.IsPartOf() && m.AccessLevel == AccessLevel.Player && m.Map == Map.Felucca) { if (m.Alive && m.Backpack != null) { List list = new List(m.Backpack.Items.Where(i => i.LootType == LootType.Cursed)); foreach (Item item in list) { item.MoveToWorld(m.Location, m.Map); } ColUtility.Free(list); } Timer.DelayCall(TimeSpan.FromMilliseconds(250), () => { Map map = m.LogoutMap; Point3D loc = ExorcismSpell.GetNearestShrine(m, ref map); if (loc != Point3D.Zero) { m.LogoutLocation = loc; m.LogoutMap = map; } else { m.LogoutLocation = new Point3D(989, 520, -50); m.LogoutMap = Map.Malas; } }); } } public static void OnLogin(LoginEventArgs e) { Mobile m = e.Mobile; if (m is PlayerMobile && !m.Alive && (m.Corpse == null || m.Corpse.Deleted) && m.Region.IsPartOf() && m.Map == Map.Felucca) { Map map = m.Map; Point3D loc = ExorcismSpell.GetNearestShrine(m, ref map); if (loc != Point3D.Zero) { m.MoveToWorld(loc, map); } else { m.MoveToWorld(new Point3D(989, 520, -50), Map.Malas); } } } } public class IdolOfTheChampion : Item { private ChampionSpawn m_Spawn; public ChampionSpawn Spawn => m_Spawn; public override string DefaultName => "Idol of the Champion"; public IdolOfTheChampion(ChampionSpawn spawn) : base(0x1F18) { m_Spawn = spawn; Movable = false; } public override void OnAfterDelete() { base.OnAfterDelete(); if (m_Spawn != null) m_Spawn.Delete(); } public IdolOfTheChampion(Serial serial) : base(serial) { } public override void Serialize(GenericWriter writer) { base.Serialize(writer); writer.Write(0); // version writer.Write(m_Spawn); } public override void Deserialize(GenericReader reader) { base.Deserialize(reader); int version = reader.ReadInt(); switch (version) { case 0: { m_Spawn = reader.ReadItem() as ChampionSpawn; if (m_Spawn == null) Delete(); break; } } } } }