838 lines
22 KiB
C#
838 lines
22 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using Server.Gumps;
|
|
using Server.Mobiles;
|
|
using Server.Multis;
|
|
|
|
namespace Server.Engines.ConPVP
|
|
{
|
|
public class ArenaController : Item
|
|
{
|
|
[Constructible]
|
|
public ArenaController() : base(0x1B7A)
|
|
{
|
|
Visible = false;
|
|
Movable = false;
|
|
|
|
Arena = new Arena();
|
|
|
|
Instances.Add(this);
|
|
}
|
|
|
|
public ArenaController(Serial serial) : base(serial)
|
|
{
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public Arena Arena { get; private set; }
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public bool IsPrivate { get; set; }
|
|
|
|
public override string DefaultName => "arena controller";
|
|
|
|
public static List<ArenaController> Instances { get; set; } = new List<ArenaController>();
|
|
|
|
public override void OnDelete()
|
|
{
|
|
base.OnDelete();
|
|
|
|
Instances.Remove(this);
|
|
Arena.Delete();
|
|
}
|
|
|
|
public override void OnDoubleClick(Mobile from)
|
|
{
|
|
if (from.AccessLevel >= AccessLevel.GameMaster)
|
|
{
|
|
from.SendGump(new PropertiesGump(from, Arena));
|
|
}
|
|
}
|
|
|
|
public override void Serialize(IGenericWriter writer)
|
|
{
|
|
base.Serialize(writer);
|
|
|
|
writer.Write(1);
|
|
|
|
writer.Write(IsPrivate);
|
|
|
|
Arena.Serialize(writer);
|
|
}
|
|
|
|
public override void Deserialize(IGenericReader reader)
|
|
{
|
|
base.Deserialize(reader);
|
|
|
|
var version = reader.ReadInt();
|
|
|
|
switch (version)
|
|
{
|
|
case 1:
|
|
{
|
|
IsPrivate = reader.ReadBool();
|
|
|
|
goto case 0;
|
|
}
|
|
case 0:
|
|
{
|
|
Arena = new Arena(reader);
|
|
break;
|
|
}
|
|
}
|
|
|
|
Instances.Add(this);
|
|
}
|
|
}
|
|
|
|
[PropertyObject]
|
|
public class ArenaStartPoints
|
|
{
|
|
public ArenaStartPoints(Point3D[] points = null) => Points = points ?? new Point3D[8];
|
|
|
|
public ArenaStartPoints(IGenericReader reader)
|
|
{
|
|
Points = new Point3D[reader.ReadEncodedInt()];
|
|
|
|
for (var i = 0; i < Points.Length; ++i)
|
|
{
|
|
Points[i] = reader.ReadPoint3D();
|
|
}
|
|
}
|
|
|
|
public Point3D[] Points { get; }
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public Point3D EdgeWest
|
|
{
|
|
get => Points[0];
|
|
set => Points[0] = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public Point3D EdgeEast
|
|
{
|
|
get => Points[1];
|
|
set => Points[1] = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public Point3D EdgeNorth
|
|
{
|
|
get => Points[2];
|
|
set => Points[2] = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public Point3D EdgeSouth
|
|
{
|
|
get => Points[3];
|
|
set => Points[3] = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public Point3D CornerNW
|
|
{
|
|
get => Points[4];
|
|
set => Points[4] = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public Point3D CornerSE
|
|
{
|
|
get => Points[5];
|
|
set => Points[5] = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public Point3D CornerSW
|
|
{
|
|
get => Points[6];
|
|
set => Points[6] = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public Point3D CornerNE
|
|
{
|
|
get => Points[7];
|
|
set => Points[7] = value;
|
|
}
|
|
|
|
public override string ToString() => "...";
|
|
|
|
public void Serialize(IGenericWriter writer)
|
|
{
|
|
writer.WriteEncodedInt(Points.Length);
|
|
|
|
for (var i = 0; i < Points.Length; ++i)
|
|
{
|
|
writer.Write(Points[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
[PropertyObject]
|
|
public class Arena : IComparable<Arena>
|
|
{
|
|
private static readonly Point2D[] m_EdgeOffsets =
|
|
{
|
|
/*
|
|
* /\
|
|
* /\/\
|
|
* /\/\/\
|
|
* \/\/\/
|
|
* \/\/\
|
|
* \/\/
|
|
*/
|
|
new Point2D(0, 0),
|
|
new Point2D(0, -1),
|
|
new Point2D(0, +1),
|
|
new Point2D(1, 0),
|
|
new Point2D(1, -1),
|
|
new Point2D(1, +1),
|
|
new Point2D(2, 0),
|
|
new Point2D(2, -1),
|
|
new Point2D(2, +1),
|
|
new Point2D(3, 0)
|
|
};
|
|
|
|
// nw corner
|
|
private static readonly Point2D[] m_CornerOffsets =
|
|
{
|
|
/*
|
|
* /\
|
|
* /\/\
|
|
* /\/\/\
|
|
* /\/\/\/\
|
|
* \/\/\/\/
|
|
*/
|
|
new Point2D(0, 0),
|
|
new Point2D(0, 1),
|
|
new Point2D(1, 0),
|
|
new Point2D(1, 1),
|
|
new Point2D(0, 2),
|
|
new Point2D(2, 0),
|
|
new Point2D(2, 1),
|
|
new Point2D(1, 2),
|
|
new Point2D(0, 3),
|
|
new Point2D(3, 0)
|
|
};
|
|
|
|
private static readonly int[][,] m_Rotate =
|
|
{
|
|
new[,] { { +1, 0 }, { 0, +1 } }, // west
|
|
new[,] { { -1, 0 }, { 0, -1 } }, // east
|
|
new[,] { { 0, +1 }, { +1, 0 } }, // north
|
|
new[,] { { 0, -1 }, { -1, 0 } }, // south
|
|
new[,] { { +1, 0 }, { 0, +1 } }, // nw
|
|
new[,] { { -1, 0 }, { 0, -1 } }, // se
|
|
new[,] { { 0, +1 }, { +1, 0 } }, // sw
|
|
new[,] { { 0, -1 }, { -1, 0 } } // ne
|
|
};
|
|
|
|
private bool m_Active;
|
|
private Rectangle2D m_Bounds;
|
|
private Map m_Facet;
|
|
private Point3D m_GateOut;
|
|
|
|
private bool m_IsGuarded;
|
|
private string m_Name;
|
|
|
|
private SafeZone m_Region;
|
|
|
|
private TournamentController m_Tournament;
|
|
private Rectangle2D m_Zone;
|
|
|
|
public Arena()
|
|
{
|
|
Points = new ArenaStartPoints();
|
|
Players = new List<Mobile>();
|
|
}
|
|
|
|
public Arena(IGenericReader reader)
|
|
{
|
|
var version = reader.ReadEncodedInt();
|
|
|
|
switch (version)
|
|
{
|
|
case 7:
|
|
{
|
|
m_IsGuarded = reader.ReadBool();
|
|
|
|
goto case 6;
|
|
}
|
|
case 6:
|
|
{
|
|
Ladder = reader.ReadItem() as LadderController;
|
|
|
|
goto case 5;
|
|
}
|
|
case 5:
|
|
{
|
|
m_Tournament = reader.ReadItem() as TournamentController;
|
|
Announcer = reader.ReadMobile();
|
|
|
|
goto case 4;
|
|
}
|
|
case 4:
|
|
{
|
|
m_Name = reader.ReadString();
|
|
|
|
goto case 3;
|
|
}
|
|
case 3:
|
|
{
|
|
m_Zone = reader.ReadRect2D();
|
|
|
|
goto case 2;
|
|
}
|
|
case 2:
|
|
{
|
|
GateIn = reader.ReadPoint3D();
|
|
m_GateOut = reader.ReadPoint3D();
|
|
Teleporter = reader.ReadItem();
|
|
|
|
goto case 1;
|
|
}
|
|
case 1:
|
|
{
|
|
Players = reader.ReadStrongMobileList();
|
|
|
|
goto case 0;
|
|
}
|
|
case 0:
|
|
{
|
|
m_Facet = reader.ReadMap();
|
|
m_Bounds = reader.ReadRect2D();
|
|
Outside = reader.ReadPoint3D();
|
|
Wall = reader.ReadPoint3D();
|
|
|
|
if (version == 0)
|
|
{
|
|
reader.ReadBool();
|
|
Players = new List<Mobile>();
|
|
}
|
|
|
|
m_Active = reader.ReadBool();
|
|
Points = new ArenaStartPoints(reader);
|
|
|
|
if (m_Active)
|
|
{
|
|
Arenas.Add(this);
|
|
Arenas.Sort();
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (m_Zone.Start != Point2D.Zero && m_Zone.End != Point2D.Zero && m_Facet != null)
|
|
{
|
|
m_Region = new SafeZone(m_Zone, Outside, m_Facet, m_IsGuarded);
|
|
}
|
|
|
|
if (IsOccupied)
|
|
{
|
|
Timer.DelayCall(TimeSpan.FromSeconds(2.0), Evict);
|
|
}
|
|
|
|
if (m_Tournament != null)
|
|
{
|
|
Timer.DelayCall(AttachToTournament_Sandbox);
|
|
}
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public LadderController Ladder { get; set; }
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public bool IsGuarded
|
|
{
|
|
get => m_IsGuarded;
|
|
set
|
|
{
|
|
m_IsGuarded = value;
|
|
|
|
if (m_Region != null)
|
|
{
|
|
m_Region.Disabled = !m_IsGuarded;
|
|
}
|
|
}
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public TournamentController Tournament
|
|
{
|
|
get => m_Tournament;
|
|
set
|
|
{
|
|
m_Tournament?.Tournament.Arenas.Remove(this);
|
|
|
|
m_Tournament = value;
|
|
|
|
m_Tournament?.Tournament.Arenas.Add(this);
|
|
}
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public Mobile Announcer { get; set; }
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public string Name
|
|
{
|
|
get => m_Name;
|
|
set
|
|
{
|
|
m_Name = value;
|
|
if (m_Active)
|
|
{
|
|
Arenas.Sort();
|
|
}
|
|
}
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public Map Facet
|
|
{
|
|
get => m_Facet;
|
|
set
|
|
{
|
|
m_Facet = value;
|
|
|
|
if (Teleporter != null)
|
|
{
|
|
Teleporter.Map = value;
|
|
}
|
|
|
|
m_Region?.Unregister();
|
|
|
|
if (m_Zone.Start != Point2D.Zero && m_Zone.End != Point2D.Zero && m_Facet != null)
|
|
{
|
|
m_Region = new SafeZone(m_Zone, Outside, m_Facet, m_IsGuarded);
|
|
}
|
|
else
|
|
{
|
|
m_Region = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public Rectangle2D Bounds
|
|
{
|
|
get => m_Bounds;
|
|
set => m_Bounds = value;
|
|
}
|
|
|
|
public int Spectators => m_Region == null ? 0 : Math.Max(m_Region.GetPlayerCount() - Players.Count, 0);
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public Rectangle2D Zone
|
|
{
|
|
get => m_Zone;
|
|
set
|
|
{
|
|
m_Zone = value;
|
|
|
|
if (m_Zone.Start != Point2D.Zero && m_Zone.End != Point2D.Zero && m_Facet != null)
|
|
{
|
|
m_Region?.Unregister();
|
|
|
|
m_Region = new SafeZone(m_Zone, Outside, m_Facet, m_IsGuarded);
|
|
}
|
|
else
|
|
{
|
|
m_Region?.Unregister();
|
|
|
|
m_Region = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public Point3D Outside { get; set; }
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public Point3D GateIn { get; set; }
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public Point3D GateOut
|
|
{
|
|
get => m_GateOut;
|
|
set
|
|
{
|
|
m_GateOut = value;
|
|
if (Teleporter != null)
|
|
{
|
|
Teleporter.Location = m_GateOut;
|
|
}
|
|
}
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public Point3D Wall { get; set; }
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public bool IsOccupied => Players.Count > 0;
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public ArenaStartPoints Points { get; }
|
|
|
|
public Item Teleporter { get; set; }
|
|
|
|
public List<Mobile> Players { get; }
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public bool Active
|
|
{
|
|
get => m_Active;
|
|
set
|
|
{
|
|
if (m_Active == value)
|
|
{
|
|
return;
|
|
}
|
|
|
|
m_Active = value;
|
|
|
|
if (m_Active)
|
|
{
|
|
Arenas.Add(this);
|
|
Arenas.Sort();
|
|
}
|
|
else
|
|
{
|
|
Arenas.Remove(this);
|
|
}
|
|
}
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.Administrator, AccessLevel.Administrator)]
|
|
public bool ForceEvict
|
|
{
|
|
get => false;
|
|
set
|
|
{
|
|
if (value)
|
|
{
|
|
Evict();
|
|
}
|
|
}
|
|
}
|
|
|
|
public static List<Arena> Arenas { get; } = new List<Arena>();
|
|
|
|
public int CompareTo(Arena c) => string.CompareOrdinal(m_Name, c?.m_Name);
|
|
|
|
public Ladder AcquireLadder() => Ladder?.Ladder ?? ConPVP.Ladder.Instance;
|
|
|
|
public void Delete()
|
|
{
|
|
Active = false;
|
|
m_Region?.Unregister();
|
|
m_Region = null;
|
|
}
|
|
|
|
public override string ToString() => "...";
|
|
|
|
public Point3D GetBaseStartPoint(int index) => Points.Points[Math.Max(index, 0) % Points.Points.Length];
|
|
|
|
public void MoveInside(DuelPlayer[] players, int index)
|
|
{
|
|
index = Math.Min(index, 0) % Points.Points.Length;
|
|
|
|
var start = Points.Points[index];
|
|
|
|
var offset = 0;
|
|
|
|
var offsets = index < 4 ? m_EdgeOffsets : m_CornerOffsets;
|
|
var matrix = m_Rotate[index];
|
|
|
|
for (var i = 0; i < players.Length; ++i)
|
|
{
|
|
var pl = players[i];
|
|
|
|
if (pl == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var mob = pl.Mobile;
|
|
|
|
Point2D p;
|
|
|
|
if (offset < offsets.Length)
|
|
{
|
|
p = offsets[offset++];
|
|
}
|
|
else
|
|
{
|
|
p = offsets[^1];
|
|
}
|
|
|
|
p.X = p.X * matrix[0, 0] + p.Y * matrix[0, 1];
|
|
p.Y = p.X * matrix[1, 0] + p.Y * matrix[1, 1];
|
|
|
|
mob.MoveToWorld(new Point3D(start.X + p.X, start.Y + p.Y, start.Z), m_Facet);
|
|
mob.Direction = mob.GetDirectionTo(Wall);
|
|
|
|
Players.Add(mob);
|
|
}
|
|
}
|
|
|
|
private void AttachToTournament_Sandbox()
|
|
{
|
|
m_Tournament?.Tournament.Arenas.Add(this);
|
|
}
|
|
|
|
public void Evict()
|
|
{
|
|
Point3D loc;
|
|
Map facet;
|
|
|
|
if (m_Facet == null)
|
|
{
|
|
loc = new Point3D(2715, 2165, 0);
|
|
facet = Map.Felucca;
|
|
}
|
|
else
|
|
{
|
|
loc = Outside;
|
|
facet = m_Facet;
|
|
}
|
|
|
|
var hasBounds = m_Bounds.Start != Point2D.Zero && m_Bounds.End != Point2D.Zero;
|
|
|
|
for (var i = 0; i < Players.Count; ++i)
|
|
{
|
|
var mob = Players[i];
|
|
|
|
if (mob == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (mob.Map == Map.Internal)
|
|
{
|
|
if ((m_Facet == null || mob.LogoutMap == m_Facet) &&
|
|
(!hasBounds || m_Bounds.Contains(mob.LogoutLocation)))
|
|
{
|
|
mob.LogoutLocation = loc;
|
|
}
|
|
}
|
|
else if ((m_Facet == null || mob.Map == m_Facet) && (!hasBounds || m_Bounds.Contains(mob.Location)))
|
|
{
|
|
mob.MoveToWorld(loc, facet);
|
|
}
|
|
|
|
mob.Combatant = null;
|
|
mob.Frozen = false;
|
|
DuelContext.Debuff(mob);
|
|
DuelContext.CancelSpell(mob);
|
|
}
|
|
|
|
if (hasBounds)
|
|
{
|
|
var pets = new List<Mobile>();
|
|
|
|
foreach (var mob in facet.GetMobilesInBounds(m_Bounds))
|
|
{
|
|
if (mob is BaseCreature pet && pet.Controlled && pet.ControlMaster != null &&
|
|
Players.Contains(pet.ControlMaster))
|
|
{
|
|
pets.Add(pet);
|
|
}
|
|
}
|
|
|
|
foreach (var pet in pets)
|
|
{
|
|
pet.Combatant = null;
|
|
pet.Frozen = false;
|
|
|
|
pet.MoveToWorld(loc, facet);
|
|
}
|
|
}
|
|
|
|
Players.Clear();
|
|
}
|
|
|
|
public void Serialize(IGenericWriter writer)
|
|
{
|
|
writer.WriteEncodedInt(7);
|
|
|
|
writer.Write(m_IsGuarded);
|
|
|
|
writer.Write(Ladder);
|
|
|
|
writer.Write(m_Tournament);
|
|
writer.Write(Announcer);
|
|
|
|
writer.Write(m_Name);
|
|
|
|
writer.Write(m_Zone);
|
|
|
|
writer.Write(GateIn);
|
|
writer.Write(m_GateOut);
|
|
writer.Write(Teleporter);
|
|
|
|
writer.Write(Players);
|
|
|
|
writer.Write(m_Facet);
|
|
writer.Write(m_Bounds);
|
|
writer.Write(Outside);
|
|
writer.Write(Wall);
|
|
writer.Write(m_Active);
|
|
|
|
Points.Serialize(writer);
|
|
}
|
|
|
|
public static Arena FindArena(List<Mobile> players)
|
|
{
|
|
var prefs = Preferences.Instance;
|
|
|
|
if (prefs == null)
|
|
{
|
|
return FindArena();
|
|
}
|
|
|
|
if (Arenas.Count == 0)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
if (players.Count > 0)
|
|
{
|
|
var first = players[0];
|
|
|
|
var allControllers = ArenaController.Instances;
|
|
|
|
for (var i = 0; i < allControllers.Count; ++i)
|
|
{
|
|
var controller = allControllers[i];
|
|
|
|
if (controller?.Deleted == false && controller.Arena != null && controller.IsPrivate &&
|
|
controller.Map == first.Map && first.InRange(controller, 24))
|
|
{
|
|
var house = BaseHouse.FindHouseAt(controller);
|
|
var allNear = true;
|
|
|
|
for (var j = 0; j < players.Count; ++j)
|
|
{
|
|
var check = players[j];
|
|
bool isNear;
|
|
|
|
if (house == null)
|
|
{
|
|
isNear = controller.Map == check.Map && check.InRange(controller, 24);
|
|
}
|
|
else
|
|
{
|
|
isNear = BaseHouse.FindHouseAt(check) == house;
|
|
}
|
|
|
|
if (!isNear)
|
|
{
|
|
allNear = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (allNear)
|
|
{
|
|
return controller.Arena;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
var arenas = new List<ArenaEntry>();
|
|
|
|
for (var i = 0; i < Arenas.Count; ++i)
|
|
{
|
|
var arena = Arenas[i];
|
|
|
|
if (!arena.IsOccupied)
|
|
{
|
|
arenas.Add(new ArenaEntry(arena));
|
|
}
|
|
}
|
|
|
|
if (arenas.Count == 0)
|
|
{
|
|
return Arenas[0];
|
|
}
|
|
|
|
var tc = 0;
|
|
|
|
for (var i = 0; i < arenas.Count; ++i)
|
|
{
|
|
var ae = arenas[i];
|
|
|
|
for (var j = 0; j < players.Count; ++j)
|
|
{
|
|
var pe = prefs.Find(players[j]);
|
|
|
|
if (pe.Disliked.Contains(ae.m_Arena.Name))
|
|
{
|
|
++ae.m_VotesAgainst;
|
|
}
|
|
else
|
|
{
|
|
++ae.m_VotesFor;
|
|
}
|
|
}
|
|
|
|
tc += ae.Value;
|
|
}
|
|
|
|
var rn = Utility.Random(tc);
|
|
|
|
for (var i = 0; i < arenas.Count; ++i)
|
|
{
|
|
var ae = arenas[i];
|
|
|
|
if (rn < ae.Value)
|
|
{
|
|
return ae.m_Arena;
|
|
}
|
|
|
|
rn -= ae.Value;
|
|
}
|
|
|
|
return arenas.RandomElement().m_Arena;
|
|
}
|
|
|
|
public static Arena FindArena()
|
|
{
|
|
if (Arenas.Count == 0)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var offset = Utility.Random(Arenas.Count);
|
|
|
|
for (var i = 0; i < Arenas.Count; ++i)
|
|
{
|
|
var arena = Arenas[(i + offset) % Arenas.Count];
|
|
|
|
if (!arena.IsOccupied)
|
|
{
|
|
return arena;
|
|
}
|
|
}
|
|
|
|
return Arenas[offset];
|
|
}
|
|
|
|
private class ArenaEntry
|
|
{
|
|
public readonly Arena m_Arena;
|
|
public int m_VotesAgainst;
|
|
public int m_VotesFor;
|
|
|
|
public ArenaEntry(Arena arena) => m_Arena = arena;
|
|
|
|
public int Value => m_VotesFor;
|
|
}
|
|
}
|
|
}
|