Reorganizes Project (#41)

This commit is contained in:
Kamron Batman 2019-08-02 18:13:40 -07:00 committed by GitHub
parent 08bf44af9a
commit 3614a66aee
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3499 changed files with 79 additions and 55 deletions

View file

@ -0,0 +1,279 @@
using System;
using System.Collections.Generic;
using Server.Gumps;
using Server.Mobiles;
using Server.Network;
namespace Server.Engines.ConPVP
{
public class AcceptDuelGump : Gump
{
private const int LabelColor32 = 0xFFFFFF;
private const int BlackColor32 = 0x000008;
private static Dictionary<Mobile, List<IgnoreEntry>> m_IgnoreLists = new Dictionary<Mobile, List<IgnoreEntry>>();
private bool m_Active = true;
private Mobile m_Challenger, m_Challenged;
private DuelContext m_Context;
private Participant m_Participant;
private int m_Slot;
public AcceptDuelGump(Mobile challenger, Mobile challenged, DuelContext context, Participant p, int slot) : base(50,
50)
{
m_Challenger = challenger;
m_Challenged = challenged;
m_Context = context;
m_Participant = p;
m_Slot = slot;
challenged.CloseGump<AcceptDuelGump>();
Closable = false;
AddPage(0);
//AddBackground( 0, 0, 400, 220, 9150 );
AddBackground(1, 1, 398, 218, 3600);
//AddBackground( 16, 15, 369, 189, 9100 );
AddImageTiled(16, 15, 369, 189, 3604);
AddAlphaRegion(16, 15, 369, 189);
AddImage(215, -43, 0xEE40);
//AddImage( 330, 141, 0x8BA );
AddHtml(22 - 1, 22, 294, 20, Color(Center("Duel Challenge"), BlackColor32));
AddHtml(22 + 1, 22, 294, 20, Color(Center("Duel Challenge"), BlackColor32));
AddHtml(22, 22 - 1, 294, 20, Color(Center("Duel Challenge"), BlackColor32));
AddHtml(22, 22 + 1, 294, 20, Color(Center("Duel Challenge"), BlackColor32));
AddHtml(22, 22, 294, 20, Color(Center("Duel Challenge"), LabelColor32));
string fmt;
if (p.Contains(challenger))
fmt = "You have been asked to join sides with {0} in a duel. Do you accept?";
else
fmt = "You have been challenged to a duel from {0}. Do you accept?";
AddHtml(22 - 1, 50, 294, 40, Color(string.Format(fmt, challenger.Name), BlackColor32));
AddHtml(22 + 1, 50, 294, 40, Color(string.Format(fmt, challenger.Name), BlackColor32));
AddHtml(22, 50 - 1, 294, 40, Color(string.Format(fmt, challenger.Name), BlackColor32));
AddHtml(22, 50 + 1, 294, 40, Color(string.Format(fmt, challenger.Name), BlackColor32));
AddHtml(22, 50, 294, 40, Color(string.Format(fmt, challenger.Name), 0xB0C868));
AddImageTiled(32, 88, 264, 1, 9107);
AddImageTiled(42, 90, 264, 1, 9157);
AddRadio(24, 100, 9727, 9730, true, 1);
AddHtml(60 - 1, 105, 250, 20, Color("Yes, I will fight this duel.", BlackColor32));
AddHtml(60 + 1, 105, 250, 20, Color("Yes, I will fight this duel.", BlackColor32));
AddHtml(60, 105 - 1, 250, 20, Color("Yes, I will fight this duel.", BlackColor32));
AddHtml(60, 105 + 1, 250, 20, Color("Yes, I will fight this duel.", BlackColor32));
AddHtml(60, 105, 250, 20, Color("Yes, I will fight this duel.", LabelColor32));
AddRadio(24, 135, 9727, 9730, false, 2);
AddHtml(60 - 1, 140, 250, 20, Color("No, I do not wish to fight.", BlackColor32));
AddHtml(60 + 1, 140, 250, 20, Color("No, I do not wish to fight.", BlackColor32));
AddHtml(60, 140 - 1, 250, 20, Color("No, I do not wish to fight.", BlackColor32));
AddHtml(60, 140 + 1, 250, 20, Color("No, I do not wish to fight.", BlackColor32));
AddHtml(60, 140, 250, 20, Color("No, I do not wish to fight.", LabelColor32));
AddRadio(24, 170, 9727, 9730, false, 3);
AddHtml(60 - 1, 175, 250, 20, Color("No, knave. Do not ask again.", BlackColor32));
AddHtml(60 + 1, 175, 250, 20, Color("No, knave. Do not ask again.", BlackColor32));
AddHtml(60, 175 - 1, 250, 20, Color("No, knave. Do not ask again.", BlackColor32));
AddHtml(60, 175 + 1, 250, 20, Color("No, knave. Do not ask again.", BlackColor32));
AddHtml(60, 175, 250, 20, Color("No, knave. Do not ask again.", LabelColor32));
AddButton(314, 173, 247, 248, 1);
Timer.DelayCall(TimeSpan.FromSeconds(15.0), AutoReject);
}
public string Center(string text)
{
return $"<CENTER>{text}</CENTER>";
}
public string Color(string text, int color)
{
return $"<BASEFONT COLOR=#{color:X6}>{text}</BASEFONT>";
}
public void AutoReject()
{
if (!m_Active)
return;
m_Active = false;
m_Challenged.CloseGump<AcceptDuelGump>();
m_Challenger.SendMessage("{0} seems unresponsive.", m_Challenged.Name);
m_Challenged.SendMessage("You decline the challenge.");
}
public static void BeginIgnore(Mobile source, Mobile toIgnore)
{
if (!m_IgnoreLists.TryGetValue(source, out List<IgnoreEntry> list))
m_IgnoreLists[source] = list = new List<IgnoreEntry>();
for (int i = 0; i < list.Count; ++i)
{
IgnoreEntry ie = list[i];
if (ie.Ignored == toIgnore)
{
ie.Refresh();
return;
}
if (ie.Expired)
list.RemoveAt(i--);
}
list.Add(new IgnoreEntry(toIgnore));
}
public static bool IsIgnored(Mobile source, Mobile check)
{
if (!m_IgnoreLists.TryGetValue(source, out List<IgnoreEntry> list))
return false;
for (int i = 0; i < list.Count; ++i)
{
IgnoreEntry ie = list[i];
if (ie.Expired)
list.RemoveAt(i--);
else if (ie.Ignored == check)
return true;
}
return false;
}
public override void OnResponse(NetState sender, RelayInfo info)
{
if (info.ButtonID != 1 || !m_Active || !m_Context.Registered)
return;
m_Active = false;
if (!m_Context.Participants.Contains(m_Participant))
return;
if (info.IsSwitched(1))
{
if (!(m_Challenged is PlayerMobile pm))
return;
if (pm.DuelContext != null)
{
if (pm.DuelContext.Initiator == pm)
pm.SendMessage(0x22, "You have already started a duel.");
else
pm.SendMessage(0x22, "You have already been challenged in a duel.");
m_Challenger.SendMessage("{0} cannot fight because they are already assigned to another duel.", pm.Name);
}
else if (DuelContext.CheckCombat(pm))
{
pm.SendMessage(0x22,
"You have recently been in combat with another player and must wait before starting a duel.");
m_Challenger.SendMessage(
"{0} cannot fight because they have recently been in combat with another player.", pm.Name);
}
else if (TournamentController.IsActive)
{
pm.SendMessage(0x22, "A tournament is currently active and you may not duel.");
m_Challenger.SendMessage(0x22, "A tournament is currently active and you may not duel.");
}
else
{
bool added = false;
if (m_Slot >= 0 && m_Slot < m_Participant.Players.Length && m_Participant.Players[m_Slot] == null)
{
added = true;
m_Participant.Players[m_Slot] = new DuelPlayer(m_Challenged, m_Participant);
}
else
{
for (int i = 0; i < m_Participant.Players.Length; ++i)
if (m_Participant.Players[i] == null)
{
added = true;
m_Participant.Players[i] = new DuelPlayer(m_Challenged, m_Participant);
break;
}
}
if (added)
{
m_Challenger.SendMessage("{0} has accepted the request.", m_Challenged.Name);
m_Challenged.SendMessage("You have accepted the request from {0}.", m_Challenger.Name);
NetState ns = m_Challenger.NetState;
if (ns != null)
foreach (Gump g in ns.Gumps)
{
if (g is ParticipantGump pg && pg.Participant == m_Participant)
{
m_Challenger.SendGump(new ParticipantGump(m_Challenger, m_Context, m_Participant));
break;
}
if (g is DuelContextGump dcg && dcg.Context == m_Context)
{
m_Challenger.SendGump(new DuelContextGump(m_Challenger, m_Context));
break;
}
}
}
else
{
m_Challenger.SendMessage("The participant list was full and so {0} could not join.",
m_Challenged.Name);
m_Challenged.SendMessage(
"The participant list was full and so you could not join the fight {1} {0}.", m_Challenger.Name,
m_Participant.Contains(m_Challenger) ? "with" : "against");
}
}
}
else
{
if (info.IsSwitched(3))
BeginIgnore(m_Challenged, m_Challenger);
m_Challenger.SendMessage("{0} does not wish to fight.", m_Challenged.Name);
m_Challenged.SendMessage("You chose not to fight {1} {0}.", m_Challenger.Name,
m_Participant.Contains(m_Challenger) ? "with" : "against");
}
}
private class IgnoreEntry
{
private static TimeSpan ExpireDelay = TimeSpan.FromMinutes(15.0);
public DateTime m_Expire;
public Mobile m_Ignored;
public IgnoreEntry(Mobile ignored)
{
m_Ignored = ignored;
Refresh();
}
public Mobile Ignored => m_Ignored;
public bool Expired => DateTime.UtcNow >= m_Expire;
public void Refresh()
{
m_Expire = DateTime.UtcNow + ExpireDelay;
}
}
}
}

View file

@ -0,0 +1,825 @@
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(GenericWriter writer)
{
base.Serialize(writer);
writer.Write(1);
writer.Write(IsPrivate);
Arena.Serialize(writer);
}
public override void Deserialize(GenericReader reader)
{
base.Deserialize(reader);
int 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(GenericReader reader)
{
Points = new Point3D[reader.ReadEncodedInt()];
for (int 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()
{
return "...";
}
public void Serialize(GenericWriter writer)
{
writer.WriteEncodedInt(Points.Length);
for (int i = 0; i < Points.Length; ++i)
writer.Write(Points[i]);
}
}
[PropertyObject]
public class Arena : IComparable<Arena>
{
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(GenericReader reader)
{
int 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(TimeSpan.Zero, 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
{
get
{
if (m_Region == null)
return 0;
int specs = m_Region.GetPlayerCount() - Players.Count;
if (specs < 0)
specs = 0;
return specs;
}
}
[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; private set; }
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 a = m_Name;
string b = c.m_Name;
if (a == null && b == null)
return 0;
if (a == null)
return -1;
if (b == null)
return +1;
return a.CompareTo(b);
}
public Ladder AcquireLadder()
{
return Ladder?.Ladder ?? ConPVP.Ladder.Instance;
}
public void Delete()
{
Active = false;
m_Region?.Unregister();
m_Region = null;
}
public override string ToString()
{
return "...";
}
public Point3D GetBaseStartPoint(int index)
{
if (index < 0)
index = 0;
return Points.Points[index % Points.Points.Length];
}
public void MoveInside(DuelPlayer[] players, int index)
{
if (index < 0)
index = 0;
else
index %= Points.Points.Length;
Point3D start = GetBaseStartPoint(index);
int offset = 0;
Point2D[] offsets = index < 4 ? m_EdgeOffsets : m_CornerOffsets;
int[,] matrix = m_Rotate[index];
for (int i = 0; i < players.Length; ++i)
{
DuelPlayer pl = players[i];
if (pl == null)
continue;
Mobile mob = pl.Mobile;
Point2D p;
if (offset < offsets.Length)
p = offsets[offset++];
else
p = offsets[offsets.Length - 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;
}
bool hasBounds = m_Bounds.Start != Point2D.Zero && m_Bounds.End != Point2D.Zero;
for (int i = 0; i < Players.Count; ++i)
{
Mobile 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)
{
List<Mobile> pets = new List<Mobile>();
foreach (Mobile mob in facet.GetMobilesInBounds(m_Bounds))
if (mob is BaseCreature pet && pet.Controlled && pet.ControlMaster != null &&
Players.Contains(pet.ControlMaster))
pets.Add(pet);
foreach (Mobile pet in pets)
{
pet.Combatant = null;
pet.Frozen = false;
pet.MoveToWorld(loc, facet);
}
}
Players.Clear();
}
public void Serialize(GenericWriter 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)
{
Preferences prefs = Preferences.Instance;
if (prefs == null)
return FindArena();
if (Arenas.Count == 0)
return null;
if (players.Count > 0)
{
Mobile first = players[0];
List<ArenaController> allControllers = ArenaController.Instances;
for (int i = 0; i < allControllers.Count; ++i)
{
ArenaController controller = allControllers[i];
if (controller?.Deleted == false && controller.Arena != null && controller.IsPrivate &&
controller.Map == first.Map && first.InRange(controller, 24))
{
BaseHouse house = BaseHouse.FindHouseAt(controller);
bool allNear = true;
for (int j = 0; j < players.Count; ++j)
{
Mobile 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;
}
}
}
List<ArenaEntry> arenas = new List<ArenaEntry>();
for (int i = 0; i < Arenas.Count; ++i)
{
Arena arena = Arenas[i];
if (!arena.IsOccupied)
arenas.Add(new ArenaEntry(arena));
}
if (arenas.Count == 0)
return Arenas[0];
int tc = 0;
for (int i = 0; i < arenas.Count; ++i)
{
ArenaEntry ae = arenas[i];
for (int j = 0; j < players.Count; ++j)
{
PreferencesEntry pe = prefs.Find(players[j]);
if (pe.Disliked.Contains(ae.m_Arena.Name))
++ae.m_VotesAgainst;
else
++ae.m_VotesFor;
}
tc += ae.Value;
}
int rn = Utility.Random(tc);
for (int i = 0; i < arenas.Count; ++i)
{
ArenaEntry ae = arenas[i];
if (rn < ae.Value)
return ae.m_Arena;
rn -= ae.Value;
}
return arenas[Utility.Random(arenas.Count)].m_Arena;
}
public static Arena FindArena()
{
if (Arenas.Count == 0)
return null;
int offset = Utility.Random(Arenas.Count);
for (int i = 0; i < Arenas.Count; ++i)
{
Arena arena = Arenas[(i + offset) % Arenas.Count];
if (!arena.IsOccupied)
return arena;
}
return Arenas[offset];
}
private class ArenaEntry
{
public Arena m_Arena;
public int m_VotesAgainst;
public int m_VotesFor;
public ArenaEntry(Arena arena)
{
m_Arena = arena;
}
public int Value => m_VotesFor;
}
#region Offsets & Rotation
private static 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 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 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
};
#endregion
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,89 @@
using Server.Items;
namespace Server.Engines.ConPVP
{
public enum DuelTeleporterType
{
Squares = 6095,
Buds = 6104,
Flowers = 6113,
Spikes = 6122,
Arrows = 6140,
Links = 6149
}
public class DuelTeleporterAddon : BaseAddon
{
[Constructible]
public DuelTeleporterAddon(DuelTeleporterType type = DuelTeleporterType.Squares)
{
int itemID = (int)type;
AddComponent(new AddonComponent(itemID + 0), -1, -1, 5);
AddComponent(new AddonComponent(itemID + 1), -1, 0, 5);
AddComponent(new AddonComponent(itemID + 2), 0, -1, 5);
AddComponent(new AddonComponent(itemID + 3), -1, +1, 5);
AddComponent(new AddonComponent(itemID + 4), 0, 0, 5);
AddComponent(new AddonComponent(itemID + 5), +1, -1, 5);
AddComponent(new AddonComponent(itemID + 6), 0, +1, 5);
AddComponent(new AddonComponent(itemID + 7), +1, 0, 5);
AddComponent(new AddonComponent(itemID + 8), +1, +1, 5);
AddComponent(new AddonComponent(0x759), -2, -2, 0);
AddComponent(new AddonComponent(0x75A), +2, +2, 0);
AddComponent(new AddonComponent(0x75B), -2, +2, 0);
AddComponent(new AddonComponent(0x75C), +2, -2, 0);
AddComponent(new AddonComponent(0x751), -1, +2, 0);
AddComponent(new AddonComponent(0x751), 0, +2, 0);
AddComponent(new AddonComponent(0x751), +1, +2, 0);
AddComponent(new AddonComponent(0x752), +2, -1, 0);
AddComponent(new AddonComponent(0x752), +2, 0, 0);
AddComponent(new AddonComponent(0x752), +2, +1, 0);
AddComponent(new AddonComponent(0x753), -1, -2, 0);
AddComponent(new AddonComponent(0x753), 0, -2, 0);
AddComponent(new AddonComponent(0x753), +1, -2, 0);
AddComponent(new AddonComponent(0x754), -2, -1, 0);
AddComponent(new AddonComponent(0x754), -2, 0, 0);
AddComponent(new AddonComponent(0x754), -2, +1, 0);
}
public DuelTeleporterAddon(Serial serial) : base(serial)
{
}
[CommandProperty(AccessLevel.GameMaster)]
public DuelTeleporterType Type
{
get
{
if (Components.Count > 0)
return (DuelTeleporterType)Components[0].ItemID;
return DuelTeleporterType.Squares;
}
set
{
for (int i = 0; i < Components.Count && i < 9; ++i)
Components[i].ItemID = i + (int)value;
}
}
public override void Serialize(GenericWriter writer)
{
base.Serialize(writer);
writer.Write(0); // version
}
public override void Deserialize(GenericReader reader)
{
base.Deserialize(reader);
int version = reader.ReadInt();
}
}
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,77 @@
using Server.Gumps;
using Server.Items;
namespace Server.Engines.ConPVP
{
public abstract class EventController : Item
{
public EventController()
: base(0x1B7A)
{
Visible = false;
Movable = false;
}
public EventController(Serial serial)
: base(serial)
{
}
public abstract string Title{ get; }
public abstract EventGame Construct(DuelContext dc);
public abstract string GetTeamName(int teamID);
public override void Serialize(GenericWriter writer)
{
base.Serialize(writer);
writer.Write(0);
}
public override void Deserialize(GenericReader reader)
{
base.Deserialize(reader);
int version = reader.ReadInt();
}
public override void OnDoubleClick(Mobile from)
{
if (from.AccessLevel >= AccessLevel.GameMaster)
from.SendGump(new PropertiesGump(from, this));
}
}
public abstract class EventGame
{
protected DuelContext m_Context;
public EventGame(DuelContext context)
{
m_Context = context;
}
public DuelContext Context => m_Context;
public virtual bool FreeConsume => true;
public virtual bool OnDeath(Mobile mob, Container corpse)
{
return true;
}
public virtual bool CantDoAnything(Mobile mob)
{
return false;
}
public virtual void OnStart()
{
}
public virtual void OnStop()
{
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,123 @@
using System;
using System.Collections.Generic;
using System.Text;
using Server.Network;
namespace Server.Engines.ConPVP
{
public class TourneyMatch
{
public TourneyMatch(List<TourneyParticipant> participants)
{
Participants = participants;
for (int i = 0; i < participants.Count; ++i)
{
TourneyParticipant part = participants[i];
StringBuilder sb = new StringBuilder();
sb.Append("Matched in a duel against ");
if (participants.Count > 2)
sb.AppendFormat("{0} other {1}: ", participants.Count - 1,
part.Players.Count == 1 ? "players" : "teams");
bool hasAppended = false;
for (int j = 0; j < participants.Count; ++j)
{
if (i == j)
continue;
if (hasAppended)
sb.Append(", ");
sb.Append(participants[j].NameList);
hasAppended = true;
}
sb.Append(".");
part.AddLog(sb.ToString());
}
}
public List<TourneyParticipant> Participants{ get; set; }
public TourneyParticipant Winner{ get; set; }
public DuelContext Context{ get; set; }
public bool InProgress => Context?.Registered == true;
public void Start(Arena arena, Tournament tourney)
{
TourneyParticipant first = Participants[0];
DuelContext dc = new DuelContext(first.Players[0], tourney.Ruleset.Layout, false);
dc.Ruleset.Options.SetAll(false);
dc.Ruleset.Options.Or(tourney.Ruleset.Options);
for (int i = 0; i < Participants.Count; ++i)
{
TourneyParticipant tourneyPart = Participants[i];
Participant duelPart = new Participant(dc, tourneyPart.Players.Count)
{
TourneyPart = tourneyPart
};
for (int j = 0; j < tourneyPart.Players.Count; ++j)
duelPart.Add(tourneyPart.Players[j]);
for (int j = 0; j < duelPart.Players.Length; ++j)
if (duelPart.Players[j] != null)
duelPart.Players[j].Ready = true;
dc.Participants.Add(duelPart);
}
if (tourney.EventController != null)
dc.m_EventGame = tourney.EventController.Construct(dc);
dc.m_Tournament = tourney;
dc.m_Match = this;
dc.m_OverrideArena = arena;
if (tourney.SuddenDeath > TimeSpan.Zero &&
(tourney.SuddenDeathRounds == 0 || tourney.Pyramid.Levels.Count <= tourney.SuddenDeathRounds))
dc.StartSuddenDeath(tourney.SuddenDeath);
dc.SendReadyGump(0);
if (dc.StartedBeginCountdown)
{
Context = dc;
for (int i = 0; i < Participants.Count; ++i)
{
TourneyParticipant p = Participants[i];
for (int j = 0; j < p.Players.Count; ++j)
{
Mobile mob = p.Players[j];
foreach (Mobile view in mob.GetMobilesInRange(18))
if (!mob.CanSee(view))
mob.Send(view.RemovePacket);
mob.LocalOverheadMessage(MessageType.Emote, 0x3B2, false,
"* Your mind focuses intently on the fight and all other distractions fade away *");
}
}
}
else
{
dc.Unregister();
dc.StopCountdown();
}
}
}
}

View file

@ -0,0 +1,382 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using Server.Gumps;
using Server.Mobiles;
using Server.Network;
namespace Server.Engines.ConPVP
{
public class AcceptTeamGump : Gump
{
private const int BlackColor32 = 0x000008;
private const int LabelColor32 = 0xFFFFFF;
private bool m_Active;
private Mobile m_From;
private List<Mobile> m_Players;
private Mobile m_Registrar;
private Mobile m_Requested;
private Tournament m_Tournament;
public AcceptTeamGump(Mobile from, Mobile requested, Tournament tourney, Mobile registrar, List<Mobile> players) :
base(50, 50)
{
m_From = from;
m_Requested = requested;
m_Tournament = tourney;
m_Registrar = registrar;
m_Players = players;
m_Active = true;
#region Rules
Ruleset ruleset = tourney.Ruleset;
Ruleset basedef = ruleset.Base;
int height = 185 + 35 + 60 + 12;
int changes = 0;
BitArray defs;
if (ruleset.Flavors.Count > 0)
{
defs = new BitArray(basedef.Options);
for (int i = 0; i < ruleset.Flavors.Count; ++i)
defs.Or(ruleset.Flavors[i].Options);
height += ruleset.Flavors.Count * 18;
}
else
{
defs = basedef.Options;
}
BitArray opts = ruleset.Options;
for (int i = 0; i < opts.Length; ++i)
if (defs[i] != opts[i])
++changes;
height += changes * 22;
height += 10 + 22 + 25 + 25;
#endregion
Closable = false;
AddPage(0);
AddBackground(1, 1, 398, height, 3600);
AddImageTiled(16, 15, 369, height - 29, 3604);
AddAlphaRegion(16, 15, 369, height - 29);
AddImage(215, -43, 0xEE40);
StringBuilder sb = new StringBuilder();
if (tourney.TourneyType == TourneyType.FreeForAll)
{
sb.Append("FFA");
}
else if (tourney.TourneyType == TourneyType.RandomTeam)
{
sb.Append(tourney.ParticipantsPerMatch);
sb.Append("-Team");
}
else if (tourney.TourneyType == TourneyType.Faction)
{
sb.Append(tourney.ParticipantsPerMatch);
sb.Append("-Team Faction");
}
else if (tourney.TourneyType == TourneyType.RedVsBlue)
{
sb.Append("Red v Blue");
}
else
{
for (int i = 0; i < tourney.ParticipantsPerMatch; ++i)
{
if (sb.Length > 0)
sb.Append('v');
sb.Append(tourney.PlayersPerParticipant);
}
}
if (tourney.EventController != null)
sb.Append(' ').Append(tourney.EventController.Title);
sb.Append(" Tournament Invitation");
AddBorderedText(22, 22, 294, 20, Center(sb.ToString()), LabelColor32, BlackColor32);
AddBorderedText(22, 50, 294, 40,
$"You have been asked to partner with {from.Name} in a tournament. Do you accept?",
0xB0C868, BlackColor32);
AddImageTiled(32, 88, 264, 1, 9107);
AddImageTiled(42, 90, 264, 1, 9157);
#region Rules
int y = 100;
string groupText = null;
switch (tourney.GroupType)
{
case GroupingType.HighVsLow:
groupText = "High vs Low";
break;
case GroupingType.Nearest:
groupText = "Closest opponent";
break;
case GroupingType.Random:
groupText = "Random";
break;
}
AddBorderedText(35, y, 190, 20, $"Grouping: {groupText}", LabelColor32, BlackColor32);
y += 20;
string tieText = null;
switch (tourney.TieType)
{
case TieType.Random:
tieText = "Random";
break;
case TieType.Highest:
tieText = "Highest advances";
break;
case TieType.Lowest:
tieText = "Lowest advances";
break;
case TieType.FullAdvancement:
tieText = tourney.ParticipantsPerMatch == 2 ? "Both advance" : "Everyone advances";
break;
case TieType.FullElimination:
tieText = tourney.ParticipantsPerMatch == 2 ? "Both eliminated" : "Everyone eliminated";
break;
}
AddBorderedText(35, y, 190, 20, $"Tiebreaker: {tieText}", LabelColor32, BlackColor32);
y += 20;
string sdText = "Off";
if (tourney.SuddenDeath > TimeSpan.Zero)
{
sdText = $"{(int)tourney.SuddenDeath.TotalMinutes}:{tourney.SuddenDeath.Seconds:D2}";
if (tourney.SuddenDeathRounds > 0)
sdText = $"{sdText} (first {tourney.SuddenDeathRounds} rounds)";
else
sdText = $"{sdText} (all rounds)";
}
AddBorderedText(35, y, 240, 20, $"Sudden Death: {sdText}", LabelColor32, BlackColor32);
y += 20;
y += 6;
AddImageTiled(32, y - 1, 264, 1, 9107);
AddImageTiled(42, y + 1, 264, 1, 9157);
y += 6;
AddBorderedText(35, y, 190, 20, $"Ruleset: {basedef.Title}", LabelColor32, BlackColor32);
y += 20;
for (int i = 0; i < ruleset.Flavors.Count; ++i, y += 18)
AddBorderedText(35, y, 190, 20, $" + {ruleset.Flavors[i].Title}", LabelColor32, BlackColor32);
y += 4;
if (changes > 0)
{
AddBorderedText(35, y, 190, 20, "Modifications:", LabelColor32, BlackColor32);
y += 20;
for (int i = 0; i < opts.Length; ++i)
if (defs[i] != opts[i])
{
string name = ruleset.Layout.FindByIndex(i);
if (name != null) // sanity
{
AddImage(35, y, opts[i] ? 0xD3 : 0xD2);
AddBorderedText(60, y, 165, 22, name, LabelColor32, BlackColor32);
}
y += 22;
}
}
else
{
AddBorderedText(35, y, 190, 20, "Modifications: None", LabelColor32, BlackColor32);
y += 20;
}
#endregion
y += 8;
AddImageTiled(32, y - 1, 264, 1, 9107);
AddImageTiled(42, y + 1, 264, 1, 9157);
y += 8;
AddRadio(24, y, 9727, 9730, true, 1);
AddBorderedText(60, y + 5, 250, 20, "Yes, I will join them.", LabelColor32, BlackColor32);
y += 35;
AddRadio(24, y, 9727, 9730, false, 2);
AddBorderedText(60, y + 5, 250, 20, "No, I do not wish to fight.", LabelColor32, BlackColor32);
y += 35;
AddRadio(24, y, 9727, 9730, false, 3);
AddBorderedText(60, y + 5, 270, 20, "No, most certainly not. Do not ask again.", LabelColor32, BlackColor32);
y += 35;
y -= 3;
AddButton(314, y, 247, 248, 1);
Timer.DelayCall(TimeSpan.FromSeconds(15.0), AutoReject);
}
public string Center(string text)
{
return $"<CENTER>{text}</CENTER>";
}
public string Color(string text, int color)
{
return $"<BASEFONT COLOR=#{color:X6}>{text}</BASEFONT>";
}
private void AddBorderedText(int x, int y, int width, int height, string text, int color, int borderColor)
{
AddColoredText(x - 1, y - 1, width, height, text, borderColor);
AddColoredText(x - 1, y + 1, width, height, text, borderColor);
AddColoredText(x + 1, y - 1, width, height, text, borderColor);
AddColoredText(x + 1, y + 1, width, height, text, borderColor);
AddColoredText(x, y, width, height, text, color);
}
private void AddColoredText(int x, int y, int width, int height, string text, int color)
{
if (color == 0)
AddHtml(x, y, width, height, text);
else
AddHtml(x, y, width, height, Color(text, color));
}
public void AutoReject()
{
if (!m_Active)
return;
m_Active = false;
m_Requested.CloseGump<AcceptTeamGump>();
m_From.SendGump(new ConfirmSignupGump(m_From, m_Registrar, m_Tournament, m_Players));
if (m_Registrar != null)
{
m_Registrar.PrivateOverheadMessage(MessageType.Regular,
0x22, false, $"{m_Requested.Name} seems unresponsive.", m_From.NetState);
m_Registrar.PrivateOverheadMessage(MessageType.Regular,
0x22, false, $"You have declined the partnership with {m_From.Name}.", m_Requested.NetState);
}
}
public override void OnResponse(NetState sender, RelayInfo info)
{
Mobile from = m_From;
Mobile mob = m_Requested;
if (info.ButtonID != 1 || !m_Active)
return;
m_Active = false;
if (info.IsSwitched(1))
{
if (!(mob is PlayerMobile pm))
return;
if (AcceptDuelGump.IsIgnored(mob, from) || mob.Blessed)
{
m_From.SendGump(new ConfirmSignupGump(m_From, m_Registrar, m_Tournament, m_Players));
m_Registrar?.PrivateOverheadMessage(MessageType.Regular,
0x22, false, "They ignore your invitation.", from.NetState);
}
else if (pm.DuelContext != null)
{
m_From.SendGump(new ConfirmSignupGump(m_From, m_Registrar, m_Tournament, m_Players));
m_Registrar?.PrivateOverheadMessage(MessageType.Regular,
0x22, false, "They are already assigned to another duel.", from.NetState);
}
else if (m_Players.Contains(mob))
{
m_From.SendGump(new ConfirmSignupGump(m_From, m_Registrar, m_Tournament, m_Players));
m_Registrar?.PrivateOverheadMessage(MessageType.Regular,
0x22, false, "You have already named them as a team member.", from.NetState);
}
else if (m_Tournament.HasParticipant(mob))
{
m_From.SendGump(new ConfirmSignupGump(m_From, m_Registrar, m_Tournament, m_Players));
m_Registrar?.PrivateOverheadMessage(MessageType.Regular,
0x22, false, "They have already entered this tournament.", from.NetState);
}
else if (m_Players.Count >= m_Tournament.PlayersPerParticipant)
{
m_From.SendGump(new ConfirmSignupGump(m_From, m_Registrar, m_Tournament, m_Players));
m_Registrar?.PrivateOverheadMessage(MessageType.Regular,
0x22, false, "Your team is full.", from.NetState);
}
else
{
m_Players.Add(mob);
m_From.SendGump(new ConfirmSignupGump(m_From, m_Registrar, m_Tournament, m_Players));
if (m_Registrar != null)
{
m_Registrar.PrivateOverheadMessage(MessageType.Regular,
0x59, false, $"{mob.Name} has accepted your offer of partnership.", from.NetState);
m_Registrar.PrivateOverheadMessage(MessageType.Regular,
0x59, false, $"You have accepted the partnership with {from.Name}.", mob.NetState);
}
}
}
else
{
if (info.IsSwitched(3))
AcceptDuelGump.BeginIgnore(m_Requested, m_From);
m_From.SendGump(new ConfirmSignupGump(m_From, m_Registrar, m_Tournament, m_Players));
if (m_Registrar != null)
{
m_Registrar.PrivateOverheadMessage(MessageType.Regular,
0x22, false, $"{mob.Name} has declined your offer of partnership.", from.NetState);
m_Registrar.PrivateOverheadMessage(MessageType.Regular,
0x22, false, $"You have declined the partnership with {from.Name}.", mob.NetState);
}
}
}
}
}

View file

@ -0,0 +1,285 @@
using System.Collections.Generic;
using System.Text;
using Server.Gumps;
using Server.Mobiles;
using Server.Network;
namespace Server.Engines.ConPVP
{
public class ArenasMoongate : Item
{
[Constructible]
public ArenasMoongate() : base(0x1FD4)
{
Movable = false;
Light = LightType.Circle300;
}
public ArenasMoongate(Serial serial) : base(serial)
{
}
public override string DefaultName => "arena moongate";
public override void Serialize(GenericWriter writer)
{
base.Serialize(writer);
writer.Write(0);
}
public override void Deserialize(GenericReader reader)
{
base.Deserialize(reader);
int version = reader.ReadInt();
Light = LightType.Circle300;
}
public bool UseGate(Mobile from)
{
if (DuelContext.CheckCombat(from))
{
from.SendMessage(0x22, "You have recently been in combat with another player and cannot use this moongate.");
return false;
}
if (from.Spell != null)
{
from.SendLocalizedMessage(1049616); // You are too busy to do that at the moment.
return false;
}
from.CloseGump<ArenaGump>();
from.SendGump(new ArenaGump(from, this));
if (!from.Hidden || from.AccessLevel == AccessLevel.Player)
Effects.PlaySound(from.Location, from.Map, 0x20E);
return true;
}
public override void OnDoubleClick(Mobile from)
{
if (from.InRange(GetWorldLocation(), 1))
UseGate(from);
else
from.LocalOverheadMessage(MessageType.Regular, 0x3B2, 1019045); // I can't reach that
}
public override bool OnMoveOver(Mobile m)
{
return !m.Player || UseGate(m);
}
}
public class ArenaGump : Gump
{
private List<Arena> m_Arenas;
private int m_ColumnX = 12;
private Mobile m_From;
private ArenasMoongate m_Gate;
public ArenaGump(Mobile from, ArenasMoongate gate) : base(50, 50)
{
m_From = from;
m_Gate = gate;
m_Arenas = Arena.Arenas;
AddPage(0);
int height = 12 + 20 + m_Arenas.Count * 31 + 24 + 12;
AddBackground(0, 0, 499 + 40, height, 0x2436);
List<Arena> list = m_Arenas;
for (int i = 1; i < list.Count; i += 2)
AddImageTiled(12, 32 + i * 31, 475 + 40, 30, 0x2430);
AddAlphaRegion(10, 10, 479 + 40, height - 20);
AddColumnHeader(35, null);
AddColumnHeader(115, "Arena");
AddColumnHeader(325, "Participants");
AddColumnHeader(40, "Obs");
AddButton(499 + 40 - 12 - 63 - 4 - 63, height - 12 - 24, 247, 248, 1);
AddButton(499 + 40 - 12 - 63, height - 12 - 24, 241, 242, 2);
for (int i = 0; i < list.Count; ++i)
{
Arena ar = list[i];
int x = 12;
int y = 32 + i * 31;
int color = ar.Players.Count > 0 ? 0xCCFFCC : 0xCCCCCC;
AddRadio(x + 3, y + 1, 9727, 9730, false, i);
x += 35;
AddBorderedText(x + 5, y + 5, 115 - 5, ar.Name ?? "(no name)", color, 0);
x += 115;
StringBuilder sb = new StringBuilder();
if (ar.Players.Count > 0)
{
Ladder ladder = Ladder.Instance;
if (ladder == null)
continue;
LadderEntry p1 = null, p2 = null, p3 = null, p4 = null;
for (int j = 0; j < ar.Players.Count; ++j)
{
Mobile mob = ar.Players[j];
LadderEntry c = ladder.Find(mob);
if (p1 == null || c.Index < p1.Index)
{
p4 = p3;
p3 = p2;
p2 = p1;
p1 = c;
}
else if (p2 == null || c.Index < p2.Index)
{
p4 = p3;
p3 = p2;
p2 = c;
}
else if (p3 == null || c.Index < p3.Index)
{
p4 = p3;
p3 = c;
}
else if (p4 == null || c.Index < p4.Index)
{
p4 = c;
}
}
Append(sb, p1);
Append(sb, p2);
Append(sb, p3);
Append(sb, p4);
if (ar.Players.Count > 4)
sb.Append(", ...");
}
else
{
sb.Append("Empty");
}
AddBorderedText(x + 5, y + 5, 325 - 5, sb.ToString(), color, 0);
x += 325;
AddBorderedText(x, y + 5, 40, Center(ar.Spectators.ToString()), color, 0);
}
}
private void Append(StringBuilder sb, LadderEntry le)
{
if (le == null)
return;
if (sb.Length > 0)
sb.Append(", ");
sb.Append(le.Mobile.Name);
}
public override void OnResponse(NetState sender, RelayInfo info)
{
if (info.ButtonID != 1)
return;
int[] switches = info.Switches;
if (switches.Length == 0)
return;
int opt = switches[0];
if (opt < 0 || opt >= m_Arenas.Count)
return;
Arena arena = m_Arenas[opt];
if (!m_From.InRange(m_Gate.GetWorldLocation(), 1) || m_From.Map != m_Gate.Map)
{
m_From.SendLocalizedMessage(1019002); // You are too far away to use the gate.
}
else if (DuelContext.CheckCombat(m_From))
{
m_From.SendMessage(0x22,
"You have recently been in combat with another player and cannot use this moongate.");
}
else if (m_From.Spell != null)
{
m_From.SendLocalizedMessage(1049616); // You are too busy to do that at the moment.
}
else if (m_From.Map == arena.Facet && arena.Zone.Contains(m_From))
{
m_From.SendLocalizedMessage(1019003); // You are already there.
}
else
{
BaseCreature.TeleportPets(m_From, arena.GateIn, arena.Facet);
m_From.Combatant = null;
m_From.Warmode = false;
m_From.Hidden = true;
m_From.MoveToWorld(arena.GateIn, arena.Facet);
Effects.PlaySound(arena.GateIn, arena.Facet, 0x1FE);
}
}
public string Center(string text)
{
return $"<CENTER>{text}</CENTER>";
}
public string Color(string text, int color)
{
return $"<BASEFONT COLOR=#{color:X6}>{text}</BASEFONT>";
}
private void AddBorderedText(int x, int y, int width, string text, int color, int borderColor)
{
/*AddColoredText( x - 1, y, width, text, borderColor );
AddColoredText( x + 1, y, width, text, borderColor );
AddColoredText( x, y - 1, width, text, borderColor );
AddColoredText( x, y + 1, width, text, borderColor );*/
/*AddColoredText( x - 1, y - 1, width, text, borderColor );
AddColoredText( x + 1, y + 1, width, text, borderColor );*/
AddColoredText(x, y, width, text, color);
}
private void AddColoredText(int x, int y, int width, string text, int color)
{
if (color == 0)
AddHtml(x, y, width, 20, text);
else
AddHtml(x, y, width, 20, Color(text, color));
}
private void AddColumnHeader(int width, string name)
{
AddBackground(m_ColumnX, 12, width, 20, 0x242C);
AddImageTiled(m_ColumnX + 2, 14, width - 4, 16, 0x2430);
if (name != null)
AddBorderedText(m_ColumnX, 13, width, Center(name), 0xFFFFFF, 0);
m_ColumnX += width;
}
}
}

View file

@ -0,0 +1,72 @@
using Server.Gumps;
namespace Server.Engines.ConPVP
{
public class BeginGump : Gump
{
private const int LabelColor32 = 0xFFFFFF;
private const int BlackColor32 = 0x000008;
public BeginGump(int count) : base(50, 50)
{
AddPage(0);
const int offset = 50;
AddBackground(1, 1, 398, 202 - offset, 3600);
AddImageTiled(16, 15, 369, 173 - offset, 3604);
AddAlphaRegion(16, 15, 369, 173 - offset);
AddImage(215, -43, 0xEE40);
AddHtml(22 - 1, 22, 294, 20, Color(Center("Duel Countdown"), BlackColor32));
AddHtml(22 + 1, 22, 294, 20, Color(Center("Duel Countdown"), BlackColor32));
AddHtml(22, 22 - 1, 294, 20, Color(Center("Duel Countdown"), BlackColor32));
AddHtml(22, 22 + 1, 294, 20, Color(Center("Duel Countdown"), BlackColor32));
AddHtml(22, 22, 294, 20, Color(Center("Duel Countdown"), LabelColor32));
AddHtml(22 - 1, 50, 294, 80,
Color(
"The arranged duel is about to begin. During this countdown period you may not cast spells and you may not move. This message will close automatically when the period ends.",
BlackColor32));
AddHtml(22 + 1, 50, 294, 80,
Color(
"The arranged duel is about to begin. During this countdown period you may not cast spells and you may not move. This message will close automatically when the period ends.",
BlackColor32));
AddHtml(22, 50 - 1, 294, 80,
Color(
"The arranged duel is about to begin. During this countdown period you may not cast spells and you may not move. This message will close automatically when the period ends.",
BlackColor32));
AddHtml(22, 50 + 1, 294, 80,
Color(
"The arranged duel is about to begin. During this countdown period you may not cast spells and you may not move. This message will close automatically when the period ends.",
BlackColor32));
AddHtml(22, 50, 294, 80,
Color(
"The arranged duel is about to begin. During this countdown period you may not cast spells and you may not move. This message will close automatically when the period ends.",
0xFFCC66));
/*AddImageTiled( 32, 128, 264, 1, 9107 );
AddImageTiled( 42, 130, 264, 1, 9157 );
AddHtml( 60-1, 140, 250, 20, Color( String.Format( "Duel will begin in <BASEFONT COLOR=#{2:X6}>{0} <BASEFONT COLOR=#{2:X6}>second{1}.", count, count==1?"":"s", BlackColor32 ), BlackColor32 ), false, false );
AddHtml( 60+1, 140, 250, 20, Color( String.Format( "Duel will begin in <BASEFONT COLOR=#{2:X6}>{0} <BASEFONT COLOR=#{2:X6}>second{1}.", count, count==1?"":"s", BlackColor32 ), BlackColor32 ), false, false );
AddHtml( 60, 140-1, 250, 20, Color( String.Format( "Duel will begin in <BASEFONT COLOR=#{2:X6}>{0} <BASEFONT COLOR=#{2:X6}>second{1}.", count, count==1?"":"s", BlackColor32 ), BlackColor32 ), false, false );
AddHtml( 60, 140+1, 250, 20, Color( String.Format( "Duel will begin in <BASEFONT COLOR=#{2:X6}>{0} <BASEFONT COLOR=#{2:X6}>second{1}.", count, count==1?"":"s", BlackColor32 ), BlackColor32 ), false, false );
AddHtml( 60, 140, 250, 20, Color( String.Format( "Duel will begin in <BASEFONT COLOR=#FF6666>{0} <BASEFONT COLOR=#{2:X6}>second{1}.", count, count==1?"":"s", 0x66AACC ), 0x66AACC ), false, false );*/
AddButton(314 - 50, 157 - offset, 247, 248, 1);
}
public string Center(string text)
{
return $"<CENTER>{text}</CENTER>";
}
public string Color(string text, int color)
{
return $"<BASEFONT COLOR=#{color:X6}>{text}</BASEFONT>";
}
}
}

View file

@ -0,0 +1,566 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using Server.Factions;
using Server.Gumps;
using Server.Mobiles;
using Server.Network;
using Server.Targeting;
namespace Server.Engines.ConPVP
{
public class ConfirmSignupGump : Gump
{
private const int BlackColor32 = 0x000008;
private const int LabelColor32 = 0xFFFFFF;
private Mobile m_From;
private List<Mobile> m_Players;
private Mobile m_Registrar;
private Tournament m_Tournament;
public ConfirmSignupGump(Mobile from, Mobile registrar, Tournament tourney, List<Mobile> players) : base(50, 50)
{
m_From = from;
m_Registrar = registrar;
m_Tournament = tourney;
m_Players = players;
m_From.CloseGump<AcceptTeamGump>();
m_From.CloseGump<AcceptDuelGump>();
m_From.CloseGump<DuelContextGump>();
m_From.CloseGump<ConfirmSignupGump>();
#region Rules
Ruleset ruleset = tourney.Ruleset;
Ruleset basedef = ruleset.Base;
int height = 185 + 60 + 12;
int changes = 0;
BitArray defs;
if (ruleset.Flavors.Count > 0)
{
defs = new BitArray(basedef.Options);
for (int i = 0; i < ruleset.Flavors.Count; ++i)
defs.Or(ruleset.Flavors[i].Options);
height += ruleset.Flavors.Count * 18;
}
else
{
defs = basedef.Options;
}
BitArray opts = ruleset.Options;
for (int i = 0; i < opts.Length; ++i)
if (defs[i] != opts[i])
++changes;
height += changes * 22;
height += 10 + 22 + 25 + 25;
if (tourney.PlayersPerParticipant > 1)
height += 36 + tourney.PlayersPerParticipant * 20;
#endregion
Closable = false;
AddPage(0);
//AddBackground( 0, 0, 400, 220, 9150 );
AddBackground(1, 1, 398, height, 3600);
//AddBackground( 16, 15, 369, 189, 9100 );
AddImageTiled(16, 15, 369, height - 29, 3604);
AddAlphaRegion(16, 15, 369, height - 29);
AddImage(215, -43, 0xEE40);
//AddImage( 330, 141, 0x8BA );
StringBuilder sb = new StringBuilder();
if (tourney.TourneyType == TourneyType.FreeForAll)
{
sb.Append("FFA");
}
else if (tourney.TourneyType == TourneyType.RandomTeam)
{
sb.Append(tourney.ParticipantsPerMatch);
sb.Append("-Team");
}
else if (tourney.TourneyType == TourneyType.Faction)
{
sb.Append(tourney.ParticipantsPerMatch);
sb.Append("-Team Faction");
}
else if (tourney.TourneyType == TourneyType.RedVsBlue)
{
sb.Append("Red v Blue");
}
else
{
for (int i = 0; i < tourney.ParticipantsPerMatch; ++i)
{
if (sb.Length > 0)
sb.Append('v');
sb.Append(tourney.PlayersPerParticipant);
}
}
if (tourney.EventController != null)
sb.Append(' ').Append(tourney.EventController.Title);
sb.Append(" Tournament Signup");
AddBorderedText(22, 22, 294, 20, Center(sb.ToString()), LabelColor32, BlackColor32);
AddBorderedText(22, 50, 294, 40, "You have requested to join the tournament. Do you accept the rules?", 0xB0C868,
BlackColor32);
AddImageTiled(32, 88, 264, 1, 9107);
AddImageTiled(42, 90, 264, 1, 9157);
#region Rules
int y = 100;
string groupText = null;
switch (tourney.GroupType)
{
case GroupingType.HighVsLow:
groupText = "High vs Low";
break;
case GroupingType.Nearest:
groupText = "Closest opponent";
break;
case GroupingType.Random:
groupText = "Random";
break;
}
AddBorderedText(35, y, 190, 20, $"Grouping: {groupText}", LabelColor32, BlackColor32);
y += 20;
string tieText = null;
switch (tourney.TieType)
{
case TieType.Random:
tieText = "Random";
break;
case TieType.Highest:
tieText = "Highest advances";
break;
case TieType.Lowest:
tieText = "Lowest advances";
break;
case TieType.FullAdvancement:
tieText = tourney.ParticipantsPerMatch == 2 ? "Both advance" : "Everyone advances";
break;
case TieType.FullElimination:
tieText = tourney.ParticipantsPerMatch == 2 ? "Both eliminated" : "Everyone eliminated";
break;
}
AddBorderedText(35, y, 190, 20, $"Tiebreaker: {tieText}", LabelColor32, BlackColor32);
y += 20;
string sdText = "Off";
if (tourney.SuddenDeath > TimeSpan.Zero)
{
sdText = $"{(int)tourney.SuddenDeath.TotalMinutes}:{tourney.SuddenDeath.Seconds:D2}";
if (tourney.SuddenDeathRounds > 0)
sdText = $"{sdText} (first {tourney.SuddenDeathRounds} rounds)";
else
sdText = $"{sdText} (all rounds)";
}
AddBorderedText(35, y, 240, 20, $"Sudden Death: {sdText}", LabelColor32, BlackColor32);
y += 20;
y += 6;
AddImageTiled(32, y - 1, 264, 1, 9107);
AddImageTiled(42, y + 1, 264, 1, 9157);
y += 6;
AddBorderedText(35, y, 190, 20, $"Ruleset: {basedef.Title}", LabelColor32, BlackColor32);
y += 20;
for (int i = 0; i < ruleset.Flavors.Count; ++i, y += 18)
AddBorderedText(35, y, 190, 20, $" + {ruleset.Flavors[i].Title}", LabelColor32, BlackColor32);
y += 4;
if (changes > 0)
{
AddBorderedText(35, y, 190, 20, "Modifications:", LabelColor32, BlackColor32);
y += 20;
for (int i = 0; i < opts.Length; ++i)
if (defs[i] != opts[i])
{
string name = ruleset.Layout.FindByIndex(i);
if (name != null) // sanity
{
AddImage(35, y, opts[i] ? 0xD3 : 0xD2);
AddBorderedText(60, y, 165, 22, name, LabelColor32, BlackColor32);
}
y += 22;
}
}
else
{
AddBorderedText(35, y, 190, 20, "Modifications: None", LabelColor32, BlackColor32);
y += 20;
}
#endregion
#region Team
if (tourney.PlayersPerParticipant > 1)
{
y += 8;
AddImageTiled(32, y - 1, 264, 1, 9107);
AddImageTiled(42, y + 1, 264, 1, 9157);
y += 8;
AddBorderedText(35, y, 190, 20, "Your Team", LabelColor32, BlackColor32);
y += 20;
for (int i = 0; i < players.Count; ++i, y += 20)
{
if (i == 0)
AddImage(35, y, 0xD2);
else
AddGoldenButton(35, y, 1 + i);
AddBorderedText(60, y, 200, 20, players[i].Name, LabelColor32, BlackColor32);
}
for (int i = players.Count; i < tourney.PlayersPerParticipant; ++i, y += 20)
{
if (i == 0)
AddImage(35, y, 0xD2);
else
AddGoldenButton(35, y, 1 + i);
AddBorderedText(60, y, 200, 20, "(Empty)", LabelColor32, BlackColor32);
}
}
#endregion
y += 8;
AddImageTiled(32, y - 1, 264, 1, 9107);
AddImageTiled(42, y + 1, 264, 1, 9157);
y += 8;
AddRadio(24, y, 9727, 9730, true, 1);
AddBorderedText(60, y + 5, 250, 20, "Yes, I wish to join the tournament.", LabelColor32, BlackColor32);
y += 35;
AddRadio(24, y, 9727, 9730, false, 2);
AddBorderedText(60, y + 5, 250, 20, "No, I do not wish to join.", LabelColor32, BlackColor32);
y += 35;
y -= 3;
AddButton(314, y, 247, 248, 1);
}
public string Center(string text)
{
return $"<CENTER>{text}</CENTER>";
}
public string Color(string text, int color)
{
return $"<BASEFONT COLOR=#{color:X6}>{text}</BASEFONT>";
}
private void AddBorderedText(int x, int y, int width, int height, string text, int color, int borderColor)
{
AddColoredText(x - 1, y - 1, width, height, text, borderColor);
AddColoredText(x - 1, y + 1, width, height, text, borderColor);
AddColoredText(x + 1, y - 1, width, height, text, borderColor);
AddColoredText(x + 1, y + 1, width, height, text, borderColor);
AddColoredText(x, y, width, height, text, color);
}
private void AddColoredText(int x, int y, int width, int height, string text, int color)
{
if (color == 0)
AddHtml(x, y, width, height, text);
else
AddHtml(x, y, width, height, Color(text, color));
}
public void AddGoldenButton(int x, int y, int bid)
{
AddButton(x, y, 0xD2, 0xD2, bid);
AddButton(x + 3, y + 3, 0xD8, 0xD8, bid);
}
public override void OnResponse(NetState sender, RelayInfo info)
{
if (info.ButtonID == 1 && info.IsSwitched(1))
{
Tournament tourney = m_Tournament;
Mobile from = m_From;
switch (tourney.Stage)
{
case TournamentStage.Fighting:
{
if (m_Registrar != null)
{
if (m_Tournament.HasParticipant(from))
m_Registrar.PrivateOverheadMessage(MessageType.Regular,
0x35, false, "Excuse me? You are already signed up.", from.NetState);
else
m_Registrar.PrivateOverheadMessage(MessageType.Regular,
0x22, false, "The tournament has already begun. You are too late to signup now.",
from.NetState);
}
break;
}
case TournamentStage.Inactive:
{
m_Registrar?.PrivateOverheadMessage(MessageType.Regular,
0x35, false, "The tournament is closed.", from.NetState);
break;
}
case TournamentStage.Signup:
{
if (m_Players.Count != tourney.PlayersPerParticipant)
{
m_Registrar?.PrivateOverheadMessage(MessageType.Regular,
0x35, false, "You have not yet chosen your team.", from.NetState);
m_From.SendGump(new ConfirmSignupGump(m_From, m_Registrar, m_Tournament, m_Players));
break;
}
Ladder ladder = Ladder.Instance;
for (int i = 0; i < m_Players.Count; ++i)
{
Mobile mob = m_Players[i];
LadderEntry entry = ladder?.Find(mob);
if (entry != null && Ladder.GetLevel(entry.Experience) < tourney.LevelRequirement)
{
if (m_Registrar != null)
{
if (mob == from)
m_Registrar.PrivateOverheadMessage(MessageType.Regular,
0x35, false, "You have not yet proven yourself a worthy dueler.", from.NetState);
else
m_Registrar.PrivateOverheadMessage(MessageType.Regular,
0x35, false, $"{mob.Name} has not yet proven themselves a worthy dueler.",
from.NetState);
}
m_From.SendGump(new ConfirmSignupGump(m_From, m_Registrar, m_Tournament, m_Players));
return;
}
if (tourney.IsFactionRestricted && Faction.Find(mob) == null)
{
m_Registrar?.PrivateOverheadMessage(MessageType.Regular,
0x35, false, "Only those who have declared their faction allegiance may participate.",
from.NetState);
m_From.SendGump(new ConfirmSignupGump(m_From, m_Registrar, m_Tournament, m_Players));
return;
}
if (tourney.HasParticipant(mob))
{
if (m_Registrar != null)
{
if (mob == from)
m_Registrar.PrivateOverheadMessage(MessageType.Regular,
0x35, false, "You have already entered this tournament.", from.NetState);
else
m_Registrar.PrivateOverheadMessage(MessageType.Regular,
0x35, false, $"{mob.Name} has already entered this tournament.", from.NetState);
}
m_From.SendGump(new ConfirmSignupGump(m_From, m_Registrar, m_Tournament, m_Players));
return;
}
if (mob is PlayerMobile mobile && mobile.DuelContext != null)
{
if (mob == from)
m_Registrar?.PrivateOverheadMessage(MessageType.Regular,
0x35, false,
"You are already assigned to a duel. You must yield it before joining this tournament.",
from.NetState);
else
m_Registrar?.PrivateOverheadMessage(MessageType.Regular,
0x35, false,
$"{mobile.Name} is already assigned to a duel. They must yield it before joining this tournament.",
from.NetState);
m_From.SendGump(new ConfirmSignupGump(m_From, m_Registrar, m_Tournament, m_Players));
return;
}
}
if (m_Registrar != null)
{
string fmt;
if (tourney.PlayersPerParticipant == 1)
fmt =
"As you say m'{0}. I've written your name to the bracket. The tournament will begin {1}.";
else if (tourney.PlayersPerParticipant == 2)
fmt =
"As you wish m'{0}. The tournament will begin {1}, but first you must name your partner.";
else
fmt = "As you wish m'{0}. The tournament will begin {1}, but first you must name your team.";
string timeUntil;
int minutesUntil = (int)Math.Round((tourney.SignupStart + tourney.SignupPeriod - DateTime.UtcNow)
.TotalMinutes);
if (minutesUntil == 0)
timeUntil = "momentarily";
else
timeUntil = $"in {minutesUntil} minute{(minutesUntil == 1 ? "" : "s")}";
m_Registrar.PrivateOverheadMessage(MessageType.Regular,
0x35, false, string.Format(fmt, from.Female ? "Lady" : "Lord", timeUntil), from.NetState);
}
TourneyParticipant part = new TourneyParticipant(from);
part.Players.Clear();
part.Players.AddRange(m_Players);
tourney.Participants.Add(part);
break;
}
}
}
else if (info.ButtonID > 1)
{
int index = info.ButtonID - 1;
if (index > 0 && index < m_Players.Count)
{
m_Players.RemoveAt(index);
m_From.SendGump(new ConfirmSignupGump(m_From, m_Registrar, m_Tournament, m_Players));
}
else if (m_Players.Count < m_Tournament.PlayersPerParticipant)
{
m_From.BeginTarget(12, false, TargetFlags.None, AddPlayer_OnTarget);
m_From.SendGump(new ConfirmSignupGump(m_From, m_Registrar, m_Tournament, m_Players));
}
}
}
private void AddPlayer_OnTarget(Mobile from, object obj)
{
if (!(obj is Mobile mob) || mob == from)
{
m_From.SendGump(new ConfirmSignupGump(m_From, m_Registrar, m_Tournament, m_Players));
m_Registrar?.PrivateOverheadMessage(MessageType.Regular,
0x22, false, "Excuse me?", from.NetState);
}
else if (!mob.Player)
{
m_From.SendGump(new ConfirmSignupGump(m_From, m_Registrar, m_Tournament, m_Players));
if (mob.Body.IsHuman)
mob.SayTo(from, 1005443); // Nay, I would rather stay here and watch a nail rust.
else
mob.SayTo(from, 1005444); // The creature ignores your offer.
}
else if (AcceptDuelGump.IsIgnored(mob, from) || mob.Blessed)
{
m_From.SendGump(new ConfirmSignupGump(m_From, m_Registrar, m_Tournament, m_Players));
m_Registrar?.PrivateOverheadMessage(MessageType.Regular,
0x22, false, "They ignore your invitation.", from.NetState);
}
else
{
if (!(mob is PlayerMobile pm))
return;
if (pm.DuelContext != null)
{
m_From.SendGump(new ConfirmSignupGump(m_From, m_Registrar, m_Tournament, m_Players));
m_Registrar?.PrivateOverheadMessage(MessageType.Regular,
0x22, false, "They are already assigned to another duel.", from.NetState);
}
else if (mob.HasGump<AcceptTeamGump>())
{
m_From.SendGump(new ConfirmSignupGump(m_From, m_Registrar, m_Tournament, m_Players));
m_Registrar?.PrivateOverheadMessage(MessageType.Regular,
0x22, false, "They have already been offered a partnership.", from.NetState);
}
else if (mob.HasGump<ConfirmSignupGump>())
{
m_From.SendGump(new ConfirmSignupGump(m_From, m_Registrar, m_Tournament, m_Players));
m_Registrar?.PrivateOverheadMessage(MessageType.Regular,
0x22, false, "They are already trying to join this tournament.", from.NetState);
}
else if (m_Players.Contains(mob))
{
m_From.SendGump(new ConfirmSignupGump(m_From, m_Registrar, m_Tournament, m_Players));
m_Registrar?.PrivateOverheadMessage(MessageType.Regular,
0x22, false, "You have already named them as a team member.", from.NetState);
}
else if (m_Tournament.HasParticipant(mob))
{
m_From.SendGump(new ConfirmSignupGump(m_From, m_Registrar, m_Tournament, m_Players));
m_Registrar?.PrivateOverheadMessage(MessageType.Regular,
0x22, false, "They have already entered this tournament.", from.NetState);
}
else if (m_Players.Count >= m_Tournament.PlayersPerParticipant)
{
m_From.SendGump(new ConfirmSignupGump(m_From, m_Registrar, m_Tournament, m_Players));
m_Registrar?.PrivateOverheadMessage(MessageType.Regular,
0x22, false, "Your team is full.", from.NetState);
}
else
{
m_From.SendGump(new ConfirmSignupGump(m_From, m_Registrar, m_Tournament, m_Players));
mob.SendGump(new AcceptTeamGump(from, mob, m_Tournament, m_Registrar, m_Players));
m_Registrar?.PrivateOverheadMessage(MessageType.Regular,
0x59, false,
$"As you command m'{(from.Female ? "Lady" : "Lord")}. I've given your offer to {mob.Name}.",
from.NetState);
}
}
}
}
}

View file

@ -0,0 +1,139 @@
using Server.Gumps;
using Server.Network;
namespace Server.Engines.ConPVP
{
public class DuelContextGump : Gump
{
public DuelContextGump(Mobile from, DuelContext context) : base(50, 50)
{
From = from;
Context = context;
from.CloseGump<RulesetGump>();
from.CloseGump<DuelContextGump>();
from.CloseGump<ParticipantGump>();
int count = context.Participants.Count;
if (count < 3)
count = 3;
int height = 35 + 10 + 22 + 30 + 22 + 22 + 2 + count * 22 + 2 + 30;
AddPage(0);
AddBackground(0, 0, 300, height, 9250);
AddBackground(10, 10, 280, height - 20, 0xDAC);
AddHtml(35, 25, 230, 20, Center("Duel Setup"));
int x = 35;
int y = 47;
AddGoldenButtonLabeled(x, y, 1, "Rules");
y += 22;
AddGoldenButtonLabeled(x, y, 2, "Start");
y += 22;
AddGoldenButtonLabeled(x, y, 3, "Add Participant");
y += 30;
AddHtml(35, y, 230, 20, Center("Participants"));
y += 22;
for (int i = 0; i < context.Participants.Count; ++i)
{
Participant p = context.Participants[i];
AddGoldenButtonLabeled(x, y, 4 + i,
string.Format(p.Count == 1 ? "Player {0}: {3}" : "Team {0}: {1}/{2}: {3}", 1 + i, p.FilledSlots, p.Count,
p.NameList));
y += 22;
}
}
public Mobile From{ get; }
public DuelContext Context{ get; }
public string Center(string text)
{
return $"<CENTER>{text}</CENTER>";
}
public void AddGoldenButton(int x, int y, int bid)
{
AddButton(x, y, 0xD2, 0xD2, bid);
AddButton(x + 3, y + 3, 0xD8, 0xD8, bid);
}
public void AddGoldenButtonLabeled(int x, int y, int bid, string text)
{
AddGoldenButton(x, y, bid);
AddHtml(x + 25, y, 200, 20, text);
}
public override void OnResponse(NetState sender, RelayInfo info)
{
if (!Context.Registered)
return;
int index = info.ButtonID;
switch (index)
{
case -1: // CloseGump
{
break;
}
case 0: // closed
{
Context.Unregister();
break;
}
case 1: // Rules
{
//m_From.SendGump( new RulesetGump( m_From, m_Context.Ruleset, m_Context.Ruleset.Layout, m_Context ) );
From.SendGump(new PickRulesetGump(From, Context, Context.Ruleset));
break;
}
case 2: // Start
{
if (Context.CheckFull())
{
Context.CloseAllGumps();
Context.SendReadyUpGump();
//m_Context.SendReadyGump();
}
else
{
From.SendMessage("You cannot start the duel before all participating players have been assigned.");
From.SendGump(new DuelContextGump(From, Context));
}
break;
}
case 3: // New Participant
{
if (Context.Participants.Count < 10)
Context.Participants.Add(new Participant(Context, 1));
else
From.SendMessage("The number of participating parties may not be increased further.");
From.SendGump(new DuelContextGump(From, Context));
break;
}
default: // Participant
{
index -= 4;
if (index >= 0 && index < Context.Participants.Count)
From.SendGump(new ParticipantGump(From, Context, Context.Participants[index]));
break;
}
}
}
}
}

View file

@ -0,0 +1,248 @@
using System;
using System.Collections.Generic;
using Server.Gumps;
using Server.Network;
namespace Server.Engines.ConPVP
{
public class LadderItem : Item
{
[Constructible]
public LadderItem() : base(0x117F)
{
Movable = false;
}
public LadderItem(Serial serial) : base(serial)
{
}
[CommandProperty(AccessLevel.GameMaster)]
public LadderController Ladder{ get; set; }
public override string DefaultName => "1v1 leaderboard";
public override void Serialize(GenericWriter writer)
{
base.Serialize(writer);
writer.Write(1);
writer.Write(Ladder);
}
public override void Deserialize(GenericReader reader)
{
base.Deserialize(reader);
int version = reader.ReadInt();
switch (version)
{
case 1:
{
Ladder = reader.ReadItem<LadderController>();
break;
}
}
}
public override void OnDoubleClick(Mobile from)
{
if (from.InRange(GetWorldLocation(), 2))
{
Ladder ladder = ConPVP.Ladder.Instance ?? Ladder.Ladder;
if (ladder != null)
{
from.CloseGump<LadderGump>();
from.SendGump(new LadderGump(ladder));
}
}
else
{
from.LocalOverheadMessage(MessageType.Regular, 0x3B2, 1019045); // I can't reach that
}
}
}
public class LadderGump : Gump
{
private int m_ColumnX = 12;
private Ladder m_Ladder;
private List<LadderEntry> m_List;
private int m_Page;
public LadderGump(Ladder ladder, int page = 0) : base(50, 50)
{
m_Ladder = ladder;
m_Page = page;
AddPage(0);
m_List = new List<LadderEntry>(ladder.Entries);
int lc = Math.Min(m_List.Count, 150);
int start = page * 15;
int end = start + 15;
if (end > lc)
end = lc;
int ct = end - start;
int height = 12 + 20 + ct * 20 + 23 + 12;
AddBackground(0, 0, 499, height, 0x2436);
for (int i = start + 1; i < end; i += 2)
AddImageTiled(12, 32 + (i - start) * 20, 475, 20, 0x2430);
AddAlphaRegion(10, 10, 479, height - 20);
if (page > 0)
AddButton(446, height - 12 - 2 - 16, 0x15E3, 0x15E7, 1);
else
AddImage(446, height - 12 - 2 - 16, 0x2626);
if ((page + 1) * 15 < lc)
AddButton(466, height - 12 - 2 - 16, 0x15E1, 0x15E5, 2);
else
AddImage(466, height - 12 - 2 - 16, 0x2622);
AddHtml(16, height - 12 - 2 - 18, 400, 20,
Color(string.Format("Top {3} of {0:N0} duelists, page {1} of {2}", m_List.Count, page + 1, (lc + 14) / 15, lc),
0xFFC000));
AddColumnHeader(75, "Rank");
AddColumnHeader(115, "Level");
AddColumnHeader(50, "Guild");
AddColumnHeader(115, "Name");
AddColumnHeader(60, "Wins");
AddColumnHeader(60, "Losses");
for (int i = start; i < end && i < lc; ++i)
{
LadderEntry entry = m_List[i];
int y = 32 + (i - start) * 20;
int x = 12;
AddBorderedText(x, y, 75, Center(Rank(i + 1)), 0xFFFFFF, 0);
x += 75;
/*AddImage( 20, y + 5, 0x2616, 0x96C );
AddImage( 22, y + 5, 0x2616, 0x96C );
AddImage( 20, y + 7, 0x2616, 0x96C );
AddImage( 22, y + 7, 0x2616, 0x96C );
AddImage( 21, y + 6, 0x2616, 0x454 );*/
AddImage(x + 3, y + 4, 0x805);
int xp = entry.Experience;
int level = Ladder.GetLevel(xp);
Ladder.GetLevelInfo(level, out int xpBase, out int xpAdvance);
int width;
int xpOffset = xp - xpBase;
if (xpOffset >= xpAdvance)
width = 109; // level 50
else
width = (109 * xpOffset + xpAdvance / 2) / (xpAdvance - 1);
//AddImageTiled( 21, y + 6, width, 8, 0x2617 );
AddImageTiled(x + 3, y + 4, width, 11, 0x806);
AddBorderedText(x, y, 115, Center(level.ToString()), 0xFFFFFF, 0);
x += 115;
Mobile mob = entry.Mobile;
if (mob.Guild != null)
AddBorderedText(x, y, 50, Center(mob.Guild.Abbreviation), 0xFFFFFF, 0);
x += 50;
AddBorderedText(x + 5, y, 115 - 5, mob.Name, 0xFFFFFF, 0);
x += 115;
AddBorderedText(x, y, 60, Center(entry.Wins.ToString()), 0xFFFFFF, 0);
x += 60;
AddBorderedText(x, y, 60, Center(entry.Losses.ToString()), 0xFFFFFF, 0);
x += 60;
//AddBorderedText( 292 + 15, y, 115 - 30, String.Format( "{0} <DIV ALIGN=CENTER>/</DIV> <DIV ALIGN=RIGHT>{1}</DIV>", entry.Wins, entry.Losses ), 0xFFC000, 0 );
}
}
public static string Rank(int num)
{
string numStr = num.ToString("N0");
if (num % 100 > 10 && num % 100 < 20)
return numStr + "th";
switch (num % 10)
{
case 1: return numStr + "st";
case 2: return numStr + "nd";
case 3: return numStr + "rd";
default: return numStr + "th";
}
}
public override void OnResponse(NetState sender, RelayInfo info)
{
Mobile from = sender.Mobile;
if (info.ButtonID == 1 && m_Page > 0)
from.SendGump(new LadderGump(m_Ladder, m_Page - 1));
else if (info.ButtonID == 2 && (m_Page + 1) * 15 < Math.Min(m_List.Count, 150))
from.SendGump(new LadderGump(m_Ladder, m_Page + 1));
}
public string Center(string text)
{
return $"<CENTER>{text}</CENTER>";
}
public string Color(string text, int color)
{
return $"<BASEFONT COLOR=#{color:X6}>{text}</BASEFONT>";
}
private void AddBorderedText(int x, int y, int width, string text, int color, int borderColor)
{
/*AddColoredText( x - 1, y, width, text, borderColor );
AddColoredText( x + 1, y, width, text, borderColor );
AddColoredText( x, y - 1, width, text, borderColor );
AddColoredText( x, y + 1, width, text, borderColor );*/
/*AddColoredText( x - 1, y - 1, width, text, borderColor );
AddColoredText( x + 1, y + 1, width, text, borderColor );*/
AddColoredText(x, y, width, text, color);
}
private void AddColoredText(int x, int y, int width, string text, int color)
{
if (color == 0)
AddHtml(x, y, width, 20, text);
else
AddHtml(x, y, width, 20, Color(text, color));
}
private void AddColumnHeader(int width, string name)
{
AddBackground(m_ColumnX, 12, width, 20, 0x242C);
AddImageTiled(m_ColumnX + 2, 14, width - 4, 16, 0x2430);
AddBorderedText(m_ColumnX, 13, width, Center(name), 0xFFFFFF, 0);
m_ColumnX += width;
}
}
}

View file

@ -0,0 +1,253 @@
using Server.Gumps;
using Server.Mobiles;
using Server.Network;
using Server.Targeting;
namespace Server.Engines.ConPVP
{
public class ParticipantGump : Gump
{
public ParticipantGump(Mobile from, DuelContext context, Participant p) : base(50, 50)
{
From = from;
Context = context;
Participant = p;
from.CloseGump<RulesetGump>();
from.CloseGump<DuelContextGump>();
from.CloseGump<ParticipantGump>();
int count = p.Players.Length;
if (count < 4)
count = 4;
AddPage(0);
int height = 35 + 10 + 22 + 22 + 30 + 22 + 2 + count * 22 + 2 + 30;
AddBackground(0, 0, 300, height, 9250);
AddBackground(10, 10, 280, height - 20, 0xDAC);
AddButton(240, 25, 0xFB1, 0xFB3, 3);
//AddButton( 223, 54, 0x265A, 0x265A, 4, );
AddHtml(35, 25, 230, 20, Center("Participant Setup"));
int x = 35;
int y = 47;
AddHtml(x, y, 200, 20, $"Team Size: {p.Players.Length}");
y += 22;
AddGoldenButtonLabeled(x + 20, y, 1, "Increase");
y += 22;
AddGoldenButtonLabeled(x + 20, y, 2, "Decrease");
y += 30;
AddHtml(35, y, 230, 20, Center("Players"));
y += 22;
for (int i = 0; i < p.Players.Length; ++i)
{
DuelPlayer pl = p.Players[i];
AddGoldenButtonLabeled(x, y, 5 + i, $"{1 + i}: {(pl == null ? "Empty" : pl.Mobile.Name)}");
y += 22;
}
}
public Mobile From{ get; }
public DuelContext Context{ get; }
public Participant Participant{ get; }
public string Center(string text)
{
return $"<CENTER>{text}</CENTER>";
}
public void AddGoldenButton(int x, int y, int bid)
{
AddButton(x, y, 0xD2, 0xD2, bid);
AddButton(x + 3, y + 3, 0xD8, 0xD8, bid);
}
public void AddGoldenButtonLabeled(int x, int y, int bid, string text)
{
AddGoldenButton(x, y, bid);
AddHtml(x + 25, y, 200, 20, text);
}
public override void OnResponse(NetState sender, RelayInfo info)
{
if (!Context.Registered)
return;
int bid = info.ButtonID;
if (bid == 0)
{
From.SendGump(new DuelContextGump(From, Context));
}
else if (bid == 1)
{
if (Participant.Count < 8)
Participant.Resize(Participant.Count + 1);
else
From.SendMessage("You may not raise the team size any further.");
From.SendGump(new ParticipantGump(From, Context, Participant));
}
else if (bid == 2)
{
if (Participant.Count > 1 && Participant.Count > Participant.FilledSlots)
Participant.Resize(Participant.Count - 1);
else
From.SendMessage("You may not lower the team size any further.");
From.SendGump(new ParticipantGump(From, Context, Participant));
}
else if (bid == 3)
{
if (Participant.FilledSlots > 0)
{
From.SendMessage("There is at least one currently active player. You must remove them first.");
From.SendGump(new ParticipantGump(From, Context, Participant));
}
else if (Context.Participants.Count > 2)
{
/*Container cont = m_Participant.Stakes;
if ( cont != null )
cont.Delete();*/
Context.Participants.Remove(Participant);
From.SendGump(new DuelContextGump(From, Context));
}
else
{
From.SendMessage("Duels must have at least two participating parties.");
From.SendGump(new ParticipantGump(From, Context, Participant));
}
}
/*else if ( bid == 4 )
{
m_From.SendGump( new ParticipantGump( m_From, m_Context, m_Participant ) );
Container cont = m_Participant.Stakes;
if ( cont != null && !cont.Deleted )
{
cont.DisplayTo( m_From );
Item[] checks = cont.FindItemsByType( typeof( BankCheck ) );
int gold = cont.TotalGold;
for ( int i = 0; i < checks.Length; ++i )
gold += ((BankCheck)checks[i]).Worth;
m_From.SendMessage( "This container has {0} item{1} and {2} stone{3}. In gold or check form there is a total of {4:D}gp.", cont.TotalItems, cont.TotalItems==1?"":"s", cont.TotalWeight, cont.TotalWeight==1?"":"s", gold );
}
}*/
else
{
bid -= 5;
if (bid >= 0 && bid < Participant.Players.Length)
{
if (Participant.Players[bid] == null)
{
From.Target = new ParticipantTarget(Context, Participant, bid);
From.SendMessage("Target a player.");
}
else
{
Participant.Players[bid].Mobile.SendMessage("You have been removed from the duel.");
if (Participant.Players[bid].Mobile is PlayerMobile)
((PlayerMobile)Participant.Players[bid].Mobile).DuelPlayer = null;
Participant.Players[bid] = null;
From.SendMessage("They have been removed from the duel.");
From.SendGump(new ParticipantGump(From, Context, Participant));
}
}
}
}
private class ParticipantTarget : Target
{
private DuelContext m_Context;
private int m_Index;
private Participant m_Participant;
public ParticipantTarget(DuelContext context, Participant p, int index) : base(12, false, TargetFlags.None)
{
m_Context = context;
m_Participant = p;
m_Index = index;
}
protected override void OnTarget(Mobile from, object targeted)
{
if (!m_Context.Registered)
return;
int index = m_Index;
if (index < 0 || index >= m_Participant.Players.Length)
return;
if (!(targeted is Mobile mob))
{
from.SendMessage("That is not a player.");
}
else if (!mob.Player)
{
if (mob.Body.IsHuman)
mob.SayTo(from, 1005443); // Nay, I would rather stay here and watch a nail rust.
else
mob.SayTo(from, 1005444); // The creature ignores your offer.
}
else if (AcceptDuelGump.IsIgnored(mob, from) || mob.Blessed)
{
from.SendMessage("They ignore your offer.");
}
else
{
if (!(mob is PlayerMobile pm))
return;
if (pm.DuelContext != null)
{
from.SendMessage("{0} cannot fight because they are already assigned to another duel.", pm.Name);
}
else if (DuelContext.CheckCombat(pm))
{
from.SendMessage("{0} cannot fight because they have recently been in combat with another player.",
pm.Name);
}
else if (mob.HasGump<AcceptDuelGump>())
{
from.SendMessage("{0} has already been offered a duel.");
}
else
{
from.SendMessage("You send {0} to {1}.",
m_Participant.Find(from) == null ? "a challenge" : "an invitation", mob.Name);
mob.SendGump(new AcceptDuelGump(from, mob, m_Context, m_Participant, m_Index));
}
}
}
protected override void OnTargetFinish(Mobile from)
{
from.SendGump(new ParticipantGump(from, m_Context, m_Participant));
}
}
}
}

View file

@ -0,0 +1,126 @@
using Server.Gumps;
using Server.Network;
namespace Server.Engines.ConPVP
{
public class PickRulesetGump : Gump
{
private DuelContext m_Context;
private Ruleset[] m_Defaults;
private Ruleset[] m_Flavors;
private Mobile m_From;
private Ruleset m_Ruleset;
public PickRulesetGump(Mobile from, DuelContext context, Ruleset ruleset) : base(50, 50)
{
m_From = from;
m_Context = context;
m_Ruleset = ruleset;
m_Defaults = ruleset.Layout.Defaults;
m_Flavors = ruleset.Layout.Flavors;
int height = 25 + 20 + (m_Defaults.Length + 1) * 22 + 6 + 20 + m_Flavors.Length * 22 + 25;
AddPage(0);
AddBackground(0, 0, 260, height, 9250);
AddBackground(10, 10, 240, height - 20, 0xDAC);
AddHtml(35, 25, 190, 20, Center("Rules"));
int y = 25 + 20;
for (int i = 0; i < m_Defaults.Length; ++i)
{
Ruleset cur = m_Defaults[i];
AddHtml(35 + 14, y, 176, 20, cur.Title);
if (ruleset.Base == cur && !ruleset.Changed)
AddImage(35, y + 4, 0x939);
else if (ruleset.Base == cur)
AddButton(35, y + 4, 0x93A, 0x939, 2 + i);
else
AddButton(35, y + 4, 0x938, 0x939, 2 + i);
y += 22;
}
AddHtml(35 + 14, y, 176, 20, "Custom");
AddButton(35, y + 4, ruleset.Changed ? 0x939 : 0x938, 0x939, 1);
y += 22;
y += 6;
AddHtml(35, y, 190, 20, Center("Flavors"));
y += 20;
for (int i = 0; i < m_Flavors.Length; ++i)
{
Ruleset cur = m_Flavors[i];
AddHtml(35 + 14, y, 176, 20, cur.Title);
if (ruleset.Flavors.Contains(cur))
AddButton(35, y + 4, 0x939, 0x938, 2 + m_Defaults.Length + i);
else
AddButton(35, y + 4, 0x938, 0x939, 2 + m_Defaults.Length + i);
y += 22;
}
}
public string Center(string text)
{
return $"<CENTER>{text}</CENTER>";
}
public override void OnResponse(NetState sender, RelayInfo info)
{
if (m_Context?.Registered == false)
return;
switch (info.ButtonID)
{
case 0: // closed
{
if (m_Context != null)
m_From.SendGump(new DuelContextGump(m_From, m_Context));
break;
}
case 1: // customize
{
m_From.SendGump(new RulesetGump(m_From, m_Ruleset, m_Ruleset.Layout, m_Context));
break;
}
default:
{
int idx = info.ButtonID - 2;
if (idx >= 0 && idx < m_Defaults.Length)
{
m_Ruleset.ApplyDefault(m_Defaults[idx]);
m_From.SendGump(new PickRulesetGump(m_From, m_Context, m_Ruleset));
}
else
{
idx -= m_Defaults.Length;
if (idx >= 0 && idx < m_Flavors.Length)
{
if (m_Ruleset.Flavors.Contains(m_Flavors[idx]))
m_Ruleset.RemoveFlavor(m_Flavors[idx]);
else
m_Ruleset.AddFlavor(m_Flavors[idx]);
m_From.SendGump(new PickRulesetGump(m_From, m_Context, m_Ruleset));
}
}
break;
}
}
}
}
}

View file

@ -0,0 +1,109 @@
using System.Collections.Generic;
using Server.Gumps;
using Server.Network;
namespace Server.Engines.ConPVP
{
public class ReadyGump : Gump
{
private DuelContext m_Context;
private int m_Count;
private Mobile m_From;
public ReadyGump(Mobile from, DuelContext context, int count) : base(50, 50)
{
m_From = from;
m_Context = context;
m_Count = count;
List<Participant> parts = context.Participants;
int height = 25 + 20;
for (int i = 0; i < parts.Count; ++i)
{
Participant p = parts[i];
height += 4;
if (p.Players.Length > 1)
height += 22;
height += p.Players.Length * 22;
}
height += 25;
Closable = false;
Draggable = false;
AddPage(0);
AddBackground(0, 0, 260, height, 9250);
AddBackground(10, 10, 240, height - 20, 0xDAC);
if (count == -1)
{
AddHtml(35, 25, 190, 20, Center("Ready"));
}
else
{
AddHtml(35, 25, 190, 20, Center("Starting"));
AddHtml(35, 25, 190, 20, "<DIV ALIGN=RIGHT>" + count);
}
int y = 25 + 20;
for (int i = 0; i < parts.Count; ++i)
{
Participant p = parts[i];
y += 4;
bool isAllReady = true;
int yStore = y;
int offset = 0;
if (p.Players.Length > 1)
{
AddHtml(35 + 14, y, 176, 20, $"Participant #{i + 1}");
y += 22;
offset = 10;
}
for (int j = 0; j < p.Players.Length; ++j)
{
DuelPlayer pl = p.Players[j];
if (pl?.Ready == true)
{
AddImage(35 + offset, y + 4, 0x939);
}
else
{
AddImage(35 + offset, y + 4, 0x938);
isAllReady = false;
}
string name = pl == null ? "(Empty)" : pl.Mobile.Name;
AddHtml(35 + offset + 14, y, 166, 20, name);
y += 22;
}
if (p.Players.Length > 1)
AddImage(35, yStore + 4, isAllReady ? 0x939 : 0x938);
}
}
public string Center(string text)
{
return $"<CENTER>{text}</CENTER>";
}
public override void OnResponse(NetState sender, RelayInfo info)
{
}
}
}

View file

@ -0,0 +1,233 @@
using System.Collections;
using System.Collections.Generic;
using Server.Gumps;
using Server.Mobiles;
using Server.Network;
namespace Server.Engines.ConPVP
{
public class ReadyUpGump : Gump
{
private DuelContext m_Context;
private Mobile m_From;
public ReadyUpGump(Mobile from, DuelContext context) : base(50, 50)
{
m_From = from;
m_Context = context;
Closable = false;
AddPage(0);
if (context.Rematch)
{
int height = 25 + 20 + 10 + 22 + 25;
AddBackground(0, 0, 210, height, 9250);
AddBackground(10, 10, 190, height - 20, 0xDAC);
AddHtml(35, 25, 140, 20, Center("Rematch?"));
AddButton(35, 55, 247, 248, 1);
AddButton(115, 55, 242, 241, 2);
}
else
{
#region Participants
AddPage(1);
List<Participant> parts = context.Participants;
int height = 25 + 20;
for (int i = 0; i < parts.Count; ++i)
{
Participant p = parts[i];
height += 4;
if (p.Players.Length > 1)
height += 22;
height += p.Players.Length * 22;
}
height += 10 + 22 + 25;
AddBackground(0, 0, 260, height, 9250);
AddBackground(10, 10, 240, height - 20, 0xDAC);
AddHtml(35, 25, 190, 20, Center("Participants"));
int y = 20 + 25;
for (int i = 0; i < parts.Count; ++i)
{
Participant p = parts[i];
y += 4;
int offset = 0;
if (p.Players.Length > 1)
{
AddHtml(35, y, 176, 20, $"Team #{i + 1}");
y += 22;
offset = 10;
}
for (int j = 0; j < p.Players.Length; ++j)
{
DuelPlayer pl = p.Players[j];
string name = pl == null ? "(Empty)" : pl.Mobile.Name;
AddHtml(35 + offset, y, 166, 20, name);
y += 22;
}
}
y += 8;
AddHtml(35, y, 176, 20, "Continue?");
y -= 2;
AddButton(102, y, 247, 248, 0, GumpButtonType.Page, 2);
AddButton(169, y, 242, 241, 2);
#endregion
#region Rules
AddPage(2);
Ruleset ruleset = context.Ruleset;
Ruleset basedef = ruleset.Base;
height = 25 + 20 + 5 + 20 + 20 + 4;
int changes = 0;
BitArray defs;
if (ruleset.Flavors.Count > 0)
{
defs = new BitArray(basedef.Options);
for (int i = 0; i < ruleset.Flavors.Count; ++i)
defs.Or(ruleset.Flavors[i].Options);
height += ruleset.Flavors.Count * 18;
}
else
{
defs = basedef.Options;
}
BitArray opts = ruleset.Options;
for (int i = 0; i < opts.Length; ++i)
if (defs[i] != opts[i])
++changes;
height += changes * 22;
height += 10 + 22 + 25;
AddBackground(0, 0, 260, height, 9250);
AddBackground(10, 10, 240, height - 20, 0xDAC);
AddHtml(35, 25, 190, 20, Center("Rules"));
AddHtml(35, 50, 190, 20, $"Set: {basedef.Title}");
y = 70;
for (int i = 0; i < ruleset.Flavors.Count; ++i, y += 18)
AddHtml(35, y, 190, 20, $" + {ruleset.Flavors[i].Title}");
y += 4;
if (changes > 0)
{
AddHtml(35, y, 190, 20, "Modifications:");
y += 20;
for (int i = 0; i < opts.Length; ++i)
if (defs[i] != opts[i])
{
string name = ruleset.Layout.FindByIndex(i);
if (name != null) // sanity
{
AddImage(35, y, opts[i] ? 0xD3 : 0xD2);
AddHtml(60, y, 165, 22, name);
}
y += 22;
}
}
else
{
AddHtml(35, y, 190, 20, "Modifications: None");
y += 20;
}
y += 8;
AddHtml(35, y, 176, 20, "Continue?");
y -= 2;
AddButton(102, y, 247, 248, 1);
AddButton(169, y, 242, 241, 3);
#endregion
}
}
public string Center(string text)
{
return $"<CENTER>{text}</CENTER>";
}
public void AddGoldenButton(int x, int y, int bid)
{
AddButton(x, y, 0xD2, 0xD2, bid);
AddButton(x + 3, y + 3, 0xD8, 0xD8, bid);
}
public override void OnResponse(NetState sender, RelayInfo info)
{
if (!m_Context.Registered || !m_Context.ReadyWait)
return;
switch (info.ButtonID)
{
case 1: // okay
{
if (!(m_From is PlayerMobile pm))
break;
pm.DuelPlayer.Ready = true;
m_Context.SendReadyGump();
break;
}
case 2: // reject participants
{
m_Context.RejectReady(m_From, "participants");
break;
}
case 3: // reject rules
{
m_Context.RejectReady(m_From, "rules");
break;
}
}
}
}
}

View file

@ -0,0 +1,130 @@
using System.Collections;
using Server.Gumps;
using Server.Network;
namespace Server.Engines.ConPVP
{
public class RulesetGump : Gump
{
private DuelContext m_DuelContext;
private Mobile m_From;
private RulesetLayout m_Page;
private bool m_ReadOnly;
private Ruleset m_Ruleset;
public RulesetGump(Mobile from, Ruleset ruleset, RulesetLayout page, DuelContext duelContext, bool readOnly = false)
: base(readOnly ? 310 : 50, 50)
{
m_From = from;
m_Ruleset = ruleset;
m_Page = page;
m_DuelContext = duelContext;
m_ReadOnly = readOnly;
Draggable = !readOnly;
from.CloseGump<RulesetGump>();
from.CloseGump<DuelContextGump>();
from.CloseGump<ParticipantGump>();
RulesetLayout depthCounter = page;
int depth = 0;
while (depthCounter != null)
{
++depth;
depthCounter = depthCounter.Parent;
}
int count = page.Children.Length + page.Options.Length;
AddPage(0);
int height = 35 + 10 + 2 + count * 22 + 2 + 30;
AddBackground(0, 0, 260, height, 9250);
AddBackground(10, 10, 240, height - 20, 0xDAC);
AddHtml(35, 25, 190, 20, Center(page.Title));
int x = 35;
int y = 47;
for (int i = 0; i < page.Children.Length; ++i)
{
AddGoldenButton(x, y, 1 + i);
AddHtml(x + 25, y, 250, 22, page.Children[i].Title);
y += 22;
}
for (int i = 0; i < page.Options.Length; ++i)
{
bool enabled = ruleset.Options[page.Offset + i];
if (readOnly)
AddImage(x, y, enabled ? 0xD3 : 0xD2);
else
AddCheck(x, y, 0xD2, 0xD3, enabled, i);
AddHtml(x + 25, y, 250, 22, page.Options[i]);
y += 22;
}
}
public string Center(string text)
{
return $"<CENTER>{text}</CENTER>";
}
public void AddGoldenButton(int x, int y, int bid)
{
AddButton(x, y, 0xD2, 0xD2, bid);
AddButton(x + 3, y + 3, 0xD8, 0xD8, bid);
}
public override void OnResponse(NetState sender, RelayInfo info)
{
if (m_DuelContext?.Registered == false)
return;
if (!m_ReadOnly)
{
BitArray opts = new BitArray(m_Page.Options.Length);
for (int i = 0; i < info.Switches.Length; ++i)
{
int sid = info.Switches[i];
if (sid >= 0 && sid < m_Page.Options.Length)
opts[sid] = true;
}
for (int i = 0; i < opts.Length; ++i)
if (m_Ruleset.Options[m_Page.Offset + i] != opts[i])
{
m_Ruleset.Options[m_Page.Offset + i] = opts[i];
m_Ruleset.Changed = true;
}
}
int bid = info.ButtonID;
if (bid == 0)
{
if (m_Page.Parent != null)
m_From.SendGump(new RulesetGump(m_From, m_Ruleset, m_Page.Parent, m_DuelContext, m_ReadOnly));
else if (!m_ReadOnly)
m_From.SendGump(new PickRulesetGump(m_From, m_DuelContext, m_Ruleset));
}
else
{
bid -= 1;
if (bid >= 0 && bid < m_Page.Children.Length)
m_From.SendGump(new RulesetGump(m_From, m_Ruleset, m_Page.Children[bid], m_DuelContext, m_ReadOnly));
}
}
}
}

View file

@ -0,0 +1,856 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using Server.Gumps;
using Server.Mobiles;
using Server.Network;
namespace Server.Engines.ConPVP
{
public enum TourneyBracketGumpType
{
Index,
Rules_Info,
Participant_List,
Participant_Info,
Round_List,
Round_Info,
Match_Info,
Player_Info
}
public class TournamentBracketGump : Gump
{
private const int BlackColor32 = 0x000008;
private const int LabelColor32 = 0xFFFFFF;
private Mobile m_From;
private List<object> m_List;
private object m_Object;
private int m_Page;
private int m_PerPage;
private Tournament m_Tournament;
private TourneyBracketGumpType m_Type;
public TournamentBracketGump(Mobile from, Tournament tourney, TourneyBracketGumpType type,
List<object> list = null, int page = 0, object obj = null) : base(50, 50)
{
m_From = from;
m_Tournament = tourney;
m_Type = type;
m_List = list;
m_Page = page;
m_Object = obj;
m_PerPage = 12;
switch (type)
{
case TourneyBracketGumpType.Index:
{
AddPage(0);
AddBackground(0, 0, 300, 300, 9380);
StringBuilder sb = new StringBuilder();
if (tourney.TourneyType == TourneyType.FreeForAll)
{
sb.Append("FFA");
}
else if (tourney.TourneyType == TourneyType.RandomTeam)
{
sb.Append(tourney.ParticipantsPerMatch);
sb.Append("-Team");
}
else if (tourney.TourneyType == TourneyType.RedVsBlue)
{
sb.Append("Red v Blue");
}
else if (tourney.TourneyType == TourneyType.Faction)
{
sb.Append(tourney.ParticipantsPerMatch);
sb.Append("-Team Faction");
}
else
{
for (int i = 0; i < tourney.ParticipantsPerMatch; ++i)
{
if (sb.Length > 0)
sb.Append('v');
sb.Append(tourney.PlayersPerParticipant);
}
}
if (tourney.EventController != null)
sb.Append(' ').Append(tourney.EventController.Title);
sb.Append(" Tournament Bracket");
AddHtml(25, 35, 250, 20, Center(sb.ToString()));
AddRightArrow(25, 53, ToButtonID(0, 4), "Rules");
AddRightArrow(25, 71, ToButtonID(0, 1), "Participants");
if (m_Tournament.Stage == TournamentStage.Signup)
{
TimeSpan until = m_Tournament.SignupStart + m_Tournament.SignupPeriod - DateTime.UtcNow;
string text;
int secs = (int)until.TotalSeconds;
if (secs > 0)
{
int mins = secs / 60;
secs %= 60;
if (mins > 0 && secs > 0)
text =
$"The tournament will begin in {mins} minute{(mins == 1 ? "" : "s")} and {secs} second{(secs == 1 ? "" : "s")}.";
else if (mins > 0)
text = $"The tournament will begin in {mins} minute{(mins == 1 ? "" : "s")}.";
else if (secs > 0)
text = $"The tournament will begin in {secs} second{(secs == 1 ? "" : "s")}.";
else
text = "The tournament will begin shortly.";
}
else
{
text = "The tournament will begin shortly.";
}
AddHtml(25, 92, 250, 40, text);
}
else
{
AddRightArrow(25, 89, ToButtonID(0, 2), "Rounds");
}
break;
}
case TourneyBracketGumpType.Rules_Info:
{
Ruleset ruleset = tourney.Ruleset;
Ruleset basedef = ruleset.Base;
BitArray defs;
if (ruleset.Flavors.Count > 0)
{
defs = new BitArray(basedef.Options);
for (int i = 0; i < ruleset.Flavors.Count; ++i)
defs.Or(ruleset.Flavors[i].Options);
}
else
{
defs = basedef.Options;
}
int changes = 0;
BitArray opts = ruleset.Options;
for (int i = 0; i < opts.Length; ++i)
if (defs[i] != opts[i])
++changes;
AddPage(0);
AddBackground(0, 0, 300,
60 + 18 + 20 + 20 + 20 + 8 + 20 + ruleset.Flavors.Count * 18 + 4 + 20 + changes * 22 + 6, 9380);
AddLeftArrow(25, 11, ToButtonID(0, 0));
AddHtml(25, 35, 250, 20, Center("Rules"));
int y = 53;
string groupText = null;
switch (tourney.GroupType)
{
case GroupingType.HighVsLow:
groupText = "High vs Low";
break;
case GroupingType.Nearest:
groupText = "Closest opponent";
break;
case GroupingType.Random:
groupText = "Random";
break;
}
AddHtml(35, y, 190, 20, $"Grouping: {groupText}");
y += 20;
string tieText = null;
switch (tourney.TieType)
{
case TieType.Random:
tieText = "Random";
break;
case TieType.Highest:
tieText = "Highest advances";
break;
case TieType.Lowest:
tieText = "Lowest advances";
break;
case TieType.FullAdvancement:
tieText = tourney.ParticipantsPerMatch == 2 ? "Both advance" : "Everyone advances";
break;
case TieType.FullElimination:
tieText = tourney.ParticipantsPerMatch == 2 ? "Both eliminated" : "Everyone eliminated";
break;
}
AddHtml(35, y, 190, 20, $"Tiebreaker: {tieText}");
y += 20;
string sdText = "Off";
if (tourney.SuddenDeath > TimeSpan.Zero)
{
sdText = $"{(int)tourney.SuddenDeath.TotalMinutes}:{tourney.SuddenDeath.Seconds:D2}";
if (tourney.SuddenDeathRounds > 0)
sdText = $"{sdText} (first {tourney.SuddenDeathRounds} rounds)";
else
sdText = $"{sdText} (all rounds)";
}
AddHtml(35, y, 240, 20, $"Sudden Death: {sdText}");
y += 20;
y += 8;
AddHtml(35, y, 190, 20, $"Ruleset: {basedef.Title}");
y += 20;
for (int i = 0; i < ruleset.Flavors.Count; ++i, y += 18)
AddHtml(35, y, 190, 20, $" + {ruleset.Flavors[i].Title}");
y += 4;
if (changes > 0)
{
AddHtml(35, y, 190, 20, "Modifications:");
y += 20;
for (int i = 0; i < opts.Length; ++i)
if (defs[i] != opts[i])
{
string name = ruleset.Layout.FindByIndex(i);
if (name != null) // sanity
{
AddImage(35, y, opts[i] ? 0xD3 : 0xD2);
AddHtml(60, y, 165, 22, name);
}
y += 22;
}
}
else
{
AddHtml(35, y, 190, 20, "Modifications: None");
}
break;
}
case TourneyBracketGumpType.Participant_List:
{
AddPage(0);
AddBackground(0, 0, 300, 300, 9380);
List<TourneyParticipant> pList = m_List != null
? Utility.CastListCovariant<object, TourneyParticipant>(m_List)
: new List<TourneyParticipant>(tourney.Participants);
AddLeftArrow(25, 11, ToButtonID(0, 0));
AddHtml(25, 35, 250, 20, Center($"{pList.Count} Participant{(pList.Count == 1 ? "" : "s")}"));
StartPage(out int index, out int count, out int y, 12);
for (int i = 0; i < count; ++i, y += 18)
{
TourneyParticipant part = pList[index + i];
string name = part.NameList;
if (m_Tournament.TourneyType != TourneyType.Standard && part.Players.Count == 1)
if (part.Players[0] is PlayerMobile pm && pm.DuelPlayer != null)
name = Color(name, pm.DuelPlayer.Eliminated ? 0x6633333 : 0x336666);
AddRightArrow(25, y, ToButtonID(2, index + i), name);
}
break;
}
case TourneyBracketGumpType.Participant_Info:
{
if (!(obj is TourneyParticipant part))
break;
AddPage(0);
AddBackground(0, 0, 300, 60 + 18 + 20 + part.Players.Count * 18 + 20 + 20 + 160, 9380);
AddLeftArrow(25, 11, ToButtonID(0, 1));
AddHtml(25, 35, 250, 20, Center("Participants"));
int y = 53;
AddHtml(25, y, 200, 20, part.Players.Count == 1 ? "Players" : "Team");
y += 20;
for (int i = 0; i < part.Players.Count; ++i)
{
Mobile mob = part.Players[i];
string name = mob.Name;
if (m_Tournament.TourneyType != TourneyType.Standard)
if (mob is PlayerMobile pm && pm.DuelPlayer != null)
name = Color(name, pm.DuelPlayer.Eliminated ? 0x6633333 : 0x336666);
AddRightArrow(35, y, ToButtonID(4, i), name);
y += 18;
}
AddHtml(25, y, 200, 20,
$"Free Advances: {(part.FreeAdvances == 0 ? "None" : part.FreeAdvances.ToString())}");
y += 20;
AddHtml(25, y, 200, 20, "Log:");
y += 20;
StringBuilder sb = new StringBuilder();
for (int i = 0; i < part.Log.Count; ++i)
{
if (sb.Length > 0)
sb.Append("<br>");
sb.Append(part.Log[i]);
}
if (sb.Length == 0)
sb.Append("Nothing logged yet.");
AddHtml(25, y, 250, 150, Color(sb.ToString(), BlackColor32), false, true);
break;
}
case TourneyBracketGumpType.Player_Info:
{
AddPage(0);
AddBackground(0, 0, 300, 300, 9380);
AddLeftArrow(25, 11, ToButtonID(0, 3));
AddHtml(25, 35, 250, 20, Center("Participants"));
if (!(obj is Mobile mob))
break;
Ladder ladder = Ladder.Instance;
LadderEntry entry = ladder?.Find(mob);
AddHtml(25, 53, 250, 20, $"Name: {mob.Name}");
AddHtml(25, 73, 250, 20,
$"Guild: {(mob.Guild == null ? "None" : mob.Guild.Name + " [" + mob.Guild.Abbreviation + "]")}");
AddHtml(25, 93, 250, 20, $"Rank: {(entry == null ? "N/A" : LadderGump.Rank(entry.Index + 1))}");
AddHtml(25, 113, 250, 20, $"Level: {(entry == null ? 0 : Ladder.GetLevel(entry.Experience))}");
AddHtml(25, 133, 250, 20, $"Wins: {entry?.Wins ?? 0:N0}");
AddHtml(25, 153, 250, 20, $"Losses: {entry?.Losses ?? 0:N0}");
break;
}
case TourneyBracketGumpType.Round_List:
{
AddPage(0);
AddBackground(0, 0, 300, 300, 9380);
AddLeftArrow(25, 11, ToButtonID(0, 0));
AddHtml(25, 35, 250, 20, Center("Rounds"));
// List<PyramidLevel> levelsList = m_List != null
// ? Utility.CastListCovariant<object, PyramidLevel>(m_List)
// : new List<PyramidLevel>(tourney.Pyramid.Levels);
StartPage(out int index, out int count, out int y, 12);
for (int i = 0; i < count; ++i, y += 18)
AddRightArrow(25, y, ToButtonID(3, index + i), "Round #" + (index + i + 1));
break;
}
case TourneyBracketGumpType.Round_Info:
{
AddPage(0);
AddBackground(0, 0, 300, 300, 9380);
AddLeftArrow(25, 11, ToButtonID(0, 2));
AddHtml(25, 35, 250, 20, Center("Rounds"));
if (!(m_Object is PyramidLevel level))
break;
List<TourneyMatch> matchesList = m_List != null
? Utility.CastListCovariant<object, TourneyMatch>(m_List)
: new List<TourneyMatch>(level.Matches);
AddRightArrow(25, 53, ToButtonID(5, 0),
$"Free Advance: {(level.FreeAdvance == null ? "None" : level.FreeAdvance.NameList)}");
AddHtml(25, 73, 200, 20, $"{matchesList.Count} Match{(matchesList.Count == 1 ? "" : "es")}");
StartPage(out int index, out int count, out int y, 10);
for (int i = 0; i < count; ++i, y += 18)
{
TourneyMatch match = matchesList[index + i];
int color = -1;
if (match.InProgress)
color = 0x336666;
else if (match.Context != null && match.Winner == null)
color = 0x666666;
StringBuilder sb = new StringBuilder();
if (m_Tournament.TourneyType == TourneyType.Standard)
for (int j = 0; j < match.Participants.Count; ++j)
{
if (sb.Length > 0)
sb.Append(" vs ");
TourneyParticipant part = match.Participants[j];
string txt = part.NameList;
if (color == -1 && match.Context != null && match.Winner == part)
txt = Color(txt, 0x336633);
else if (color == -1 && match.Context != null)
txt = Color(txt, 0x663333);
sb.Append(txt);
}
else if (m_Tournament.EventController != null || m_Tournament.TourneyType == TourneyType.RandomTeam ||
m_Tournament.TourneyType == TourneyType.RedVsBlue ||
m_Tournament.TourneyType == TourneyType.Faction)
for (int j = 0; j < match.Participants.Count; ++j)
{
if (sb.Length > 0)
sb.Append(" vs ");
TourneyParticipant part = match.Participants[j];
string txt;
if (m_Tournament.EventController != null)
{
txt = $"Team {m_Tournament.EventController.GetTeamName(j)} ({part.Players.Count})";
}
else if (m_Tournament.TourneyType == TourneyType.RandomTeam)
{
txt = $"Team {j + 1} ({part.Players.Count})";
}
else if (m_Tournament.TourneyType == TourneyType.Faction)
{
if (m_Tournament.ParticipantsPerMatch == 4)
{
string name = "(null)";
switch (j)
{
case 0:
{
name = "Minax";
break;
}
case 1:
{
name = "Council of Mages";
break;
}
case 2:
{
name = "True Britannians";
break;
}
case 3:
{
name = "Shadowlords";
break;
}
}
txt = $"{name} ({part.Players.Count})";
}
else if (m_Tournament.ParticipantsPerMatch == 2)
{
txt = $"{(j == 0 ? "Evil" : "Hero")} Team ({part.Players.Count})";
}
else
{
txt = $"Team {j + 1} ({part.Players.Count})";
}
}
else
{
txt = $"Team {(j == 0 ? "Red" : "Blue")} ({part.Players.Count})";
}
if (color == -1 && match.Context != null && match.Winner == part)
txt = Color(txt, 0x336633);
else if (color == -1 && match.Context != null)
txt = Color(txt, 0x663333);
sb.Append(txt);
}
else if (m_Tournament.TourneyType == TourneyType.FreeForAll) sb.Append("Free For All");
string str = sb.ToString();
if (color >= 0)
str = Color(str, color);
AddRightArrow(25, y, ToButtonID(5, index + i + 1), str);
}
break;
}
case TourneyBracketGumpType.Match_Info:
{
if (!(obj is TourneyMatch match))
break;
int ct = m_Tournament.TourneyType == TourneyType.FreeForAll ? 2 : match.Participants.Count;
AddPage(0);
AddBackground(0, 0, 300, 60 + 18 + 20 + 20 + 20 + ct * 18 + 6, 9380);
AddLeftArrow(25, 11, ToButtonID(0, 5));
AddHtml(25, 35, 250, 20, Center("Rounds"));
AddHtml(25, 53, 250, 20, $"Winner: {(match.Winner == null ? "N/A" : match.Winner.NameList)}");
AddHtml(25, 73, 250, 20,
$"State: {(match.InProgress ? "In progress" : match.Context != null ? "Complete" : "Waiting")}");
AddHtml(25, 93, 250, 20, "Participants:");
if (m_Tournament.TourneyType == TourneyType.Standard)
for (int i = 0; i < match.Participants.Count; ++i)
{
TourneyParticipant part = match.Participants[i];
AddRightArrow(25, 113 + i * 18, ToButtonID(6, i), part.NameList);
}
else if (m_Tournament.EventController != null || m_Tournament.TourneyType == TourneyType.RandomTeam ||
m_Tournament.TourneyType == TourneyType.RedVsBlue ||
m_Tournament.TourneyType == TourneyType.Faction)
for (int i = 0; i < match.Participants.Count; ++i)
{
TourneyParticipant part = match.Participants[i];
if (m_Tournament.EventController != null)
{
AddRightArrow(25, 113 + i * 18, ToButtonID(6, i),
$"Team {m_Tournament.EventController.GetTeamName(i)} ({part.Players.Count})");
}
else if (m_Tournament.TourneyType == TourneyType.RandomTeam)
{
AddRightArrow(25, 113 + i * 18, ToButtonID(6, i),
$"Team {i + 1} ({part.Players.Count})");
}
else if (m_Tournament.TourneyType == TourneyType.Faction)
{
if (m_Tournament.ParticipantsPerMatch == 4)
{
string name = "(null)";
switch (i)
{
case 0:
{
name = "Minax";
break;
}
case 1:
{
name = "Council of Mages";
break;
}
case 2:
{
name = "True Britannians";
break;
}
case 3:
{
name = "Shadowlords";
break;
}
}
AddRightArrow(25, 113 + i * 18, ToButtonID(6, i),
$"{name} ({part.Players.Count})");
}
else if (m_Tournament.ParticipantsPerMatch == 2)
{
AddRightArrow(25, 113 + i * 18, ToButtonID(6, i),
$"{(i == 0 ? "Evil" : "Hero")} Team ({part.Players.Count})");
}
else
{
AddRightArrow(25, 113 + i * 18, ToButtonID(6, i),
$"Team {i + 1} ({part.Players.Count})");
}
}
else
{
AddRightArrow(25, 113 + i * 18, ToButtonID(6, i),
$"Team {(i == 0 ? "Red" : "Blue")} ({part.Players.Count})");
}
}
else if (m_Tournament.TourneyType == TourneyType.FreeForAll)
AddHtml(25, 113, 250, 20, "Free For All");
break;
}
}
}
public string Center(string text)
{
return $"<CENTER>{text}</CENTER>";
}
public string Color(string text, int color)
{
return $"<BASEFONT COLOR=#{color:X6}>{text}</BASEFONT>";
}
private void AddBorderedText(int x, int y, int width, int height, string text, int color, int borderColor)
{
AddColoredText(x - 1, y - 1, width, height, text, borderColor);
AddColoredText(x - 1, y + 1, width, height, text, borderColor);
AddColoredText(x + 1, y - 1, width, height, text, borderColor);
AddColoredText(x + 1, y + 1, width, height, text, borderColor);
AddColoredText(x, y, width, height, text, color);
}
private void AddColoredText(int x, int y, int width, int height, string text, int color)
{
if (color == 0)
AddHtml(x, y, width, height, text);
else
AddHtml(x, y, width, height, Color(text, color));
}
public void AddRightArrow(int x, int y, int bid, string text)
{
AddButton(x, y, 0x15E1, 0x15E5, bid);
if (text != null)
AddHtml(x + 20, y - 1, 230, 20, text);
}
public void AddRightArrow(int x, int y, int bid)
{
AddRightArrow(x, y, bid, null);
}
public void AddLeftArrow(int x, int y, int bid, string text)
{
AddButton(x, y, 0x15E3, 0x15E7, bid);
if (text != null)
AddHtml(x + 20, y - 1, 230, 20, text);
}
public void AddLeftArrow(int x, int y, int bid)
{
AddLeftArrow(x, y, bid, null);
}
public int ToButtonID(int type, int index)
{
return 1 + index * 7 + type;
}
public bool FromButtonID(int bid, out int type, out int index)
{
type = (bid - 1) % 7;
index = (bid - 1) / 7;
return bid >= 1;
}
public void StartPage(out int index, out int count, out int y, int perPage)
{
m_PerPage = perPage;
index = Math.Max(m_Page * perPage, 0);
count = Math.Max(Math.Min(m_List.Count - index, perPage), 0);
y = 53 + (12 - perPage) * 18;
if (m_Page > 0)
AddLeftArrow(242, 35, ToButtonID(1, 0));
if ((m_Page + 1) * perPage < m_List.Count)
AddRightArrow(260, 35, ToButtonID(1, 1));
}
public override void OnResponse(NetState sender, RelayInfo info)
{
if (!FromButtonID(info.ButtonID, out int type, out int index))
return;
switch (type)
{
case 0:
{
switch (index)
{
case 0:
m_From.SendGump(new TournamentBracketGump(m_From, m_Tournament, TourneyBracketGumpType.Index));
break;
case 1:
m_From.SendGump(new TournamentBracketGump(m_From, m_Tournament,
TourneyBracketGumpType.Participant_List));
break;
case 2:
m_From.SendGump(new TournamentBracketGump(m_From, m_Tournament, TourneyBracketGumpType.Round_List));
break;
case 4:
m_From.SendGump(new TournamentBracketGump(m_From, m_Tournament, TourneyBracketGumpType.Rules_Info));
break;
case 3:
{
Mobile mob = m_Object as Mobile;
for (int i = 0; i < m_Tournament.Participants.Count; ++i)
{
TourneyParticipant part = m_Tournament.Participants[i];
if (part.Players.Contains(mob))
{
m_From.SendGump(new TournamentBracketGump(m_From, m_Tournament,
TourneyBracketGumpType.Participant_Info, null, 0, part));
break;
}
}
break;
}
case 5:
{
if (!(m_Object is TourneyMatch match))
break;
for (int i = 0; i < m_Tournament.Pyramid.Levels.Count; ++i)
{
PyramidLevel level = m_Tournament.Pyramid.Levels[i];
if (level.Matches.Contains(match))
m_From.SendGump(new TournamentBracketGump(m_From, m_Tournament,
TourneyBracketGumpType.Round_Info, null, 0, level));
}
break;
}
}
break;
}
case 1:
{
switch (index)
{
case 0:
{
if (m_List != null && m_Page > 0)
m_From.SendGump(new TournamentBracketGump(m_From, m_Tournament, m_Type, m_List, m_Page - 1,
m_Object));
break;
}
case 1:
{
if (m_List != null && (m_Page + 1) * m_PerPage < m_List.Count)
m_From.SendGump(new TournamentBracketGump(m_From, m_Tournament, m_Type, m_List, m_Page + 1,
m_Object));
break;
}
}
break;
}
case 2:
{
if (m_Type != TourneyBracketGumpType.Participant_List)
break;
if (index >= 0 && index < m_List.Count)
m_From.SendGump(new TournamentBracketGump(m_From, m_Tournament,
TourneyBracketGumpType.Participant_Info, null, 0, m_List[index]));
break;
}
case 3:
{
if (m_Type != TourneyBracketGumpType.Round_List)
break;
if (index >= 0 && index < m_List.Count)
m_From.SendGump(new TournamentBracketGump(m_From, m_Tournament, TourneyBracketGumpType.Round_Info,
null, 0, m_List[index]));
break;
}
case 4:
{
if (m_Type != TourneyBracketGumpType.Participant_Info)
break;
if (m_Object is TourneyParticipant part && index >= 0 && index < part.Players.Count)
m_From.SendGump(new TournamentBracketGump(m_From, m_Tournament, TourneyBracketGumpType.Player_Info,
null, 0, part.Players[index]));
break;
}
case 5:
{
if (m_Type != TourneyBracketGumpType.Round_Info)
break;
if (!(m_Object is PyramidLevel level))
break;
if (index == 0)
{
if (level.FreeAdvance != null)
m_From.SendGump(new TournamentBracketGump(m_From, m_Tournament,
TourneyBracketGumpType.Participant_Info, null, 0, level.FreeAdvance));
else
m_From.SendGump(
new TournamentBracketGump(m_From, m_Tournament, m_Type, m_List, m_Page, m_Object));
}
else if (index >= 1 && index <= level.Matches.Count)
{
m_From.SendGump(new TournamentBracketGump(m_From, m_Tournament, TourneyBracketGumpType.Match_Info,
null, 0, level.Matches[index - 1]));
}
break;
}
case 6:
{
if (m_Type != TourneyBracketGumpType.Match_Info)
break;
if (m_Object is TourneyMatch match && index >= 0 && index < match.Participants.Count)
m_From.SendGump(new TournamentBracketGump(m_From, m_Tournament,
TourneyBracketGumpType.Participant_Info, null, 0, match.Participants[index]));
break;
}
}
}
}
}

View file

@ -0,0 +1,366 @@
using System;
using System.Collections.Generic;
namespace Server.Engines.ConPVP
{
public class LadderController : Item
{
[Constructible]
public LadderController() : base(0x1B7A)
{
Visible = false;
Movable = false;
Ladder = new Ladder();
if (Ladder.Instance == null)
Ladder.Instance = Ladder;
}
public LadderController(Serial serial) : base(serial)
{
}
[CommandProperty( AccessLevel.Administrator )]
public Ladder Ladder{ get; private set; }
public override string DefaultName => "ladder controller";
public override void Delete()
{
if (Ladder.Instance == Ladder)
Ladder.Instance = null;
base.Delete();
}
public override void Serialize(GenericWriter writer)
{
base.Serialize(writer);
writer.Write(1);
Ladder.Serialize(writer);
writer.Write(Ladder.Instance == Ladder);
}
public override void Deserialize(GenericReader reader)
{
base.Deserialize(reader);
int version = reader.ReadInt();
switch (version)
{
case 1:
case 0:
{
Ladder = new Ladder(reader);
if (version < 1 || reader.ReadBool())
Ladder.Instance = Ladder;
break;
}
}
}
}
public class Ladder
{
private static int[] m_ShortLevels =
{
1,
2,
3, 3,
4, 4,
5, 5, 5,
6, 6, 6,
7, 7, 7, 7,
8, 8, 8, 8,
9, 9, 9, 9, 9
};
private static int[] m_BaseXP =
{
0, 100, 200, 400, 600, 900, 1200, 1600, 2000, 2500
};
private static int[] m_LossFactors =
{
10,
11, 11,
25, 25,
43, 43,
67, 67
};
private static int[,] m_OffsetScalar =
{
/* { win, los } */
/* -6 */ { 175, 25 },
/* -5 */ { 165, 35 },
/* -4 */ { 155, 45 },
/* -3 */ { 145, 55 },
/* -2 */ { 130, 70 },
/* -1 */ { 115, 85 },
/* 0 */ { 100, 100 },
/* +1 */ { 90, 110 },
/* +2 */ { 80, 120 },
/* +3 */ { 70, 130 },
/* +4 */ { 60, 140 },
/* +5 */ { 50, 150 },
/* +6 */ { 40, 160 }
};
public List<LadderEntry> Entries{ get; } = new List<LadderEntry>();
private Dictionary<Mobile, LadderEntry> m_Table;
public Ladder()
{
m_Table = new Dictionary<Mobile, LadderEntry>();
}
public Ladder(GenericReader reader)
{
int version = reader.ReadEncodedInt();
switch (version)
{
case 1:
case 0:
{
int count = reader.ReadEncodedInt();
m_Table = new Dictionary<Mobile, LadderEntry>(count);
Entries = new List<LadderEntry>(count);
for (int i = 0; i < count; ++i)
{
LadderEntry entry = new LadderEntry(reader, this, version);
if (entry.Mobile != null)
{
m_Table[entry.Mobile] = entry;
entry.Index = Entries.Count;
Entries.Add(entry);
}
}
if (version == 0)
{
Entries.Sort();
for (int i = 0; i < Entries.Count; ++i)
{
LadderEntry entry = Entries[i];
entry.Index = i;
}
}
break;
}
}
}
public static Ladder Instance{ get; set; }
public static int GetLevel(int xp)
{
if (xp >= 22500)
return 50;
if (xp >= 2500)
return 10 + (xp - 2500) / 500;
if (xp < 0)
xp = 0;
return m_ShortLevels[xp / 100];
}
public static void GetLevelInfo(int level, out int xpBase, out int xpAdvance)
{
if (level >= 10)
{
xpBase = 2500 + (level - 10) * 500;
xpAdvance = 500;
}
else
{
xpBase = m_BaseXP[level - 1];
xpAdvance = m_BaseXP[level] - xpBase;
}
}
public static int GetLossFactor(int level)
{
if (level >= 10)
return 100;
return m_LossFactors[level - 1];
}
public static int GetOffsetScalar(int ourLevel, int theirLevel, bool win)
{
int x = ourLevel - theirLevel;
if (x < -6 || x > +6)
return 0;
int y = win ? 0 : 1;
return m_OffsetScalar[x + 6, y];
}
public static int GetExperienceGain(LadderEntry us, LadderEntry them, bool weWon)
{
if (us == null || them == null)
return 0;
int ourLevel = GetLevel(us.Experience);
int theirLevel = GetLevel(them.Experience);
int scalar = GetOffsetScalar(ourLevel, theirLevel, weWon);
if (scalar == 0)
return 0;
int xp = 25 * scalar;
if (!weWon)
xp = xp * GetLossFactor(ourLevel) / 100;
xp /= 100;
if (xp <= 0)
xp = 1;
return xp * (weWon ? 1 : -1);
}
private int Swap(int idx, int newIdx)
{
LadderEntry hold = Entries[idx];
Entries[idx] = Entries[newIdx];
Entries[newIdx] = hold;
Entries[idx].Index = idx;
Entries[newIdx].Index = newIdx;
return newIdx;
}
public void UpdateEntry(LadderEntry entry)
{
int index = entry.Index;
if (index >= 0 && index < Entries.Count)
{
while (index - 1 >= 0 && (entry.CompareTo(Entries[index - 1])) < 0)
index = Swap(index, index - 1);
while (index + 1 < Entries.Count && (entry.CompareTo(Entries[index + 1])) > 0)
index = Swap(index, index + 1);
}
}
public LadderEntry Find(Mobile mob)
{
if (m_Table.TryGetValue(mob, out LadderEntry entry))
{
m_Table[mob] = entry = new LadderEntry(mob, this);
entry.Index = Entries.Count;
Entries.Add(entry);
}
return entry;
}
public LadderEntry FindNoCreate(Mobile mob)
{
m_Table.TryGetValue(mob, out LadderEntry entry);
return entry;
}
public void Serialize(GenericWriter writer)
{
writer.WriteEncodedInt(1); // version;
writer.WriteEncodedInt(Entries.Count);
for (int i = 0; i < Entries.Count; ++i)
Entries[i].Serialize(writer);
}
}
public class LadderEntry : IComparable<LadderEntry>
{
private int m_Experience;
private Ladder m_Ladder;
public LadderEntry(Mobile mob, Ladder ladder)
{
m_Ladder = ladder;
Mobile = mob;
}
public LadderEntry(GenericReader reader, Ladder ladder, int version)
{
m_Ladder = ladder;
switch (version)
{
case 1:
case 0:
{
Mobile = reader.ReadMobile();
m_Experience = reader.ReadEncodedInt();
Wins = reader.ReadEncodedInt();
Losses = reader.ReadEncodedInt();
break;
}
}
}
public Mobile Mobile{ get; }
[CommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)]
public int Experience
{
get => m_Experience;
set
{
m_Experience = value;
m_Ladder.UpdateEntry(this);
}
}
[CommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)]
public int Wins{ get; set; }
[CommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)]
public int Losses{ get; set; }
public int Index{ get; set; }
[CommandProperty(AccessLevel.GameMaster)]
public int Rank => Index;
public int CompareTo(LadderEntry l)
{
return (l?.m_Experience ?? 0) - m_Experience;
}
public void Serialize(GenericWriter writer)
{
writer.Write(Mobile);
writer.WriteEncodedInt(m_Experience);
writer.WriteEncodedInt(Wins);
writer.WriteEncodedInt(Losses);
}
}
}

View file

@ -0,0 +1,231 @@
using System;
using System.Text;
using Server.Mobiles;
using Server.Network;
namespace Server.Engines.ConPVP
{
public class Participant
{
public Participant(DuelContext context, int count)
{
Context = context;
//m_Stakes = new StakesContainer( context, this );
Resize(count);
}
public int Count => Players.Length;
public DuelPlayer[] Players{ get; private set; }
public DuelContext Context{ get; }
public TourneyParticipant TourneyPart{ get; set; }
public int FilledSlots
{
get
{
int count = 0;
for (int i = 0; i < Players.Length; ++i)
if (Players[i] != null)
++count;
return count;
}
}
public bool HasOpenSlot
{
get
{
for (int i = 0; i < Players.Length; ++i)
if (Players[i] == null)
return true;
return false;
}
}
public bool Eliminated
{
get
{
for (int i = 0; i < Players.Length; ++i)
if (Players[i]?.Eliminated == false)
return false;
return true;
}
}
public string NameList
{
get
{
StringBuilder sb = new StringBuilder();
for (int i = 0; i < Players.Length; ++i)
{
if (Players[i] == null)
continue;
Mobile mob = Players[i].Mobile;
if (sb.Length > 0)
sb.Append(", ");
sb.Append(mob.Name);
}
if (sb.Length == 0)
return "Empty";
return sb.ToString();
}
}
public DuelPlayer Find(Mobile mob)
{
if (mob is PlayerMobile pm)
{
if (pm.DuelContext == Context && pm.DuelPlayer.Participant == this)
return pm.DuelPlayer;
return null;
}
for (int i = 0; i < Players.Length; ++i)
if (Players[i]?.Mobile == mob)
return Players[i];
return null;
}
public bool Contains(Mobile mob)
{
return Find(mob) != null;
}
public void Broadcast(int hue, string message, string nonLocalOverhead, string localOverhead)
{
for (int i = 0; i < Players.Length; ++i)
if (Players[i] != null)
{
if (message != null)
Players[i].Mobile.SendMessage(hue, message);
if (nonLocalOverhead != null)
Players[i].Mobile.NonlocalOverheadMessage(MessageType.Regular, hue, false,
string.Format(nonLocalOverhead, Players[i].Mobile.Name,
Players[i].Mobile.Female ? "her" : "his"));
if (localOverhead != null)
Players[i].Mobile.LocalOverheadMessage(MessageType.Regular, hue, false, localOverhead);
}
}
public void Nullify(DuelPlayer player)
{
if (player == null)
return;
int index = Array.IndexOf(Players, player);
if (index == -1)
return;
Players[index] = null;
}
public void Remove(DuelPlayer player)
{
if (player == null)
return;
int index = Array.IndexOf(Players, player);
if (index == -1)
return;
DuelPlayer[] old = Players;
Players = new DuelPlayer[old.Length - 1];
for (int i = 0; i < index; ++i)
Players[i] = old[i];
for (int i = index + 1; i < old.Length; ++i)
Players[i - 1] = old[i];
}
public void Remove(Mobile player)
{
Remove(Find(player));
}
public void Add(Mobile player)
{
if (Contains(player))
return;
for (int i = 0; i < Players.Length; ++i)
if (Players[i] == null)
{
Players[i] = new DuelPlayer(player, this);
return;
}
Resize(Players.Length + 1);
Players[Players.Length - 1] = new DuelPlayer(player, this);
}
public void Resize(int count)
{
DuelPlayer[] old = Players;
Players = new DuelPlayer[count];
if (old != null)
{
int ct = 0;
for (int i = 0; i < old.Length; ++i)
if (old[i] != null && ct < count)
Players[ct++] = old[i];
}
}
}
public class DuelPlayer
{
private bool m_Eliminated;
public DuelPlayer(Mobile mob, Participant p)
{
Mobile = mob;
Participant = p;
if (mob is PlayerMobile mobile)
mobile.DuelPlayer = this;
}
public Mobile Mobile{ get; }
public bool Ready{ get; set; }
public bool Eliminated
{
get => m_Eliminated;
set
{
m_Eliminated = value;
if (Participant.Context.m_Tournament != null && m_Eliminated)
{
Participant.Context.m_Tournament.OnEliminated(this);
Mobile.SendEverything();
}
}
}
public Participant Participant{ get; set; }
}
}

View file

@ -0,0 +1,277 @@
using System.Collections.Generic;
using Server.Gumps;
using Server.Network;
namespace Server.Engines.ConPVP
{
public class PreferencesController : Item
{
[Constructible]
public PreferencesController() : base(0x1B7A)
{
Visible = false;
Movable = false;
Preferences = new Preferences();
if (Preferences.Instance == null)
Preferences.Instance = Preferences;
else
Delete();
}
public PreferencesController(Serial serial) : base(serial)
{
}
[CommandProperty( AccessLevel.Administrator )]
public Preferences Preferences{ get; private set; }
public override string DefaultName => "preferences controller";
public override void Delete()
{
if (Preferences.Instance != Preferences)
base.Delete();
}
public override void Serialize(GenericWriter writer)
{
base.Serialize(writer);
writer.Write(0);
Preferences.Serialize(writer);
}
public override void Deserialize(GenericReader reader)
{
base.Deserialize(reader);
int version = reader.ReadInt();
switch (version)
{
case 0:
{
Preferences = new Preferences(reader);
Preferences.Instance = Preferences;
break;
}
}
}
}
public class Preferences
{
private Dictionary<Mobile, PreferencesEntry> m_Table;
public Preferences()
{
m_Table = new Dictionary<Mobile, PreferencesEntry>();
Entries = new List<PreferencesEntry>();
}
public Preferences(GenericReader reader)
{
int version = reader.ReadEncodedInt();
switch (version)
{
case 0:
{
int count = reader.ReadEncodedInt();
m_Table = new Dictionary<Mobile, PreferencesEntry>(count);
Entries = new List<PreferencesEntry>(count);
for (int i = 0; i < count; ++i)
{
PreferencesEntry entry = new PreferencesEntry(reader, version);
if (entry.Mobile != null)
{
m_Table[entry.Mobile] = entry;
Entries.Add(entry);
}
}
break;
}
}
}
public List<PreferencesEntry> Entries{ get; }
public static Preferences Instance{ get; set; }
public PreferencesEntry Find(Mobile mob)
{
if (m_Table.TryGetValue(mob, out PreferencesEntry entry))
{
m_Table[mob] = entry = new PreferencesEntry(mob);
Entries.Add(entry);
}
return entry;
}
public void Serialize(GenericWriter writer)
{
writer.WriteEncodedInt(0); // version;
writer.WriteEncodedInt(Entries.Count);
for (int i = 0; i < Entries.Count; ++i)
Entries[i].Serialize(writer);
}
}
public class PreferencesEntry
{
public PreferencesEntry(Mobile mob)
{
Mobile = mob;
Disliked = new List<string>();
}
public PreferencesEntry(GenericReader reader, int version)
{
switch (version)
{
case 0:
{
Mobile = reader.ReadMobile();
int count = reader.ReadEncodedInt();
Disliked = new List<string>(count);
for (int i = 0; i < count; ++i)
Disliked.Add(reader.ReadString());
break;
}
}
}
public Mobile Mobile{ get; }
public List<string> Disliked{ get; }
public void Serialize(GenericWriter writer)
{
writer.Write(Mobile);
writer.WriteEncodedInt(Disliked.Count);
for (int i = 0; i < Disliked.Count; ++i)
writer.Write(Disliked[i]);
}
}
public class PreferencesGump : Gump
{
private int m_ColumnX = 12;
private PreferencesEntry m_Entry;
public PreferencesGump(Mobile from, Preferences prefs) : base(50, 50)
{
m_Entry = prefs.Find(from);
if (m_Entry == null)
return;
List<Arena> arenas = Arena.Arenas;
AddPage(0);
int height = 12 + 20 + arenas.Count * 31 + 24 + 12;
AddBackground(0, 0, 499 + 40 - 365, height, 0x2436);
for (int i = 1; i < arenas.Count; i += 2)
AddImageTiled(12, 32 + i * 31, 475 + 40 - 365, 30, 0x2430);
AddAlphaRegion(10, 10, 479 + 40 - 365, height - 20);
AddColumnHeader(35, null);
AddColumnHeader(115, "Arena");
AddButton(499 + 40 - 365 - 12 - 63 - 4 - 63, height - 12 - 24, 247, 248, 1);
AddButton(499 + 40 - 365 - 12 - 63, height - 12 - 24, 241, 242, 2);
for (int i = 0; i < arenas.Count; ++i)
{
Arena ar = arenas[i];
string name = ar.Name ?? "(no name)";
int x = 12;
int y = 32 + i * 31;
int color = 0xCCFFCC;
AddCheck(x + 3, y + 1, 9730, 9727, m_Entry.Disliked.Contains(name), i);
x += 35;
AddBorderedText(x + 5, y + 5, 115 - 5, name, color, 0);
}
}
public override void OnResponse(NetState sender, RelayInfo info)
{
if (m_Entry == null)
return;
if (info.ButtonID != 1)
return;
m_Entry.Disliked.Clear();
List<Arena> arenas = Arena.Arenas;
for (int i = 0; i < info.Switches.Length; ++i)
{
int idx = info.Switches[i];
if (idx >= 0 && idx < arenas.Count)
m_Entry.Disliked.Add(arenas[idx].Name);
}
}
public string Center(string text)
{
return $"<CENTER>{text}</CENTER>";
}
public string Color(string text, int color)
{
return $"<BASEFONT COLOR=#{color:X6}>{text}</BASEFONT>";
}
private void AddBorderedText(int x, int y, int width, string text, int color, int borderColor)
{
AddColoredText(x, y, width, text, color);
}
private void AddColoredText(int x, int y, int width, string text, int color)
{
if (color == 0)
AddHtml(x, y, width, 20, text);
else
AddHtml(x, y, width, 20, Color(text, color));
}
private void AddColumnHeader(int width, string name)
{
AddBackground(m_ColumnX, 12, width, 20, 0x242C);
AddImageTiled(m_ColumnX + 2, 14, width - 4, 16, 0x2430);
if (name != null)
AddBorderedText(m_ColumnX, 13, width, Center(name), 0xFFFFFF, 0);
m_ColumnX += width;
}
}
}

View file

@ -0,0 +1,102 @@
using System.Collections;
using System.Collections.Generic;
namespace Server.Engines.ConPVP
{
public class Ruleset
{
public Ruleset(RulesetLayout layout)
{
Layout = layout;
Options = new BitArray(layout.TotalLength);
}
public RulesetLayout Layout{ get; }
public BitArray Options{ get; private set; }
public string Title{ get; set; }
public Ruleset Base{ get; private set; }
public List<Ruleset> Flavors{ get; } = new List<Ruleset>();
public bool Changed{ get; set; }
public void ApplyDefault(Ruleset newDefault)
{
Base = newDefault;
Changed = false;
Options = new BitArray(newDefault.Options);
ApplyFlavorsTo(this);
}
public void ApplyFlavorsTo(Ruleset ruleset)
{
for (int i = 0; i < Flavors.Count; ++i)
{
Ruleset flavor = Flavors[i];
Options.Or(flavor.Options);
}
}
public void AddFlavor(Ruleset flavor)
{
if (Flavors.Contains(flavor))
return;
Flavors.Add(flavor);
Options.Or(flavor.Options);
}
public void RemoveFlavor(Ruleset flavor)
{
if (!Flavors.Contains(flavor))
return;
Flavors.Remove(flavor);
Options.And(flavor.Options.Not());
flavor.Options.Not();
}
public void SetOptionRange(string title, bool value)
{
RulesetLayout layout = Layout.FindByTitle(title);
if (layout == null)
return;
for (int i = 0; i < layout.TotalLength; ++i)
Options[i + layout.Offset] = value;
Changed = true;
}
public bool GetOption(string title, string option)
{
int index = 0;
RulesetLayout layout = Layout.FindByOption(title, option, ref index);
if (layout == null)
return true;
return Options[layout.Offset + index];
}
public void SetOption(string title, string option, bool value)
{
int index = 0;
RulesetLayout layout = Layout.FindByOption(title, option, ref index);
if (layout == null)
return;
Options[layout.Offset + index] = value;
Changed = true;
}
}
}

View file

@ -0,0 +1,764 @@
using System;
using System.Collections.Generic;
namespace Server.Engines.ConPVP
{
public class RulesetLayout
{
private static RulesetLayout m_Root;
public RulesetLayout(string title, string[] options) : this(title, title, new RulesetLayout[0], options)
{
}
public RulesetLayout(string title, string description, string[] options) : this(title, description,
new RulesetLayout[0], options)
{
}
public RulesetLayout(string title, RulesetLayout[] children) : this(title, title, children, new string[0])
{
}
public RulesetLayout(string title, string description, RulesetLayout[] children) : this(title, description, children,
new string[0])
{
}
public RulesetLayout(string title, RulesetLayout[] children, string[] options) : this(title, title, children,
options)
{
}
public RulesetLayout(string title, string description, RulesetLayout[] children, string[] options)
{
Title = title;
Description = description;
Children = children;
Options = options;
for (int i = 0; i < children.Length; ++i)
children[i].Parent = this;
}
public static RulesetLayout Root
{
get
{
if (m_Root != null)
return m_Root;
List<RulesetLayout> entries = new List<RulesetLayout>
{
new RulesetLayout("Spells",
new[]
{
new RulesetLayout("1st Circle", "Spells",
new[]
{
"Reactive Armor", "Clumsy", "Create Food", "Feeblemind", "Heal", "Magic Arrow", "Night Sight",
"Weaken"
}),
new RulesetLayout("2nd Circle", "Spells",
new[] { "Agility", "Cunning", "Cure", "Harm", "Magic Trap", "Untrap", "Protection", "Strength" }),
new RulesetLayout("3rd Circle", "Spells",
new[]
{
"Bless", "Fireball", "Magic Lock", "Poison", "Telekinesis", "Teleport", "Unlock Spell",
"Wall of Stone"
}),
new RulesetLayout("4th Circle", "Spells",
new[]
{
"Arch Cure", "Arch Protection", "Curse", "Fire Field", "Greater Heal", "Lightning", "Mana Drain",
"Recall"
}),
new RulesetLayout("5th Circle", "Spells",
new[]
{
"Blade Spirits", "Dispel Field", "Incognito", "Magic Reflection", "Mind Blast", "Paralyze",
"Poison Field", "Summon Creature"
}),
new RulesetLayout("6th Circle", "Spells",
new[]
{
"Dispel", "Energy Bolt", "Explosion", "Invisibility", "Mark", "Mass Curse", "Paralyze Field",
"Reveal"
}),
new RulesetLayout("7th Circle", "Spells",
new[]
{
"Chain Lightning", "Energy Field", "Flame Strike", "Gate Travel", "Mana Vampire", "Mass Dispel",
"Meteor Swarm", "Polymorph"
}),
new RulesetLayout("8th Circle", "Spells",
new[]
{
"Earthquake", "Energy Vortex", "Resurrection", "Air Elemental", "Summon Daemon", "Earth Elemental",
"Fire Elemental", "Water Elemental"
})
})
};
if (Core.AOS)
{
entries.Add(new RulesetLayout("Chivalry", new[]
{
"Cleanse by Fire",
"Close Wounds",
"Consecrate Weapon",
"Dispel Evil",
"Divine Fury",
"Enemy of One",
"Holy Light",
"Noble Sacrifice",
"Remove Curse",
"Sacred Journey"
}));
entries.Add(new RulesetLayout("Necromancy", new[]
{
"Animate Dead",
"Blood Oath",
"Corpse Skin",
"Curse Weapon",
"Evil Omen",
"Horrific Beast",
"Lich Form",
"Mind Rot",
"Pain Spike",
"Poison Strike",
"Strangle",
"Summon Familiar",
"Vampiric Embrace",
"Vengeful Spirit",
"Wither",
"Wraith Form"
}));
if (Core.SE)
{
entries.Add(new RulesetLayout("Bushido", new[]
{
"Confidence",
"Counter Attack",
"Evasion",
"Honorable Execution",
"Lightning Strike",
"Momentum Strike"
}));
entries.Add(new RulesetLayout("Ninjitsu", new[]
{
"Animal Form",
"Backstab",
"Death Strike",
"Focus Attack",
"Ki Attack",
"Mirror Image",
"Shadow Jump",
"Suprise Attack"
}));
if (Core.ML)
entries.Add(new RulesetLayout("Spellweaving", new[]
{
"Arcane Circle",
"Arcane Empowerment",
"Attune Weapon",
"Dryad Allure",
"Essence of Wind",
"Ethereal Voyage",
"Gift of Life",
"Gift of Renewal",
"Immolating Weapon",
"Nature's Fury",
"Reaper Form",
"Summon Fey",
"Summon Fiend",
"Thunderstorm",
"Wildfire",
"Word of Death"
}));
}
}
if (Core.AOS)
{
if (Core.SE)
entries.Add(new RulesetLayout("Combat Abilities", new[]
{
"Stun",
"Disarm",
"Armor Ignore",
"Bleed Attack",
"Concussion Blow",
"Crushing Blow",
"Disarm",
"Dismount",
"Double Strike",
"Infectious Strike",
"Mortal Strike",
"Moving Shot",
"Paralyzing Blow",
"Shadow Strike",
"Whirlwind Attack",
"Riding Swipe",
"Frenzied Whirlwind",
"Block",
"Defense Mastery",
"Nerve Strike",
"Talon Strike",
"Feint",
"Dual Wield",
"Double Shot",
"Armor Pierce"
}));
else
entries.Add(new RulesetLayout("Combat Abilities", new[]
{
"Stun",
"Disarm",
"Armor Ignore",
"Bleed Attack",
"Concussion Blow",
"Crushing Blow",
"Disarm",
"Dismount",
"Double Strike",
"Infectious Strike",
"Mortal Strike",
"Moving Shot",
"Paralyzing Blow",
"Shadow Strike",
"Whirlwind Attack"
}));
}
else
{
entries.Add(new RulesetLayout("Combat Abilities", new[]
{
"Stun",
"Disarm",
"Concussion Blow",
"Crushing Blow",
"Paralyzing Blow"
}));
}
entries.Add(new RulesetLayout("Skills", new[]
{
"Anatomy",
"Detect Hidden",
"Evaluating Intelligence",
"Hiding",
"Poisoning",
"Snooping",
"Stealing",
"Spirit Speak",
"Stealth"
}));
if (Core.AOS)
{
entries.Add(new RulesetLayout("Weapons", new[]
{
"Magical",
"Melee",
"Ranged",
"Poisoned",
"Wrestling"
}));
entries.Add(new RulesetLayout("Armor", new[]
{
"Magical",
"Shields"
}));
}
else
{
entries.Add(new RulesetLayout("Weapons", new[]
{
"Magical",
"Melee",
"Ranged",
"Poisoned",
"Wrestling",
"Runics"
}));
entries.Add(new RulesetLayout("Armor", new[]
{
"Magical",
"Shields",
"Colored"
}));
}
if (Core.SE)
entries.Add(new RulesetLayout("Items", new[]
{
new RulesetLayout("Potions", new[]
{
"Agility",
"Cure",
"Explosion",
"Heal",
"Nightsight",
"Poison",
"Refresh",
"Strength"
})
},
new[]
{
"Bandages",
"Wands",
"Trapped Containers",
"Bolas",
"Mounts",
"Orange Petals",
"Shurikens",
"Fukiya Darts",
"Fire Horns"
}));
else
entries.Add(new RulesetLayout("Items", new[]
{
new RulesetLayout("Potions", new[]
{
"Agility",
"Cure",
"Explosion",
"Heal",
"Nightsight",
"Poison",
"Refresh",
"Strength"
})
},
new[]
{
"Bandages",
"Wands",
"Trapped Containers",
"Bolas",
"Mounts",
"Orange Petals",
"Fire Horns"
}));
m_Root = new RulesetLayout("Rules", entries.ToArray());
m_Root.ComputeOffsets();
// Set up default rulesets
if (!Core.AOS)
{
#region Mage 5x
Ruleset m5x = new Ruleset(m_Root);
m5x.Title = "Mage 5x";
m5x.SetOptionRange("Spells", true);
m5x.SetOption("Spells", "Wall of Stone", false);
m5x.SetOption("Spells", "Fire Field", false);
m5x.SetOption("Spells", "Poison Field", false);
m5x.SetOption("Spells", "Energy Field", false);
m5x.SetOption("Spells", "Reactive Armor", false);
m5x.SetOption("Spells", "Protection", false);
m5x.SetOption("Spells", "Teleport", false);
m5x.SetOption("Spells", "Wall of Stone", false);
m5x.SetOption("Spells", "Arch Protection", false);
m5x.SetOption("Spells", "Recall", false);
m5x.SetOption("Spells", "Blade Spirits", false);
m5x.SetOption("Spells", "Incognito", false);
m5x.SetOption("Spells", "Magic Reflection", false);
m5x.SetOption("Spells", "Paralyze", false);
m5x.SetOption("Spells", "Summon Creature", false);
m5x.SetOption("Spells", "Invisibility", false);
m5x.SetOption("Spells", "Mark", false);
m5x.SetOption("Spells", "Paralyze Field", false);
m5x.SetOption("Spells", "Energy Field", false);
m5x.SetOption("Spells", "Gate Travel", false);
m5x.SetOption("Spells", "Polymorph", false);
m5x.SetOption("Spells", "Energy Vortex", false);
m5x.SetOption("Spells", "Air Elemental", false);
m5x.SetOption("Spells", "Summon Daemon", false);
m5x.SetOption("Spells", "Earth Elemental", false);
m5x.SetOption("Spells", "Fire Elemental", false);
m5x.SetOption("Spells", "Water Elemental", false);
m5x.SetOption("Spells", "Earthquake", false);
m5x.SetOption("Spells", "Meteor Swarm", false);
m5x.SetOption("Spells", "Chain Lightning", false);
m5x.SetOption("Spells", "Resurrection", false);
m5x.SetOption("Weapons", "Wrestling", true);
m5x.SetOption("Skills", "Anatomy", true);
m5x.SetOption("Skills", "Detect Hidden", true);
m5x.SetOption("Skills", "Evaluating Intelligence", true);
m5x.SetOption("Items", "Trapped Containers", true);
#endregion
#region Mage 7x
Ruleset m7x = new Ruleset(m_Root);
m7x.Title = "Mage 7x";
m7x.SetOptionRange("Spells", true);
m7x.SetOption("Spells", "Wall of Stone", false);
m7x.SetOption("Spells", "Fire Field", false);
m7x.SetOption("Spells", "Poison Field", false);
m7x.SetOption("Spells", "Energy Field", false);
m7x.SetOption("Spells", "Reactive Armor", false);
m7x.SetOption("Spells", "Protection", false);
m7x.SetOption("Spells", "Teleport", false);
m7x.SetOption("Spells", "Wall of Stone", false);
m7x.SetOption("Spells", "Arch Protection", false);
m7x.SetOption("Spells", "Recall", false);
m7x.SetOption("Spells", "Blade Spirits", false);
m7x.SetOption("Spells", "Incognito", false);
m7x.SetOption("Spells", "Magic Reflection", false);
m7x.SetOption("Spells", "Paralyze", false);
m7x.SetOption("Spells", "Summon Creature", false);
m7x.SetOption("Spells", "Invisibility", false);
m7x.SetOption("Spells", "Mark", false);
m7x.SetOption("Spells", "Paralyze Field", false);
m7x.SetOption("Spells", "Energy Field", false);
m7x.SetOption("Spells", "Gate Travel", false);
m7x.SetOption("Spells", "Polymorph", false);
m7x.SetOption("Spells", "Energy Vortex", false);
m7x.SetOption("Spells", "Air Elemental", false);
m7x.SetOption("Spells", "Summon Daemon", false);
m7x.SetOption("Spells", "Earth Elemental", false);
m7x.SetOption("Spells", "Fire Elemental", false);
m7x.SetOption("Spells", "Water Elemental", false);
m7x.SetOption("Spells", "Earthquake", false);
m7x.SetOption("Spells", "Meteor Swarm", false);
m7x.SetOption("Spells", "Chain Lightning", false);
m7x.SetOption("Spells", "Resurrection", false);
m7x.SetOption("Combat Abilities", "Stun", true);
m7x.SetOption("Skills", "Anatomy", true);
m7x.SetOption("Skills", "Detect Hidden", true);
m7x.SetOption("Skills", "Poisoning", true);
m7x.SetOption("Skills", "Evaluating Intelligence", true);
m7x.SetOption("Weapons", "Wrestling", true);
m7x.SetOption("Potions", "Refresh", true);
m7x.SetOption("Items", "Trapped Containers", true);
m7x.SetOption("Items", "Bandages", true);
#endregion
#region Standard 7x
Ruleset s7x = new Ruleset(m_Root);
s7x.Title = "Standard 7x";
s7x.SetOptionRange("Spells", true);
s7x.SetOption("Spells", "Wall of Stone", false);
s7x.SetOption("Spells", "Fire Field", false);
s7x.SetOption("Spells", "Poison Field", false);
s7x.SetOption("Spells", "Energy Field", false);
s7x.SetOption("Spells", "Teleport", false);
s7x.SetOption("Spells", "Wall of Stone", false);
s7x.SetOption("Spells", "Arch Protection", false);
s7x.SetOption("Spells", "Recall", false);
s7x.SetOption("Spells", "Blade Spirits", false);
s7x.SetOption("Spells", "Incognito", false);
s7x.SetOption("Spells", "Magic Reflection", false);
s7x.SetOption("Spells", "Paralyze", false);
s7x.SetOption("Spells", "Summon Creature", false);
s7x.SetOption("Spells", "Invisibility", false);
s7x.SetOption("Spells", "Mark", false);
s7x.SetOption("Spells", "Paralyze Field", false);
s7x.SetOption("Spells", "Energy Field", false);
s7x.SetOption("Spells", "Gate Travel", false);
s7x.SetOption("Spells", "Polymorph", false);
s7x.SetOption("Spells", "Energy Vortex", false);
s7x.SetOption("Spells", "Air Elemental", false);
s7x.SetOption("Spells", "Summon Daemon", false);
s7x.SetOption("Spells", "Earth Elemental", false);
s7x.SetOption("Spells", "Fire Elemental", false);
s7x.SetOption("Spells", "Water Elemental", false);
s7x.SetOption("Spells", "Earthquake", false);
s7x.SetOption("Spells", "Meteor Swarm", false);
s7x.SetOption("Spells", "Chain Lightning", false);
s7x.SetOption("Spells", "Resurrection", false);
s7x.SetOptionRange("Combat Abilities", true);
s7x.SetOption("Skills", "Anatomy", true);
s7x.SetOption("Skills", "Detect Hidden", true);
s7x.SetOption("Skills", "Poisoning", true);
s7x.SetOption("Skills", "Evaluating Intelligence", true);
s7x.SetOptionRange("Weapons", true);
s7x.SetOption("Weapons", "Runics", false);
s7x.SetOptionRange("Armor", true);
s7x.SetOption("Potions", "Refresh", true);
s7x.SetOption("Items", "Bandages", true);
s7x.SetOption("Items", "Trapped Containers", true);
#endregion
m_Root.Defaults = new[] { m5x, m7x, s7x };
}
else
{
#region Standard All Skills
Ruleset all = new Ruleset(m_Root);
all.Title = "Standard All Skills";
all.SetOptionRange("Spells", true);
all.SetOption("Spells", "Wall of Stone", false);
all.SetOption("Spells", "Fire Field", false);
all.SetOption("Spells", "Poison Field", false);
all.SetOption("Spells", "Energy Field", false);
all.SetOption("Spells", "Teleport", false);
all.SetOption("Spells", "Wall of Stone", false);
all.SetOption("Spells", "Arch Protection", false);
all.SetOption("Spells", "Recall", false);
all.SetOption("Spells", "Blade Spirits", false);
all.SetOption("Spells", "Incognito", false);
all.SetOption("Spells", "Magic Reflection", false);
all.SetOption("Spells", "Paralyze", false);
all.SetOption("Spells", "Summon Creature", false);
all.SetOption("Spells", "Invisibility", false);
all.SetOption("Spells", "Mark", false);
all.SetOption("Spells", "Paralyze Field", false);
all.SetOption("Spells", "Energy Field", false);
all.SetOption("Spells", "Gate Travel", false);
all.SetOption("Spells", "Polymorph", false);
all.SetOption("Spells", "Energy Vortex", false);
all.SetOption("Spells", "Air Elemental", false);
all.SetOption("Spells", "Summon Daemon", false);
all.SetOption("Spells", "Earth Elemental", false);
all.SetOption("Spells", "Fire Elemental", false);
all.SetOption("Spells", "Water Elemental", false);
all.SetOption("Spells", "Earthquake", false);
all.SetOption("Spells", "Meteor Swarm", false);
all.SetOption("Spells", "Chain Lightning", false);
all.SetOption("Spells", "Resurrection", false);
all.SetOptionRange("Necromancy", true);
all.SetOption("Necromancy", "Summon Familiar", false);
all.SetOption("Necromancy", "Vengeful Spirit", false);
all.SetOption("Necromancy", "Animate Dead", false);
all.SetOption("Necromancy", "Wither", false);
all.SetOption("Necromancy", "Poison Strike", false);
all.SetOptionRange("Chivalry", true);
all.SetOption("Chivalry", "Sacred Journey", false);
all.SetOption("Chivalry", "Enemy of One", false);
all.SetOption("Chivalry", "Noble Sacrifice", false);
all.SetOptionRange("Combat Abilities", true);
all.SetOption("Combat Abilities", "Paralyzing Blow", false);
all.SetOption("Combat Abilities", "Shadow Strike", false);
all.SetOption("Skills", "Anatomy", true);
all.SetOption("Skills", "Detect Hidden", true);
all.SetOption("Skills", "Poisoning", true);
all.SetOption("Skills", "Spirit Speak", true);
all.SetOption("Skills", "Evaluating Intelligence", true);
all.SetOptionRange("Weapons", true);
all.SetOption("Weapons", "Poisoned", false);
all.SetOptionRange("Armor", true);
all.SetOptionRange("Ninjitsu", true);
all.SetOption("Ninjitsu", "Animal Form", false);
all.SetOption("Ninjitsu", "Mirror Image", false);
all.SetOption("Ninjitsu", "Backstab", false);
all.SetOption("Ninjitsu", "Suprise Attack", false);
all.SetOption("Ninjitsu", "Shadow Jump", false);
all.SetOptionRange("Bushido", true);
all.SetOptionRange("Spellweaving", true);
all.SetOption("Spellweaving", "Gift of Life", false);
all.SetOption("Spellweaving", "Summon Fey", false);
all.SetOption("Spellweaving", "Summon Fiend", false);
all.SetOption("Spellweaving", "Nature's Fury", false);
all.SetOption("Potions", "Refresh", true);
all.SetOption("Items", "Bandages", true);
all.SetOption("Items", "Trapped Containers", true);
m_Root.Defaults = new[] { all };
#endregion
}
// Set up flavors
Ruleset pots = new Ruleset(m_Root) { Title = "Potions" };
pots.SetOptionRange("Potions", true);
pots.SetOption("Potions", "Explosion", false);
Ruleset para = new Ruleset(m_Root) { Title = "Paralyze" };
para.SetOption("Spells", "Paralyze", true);
para.SetOption("Spells", "Paralyze Field", true);
para.SetOption("Combat Abilities", "Paralyzing Blow", true);
Ruleset fields = new Ruleset(m_Root) { Title = "Fields" };
fields.SetOption("Spells", "Wall of Stone", true);
fields.SetOption("Spells", "Fire Field", true);
fields.SetOption("Spells", "Poison Field", true);
fields.SetOption("Spells", "Energy Field", true);
fields.SetOption("Spells", "Wildfire", true);
Ruleset area = new Ruleset(m_Root) { Title = "Area Effect" };
area.SetOption("Spells", "Earthquake", true);
area.SetOption("Spells", "Meteor Swarm", true);
area.SetOption("Spells", "Chain Lightning", true);
area.SetOption("Necromancy", "Wither", true);
area.SetOption("Necromancy", "Poison Strike", true);
Ruleset summons = new Ruleset(m_Root) { Title = "Summons" };
summons.SetOption("Spells", "Blade Spirits", true);
summons.SetOption("Spells", "Energy Vortex", true);
summons.SetOption("Spells", "Air Elemental", true);
summons.SetOption("Spells", "Summon Daemon", true);
summons.SetOption("Spells", "Earth Elemental", true);
summons.SetOption("Spells", "Fire Elemental", true);
summons.SetOption("Spells", "Water Elemental", true);
summons.SetOption("Necromancy", "Summon Familiar", true);
summons.SetOption("Necromancy", "Vengeful Spirit", true);
summons.SetOption("Necromancy", "Animate Dead", true);
summons.SetOption("Ninjitsu", "Mirror Image", true);
summons.SetOption("Spellweaving", "Summon Fey", true);
summons.SetOption("Spellweaving", "Summon Fiend", true);
summons.SetOption("Spellweaving", "Nature's Fury", true);
m_Root.Flavors = new[] { pots, para, fields, area, summons };
return m_Root;
}
}
public string Title{ get; }
public string Description{ get; }
public string[] Options{ get; }
public int Offset{ get; private set; }
public int TotalLength{ get; private set; }
public RulesetLayout Parent{ get; private set; }
public RulesetLayout[] Children{ get; }
public Ruleset[] Defaults{ get; set; }
public Ruleset[] Flavors{ get; set; }
public RulesetLayout FindByTitle(string title)
{
if (Title == title)
return this;
for (int i = 0; i < Children.Length; ++i)
{
RulesetLayout layout = Children[i].FindByTitle(title);
if (layout != null)
return layout;
}
return null;
}
public string FindByIndex(int index)
{
if (index >= Offset && index < Offset + Options.Length)
return Description + ": " + Options[index - Offset];
for (int i = 0; i < Children.Length; ++i)
{
string opt = Children[i].FindByIndex(index);
if (opt != null)
return opt;
}
return null;
}
public RulesetLayout FindByOption(string title, string option, ref int index)
{
if (title == null || Title == title)
{
index = GetOptionIndex(option);
if (index >= 0)
return this;
title = null;
}
for (int i = 0; i < Children.Length; ++i)
{
RulesetLayout layout = Children[i].FindByOption(title, option, ref index);
if (layout != null)
return layout;
}
return null;
}
public int GetOptionIndex(string option)
{
return Array.IndexOf(Options, option);
}
public void ComputeOffsets()
{
int offset = 0;
RecurseComputeOffsets(ref offset);
}
private int RecurseComputeOffsets(ref int offset)
{
Offset = offset;
offset += Options.Length;
TotalLength += Options.Length;
for (int i = 0; i < Children.Length; ++i)
TotalLength += Children[i].RecurseComputeOffsets(ref offset);
return TotalLength;
}
}
}

View file

@ -0,0 +1,66 @@
using Server.Factions;
using Server.Mobiles;
using Server.Regions;
namespace Server.Engines.ConPVP
{
public class SafeZone : GuardedRegion
{
public static readonly int SafeZonePriority = HouseRegion.HousePriority + 1;
/*public override bool AllowReds => true;*/
public SafeZone(Rectangle2D area, Point3D goloc, Map map, bool isGuarded) : base(null, map, SafeZonePriority, area)
{
GoLocation = goloc;
Disabled = !isGuarded;
Register();
}
public override bool AllowHousing(Mobile from, Point3D p)
{
return from.AccessLevel >= AccessLevel.GameMaster && base.AllowHousing(from, p);
}
public override bool OnMoveInto(Mobile m, Direction d, Point3D newLocation, Point3D oldLocation)
{
if (m.Player && Sigil.ExistsOn(m))
{
m.SendMessage(0x22, "You are holding a sigil and cannot enter this zone.");
return false;
}
PlayerMobile pm = m as PlayerMobile ??
(m is BaseCreature bc && bc.Summoned ?
bc.SummonMaster as PlayerMobile : null);
if (pm?.DuelContext?.StartedBeginCountdown == true)
return true;
if (DuelContext.CheckCombat(m))
{
m.SendMessage(0x22, "You have recently been in combat and cannot enter this zone.");
return false;
}
return base.OnMoveInto(m, d, newLocation, oldLocation);
}
public override void OnEnter(Mobile m)
{
m.SendMessage("You have entered a dueling safezone. No combat other than duels are allowed in this zone.");
}
public override void OnExit(Mobile m)
{
m.SendMessage("You have left a dueling safezone. Combat is now unrestricted.");
}
public override bool CanUseStuckMenu(Mobile m)
{
return false;
}
}
}

View file

@ -0,0 +1,910 @@
using System;
using System.Collections.Generic;
using System.Text;
using Server.Factions;
using Server.Items;
using Server.Network;
using Server.Regions;
namespace Server.Engines.ConPVP
{
public enum TournamentStage
{
Inactive,
Signup,
Fighting
}
public enum GroupingType
{
HighVsLow,
Nearest,
Random
}
public enum TieType
{
Random,
Highest,
Lowest,
FullElimination,
FullAdvancement
}
public enum TourneyType
{
Standard,
FreeForAll,
RandomTeam,
RedVsBlue,
Faction
}
[PropertyObject]
public class Tournament
{
private static readonly TimeSpan SliceInterval = TimeSpan.FromSeconds(12.0);
private int m_ParticipantsPerMatch;
private int m_PlayersPerParticipant;
public Tournament(GenericReader reader)
{
int version = reader.ReadEncodedInt();
switch (version)
{
case 5:
{
FactionRestricted = reader.ReadBool();
goto case 4;
}
case 4:
{
EventController = reader.ReadItem() as EventController;
goto case 3;
}
case 3:
{
SuddenDeathRounds = reader.ReadEncodedInt();
goto case 2;
}
case 2:
{
TourneyType = (TourneyType)reader.ReadEncodedInt();
goto case 1;
}
case 1:
{
GroupType = (GroupingType)reader.ReadEncodedInt();
TieType = (TieType)reader.ReadEncodedInt();
SignupPeriod = reader.ReadTimeSpan();
goto case 0;
}
case 0:
{
if (version < 3)
SuddenDeathRounds = 3;
m_ParticipantsPerMatch = reader.ReadEncodedInt();
m_PlayersPerParticipant = reader.ReadEncodedInt();
SignupPeriod = reader.ReadTimeSpan();
CurrentStage = TournamentStage.Inactive;
Pyramid = new TourneyPyramid();
Ruleset = new Ruleset(RulesetLayout.Root);
Ruleset.ApplyDefault(Ruleset.Layout.Defaults[0]);
Participants = new List<TourneyParticipant>();
Undefeated = new List<TourneyParticipant>();
Arenas = new List<Arena>();
break;
}
}
Timer.DelayCall(SliceInterval, SliceInterval, Slice);
}
public Tournament()
{
m_ParticipantsPerMatch = 2;
m_PlayersPerParticipant = 1;
Pyramid = new TourneyPyramid();
Ruleset = new Ruleset(RulesetLayout.Root);
Ruleset.ApplyDefault(Ruleset.Layout.Defaults[0]);
Participants = new List<TourneyParticipant>();
Undefeated = new List<TourneyParticipant>();
Arenas = new List<Arena>();
SignupPeriod = TimeSpan.FromMinutes(10.0);
Timer.DelayCall(SliceInterval, SliceInterval, Slice);
}
public bool IsNotoRestricted => TourneyType != TourneyType.Standard;
[CommandProperty(AccessLevel.GameMaster)]
public EventController EventController{ get; set; }
[CommandProperty(AccessLevel.GameMaster)]
public int SuddenDeathRounds{ get; set; }
[CommandProperty(AccessLevel.GameMaster)]
public TourneyType TourneyType{ get; set; }
[CommandProperty(AccessLevel.GameMaster)]
public GroupingType GroupType{ get; set; }
[CommandProperty(AccessLevel.GameMaster)]
public TieType TieType{ get; set; }
[CommandProperty(AccessLevel.GameMaster)]
public TimeSpan SuddenDeath{ get; set; }
public Ruleset Ruleset{ get; set; }
[CommandProperty(AccessLevel.GameMaster)]
public int ParticipantsPerMatch
{
get => m_ParticipantsPerMatch;
set
{
if (value < 2) value = 2;
else if (value > 10) value = 10;
m_ParticipantsPerMatch = value;
}
}
[CommandProperty(AccessLevel.GameMaster)]
public int PlayersPerParticipant
{
get => m_PlayersPerParticipant;
set
{
if (value < 1) value = 1;
else if (value > 10) value = 10;
m_PlayersPerParticipant = value;
}
}
[CommandProperty(AccessLevel.GameMaster)]
public int LevelRequirement{ get; set; }
[CommandProperty(AccessLevel.GameMaster)]
public bool FactionRestricted{ get; set; }
[CommandProperty(AccessLevel.GameMaster)]
public TimeSpan SignupPeriod{ get; set; }
[CommandProperty(AccessLevel.GameMaster)]
public DateTime SignupStart{ get; set; }
[CommandProperty(AccessLevel.GameMaster)]
public TournamentStage CurrentStage{ get; private set; }
public TournamentStage Stage
{
get => CurrentStage;
set => CurrentStage = value;
}
public TourneyPyramid Pyramid{ get; set; }
public List<Arena> Arenas{ get; set; }
public List<TourneyParticipant> Participants{ get; set; }
public List<TourneyParticipant> Undefeated{ get; set; }
public bool IsFactionRestricted => FactionRestricted || TourneyType == TourneyType.Faction;
public bool HasParticipant(Mobile mob)
{
for (int i = 0; i < Participants.Count; ++i)
{
if (Participants[i].Players.Contains(mob))
return true;
}
return false;
}
public void Serialize(GenericWriter writer)
{
writer.WriteEncodedInt(5); // version
writer.Write(FactionRestricted);
writer.Write(EventController);
writer.WriteEncodedInt(SuddenDeathRounds);
writer.WriteEncodedInt((int)TourneyType);
writer.WriteEncodedInt((int)GroupType);
writer.WriteEncodedInt((int)TieType);
writer.Write(SuddenDeath);
writer.WriteEncodedInt(m_ParticipantsPerMatch);
writer.WriteEncodedInt(m_PlayersPerParticipant);
writer.Write(SignupPeriod);
}
public void HandleTie(Arena arena, TourneyMatch match, List<TourneyParticipant> remaining)
{
if (remaining.Count == 1)
HandleWon(arena, match, remaining[0]);
if (remaining.Count < 2)
return;
StringBuilder sb = new StringBuilder();
sb.Append("The match has ended in a tie ");
sb.Append(remaining.Count == 2 ? "between " : "among ");
sb.Append(remaining.Count);
sb.Append(remaining[0].Players.Count == 1 ? " players: " : " teams: ");
bool hasAppended = false;
for (int j = 0; j < match.Participants.Count; ++j)
{
TourneyParticipant part = match.Participants[j];
if (remaining.Contains(part))
{
if (hasAppended)
sb.Append(", ");
sb.Append(part.NameList);
hasAppended = true;
}
else
{
Undefeated.Remove(part);
}
}
sb.Append(". ");
string whole = remaining.Count == 2 ? "both" : "all";
TieType tieType = TieType;
if (tieType == TieType.FullElimination && remaining.Count >= Undefeated.Count)
tieType = TieType.FullAdvancement;
switch (tieType)
{
case TieType.FullAdvancement:
{
sb.AppendFormat("In accordance with the rules, {0} parties are advanced.", whole);
break;
}
case TieType.FullElimination:
{
for (int j = 0; j < remaining.Count; ++j)
Undefeated.Remove(remaining[j]);
sb.AppendFormat("In accordance with the rules, {0} parties are eliminated.", whole);
break;
}
case TieType.Random:
{
TourneyParticipant advanced = remaining[Utility.Random(remaining.Count)];
for (int i = 0; i < remaining.Count; ++i)
if (remaining[i] != advanced)
Undefeated.Remove(remaining[i]);
if (advanced != null)
sb.AppendFormat("In accordance with the rules, {0} {1} advanced.", advanced.NameList,
advanced.Players.Count == 1 ? "is" : "are");
break;
}
case TieType.Highest:
{
TourneyParticipant advanced = null;
for (int i = 0; i < remaining.Count; ++i)
{
TourneyParticipant part = remaining[i];
if (advanced == null || part.TotalLadderXP > advanced.TotalLadderXP)
advanced = part;
}
for (int i = 0; i < remaining.Count; ++i)
if (remaining[i] != advanced)
Undefeated.Remove(remaining[i]);
if (advanced != null)
sb.AppendFormat("In accordance with the rules, {0} {1} advanced.", advanced.NameList,
advanced.Players.Count == 1 ? "is" : "are");
break;
}
case TieType.Lowest:
{
TourneyParticipant advanced = null;
for (int i = 0; i < remaining.Count; ++i)
{
TourneyParticipant part = remaining[i];
if (advanced == null || part.TotalLadderXP < advanced.TotalLadderXP)
advanced = part;
}
for (int i = 0; i < remaining.Count; ++i)
if (remaining[i] != advanced)
Undefeated.Remove(remaining[i]);
if (advanced != null)
sb.AppendFormat("In accordance with the rules, {0} {1} advanced.", advanced.NameList,
advanced.Players.Count == 1 ? "is" : "are");
break;
}
}
Alert(arena, sb.ToString());
}
public void OnEliminated(DuelPlayer player)
{
Participant part = player.Participant;
if (!part.Eliminated)
return;
if (TourneyType == TourneyType.FreeForAll)
{
int rem = 0;
for (int i = 0; i < part.Context.Participants.Count; ++i)
{
if (part.Context.Participants[i]?.Eliminated == false)
++rem;
}
TourneyParticipant tp = part.TourneyPart;
if (tp == null)
return;
if (rem == 1)
GiveAwards(tp.Players, TrophyRank.Silver, ComputeCashAward() / 2);
else if (rem == 2)
GiveAwards(tp.Players, TrophyRank.Bronze, ComputeCashAward() / 4);
}
}
public void HandleWon(Arena arena, TourneyMatch match, TourneyParticipant winner)
{
StringBuilder sb = new StringBuilder();
sb.Append("The match is complete. ");
sb.Append(winner.NameList);
if (winner.Players.Count > 1)
sb.Append(" have bested ");
else
sb.Append(" has bested ");
if (match.Participants.Count > 2)
sb.AppendFormat("{0} other {1}: ", match.Participants.Count - 1,
winner.Players.Count == 1 ? "players" : "teams");
bool hasAppended = false;
for (int j = 0; j < match.Participants.Count; ++j)
{
TourneyParticipant part = match.Participants[j];
if (part == winner)
continue;
Undefeated.Remove(part);
if (hasAppended)
sb.Append(", ");
sb.Append(part.NameList);
hasAppended = true;
}
sb.Append(".");
if (TourneyType == TourneyType.Standard)
Alert(arena, sb.ToString());
}
private int ComputeCashAward()
{
return Participants.Count * m_PlayersPerParticipant * 2500;
}
private void GiveAwards()
{
switch (TourneyType)
{
case TourneyType.FreeForAll:
{
if (Pyramid.Levels.Count < 1)
break;
PyramidLevel top = Pyramid.Levels[Pyramid.Levels.Count - 1];
if (top.FreeAdvance != null || top.Matches.Count != 1)
break;
TourneyMatch match = top.Matches[0];
TourneyParticipant winner = match.Winner;
if (winner != null)
GiveAwards(winner.Players, TrophyRank.Gold, ComputeCashAward());
break;
}
case TourneyType.Standard:
{
if (Pyramid.Levels.Count < 2)
break;
PyramidLevel top = Pyramid.Levels[Pyramid.Levels.Count - 1];
if (top.FreeAdvance != null || top.Matches.Count != 1)
break;
int cash = ComputeCashAward();
TourneyMatch match = top.Matches[0];
TourneyParticipant winner = match.Winner;
for (int i = 0; i < match.Participants.Count; ++i)
{
TourneyParticipant part = match.Participants[i];
if (part == winner)
GiveAwards(part.Players, TrophyRank.Gold, cash);
else
GiveAwards(part.Players, TrophyRank.Silver, cash / 2);
}
PyramidLevel next = Pyramid.Levels[Pyramid.Levels.Count - 2];
if (next.Matches.Count > 2)
break;
for (int i = 0; i < next.Matches.Count; ++i)
{
match = next.Matches[i];
winner = match.Winner;
for (int j = 0; j < match.Participants.Count; ++j)
{
TourneyParticipant part = match.Participants[j];
if (part != winner)
GiveAwards(part.Players, TrophyRank.Bronze, cash / 4);
}
}
break;
}
}
}
private void GiveAwards(List<Mobile> players, TrophyRank rank, int cash)
{
if (players.Count == 0)
return;
if (players.Count > 1)
cash /= players.Count - 1;
cash += 500;
cash /= 1000;
cash *= 1000;
StringBuilder sb = new StringBuilder();
if (TourneyType == TourneyType.FreeForAll)
{
sb.Append(Participants.Count * m_PlayersPerParticipant);
sb.Append("-man FFA");
}
else if (TourneyType == TourneyType.RandomTeam)
{
sb.Append(m_ParticipantsPerMatch);
sb.Append("-Team");
}
else if (TourneyType == TourneyType.Faction)
{
sb.Append(m_ParticipantsPerMatch);
sb.Append("-Team Faction");
}
else if (TourneyType == TourneyType.RedVsBlue)
{
sb.Append("Red v Blue");
}
else
{
for (int i = 0; i < m_ParticipantsPerMatch; ++i)
{
if (sb.Length > 0)
sb.Append('v');
sb.Append(m_PlayersPerParticipant);
}
}
if (EventController != null)
sb.Append(' ').Append(EventController.Title);
sb.Append(" Champion");
string title = sb.ToString();
for (int i = 0; i < players.Count; ++i)
{
Mobile mob = players[i];
if (mob?.Deleted != false)
continue;
Item item = new Trophy(title, rank);
if (!mob.PlaceInBackpack(item))
mob.BankBox.DropItem(item);
if (cash > 0)
{
item = new BankCheck(cash);
if (!mob.PlaceInBackpack(item))
mob.BankBox.DropItem(item);
mob.SendMessage(
"You have been awarded a {0} trophy and {1:N0}gp for your participation in this tournament.",
rank.ToString().ToLower(), cash);
}
else
{
mob.SendMessage("You have been awarded a {0} trophy for your participation in this tournament.",
rank.ToString().ToLower());
}
}
}
public void Slice()
{
if (CurrentStage == TournamentStage.Signup)
{
TimeSpan until = SignupStart + SignupPeriod - DateTime.UtcNow;
if (until <= TimeSpan.Zero)
{
for (int i = Participants.Count - 1; i >= 0; --i)
{
TourneyParticipant part = Participants[i];
bool bad = false;
for (int j = 0; j < part.Players.Count; ++j)
{
Mobile check = part.Players[j];
if (check.Deleted || check.Map == null || check.Map == Map.Internal || !check.Alive ||
Sigil.ExistsOn(check) || check.Region.IsPartOf<Jail>())
{
bad = true;
break;
}
}
if (bad)
{
for (int j = 0; j < part.Players.Count; ++j)
part.Players[j].SendMessage("You have been disqualified from the tournament.");
Participants.RemoveAt(i);
}
}
if (Participants.Count >= 2)
{
CurrentStage = TournamentStage.Fighting;
Undefeated.Clear();
Pyramid.Levels.Clear();
Pyramid.AddLevel(m_ParticipantsPerMatch, Participants, GroupType, TourneyType);
PyramidLevel level = Pyramid.Levels[0];
if (level.FreeAdvance != null)
Undefeated.Add(level.FreeAdvance);
for (int i = 0; i < level.Matches.Count; ++i)
{
TourneyMatch match = level.Matches[i];
Undefeated.AddRange(match.Participants);
}
Alert("Hear ye! Hear ye!", "The tournament will begin shortly.");
}
else
{
/*Alert( "Is this all?", "Pitiful. Signup extended." );
m_SignupStart = DateTime.UtcNow;*/
Alert("Is this all?", "Pitiful. Tournament cancelled.");
CurrentStage = TournamentStage.Inactive;
}
}
else if (Math.Abs(until.TotalSeconds - TimeSpan.FromMinutes(1.0).TotalSeconds) <
SliceInterval.TotalSeconds / 2)
{
Alert("Last call!", "If you wish to enter the tournament, sign up with the registrar now.");
}
else if (Math.Abs(until.TotalSeconds - TimeSpan.FromMinutes(5.0).TotalSeconds) <
SliceInterval.TotalSeconds / 2)
{
Alert("The tournament will begin in 5 minutes.", "Sign up now before it's too late.");
}
}
else if (CurrentStage == TournamentStage.Fighting)
{
if (Undefeated.Count == 1)
{
TourneyParticipant winner = Undefeated[0];
try
{
if (EventController != null)
{
Alert("The tournament has completed!",
$"Team {EventController.GetTeamName(Pyramid.Levels[0].Matches[0].Participants.IndexOf(winner))} has won!");
}
else if (TourneyType == TourneyType.RandomTeam)
{
Alert("The tournament has completed!",
$"Team {Pyramid.Levels[0].Matches[0].Participants.IndexOf(winner) + 1} has won!");
}
else if (TourneyType == TourneyType.Faction)
{
if (m_ParticipantsPerMatch == 4)
{
string name = "(null)";
switch (Pyramid.Levels[0].Matches[0].Participants.IndexOf(
winner))
{
case 0:
{
name = "Minax";
break;
}
case 1:
{
name = "Council of Mages";
break;
}
case 2:
{
name = "True Britannians";
break;
}
case 3:
{
name = "Shadowlords";
break;
}
}
Alert("The tournament has completed!", $"The {name} team has won!");
}
else if (m_ParticipantsPerMatch == 2)
{
Alert("The tournament has completed!",
$"The {(Pyramid.Levels[0].Matches[0].Participants.IndexOf(winner) == 0 ? "Evil" : "Hero")} team has won!");
}
else
{
Alert("The tournament has completed!",
$"Team {Pyramid.Levels[0].Matches[0].Participants.IndexOf(winner) + 1} has won!");
}
}
else if (TourneyType == TourneyType.RedVsBlue)
{
Alert("The tournament has completed!",
$"Team {(Pyramid.Levels[0].Matches[0].Participants.IndexOf(winner) == 0 ? "Red" : "Blue")} has won!");
}
else
{
Alert("The tournament has completed!",
$"{winner.NameList} {(winner.Players.Count > 1 ? "are" : "is")} the champion{(winner.Players.Count == 1 ? "" : "s")}.");
}
}
catch
{
// ignored
}
GiveAwards();
CurrentStage = TournamentStage.Inactive;
Undefeated.Clear();
}
else if (Pyramid.Levels.Count > 0)
{
PyramidLevel activeLevel = Pyramid.Levels[Pyramid.Levels.Count - 1];
bool stillGoing = false;
for (int i = 0; i < activeLevel.Matches.Count; ++i)
{
TourneyMatch match = activeLevel.Matches[i];
if (match.Winner == null)
{
stillGoing = true;
if (!match.InProgress)
for (int j = 0; j < Arenas.Count; ++j)
{
Arena arena = Arenas[j];
if (!arena.IsOccupied)
{
match.Start(arena, this);
break;
}
}
}
}
if (!stillGoing)
{
for (int i = Undefeated.Count - 1; i >= 0; --i)
{
TourneyParticipant part = Undefeated[i];
bool bad = false;
for (int j = 0; j < part.Players.Count; ++j)
{
Mobile check = part.Players[j];
if (check.Deleted || check.Map == null || check.Map == Map.Internal || !check.Alive ||
Sigil.ExistsOn(check) || check.Region.IsPartOf<Jail>())
{
bad = true;
break;
}
}
if (!bad)
continue;
for (int j = 0; j < part.Players.Count; ++j)
part.Players[j].SendMessage("You have been disqualified from the tournament.");
Undefeated.RemoveAt(i);
if (Undefeated.Count == 1)
{
TourneyParticipant winner = Undefeated[0];
try
{
if (EventController != null)
{
Alert("The tournament has completed!",
$"Team {EventController.GetTeamName(Pyramid.Levels[0].Matches[0].Participants.IndexOf(winner))} has won");
}
else if (TourneyType == TourneyType.RandomTeam)
{
Alert("The tournament has completed!",
$"Team {Pyramid.Levels[0].Matches[0].Participants.IndexOf(winner) + 1} has won!");
}
else if (TourneyType == TourneyType.Faction)
{
if (m_ParticipantsPerMatch == 4)
{
string name = "(null)";
switch (Pyramid.Levels[0].Matches[0]
.Participants.IndexOf(winner))
{
case 0:
{
name = "Minax";
break;
}
case 1:
{
name = "Council of Mages";
break;
}
case 2:
{
name = "True Britannians";
break;
}
case 3:
{
name = "Shadowlords";
break;
}
}
Alert("The tournament has completed!", $"The {name} team has won!");
}
else if (m_ParticipantsPerMatch == 2)
{
Alert("The tournament has completed!",
$"The {(Pyramid.Levels[0].Matches[0].Participants.IndexOf(winner) == 0 ? "Evil" : "Hero")} team has won!");
}
else
{
Alert("The tournament has completed!",
$"Team {Pyramid.Levels[0].Matches[0].Participants.IndexOf(winner) + 1} has won!");
}
}
else if (TourneyType == TourneyType.RedVsBlue)
{
Alert("The tournament has completed!",
$"Team {(Pyramid.Levels[0].Matches[0].Participants.IndexOf(winner) == 0 ? "Red" : "Blue")} has won!");
}
else
{
Alert("The tournament has completed!",
$"{winner.NameList} {(winner.Players.Count > 1 ? "are" : "is")} the champion{(winner.Players.Count == 1 ? "" : "s")}.");
}
}
catch
{
// ignored
}
GiveAwards();
CurrentStage = TournamentStage.Inactive;
Undefeated.Clear();
break;
}
}
if (Undefeated.Count > 1)
Pyramid.AddLevel(m_ParticipantsPerMatch, Undefeated, GroupType, TourneyType);
}
}
}
}
public void Alert(params string[] alerts)
{
for (int i = 0; i < Arenas.Count; ++i)
Alert(Arenas[i], alerts);
}
public void Alert(Arena arena, params string[] alerts)
{
if (arena?.Announcer != null)
for (int j = 0; j < alerts.Length; ++j)
{
string alert = alerts[j];
Timer.DelayCall(TimeSpan.FromSeconds(Math.Max(j - 0.5, 0.0)),
() => arena.Announcer.PublicOverheadMessage(MessageType.Regular, 0x35, false, alert));
}
}
}
}

View file

@ -0,0 +1,65 @@
using Server.Network;
namespace Server.Engines.ConPVP
{
public class TournamentBracketItem : Item
{
[Constructible]
public TournamentBracketItem() : base(3774)
{
Movable = false;
}
public TournamentBracketItem(Serial serial) : base(serial)
{
}
[CommandProperty(AccessLevel.GameMaster)]
public TournamentController Tournament{ get; set; }
public override string DefaultName => "tournament bracket";
public override void OnDoubleClick(Mobile from)
{
if (!from.InRange(GetWorldLocation(), 2))
{
from.LocalOverheadMessage(MessageType.Regular, 0x3B2, 1019045); // I can't reach that
}
else
{
Tournament tourney = Tournament?.Tournament;
if (tourney != null)
{
from.CloseGump<TournamentBracketGump>();
from.SendGump(new TournamentBracketGump(from, tourney, TourneyBracketGumpType.Index));
}
}
}
public override void Serialize(GenericWriter writer)
{
base.Serialize(writer);
writer.Write(0);
writer.Write(Tournament);
}
public override void Deserialize(GenericReader reader)
{
base.Deserialize(reader);
int version = reader.ReadInt();
switch (version)
{
case 0:
{
Tournament = reader.ReadItem() as TournamentController;
break;
}
}
}
}
}

View file

@ -0,0 +1,143 @@
using System;
using System.Collections.Generic;
using Server.ContextMenus;
using Server.Gumps;
namespace Server.Engines.ConPVP
{
public class TournamentController : Item
{
private static List<TournamentController> m_Instances = new List<TournamentController>();
[Constructible]
public TournamentController() : base(0x1B7A)
{
Visible = false;
Movable = false;
Tournament = new Tournament();
m_Instances.Add(this);
}
public TournamentController(Serial serial) : base(serial)
{
}
[CommandProperty(AccessLevel.GameMaster)]
public Tournament Tournament{ get; private set; }
public static bool IsActive
{
get
{
for (int i = 0; i < m_Instances.Count; ++i)
{
TournamentController controller = m_Instances[i];
if (controller?.Deleted == false && controller.Tournament != null &&
controller.Tournament.Stage != TournamentStage.Inactive)
return true;
}
return false;
}
}
public override string DefaultName => "tournament controller";
public override void GetContextMenuEntries(Mobile from, List<ContextMenuEntry> list)
{
base.GetContextMenuEntries(from, list);
if (from.AccessLevel >= AccessLevel.GameMaster && Tournament != null)
{
list.Add(new EditEntry(Tournament));
if (Tournament.CurrentStage == TournamentStage.Inactive)
list.Add(new StartEntry(Tournament));
}
}
public override void OnDoubleClick(Mobile from)
{
if (from.AccessLevel >= AccessLevel.GameMaster && Tournament != null)
{
from.CloseGump<PickRulesetGump>();
from.CloseGump<RulesetGump>();
from.SendGump(new PickRulesetGump(from, null, Tournament.Ruleset));
}
}
public override void Serialize(GenericWriter writer)
{
base.Serialize(writer);
writer.Write(0);
Tournament.Serialize(writer);
}
public override void Deserialize(GenericReader reader)
{
base.Deserialize(reader);
int version = reader.ReadInt();
switch (version)
{
case 0:
{
Tournament = new Tournament(reader);
break;
}
}
m_Instances.Add(this);
}
public override void OnDelete()
{
base.OnDelete();
m_Instances.Remove(this);
}
private class EditEntry : ContextMenuEntry
{
private Tournament m_Tournament;
public EditEntry(Tournament tourney) : base(5101)
{
m_Tournament = tourney;
}
public override void OnClick()
{
Owner.From.SendGump(new PropertiesGump(Owner.From, m_Tournament));
}
}
private class StartEntry : ContextMenuEntry
{
private Tournament m_Tournament;
public StartEntry(Tournament tourney) : base(5113)
{
m_Tournament = tourney;
}
public override void OnClick()
{
if (m_Tournament.Stage == TournamentStage.Inactive)
{
m_Tournament.SignupStart = DateTime.UtcNow;
m_Tournament.Stage = TournamentStage.Signup;
m_Tournament.Participants.Clear();
m_Tournament.Pyramid.Levels.Clear();
m_Tournament.Alert("Hear ye! Hear ye!",
"Tournament signup has opened. You can enter by signing up with the registrar.");
}
}
}
}
}

View file

@ -0,0 +1,190 @@
using System.Collections.Generic;
using Server.Ethics;
using Server.Factions;
namespace Server.Engines.ConPVP
{
public class TourneyPyramid
{
public TourneyPyramid()
{
Levels = new List<PyramidLevel>();
}
public List<PyramidLevel> Levels{ get; set; }
public void AddLevel(int partsPerMatch, List<TourneyParticipant> participants, GroupingType groupType, TourneyType tourneyType)
{
List<TourneyParticipant> copy = new List<TourneyParticipant>(participants);
if (groupType == GroupingType.Nearest || groupType == GroupingType.HighVsLow)
copy.Sort();
PyramidLevel level = new PyramidLevel();
switch (tourneyType)
{
case TourneyType.RedVsBlue:
{
TourneyParticipant[] parts = new TourneyParticipant[2];
for (int i = 0; i < parts.Length; ++i)
parts[i] = new TourneyParticipant(new List<Mobile>());
for (int i = 0; i < copy.Count; ++i)
{
List<Mobile> players = copy[i].Players;
for (int j = 0; j < players.Count; ++j)
{
Mobile mob = players[j];
if (mob.Kills >= 5)
parts[0].Players.Add(mob);
else
parts[1].Players.Add(mob);
}
}
level.Matches.Add(new TourneyMatch(new List<TourneyParticipant>(parts)));
break;
}
case TourneyType.Faction:
{
TourneyParticipant[] parts = new TourneyParticipant[partsPerMatch];
for (int i = 0; i < parts.Length; ++i)
parts[i] = new TourneyParticipant(new List<Mobile>());
for (int i = 0; i < copy.Count; ++i)
{
List<Mobile> players = copy[i].Players;
for (int j = 0; j < players.Count; ++j)
{
Mobile mob = players[j];
int index = -1;
if (partsPerMatch == 4)
{
Faction fac = Faction.Find(mob);
if (fac != null)
index = fac.Definition.Sort;
}
else if (partsPerMatch == 2)
{
if (Ethic.Evil.IsEligible(mob))
index = 0;
else if (Ethic.Hero.IsEligible(mob)) index = 1;
}
if (index < 0 || index >= partsPerMatch) index = i % partsPerMatch;
parts[index].Players.Add(mob);
}
}
level.Matches.Add(new TourneyMatch(new List<TourneyParticipant>(parts)));
break;
}
case TourneyType.RandomTeam:
{
TourneyParticipant[] parts = new TourneyParticipant[partsPerMatch];
for (int i = 0; i < partsPerMatch; ++i)
parts[i] = new TourneyParticipant(new List<Mobile>());
for (int i = 0; i < copy.Count; ++i)
parts[i % parts.Length].Players.AddRange(copy[i].Players);
level.Matches.Add(new TourneyMatch(new List<TourneyParticipant>(parts)));
break;
}
case TourneyType.FreeForAll:
{
level.Matches.Add(new TourneyMatch(copy));
break;
}
case TourneyType.Standard:
{
if (partsPerMatch >= 2 && participants.Count % partsPerMatch == 1)
{
int lowAdvances = int.MaxValue;
for (int i = 0; i < participants.Count; ++i)
{
TourneyParticipant p = participants[i];
if (p.FreeAdvances < lowAdvances)
lowAdvances = p.FreeAdvances;
}
List<TourneyParticipant> toAdvance = new List<TourneyParticipant>();
for (int i = 0; i < participants.Count; ++i)
{
TourneyParticipant p = participants[i];
if (p.FreeAdvances == lowAdvances)
toAdvance.Add(p);
}
if (toAdvance.Count == 0)
toAdvance = copy; // sanity
int idx = Utility.Random(toAdvance.Count);
toAdvance[idx].AddLog(
"Advanced automatically due to an odd number of challengers.");
level.FreeAdvance = toAdvance[idx];
++level.FreeAdvance.FreeAdvances;
copy.Remove(toAdvance[idx]);
}
while (copy.Count >= partsPerMatch)
{
List<TourneyParticipant> thisMatch = new List<TourneyParticipant>();
for (int i = 0; i < partsPerMatch; ++i)
{
int idx = 0;
switch (groupType)
{
case GroupingType.HighVsLow:
idx = i * (copy.Count - 1) / (partsPerMatch - 1);
break;
case GroupingType.Nearest:
idx = 0;
break;
case GroupingType.Random:
idx = Utility.Random(copy.Count);
break;
}
thisMatch.Add(copy[idx]);
copy.RemoveAt(idx);
}
level.Matches.Add(new TourneyMatch(thisMatch));
}
if (copy.Count > 1)
level.Matches.Add(new TourneyMatch(copy));
break;
}
}
Levels.Add(level);
}
}
public class PyramidLevel
{
public List<TourneyMatch> Matches{ get; set; } = new List<TourneyMatch>();
public TourneyParticipant FreeAdvance{ get; set; }
}
}

View file

@ -0,0 +1,93 @@
using System;
using Server.Factions;
using Server.Mobiles;
using Server.Network;
namespace Server.Engines.ConPVP
{
public class TournamentRegistrar : Banker
{
[Constructible]
public TournamentRegistrar()
{
Timer.DelayCall(TimeSpan.FromSeconds(30.0), TimeSpan.FromSeconds(30.0), Announce_Callback);
}
public TournamentRegistrar(Serial serial) : base(serial)
{
}
[CommandProperty(AccessLevel.GameMaster)]
public TournamentController Tournament{ get; set; }
private void Announce_Callback()
{
Tournament tourney = Tournament?.Tournament;
if (tourney?.Stage == TournamentStage.Signup)
PublicOverheadMessage(MessageType.Regular, 0x35, false,
"Come one, come all! Do you aspire to be a fighter of great renown? Join this tournament and show the world your abilities.");
}
public override void OnMovement(Mobile m, Point3D oldLocation)
{
base.OnMovement(m, oldLocation);
Tournament tourney = Tournament?.Tournament;
if (InRange(m, 4) && !InRange(oldLocation, 4) && tourney != null && tourney.Stage == TournamentStage.Signup &&
m.CanBeginAction(this))
{
Ladder ladder = Ladder.Instance;
LadderEntry entry = ladder?.Find(m);
if (entry != null && Ladder.GetLevel(entry.Experience) < tourney.LevelRequirement)
return;
if (tourney.IsFactionRestricted && Faction.Find(m) == null) return;
if (tourney.HasParticipant(m))
return;
PrivateOverheadMessage(MessageType.Regular, 0x35, false,
$"Hello m'{(m.Female ? "Lady" : "Lord")}. Dost thou wish to enter this tournament? You need only to write your name in this book.",
m.NetState);
m.BeginAction(this);
Timer.DelayCall(TimeSpan.FromSeconds(10.0), ReleaseLock_Callback, m);
}
}
public void ReleaseLock_Callback(Mobile m)
{
m.EndAction(this);
}
public override void Serialize(GenericWriter writer)
{
base.Serialize(writer);
writer.Write(0);
writer.Write(Tournament);
}
public override void Deserialize(GenericReader reader)
{
base.Deserialize(reader);
int version = reader.ReadInt();
switch (version)
{
case 0:
{
Tournament = reader.ReadItem() as TournamentController;
break;
}
}
Timer.DelayCall(TimeSpan.FromSeconds(30.0), TimeSpan.FromSeconds(30.0), Announce_Callback);
}
}
}

View file

@ -0,0 +1,149 @@
using System.Collections.Generic;
using Server.Factions;
using Server.Mobiles;
using Server.Network;
namespace Server.Engines.ConPVP
{
public class TournamentSignupItem : Item
{
[Constructible]
public TournamentSignupItem() : base(4029)
{
Movable = false;
}
public TournamentSignupItem(Serial serial) : base(serial)
{
}
[CommandProperty(AccessLevel.GameMaster)]
public TournamentController Tournament{ get; set; }
[CommandProperty(AccessLevel.GameMaster)]
public Mobile Registrar{ get; set; }
public override string DefaultName => "tournament signup book";
public override void OnDoubleClick(Mobile from)
{
if (!from.InRange(GetWorldLocation(), 2))
{
from.LocalOverheadMessage(MessageType.Regular, 0x3B2, 1019045); // I can't reach that
}
else
{
Tournament tourney = Tournament?.Tournament;
if (tourney == null)
return;
if (Registrar != null)
Registrar.Direction = Registrar.GetDirectionTo(this);
switch (tourney.Stage)
{
case TournamentStage.Fighting:
{
if (Registrar != null)
{
if (tourney.HasParticipant(from))
Registrar.PrivateOverheadMessage(MessageType.Regular,
0x35, false, "Excuse me? You are already signed up.", from.NetState);
else
Registrar.PrivateOverheadMessage(MessageType.Regular,
0x22, false, "The tournament has already begun. You are too late to signup now.",
from.NetState);
}
break;
}
case TournamentStage.Inactive:
{
Registrar?.PrivateOverheadMessage(MessageType.Regular,
0x35, false, "The tournament is closed.", from.NetState);
break;
}
case TournamentStage.Signup:
{
Ladder ladder = Ladder.Instance;
LadderEntry entry = ladder?.Find(from);
if (entry != null && Ladder.GetLevel(entry.Experience) < tourney.LevelRequirement)
{
Registrar?.PrivateOverheadMessage(MessageType.Regular,
0x35, false, "You have not yet proven yourself a worthy dueler.", from.NetState);
break;
}
if (tourney.IsFactionRestricted && Faction.Find(from) == null)
{
Registrar?.PrivateOverheadMessage(MessageType.Regular,
0x35, false, "Only those who have declared their faction allegiance may participate.",
from.NetState);
break;
}
if (from.HasGump<AcceptTeamGump>())
{
Registrar?.PrivateOverheadMessage(MessageType.Regular,
0x22, false, "You must first respond to the offer I've given you.", from.NetState);
}
else if (from.HasGump<AcceptDuelGump>())
{
Registrar?.PrivateOverheadMessage(MessageType.Regular,
0x22, false, "You must first cancel your duel offer.", from.NetState);
}
else if (from is PlayerMobile mobile && mobile.DuelContext != null)
{
Registrar?.PrivateOverheadMessage(MessageType.Regular,
0x22, false, "You are already participating in a duel.", mobile.NetState);
}
else if (!tourney.HasParticipant(from))
{
from.CloseGump<ConfirmSignupGump>();
from.SendGump(new ConfirmSignupGump(from, Registrar, tourney, new List<Mobile> { from }));
}
else
{
Registrar?.PrivateOverheadMessage(MessageType.Regular,
0x35, false, "You have already entered this tournament.", from.NetState);
}
break;
}
}
}
}
public override void Serialize(GenericWriter writer)
{
base.Serialize(writer);
writer.Write(0);
writer.Write(Tournament);
writer.Write(Registrar);
}
public override void Deserialize(GenericReader reader)
{
base.Deserialize(reader);
int version = reader.ReadInt();
switch (version)
{
case 0:
{
Tournament = reader.ReadItem() as TournamentController;
Registrar = reader.ReadMobile();
break;
}
}
}
}
}

View file

@ -0,0 +1,109 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Server.Engines.ConPVP
{
public class TourneyParticipant : IComparable<TourneyParticipant>
{
public TourneyParticipant(Mobile owner)
{
Log = new List<string>();
Players = new List<Mobile> { owner };
}
public TourneyParticipant(List<Mobile> players)
{
Log = new List<string>();
Players = players;
}
public List<Mobile> Players{ get; set; }
public List<string> Log{ get; set; }
public int FreeAdvances{ get; set; }
public int TotalLadderXP
{
get
{
Ladder ladder = Ladder.Instance;
if (ladder == null)
return 0;
int total = 0;
for (int i = 0; i < Players.Count; ++i)
{
Mobile mob = Players[i];
LadderEntry entry = ladder.Find(mob);
if (entry != null)
total += entry.Experience;
}
return total;
}
}
public string NameList
{
get
{
StringBuilder sb = new StringBuilder();
for (int i = 0; i < Players.Count; ++i)
{
if (Players[i] == null)
continue;
Mobile mob = Players[i];
if (sb.Length > 0)
{
if (Players.Count == 2)
sb.Append(" and ");
else if (i + 1 < Players.Count)
sb.Append(", ");
else
sb.Append(", and ");
}
sb.Append(mob.Name);
}
if (sb.Length == 0)
return "Empty";
return sb.ToString();
}
}
public int CompareTo(TourneyParticipant p)
{
return p.TotalLadderXP - TotalLadderXP;
}
public void AddLog(string text)
{
Log.Add(text);
}
public void AddLog(string format, params object[] args)
{
AddLog(string.Format(format, args));
}
public void WonMatch(TourneyMatch match)
{
AddLog("Match won.");
}
public void LostMatch(TourneyMatch match)
{
AddLog("Match lost.");
}
}
}

View file

@ -0,0 +1,119 @@
using System;
namespace Server.Items
{
public enum TrophyRank
{
Bronze,
Silver,
Gold
}
[Flippable(5020, 4647)]
public class Trophy : Item
{
private TrophyRank m_Rank;
[Constructible]
public Trophy(string title, TrophyRank rank) : base(5020)
{
Title = title;
m_Rank = rank;
Date = DateTime.UtcNow;
LootType = LootType.Blessed;
UpdateStyle();
}
public Trophy(Serial serial) : base(serial)
{
}
[CommandProperty(AccessLevel.GameMaster)]
public string Title{ get; set; }
[CommandProperty(AccessLevel.GameMaster)]
public TrophyRank Rank
{
get => m_Rank;
set
{
m_Rank = value;
UpdateStyle();
}
}
[CommandProperty(AccessLevel.GameMaster)]
public Mobile Owner{ get; set; }
[CommandProperty(AccessLevel.GameMaster)]
public DateTime Date{ get; private set; }
public override void Serialize(GenericWriter writer)
{
base.Serialize(writer);
writer.Write(1); // version
writer.Write(Title);
writer.Write((int)m_Rank);
writer.Write(Owner);
writer.Write(Date);
}
public override void Deserialize(GenericReader reader)
{
base.Deserialize(reader);
int version = reader.ReadInt();
Title = reader.ReadString();
m_Rank = (TrophyRank)reader.ReadInt();
Owner = reader.ReadMobile();
Date = reader.ReadDateTime();
if (version == 0)
LootType = LootType.Blessed;
}
public override void OnAdded(IEntity parent)
{
base.OnAdded(parent);
if (Owner == null)
Owner = RootParent as Mobile;
}
public override void OnSingleClick(Mobile from)
{
base.OnSingleClick(from);
if (Owner != null)
LabelTo(from, "{0} -- {1}", Title, Owner.RawName);
else if (Title != null)
LabelTo(from, Title);
if (Date != DateTime.MinValue)
LabelTo(from, Date.ToString("d"));
}
public void UpdateStyle()
{
Name = $"{m_Rank.ToString().ToLower()} trophy";
switch (m_Rank)
{
case TrophyRank.Gold:
Hue = 2213;
break;
case TrophyRank.Silver:
Hue = 0;
break;
case TrophyRank.Bronze:
Hue = 2206;
break;
}
}
}
}