using Server; using System; using System.Collections.Generic; using Server.Mobiles; using Server.Items; using System.Linq; using Server.Engines.Points; using Server.Engines.Quests; namespace Server.Engines.VoidPool { public enum VoidType { Abyss, Repond, Undead, Reptile, Elemental } public class VoidPoolController : Item { public static VoidPoolController InstanceTram { get; set; } public static VoidPoolController InstanceFel { get; set; } private readonly int RestartSpan = 15; private readonly int PoolStartHits = 15; private readonly int StartPointVariance = 8; private readonly Point3D StartPoint1 = new Point3D(5592, 2012, 0); private readonly Point3D StartPoint2 = new Point3D(5466, 2007, 0); private readonly Point3D EndPoint = new Point3D(5500, 1998, 5); private readonly Rectangle2D PoolWalls = new Rectangle2D(5495, 1993, 10, 10); private bool _Active; [CommandProperty(AccessLevel.GameMaster)] public bool Active { get { return _Active; } set { if(!value) { if(Timer != null) { Timer.Stop(); Timer = null; } if (Region != null) { Region.Unregister(); Region = null; } if (Level3Spawner != null) { Level3Spawner.Deactivate(); Level3Spawner = null; } } else { if (Region == null) { Region = new VoidPoolRegion(this, this.Map); Region.Register(); } if(Timer == null) { Timer = Timer.DelayCall(TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(1), OnTick); Timer.Start(); NextStart = DateTime.UtcNow + TimeSpan.FromMinutes(RestartSpan); if(Region != null) Region.SendRegionMessage(1152526, RestartSpan.ToString()); // The battle for the Void Pool will begin in ~1_VALUE~ minutes. } if (Level3Spawner == null) { Level3Spawner = new Level3Spawner(this); } } _Active = value; } } [CommandProperty(AccessLevel.GameMaster)] public int Wave { get; set; } [CommandProperty(AccessLevel.GameMaster)] public int Stage { get { if (Wave < 2) return 0; return Math.Max(1, Wave / 5); } } public List Waves { get; set; } public List WaypointsA { get; set; } public List WaypointsB { get; set; } public int WaypointACount { get; set; } public int WaypointBCount { get; set; } public VoidPoolRegion Region { get; set; } public Timer Timer { get; set; } public Dictionary CurrentScore { get; set; } [CommandProperty(AccessLevel.GameMaster)] public bool OnGoing { get; private set; } [CommandProperty(AccessLevel.GameMaster)] public VoidType VoidType { get; set; } [CommandProperty(AccessLevel.GameMaster)] public DateTime NextStart { get; private set; } [CommandProperty(AccessLevel.GameMaster)] public DateTime NextWave { get; private set; } [CommandProperty(AccessLevel.GameMaster)] public int PoolHits { get; set; } [CommandProperty(AccessLevel.GameMaster)] public int RespawnMin { get; set; } [CommandProperty(AccessLevel.GameMaster)] public int RespawnMax { get; set; } [CommandProperty(AccessLevel.GameMaster)] public Level3Spawner Level3Spawner { get; set; } public VoidPoolController(Map map) : base(3803) { Name = "Void Pool Controller"; Visible = false; Movable = false; PoolHits = PoolStartHits; if(map == Map.Trammel) { if(InstanceTram != null) Delete(); else InstanceTram = this; } else if(map == Map.Felucca) { if(InstanceFel != null) Delete(); else InstanceFel = this; } else Delete(); WaypointsA = new List(); WaypointsB = new List(); Region = new VoidPoolRegion(this, map); Region.Register(); RespawnMin = 60; RespawnMax = 90; ClearSpawners(); Active = true; ResetLevel3Spawners(); } public override void OnDoubleClick(Mobile from) { if (from.AccessLevel >= AccessLevel.GameMaster) from.SendGump(new Server.Gumps.PropertiesGump(from, this)); } private void OnTick() { if(!OnGoing && DateTime.UtcNow > NextStart && Region != null && Region.GetPlayerCount() > 0) { NextStart = DateTime.MaxValue; OnGoing = true; VoidType = (VoidType)Utility.Random(5); PoolHits = PoolStartHits; Wave = 0; if (CurrentScore != null) CurrentScore.Clear(); if (Waves != null) { Waves.Clear(); Waves.TrimExcess(); } CurrentScore = new Dictionary(); Waves = new List(); Region.SendRegionMessage(1152527, 0x2B); // The battle for the Void Pool is beginning now! if (WaypointACount != WaypointsA.Count || WaypointBCount != WaypointsB.Count) Generate.AddWaypoints(); SpawnWave(); } else if(OnGoing) { if(DateTime.UtcNow > NextWave) { SpawnWave(); } IPooledEnumerable eable = this.Map.GetMobilesInBounds(PoolWalls); foreach(Mobile m in eable) { if (!OnGoing) break; if(m is BaseCreature && !((BaseCreature)m).Controlled && !((BaseCreature)m).Summoned && Utility.RandomDouble() > 0.25) OnVoidWallDamaged(m); } eable.Free(); } } public void SpawnWave() { Wave++; Region.SendRegionMessage(1152528, Wave.ToString()); int toSpawn = (int)Math.Ceiling(Math.Max(5, Math.Sqrt(Wave) * 2) * 1.5); List creatures = new List(); for (int i = 0; i < toSpawn; i++) { Point3D start = i % 2 == 0 ? StartPoint1 : StartPoint2; for(int j = 0; j < 25; j++) { int x = start.X + Utility.RandomMinMax(start.X - (StartPointVariance / 2), start.X + (StartPointVariance / 2)); int y = start.Y + Utility.RandomMinMax(start.Y - (StartPointVariance / 2), start.Y + (StartPointVariance / 2)); int z = this.Map.GetAverageZ(x, y); if(this.Map.CanSpawnMobile(x, y, z)) { start = new Point3D(x, y, z); break; } } int ran = Utility.RandomMinMax(0, Stage < 10 ? 12 : Stage < 15 ? 14 : 15); Type t; switch(ran) { default: case 0: case 1: case 3: case 4: t = SpawnTable[(int)VoidType][0]; break; case 5: case 6: case 7: case 8: t = SpawnTable[(int)VoidType][1]; break; case 9: case 10: case 11: t = SpawnTable[(int)VoidType][2]; break; case 12: case 13: t = SpawnTable[(int)VoidType][3]; break; case 14: case 15: t = SpawnTable[(int)VoidType][4]; break; } BaseCreature bc = Activator.CreateInstance(t, Wave, true) as BaseCreature; if(bc != null) { bc.NoLootOnDeath = true; Timer.DelayCall(TimeSpan.FromSeconds((double)i * .75), () => { if (OnGoing) { bc.MoveToWorld(start, this.Map); bc.Home = EndPoint; bc.RangeHome = 1; creatures.Add(bc); bc.CurrentWayPoint = GetNearestWaypoint(bc); } else bc.Delete(); }); } } var gate1 = new VoidPoolGate(); gate1.MoveToWorld(StartPoint1, Map); Effects.PlaySound(StartPoint1, Map, 0x20E); var gate2 = new VoidPoolGate(); gate2.MoveToWorld(StartPoint2, Map); Effects.PlaySound(StartPoint2, Map, 0x20E); Timer.DelayCall(TimeSpan.FromSeconds(toSpawn * .80), () => { Effects.SendLocationParticles(EffectItem.Create(gate1.Location, gate1.Map, EffectItem.DefaultDuration), 0x376A, 9, 20, 5042); Effects.PlaySound(gate1.GetWorldLocation(), gate1.Map, 0x201); Effects.SendLocationParticles(EffectItem.Create(gate2.Location, gate2.Map, EffectItem.DefaultDuration), 0x376A, 9, 20, 5042); Effects.PlaySound(gate2.GetWorldLocation(), gate2.Map, 0x201); gate1.Delete(); gate2.Delete(); }); Waves.Add(new WaveInfo(Wave, creatures)); NextWave = GetNextWaveTime(); } public WayPoint GetNearestWaypoint(Mobile m, int range = 15) { IPooledEnumerable eable = this.Map.GetItemsInRange(m.Location, range); int closestRange = 15; WayPoint closest = null; foreach (Item item in eable) { int dist = 0; if (item is WayPoint) { dist = (int)m.GetDistanceToSqrt(item); if (dist < closestRange || closest == null) { closest = item as WayPoint; closestRange = dist; } } } return closest; } public Item GetNearestVoidPoolWall(Mobile m) { IPooledEnumerable eable = this.Map.GetItemsInRange(m.Location, 5); int closestRange = 5; Item closest = null; foreach (Item item in eable) { int dist = 0; if (item.Name == "Void Pool") { dist = (int)m.GetDistanceToSqrt(item); if (dist < closestRange || closest == null) { closest = item; closestRange = dist; } } } return closest; } public DateTime GetNextWaveTime() { if(Wave == 1) return DateTime.UtcNow + TimeSpan.FromSeconds(10); int min = Math.Max(30, RespawnMin - Wave) + Utility.RandomMinMax(0, 10); int max = Math.Max(45, RespawnMax - Wave) - Utility.RandomMinMax(0, 5); return DateTime.UtcNow + TimeSpan.FromSeconds(Utility.RandomMinMax(min, max)); } public void OnVoidWallDamaged(Mobile damager) { if(0.5 > Utility.RandomDouble()) PoolHits--; Region.SendRegionMessage(1152529); // The Void Pool walls have been damaged! Defend the Void Pool! Item item = GetNearestVoidPoolWall(damager); if (item != null) { Point3D p = new Point3D(item.X, item.Y, item.Z + 5); Effects.SendLocationParticles(EffectItem.Create(p, item.Map, EffectItem.DefaultDuration), Utility.RandomList(0x36BD, 0x36B0, 0x3728), 20, 10, 5044); Effects.PlaySound(p, item.Map, 0x307); } if (PoolHits <= 0 && OnGoing) { OnGoing = false; EndInvasion(); } } public void EndInvasion() { Region.SendRegionMessage(1152530); // Cora's forces have destroyed the Void Pool walls. The battle is lost! VoidPoolStats.OnInvasionEnd(this /*CurrentScore, Wave*/); NextStart = DateTime.UtcNow + TimeSpan.FromMinutes(RestartSpan); Region.SendRegionMessage(1152526, RestartSpan.ToString()); // The battle for the Void Pool will begin in ~1_VALUE~ minutes. List list = Region.GetPlayers(); foreach (Mobile m in list.Where(m => GetCurrentPoints(m) > 0)) PointsSystem.VoidPool.AwardPoints(m, GetCurrentPoints(m)); foreach(Mobile m in list.Where(m => CurrentScore.ContainsKey(m))) { m.SendLocalizedMessage(1152650, String.Format("{0}\t{1}\t{2}\t{3}", GetTotalWaves(m), Wave.ToString(), Wave.ToString(), CurrentScore[m])); // During the battle, you helped fight back ~1_COUNT~ out of ~2_TOTAL~ waves of enemy forces. Your final wave was ~3_MAX~. Your total score for the battle was ~4_SCORE~ points. if (m is PlayerMobile) { var quest = QuestHelper.GetQuest((PlayerMobile)m); if (quest != null) { quest.CompleteQuest(); } } } ColUtility.Free(list); ClearSpawn(true); } public void OnCreatureKilled(BaseCreature killed) { if (Waves == null) return; Waves.ForEach(info => { if (info.Creatures.Contains(killed)) { List list = killed.GetLootingRights(); list.Sort(); for (int i = 0; i < list.Count; i++) { DamageStore ds = list[i]; Mobile m = ds.m_Mobile; if (ds.m_Mobile is BaseCreature && ((BaseCreature)ds.m_Mobile).GetMaster() is PlayerMobile) m = ((BaseCreature)ds.m_Mobile).GetMaster(); if (!info.Credit.Contains(m)) info.Credit.Add(m); if (!CurrentScore.ContainsKey(m)) CurrentScore[m] = killed.Fame / 998; else CurrentScore[m] += killed.Fame / 998; } list.Clear(); list.TrimExcess(); info.Creatures.Remove(killed); if (info.Creatures.Count == 0) { foreach (Mobile m in info.Credit.Where(m => m.Region == this.Region && m is PlayerMobile)) { double award = Math.Max(0, this.Map == Map.Felucca ? Stage * 2 : Stage); if (award > 0) { //Score Bonus if (!CurrentScore.ContainsKey(m)) CurrentScore[m] = Stage * 125; else CurrentScore[m] += Stage * 125; } } } if (killed.Corpse != null && !killed.Corpse.Deleted) ((Corpse)killed.Corpse).BeginDecay(TimeSpan.FromMinutes(1)); } }); } public void ClearSpawners() { if (Region == null) return; foreach (Item item in Region.GetEnumeratedItems().Where(i => i is ISpawner)) { if (item is XmlSpawner) { ((XmlSpawner)item).DoReset = true; } else if (item is Spawner) { ((Spawner)item).RemoveSpawned(); ((Spawner)item).Running = false; } } } public void ResetLevel3Spawners() { ResetLevel3Spawners(Map.Trammel); ResetLevel3Spawners(Map.Felucca); } public void ResetLevel3Spawners(Map map) { Server.Region r = Server.Region.Find(new Point3D(5574, 1859, 0), map); foreach (Item item in r.GetEnumeratedItems().Where(i => i is ISpawner && i.X >= 5501 && i.X <= 5627 && i.Y >= 1799 && i.Y <= 1927)) { if (item is XmlSpawner) { ((XmlSpawner)item).DoReset = true; } else if (item is Spawner) { ((Spawner)item).RemoveSpawned(); ((Spawner)item).Running = false; } } } public void ClearSpawn() { ClearSpawn(false); } public void ClearSpawn(bool effects) { if (Region == null) return; foreach (Mobile m in Region.GetEnumeratedMobiles().Where(m => m is CovetousCreature)) { if (effects) Effects.SendLocationEffect(m.Location, m.Map, 0xDDA, 30, 10, 0, 0); m.Delete(); } } public int GetCurrentPoints(Mobile from) { if (Waves == null) return 0; int points = 0; foreach (var info in Waves.Where(i => i.Credit.Contains(from))) { points += this.Map == Map.Felucca ? Stage * 2 : Stage; } return points; } public int GetTotalWaves(Mobile from) { return Waves.Where(i => i.Wave > 2 && i.Credit.Contains(from)).Count(); } public static int GetPlayerScore(Dictionary score, Mobile m) { if(score == null || m == null || !score.ContainsKey(m)) return 0; return (int)score[m]; } public static Type[][] SpawnTable = new Type[][] { new Type[] { typeof(DaemonMongbat), typeof(GargoyleAssassin), typeof(CovetousDoppleganger), typeof(LesserOni), typeof(CovetousFireDaemon) }, new Type[] { typeof(LizardmanWitchdoctor), typeof(OrcFootSoldier), typeof(RatmanAssassin), typeof(OgreBoneCrusher), typeof(TitanRockHunter) }, new Type[] { typeof(AngeredSpirit), typeof(BoneSwordSlinger), typeof(VileCadaver), typeof(DiseasedLich), typeof(CovetousRevenant) }, new Type[] { typeof(WarAlligator), typeof(MagmaLizard), typeof(ViciousDrake), typeof(CorruptedWyvern), typeof(CovetousWyrm) }, new Type[] { typeof(CovetousEarthElemental),typeof(CovetousWaterElemental), typeof(VortexElemental), typeof(SearingElemental),typeof(VenomElemental) }, }; public override void Delete() { if(OnGoing) EndInvasion(); if(Region != null) { Region.Unregister(); Region = null; } if(Timer != null) { Timer.Stop(); Timer = null; } foreach (var wp in WaypointsA.Where(w => w != null && !w.Deleted)) wp.Delete(); foreach (var wp in WaypointsB.Where(w => w != null && !w.Deleted)) wp.Delete(); if (Level3Spawner != null) { Level3Spawner.Deactivate(); Level3Spawner = null; } base.Delete(); } public VoidPoolController(Serial serial) : base(serial) { } public override void Serialize(GenericWriter writer) { base.Serialize(writer); writer.Write((int)2); if (Level3Spawner != null) { writer.Write(0); Level3Spawner.Serialize(writer); } else { writer.Write(1); } writer.Write(RespawnMin); writer.Write(RespawnMax); writer.Write(_Active); writer.Write(WaypointsA.Count); writer.Write(WaypointsB.Count); WaypointsA.ForEach(w => writer.Write(w)); WaypointsB.ForEach(w => writer.Write(w)); } public override void Deserialize(GenericReader reader) { base.Deserialize(reader); int version = reader.ReadInt(); switch (version) { case 2: case 1: if (version == 1 || reader.ReadInt() == 0) { Level3Spawner = new Level3Spawner(reader, this); } goto case 0; case 0: if (version == 0) { Level3Spawner = new Level3Spawner(this); Timer.DelayCall(() => { ResetLevel3Spawners(); }); } RespawnMin = reader.ReadInt(); RespawnMax = reader.ReadInt(); WaypointsA = new List(); WaypointsB = new List(); Active = reader.ReadBool(); NextStart = DateTime.UtcNow; int counta = reader.ReadInt(); int countb = reader.ReadInt(); for (int i = 0; i < counta; i++) { WayPoint wp = reader.ReadItem() as WayPoint; if (wp != null) WaypointsA.Add(wp); } for (int i = 0; i < countb; i++) { WayPoint wp = reader.ReadItem() as WayPoint; if (wp != null) WaypointsB.Add(wp); } break; } if (Map == Map.Felucca) InstanceFel = this; else InstanceTram = this; Timer.DelayCall(TimeSpan.FromSeconds(10), () => { ClearSpawn(); ClearSpawners(); } ); } } public class VoidPoolGate : Static { public VoidPoolGate() : base(0xF6C) { Light = LightType.Circle300; } public VoidPoolGate(Serial serial) : base(serial) { } public override void Serialize(GenericWriter writer) { base.Serialize(writer); writer.Write(0); } public override void Deserialize(GenericReader reader) { base.Deserialize(reader); reader.ReadInt(); Delete(); } } } /* wave 1: 13:30:00 wave 2: 13:30:10 +10 wave 3: 13:31:43 +93 wave 4: 13:33:05 +83 wave 5: 13:34:17 +75 wave 6: 13:35:08 +51 wave 7: 13:36:37 +89 wave 8: 13:37:38 +61 wave 9: 13:38:19 +41 wave 10: 13:39:32 +73 wave 11: 13:40:02 +30 wave 12: 13:40:54 +52 wave 13: 13:41:45 +51 wave 14: 13:42:36 +51 wave 15: 13:43:28 +52 wave 16: 13:44:09 +41 wave 17: 13:45:01 +52 */