using Server.Gumps; using Server.Items; using Server.Mobiles; using Server.Spells; using System; using System.Collections.Generic; using System.Linq; namespace Server.Engines.ArenaSystem { public enum RoomType { Public, Private } public enum BattleMode { Survival, Team } public enum TimeLimit { FiveMinutes = 5, TenMinutes = 10, FifteenMinutes = 15, TwentyMinutes = 20, ThirtyMinutes = 30 } public enum EntryFee { Zero, OneThousand = 1000, FiveThousand = 5000, TenThousand = 10000, TwentyFiveThousand = 25000, FiftyThousand = 50000 } public enum PotionRules { All, None, NoHealing } [PropertyObject] public class ArenaDuel { public static int MaxEntries = 10; public static int MaxPetSlots = 5; public static TimeSpan KickTime = TimeSpan.FromSeconds(40); public static TimeSpan EntryTime = TimeSpan.FromSeconds(90); public static TimeSpan StartTimeDuration = TimeSpan.FromSeconds(5); public PVPArena Arena { get; private set; } [CommandProperty(AccessLevel.GameMaster)] public int Entries { get; set; } [CommandProperty(AccessLevel.GameMaster)] public RoomType RoomType { get; set; } [CommandProperty(AccessLevel.GameMaster)] public BattleMode BattleMode { get; set; } [CommandProperty(AccessLevel.GameMaster)] public bool Ranked { get; set; } [CommandProperty(AccessLevel.GameMaster)] public TimeLimit TimeLimit { get; set; } [CommandProperty(AccessLevel.GameMaster)] public EntryFee EntryFee { get; set; } [CommandProperty(AccessLevel.GameMaster)] public int PetSlots { get; set; } [CommandProperty(AccessLevel.GameMaster)] public bool RidingFlyingAllowed { get; set; } [CommandProperty(AccessLevel.GameMaster)] public bool RangedWeaponsAllowed { get; set; } [CommandProperty(AccessLevel.GameMaster)] public bool SummonSpellsAllowed { get; set; } [CommandProperty(AccessLevel.GameMaster)] public bool FieldSpellsAllowed { get; set; } [CommandProperty(AccessLevel.GameMaster)] public PotionRules PotionRules { get; set; } [CommandProperty(AccessLevel.GameMaster)] public PlayerMobile Host { get; set; } [CommandProperty(AccessLevel.GameMaster)] public int Pot { get; set; } public List Teams { get; set; } public List Banned { get; set; } public List Warned { get; set; } public Dictionary KillRecord { get; set; } public ArenaGate Gate { get; set; } [CommandProperty(AccessLevel.GameMaster)] public bool HasBegun { get; private set; } [CommandProperty(AccessLevel.GameMaster)] public bool Complete { get; private set; } [CommandProperty(AccessLevel.GameMaster)] public bool InPreFight { get; set; } [CommandProperty(AccessLevel.GameMaster)] public DateTime StartTime { get; private set; } [CommandProperty(AccessLevel.GameMaster)] public DateTime EntryDeadline { get; private set; } [CommandProperty(AccessLevel.GameMaster)] public DateTime EndTime => StartTime + TimeSpan.FromMinutes((int)TimeLimit); // used in team mode public ArenaTeam TeamOrder => Teams != null && Teams.Count > 0 ? Teams[0] : null; public ArenaTeam TeamChaos => Teams != null && Teams.Count > 1 ? Teams[1] : null; public int ParticipantCount => Teams.Sum(t => t.Count); public ArenaDuel(PVPArena arena, PlayerMobile host) { Host = host; PlayerStatsEntry entry = PVPArenaSystem.Instance.GetPlayerEntry(host); if (entry.Profile != null) ConfigureFromProfile(entry.Profile); else ConfigureDefault(); Arena = arena; Teams = new List(); KillRecord = new Dictionary(); Banned = new List(); Warned = new List(); Teams.Add(new ArenaTeam(host)); Teams.Add(new ArenaTeam()); Complete = false; } public void ConfigureFromProfile(PlayerStatsEntry.DuelProfile profile) { Entries = profile.Entries; RoomType = profile.RoomType; BattleMode = profile.BattleMode; Ranked = profile.Ranked; TimeLimit = profile.TimeLimit; EntryFee = profile.EntryFee; PetSlots = profile.PetSlots; RidingFlyingAllowed = profile.RidingFlyingAllowed; RangedWeaponsAllowed = profile.RangedWeaponsAllowed; SummonSpellsAllowed = profile.SummonSpellsAllowed; FieldSpellsAllowed = profile.FieldSpellsAllowed; PotionRules = profile.PotionRules; } public void ConfigureDefault() { Entries = 2; RoomType = RoomType.Public; BattleMode = BattleMode.Team; Ranked = true; TimeLimit = TimeLimit.TenMinutes; EntryFee = EntryFee.Zero; PetSlots = 5; RidingFlyingAllowed = true; RangedWeaponsAllowed = true; SummonSpellsAllowed = true; FieldSpellsAllowed = true; PotionRules = PotionRules.All; } public override string ToString() { return "..."; } public void SetDuel() { PVPArenaSystem.SendMessage(Host, 1115800); // You have created a new duel session. Teams[0].AddParticipant(Host); } public IEnumerable> GetParticipants(bool inArena = false) { if (Teams == null || Teams.Count == 0) yield break; foreach (ArenaTeam team in Teams) { foreach (KeyValuePair player in team.Players.Where(p => !inArena || InArena(p.Key))) { yield return player; } } } public List ParticipantList() { List list = new List(); foreach (KeyValuePair part in GetParticipants()) { list.Add(part.Key); } return list; } public bool InArena(Mobile m) { if (m == null) return false; return Region.Find(m.Location, m.Map) == Arena.Region; } public PlayerStatsEntry GetStats(PlayerMobile check) { foreach (KeyValuePair kvp in GetParticipants()) { if (kvp.Key == check) return kvp.Value; } return null; } public bool SwapParticipant(PlayerMobile pm) { if (HasBegun) return false; if (TeamOrder.RemoveParticipant(pm)) { TeamChaos.AddParticipant(pm); return true; } else if (TeamChaos.RemoveParticipant(pm)) { TeamOrder.AddParticipant(pm); return true; } return false; } public bool TryAddPlayer(PlayerMobile pm) { if (ParticipantCount >= MaxEntries) { pm.SendLocalizedMessage(1115954); // This session is already full. } else if (InPreFight || HasBegun) { pm.SendLocalizedMessage(1115953); // This session has already been closed. } else if (!Arena.PendingDuels.ContainsKey(this)) { pm.SendLocalizedMessage(1115957); // This session has expired. Please create a new session and try again. } else if (pm.Map != Arena.Definition.Map) { pm.SendLocalizedMessage(1115959); // You need to return to the facet that you opened the session on to start this duel. } else { RegisterPlayer(pm); return true; } return false; } public void RegisterPlayer(PlayerMobile pm) { PVPArenaSystem.SendParticipantMessage(this, 1115956); // A new duelist has joined your session! PVPArenaSystem.SendMessage(pm, 1115955); // You have joined the session. if (BattleMode == BattleMode.Team) { if (TeamOrder.Count > TeamChaos.Count) { TeamChaos.AddParticipant(pm); } else { TeamOrder.AddParticipant(pm); } } else { if (Teams[Teams.Count - 1].Unoccupied) { Teams[Teams.Count - 1].AddParticipant(pm); } else { Teams.Add(new ArenaTeam(pm)); } } } public void RemovePlayer(PlayerMobile pm, bool ban = false) { ArenaTeam team = GetTeam(pm); if (team != null) { team.RemoveParticipant(pm); if (Teams[1] != team && team.Unoccupied) { Teams.Remove(team); } if (ban) { Banned.Add(pm); PVPArenaSystem.SendMessage(Host, 1115951); // You have banned the specified participant. PVPArenaSystem.SendParticipantMessage(this, 1115951); // You have been banned from the session by the host player. } else { PVPArenaSystem.SendMessage(pm, 1115948); // One of the participants has left your duel session. PVPArenaSystem.SendParticipantMessage(this, 1115948); // One of the participants has left your duel session. } PendingDuelGump.RefreshAll(this); } } public bool IsParticipant(PlayerMobile pm) { return GetTeam(pm) != null; } public bool IsBanned(PlayerMobile pm) { return Banned.Contains(pm); } public ArenaTeam GetTeam(PlayerMobile pm) { return Teams.FirstOrDefault(team => team.Contains(pm)); } public void MoveToArena(PlayerMobile pm) { Map map = Arena.Definition.Map; Rectangle2D rec = _StartPoints[pm]; Point3D p = map.GetRandomSpawnPoint(rec); BaseCreature.TeleportPets(pm, p, map); pm.MoveToWorld(p, Arena.Definition.Map); if (pm.Spell is Spell) { ((Spell)pm.Spell).Disturb(DisturbType.Hurt); } Targeting.Target.Cancel(pm); bool allin = true; foreach (ArenaTeam team in Teams) { foreach (PlayerMobile player in team.Players.Keys) { if (!InArena(player)) { allin = false; break; } } } if (allin) { Timer.DelayCall(TimeSpan.FromSeconds(3), BeginDuel); } } public void OnPlayerLeave(PlayerMobile pm) { if (!Complete) { List present = GetTeamsPresent(); if (present.Count == 1) { EndDuel(present[0]); } else if (present.Count == 0) { CancelDuel(); } ColUtility.Free(present); } } public void DoPreDuel() { AssignStartPoints(); Gate = new ArenaGate(this); Gate.MoveToWorld(Arena.Definition.GateLocation, Arena.Definition.Map); DoGateEffect(); Effects.PlaySound(Gate.Location, Gate.Map, 0x20E); InPreFight = true; EntryDeadline = DateTime.UtcNow + EntryTime; PVPArenaSystem.SendParticipantMessage(this, 1115875); // The arena gate has opened near the arena stone. You have ninety seconds to use the gate or you will be removed from this duel. foreach (KeyValuePair kvp in GetParticipants()) { BaseGump.SendGump(new OfferDuelGump(kvp.Key, this, Arena, false, true)); } HasBegun = true; PlaceBlockers(); } public void BeginDuel() { EntryDeadline = DateTime.MinValue; StartTime = DateTime.UtcNow; if (Gate != null) { Gate.Delete(); Gate = null; } List present = GetTeamsPresent(); if (present.Count <= 1) { if (present.Count == 1) { PVPArenaSystem.SendParticipantMessage(this, 1116476, true); // Your duel is about to begin but the opposing team has not came to the arena. This session will be aborted and you will be ejected from the arena. } CancelDuel(true); ColUtility.Free(present); RemoveBlockers(); return; } ColUtility.Free(present); PVPArenaSystem.SendParticipantMessage(this, 1115964, true); // The duel will start in 5 seconds! Timer.DelayCall(StartTimeDuration, () => { RemoveBlockers(); DoStartEffects(); InPreFight = false; foreach (KeyValuePair part in GetParticipants(true)) { part.Key.Delta(MobileDelta.Noto); } }); } private Dictionary _StartPoints; public void AssignStartPoints() { _StartPoints = new Dictionary(); PlayerMobile pm1 = Teams[0].PlayerZero; PlayerMobile pm2 = Teams[1].PlayerZero; _StartPoints[pm1] = Arena.Definition.StartLocations[0]; _StartPoints[pm2] = Arena.Definition.StartLocations[1]; List partList = ParticipantList(); foreach (PlayerMobile pm in partList.Where(p => p != pm1 && p != pm2)) { _StartPoints[pm] = Arena.Definition.StartLocations[_StartPoints.Count]; } } private List GetTeamsPresent() { List present = new List(); foreach (KeyValuePair part in GetParticipants(true)) { ArenaTeam team = GetTeam(part.Key); if (!present.Contains(team)) { present.Add(team); } } return present; } public void PlaceBlockers() { foreach (Rectangle2D rec in Arena.Definition.EffectAreas) { var list = new List(Arena.Blockers); for (int x = rec.X; x < rec.X + rec.Width; x++) { for (int y = rec.Y; y < rec.Y + rec.Height; y++) { Item blocker; if (list.Count > 0) { blocker = list[0]; list.RemoveAt(0); } else { blocker = new Blocker(); Arena.Blockers.Add(blocker); } blocker.MoveToWorld(new Point3D(x, y, Arena.Definition.Map.GetAverageZ(x, y)), Arena.Definition.Map); } } } } public void RemoveBlockers() { foreach (Item item in Arena.Blockers) { item.Z -= 20; } } public void DoStartEffects() { foreach (Rectangle2D rec in Arena.Definition.EffectAreas) { for (int x = rec.X; x < rec.X + rec.Width; x++) { for (int y = rec.Y; y < rec.Y + rec.Height; y++) { Effects.SendLocationEffect(new Point3D(x, y, Arena.Definition.Map.GetAverageZ(x, y)), Arena.Definition.Map, 0x3709, 60, 10); } } } } public void DoWinEffects(PlayerMobile pm) { Point3D loc = pm.Location; Map pmmap = pm.Map; if (pmmap != Map.Internal && pmmap != null) { for (int i = 0; i < 5; i++) { Timer.DelayCall(TimeSpan.FromMilliseconds(i * 100), index => { Misc.Geometry.Circle2D(loc, pmmap, index, (pnt, map) => { Effects.SendLocationEffect(pnt, map, 0x3709, 0x1E, 0x14, 0x5DE, 0x4); }); }, i); } } } private readonly int[] _EffectZs = new int[] { 10, 20, 30, 40, 50 }; public void DoGateEffect() { int x = Arena.Definition.GateLocation.X; int y = Arena.Definition.GateLocation.Y; int z = Arena.Definition.GateLocation.Z; Map map = Arena.Definition.Map; for (int i = 0; i < _EffectZs.Length; i++) { Effects.SendLocationEffect(new Point3D(x, y, z + _EffectZs[i]), map, 0x1AA1, 0xD, 0x10, 0x1F, 0x4); Effects.SendLocationEffect(new Point3D(x, y, z + _EffectZs[i]), map, 0x1A9F, Utility.RandomMinMax(0x13, 0x19), 0x10, 0x1F, 0x4); Effects.SendLocationEffect(new Point3D(x, y, z + _EffectZs[i]), map, 0x01A8, 0x19, 0x10, 0x1F, 0x4); } } public void CancelDuel(bool refund = false) { Timer.DelayCall(TimeSpan.FromSeconds(5), RemovePlayers, null); Complete = true; if (refund && EntryFee > EntryFee.Zero) { foreach (KeyValuePair part in GetParticipants()) { Banker.Deposit(part.Key, (int)EntryFee, true); PVPArenaSystem.SendMessage(part.Key, 1149606); // The entry fee has been refunded to your bank box. } } } public void EndDuel() { EndDuel(null); // TIE! } public void EndDuel(ArenaTeam winner) { Timer.DelayCall(KickTime, RemovePlayers, winner); Complete = true; SendResults(winner); if (winner != null) { foreach (PlayerMobile pm in winner.Players.Keys) { if (Pot > 0) { Banker.Deposit(pm, Pot / winner.Count, true); } PVPArenaSystem.SendMessage(pm, 1115975); // Congratulations! You have won the duel! Timer.DelayCall(TimeSpan.FromSeconds(1), player => { DoWinEffects(player); }, pm); } } foreach (ArenaTeam team in Teams.Where(t => t != winner)) { foreach (PlayerMobile pm in team.Players.Keys) { PVPArenaSystem.SendMessage(pm, team.Count == 1 ? 1116489 : 1116488); // You have lost the duel... : Your team has lost the duel... } } RecordStats(winner); if (Ranked) { Arena.RecordRankings(this, winner); } PVPArenaSystem.SendParticipantMessage(this, 1115965); // All participants will be ejected from this arena in 40 seconds. } public void RemovePlayers(ArenaTeam winners) { foreach (KeyValuePair part in GetParticipants(true)) { Arena.RemovePlayer(part.Key, winners != null && winners.Contains(part.Key)); PVPArenaSystem.Instance.CheckTitle(part.Key); } PVPArenaSystem.SendParticipantMessage(this, 1115973); // Thank you for your participation! Please return to the arena stone for additional dueling opportunities! Arena.OnDuelEnd(this); Closeout(); } public void RecordStats(ArenaTeam winner) { foreach (KeyValuePair kvp in GetParticipants(true)) { ArenaTeam team = Teams.FirstOrDefault(t => t.Contains(kvp.Key)); PlayerStatsEntry stats = kvp.Value; if (winner == null) { switch (BattleMode) { case BattleMode.Survival: stats.SurvivalDraws++; break; case BattleMode.Team: stats.TeamDraws++; break; } } else if (team == winner) { switch (BattleMode) { case BattleMode.Survival: stats.SurvivalWins++; break; case BattleMode.Team: stats.TeamWins++; break; } } else { switch (BattleMode) { case BattleMode.Survival: stats.SurvivalLosses++; break; case BattleMode.Team: stats.TeamLosses++; break; } } } } public void HandleDeath(Mobile victim) { Mobile killer = victim.LastKiller; if (killer is BaseCreature) killer = ((BaseCreature)killer).GetMaster(); if (victim is PlayerMobile) { if (killer != null) { KillRecord[victim.Name] = killer.Name; } if (killer is PlayerMobile && killer != victim) // if not ranked, does it keep track of this? { PlayerStatsEntry victimEntry = GetStats((PlayerMobile)victim); PlayerStatsEntry killerEntry = GetStats((PlayerMobile)killer); if (victimEntry != null) { victimEntry.Deaths++; victimEntry.HandleDeath(killer, true); } if (killerEntry != null) { killerEntry.Kills++; killerEntry.HandleDeath(victim, false); } } List stillAlive = new List(); foreach (ArenaTeam team in Teams) { if (CheckTeamAlive((PlayerMobile)victim, team)) stillAlive.Add(team); } if (stillAlive.Count == 1) { Timer.DelayCall(TimeSpan.FromSeconds(5), EndDuel, stillAlive[0]); } ColUtility.Free(stillAlive); } } public bool CheckTeamAlive(PlayerMobile justDied, ArenaTeam team) { return team.Players.Keys.FirstOrDefault(p => p != justDied && p.Alive) != null; } public void SendResults(ArenaTeam winner) { foreach (KeyValuePair part in GetParticipants()) { BaseGump.SendGump(new DuelResultsGump(part.Key, this, winner)); } } public void OnTick() { if (HasBegun && !Complete) { foreach (KeyValuePair part in GetParticipants(true)) { PlayerMobile pm = part.Key; if (!RidingFlyingAllowed && pm.Mounted) { IMount mount = pm.Mount; if (mount != null) { mount.Rider = null; pm.SetMountBlock(BlockMountType.DismountRecovery, TimeSpan.FromSeconds(10), false); } if (InPreFight) { pm.SendLocalizedMessage(1115997); // The rules prohibit riding a mount or flying. } else { pm.SendLocalizedMessage(1115998); // The rules prohibit riding a mount or flying. You have received penalty damage! int damage = 37; if (!Warned.Contains(pm)) { Warned.Add(pm); } else { damage = 237; } AOS.Damage(pm, null, damage, 0, 0, 0, 0, 0, 0, 100); } } if (!RangedWeaponsAllowed) { Item item = pm.FindItemOnLayer(Layer.TwoHanded); if (item is BaseRanged) { pm.AddToBackpack(item); pm.SendLocalizedMessage(1115996); // The rules prohibit the use of ranged weapons! } } if (!SummonSpellsAllowed) { foreach (Mobile mob in Arena.Region.GetEnumeratedMobiles()) { if (mob is BaseCreature && ((BaseCreature)mob).Summoned) { Mobile master = ((BaseCreature)mob).GetMaster(); if (master != null) { master.SendLocalizedMessage(1149603); // The rules prohibit the use of summoning spells! } mob.Delete(); } } } } if (InPreFight) { if (EntryDeadline != DateTime.MinValue && EntryDeadline < DateTime.UtcNow) { BeginDuel(); } } else if (EndTime < DateTime.UtcNow) { EndDuel(); } } } public bool IsEnemy(Mobile one, Mobile two) { if (InPreFight) return false; if (one is BaseCreature) one = ((BaseCreature)one).GetMaster(); if (two is BaseCreature) two = ((BaseCreature)two).GetMaster(); PlayerMobile pm1 = one as PlayerMobile; PlayerMobile pm2 = two as PlayerMobile; if (pm1 != null && pm2 != null) { ArenaTeam team1 = GetTeam(pm1); ArenaTeam team2 = GetTeam(pm2); return team1 != null && team2 != null && team1 != team2; } return false; } public bool IsFriendly(Mobile one, Mobile two) { if (InPreFight) return false; if (one is BaseCreature) one = ((BaseCreature)one).GetMaster(); if (two is BaseCreature) two = ((BaseCreature)two).GetMaster(); PlayerMobile pm1 = one as PlayerMobile; PlayerMobile pm2 = two as PlayerMobile; if (pm1 != null && pm2 != null) { ArenaTeam team1 = GetTeam(pm1); ArenaTeam team2 = GetTeam(pm2); return team1 != null && team2 != null && team1 == team2; } return false; } public void Closeout() { foreach (ArenaTeam team in Teams) { team.Players.Clear(); team.Players = null; team.PlayerZero = null; } ColUtility.Free(Teams); ColUtility.Free(Banned); ColUtility.Free(Warned); KillRecord.Clear(); Teams = null; Banned = null; Warned = null; KillRecord = null; } public ArenaDuel(GenericReader reader, PVPArena arena) { int version = reader.ReadInt(); Arena = arena; Teams = new List(); KillRecord = new Dictionary(); Banned = new List(); Warned = new List(); Host = reader.ReadMobile() as PlayerMobile; Pot = reader.ReadInt(); Entries = reader.ReadInt(); RoomType = (RoomType)reader.ReadInt(); BattleMode = (BattleMode)reader.ReadInt(); Ranked = reader.ReadBool(); TimeLimit = (TimeLimit)reader.ReadInt(); EntryFee = (EntryFee)reader.ReadInt(); PetSlots = reader.ReadInt(); RidingFlyingAllowed = reader.ReadBool(); RangedWeaponsAllowed = reader.ReadBool(); SummonSpellsAllowed = reader.ReadBool(); FieldSpellsAllowed = reader.ReadBool(); PotionRules = (PotionRules)reader.ReadInt(); HasBegun = reader.ReadBool(); Complete = reader.ReadBool(); InPreFight = reader.ReadBool(); StartTime = reader.ReadDateTime(); EntryDeadline = reader.ReadDateTime(); int count = reader.ReadInt(); for (int i = 0; i < count; i++) { Teams.Add(new ArenaTeam(reader)); } count = reader.ReadInt(); for (int i = 0; i < count; i++) { string a = reader.ReadString(); string b = reader.ReadString(); KillRecord[a] = b; } count = reader.ReadInt(); for (int i = 0; i < count; i++) { PlayerMobile pm = reader.ReadMobile() as PlayerMobile; if (pm != null) Warned.Add(pm); } } public void Serialize(GenericWriter writer) { writer.Write(0); writer.Write(Host); writer.Write(Pot); writer.Write(Entries); writer.Write((int)RoomType); writer.Write((int)BattleMode); writer.Write(Ranked); writer.Write((int)TimeLimit); writer.Write((int)EntryFee); writer.Write(PetSlots); writer.Write(RidingFlyingAllowed); writer.Write(RangedWeaponsAllowed); writer.Write(SummonSpellsAllowed); writer.Write(FieldSpellsAllowed); writer.Write((int)PotionRules); writer.Write(HasBegun); writer.Write(Complete); writer.Write(InPreFight); writer.Write(StartTime); writer.Write(EntryDeadline); writer.Write(Teams.Count); for (int i = 0; i < Teams.Count; i++) { Teams[i].Serialize(writer); } writer.Write(KillRecord.Count); foreach (KeyValuePair kvp in KillRecord) { writer.Write(kvp.Key); writer.Write(kvp.Value); } writer.Write(Warned.Count); foreach (PlayerMobile pm in Warned) { writer.Write(pm); } } } }