using System; using System.Collections.Generic; using System.Runtime.CompilerServices; using ModernUO.Serialization; using Server.Gumps; using Server.Items; using Server.Mobiles; using Server.Targeting; using Server.Text; namespace Server.Engines.ConPVP; [SerializationGenerator(0, false)] public partial class BRBomb : Item { private readonly BRGame m_Game; private readonly List m_Helpers; private readonly Point3DList m_Path = new(); private readonly EffectTimer m_Timer; private bool m_Flying; private int m_PathIdx; public BRBomb(BRGame game) : base(0x103C) // 0x103C = bread, 0x1042 = pie, 0x1364 = rock, 0x13a8 = pillow, 0x2256 = bagball { Movable = false; Hue = 0x35; m_Game = game; m_Helpers = new List(); m_Timer = new EffectTimer(this); m_Timer.Start(); } public override bool SkipSerialization => true; public override string DefaultName => "da bomb"; public Mobile Thrower { get; private set; } private static Mobile FindOwner(IEntity parent) { if (parent is Item item) { return item.RootParent as Mobile; } if (parent is Mobile mobile) { return mobile; } return null; } public override void OnAdded(IEntity parent) { base.OnAdded(parent); var mob = FindOwner(parent); mob?.SolidHueOverride = 0x0499; } public override void OnRemoved(IEntity parent) { base.OnRemoved(parent); var mob = FindOwner(parent); if (mob != null && m_Game != null) { mob.SolidHueOverride = m_Game.GetColor(mob); } } public void DropTo(Mobile mob, Mobile killer) { if (mob?.Deleted == false) { MoveToWorld(mob.Location, mob.Map); } else if (killer?.Deleted == false) { MoveToWorld(killer.Location, killer.Map); } else { m_Game?.ReturnBomb(); } } public override bool OnMoveOver(Mobile m) { if (m_Flying || !Visible || m_Game == null || m?.Alive != true) { return true; } var useTeam = m_Game.GetTeamInfo(m); return useTeam == null || TakeBomb(m, useTeam, "picked up"); } public override void OnLocationChange(Point3D oldLocation) { base.OnLocationChange(oldLocation); if (m_Flying || !Visible || m_Game == null || Parent != null) { return; } foreach (var ns in GetClientsAt()) { var m = ns.Mobile; if (m?.Player != true || !m.Alive) { continue; } var useTeam = m_Game.GetTeamInfo(m); if (useTeam != null) { TakeBomb(m, useTeam, "got"); } } } public override void OnAfterDelete() { base.OnAfterDelete(); m_Timer?.Stop(); } public override void OnDoubleClick(Mobile m) { if (m_Game == null || !Visible || m?.Alive != true) { return; } if (!m_Flying && IsChildOf(m.Backpack)) { m.Target = new BombTarget(this, m); } else if (Parent == null) { if (m.InRange(Location, 1) && m.Location.Z != Z) { var useTeam = m_Game.GetTeamInfo(m); if (useTeam == null) { return; } TakeBomb(m, useTeam, "grabbed"); } } } private bool OnBombTarget(Mobile from, object obj) { if (m_Game == null) { return true; } if (!IsChildOf(from.Backpack)) { return true; } // don't let them throw it to themselves if (obj == from) { return false; } if (obj is not IPoint3D) { return false; } var pt = new Point3D((IPoint3D)obj); if (obj is Mobile) { pt.Z += 10; } else if (obj is Item item) { pt.Z += item.ItemData.CalcHeight + 1; } m_Flying = true; Visible = false; Thrower = from; MoveToWorld(GetWorldLocation(), from.Map); BeginFlight(pt); return true; } private void HitObject(Point3D ballLoc, int objZ, int objHeight) { DoAnim(GetWorldLocation(), ballLoc, Map); MoveToWorld(ballLoc); m_Path.Clear(); m_PathIdx = 0; Timer.StartTimer(TimeSpan.FromSeconds(0.05), ContinueFlight); } private bool CheckCatch(Mobile m, Point3D myLoc) { if (m?.Alive != true || !m.Player || m_Game == null) { return false; } if (m_Game.GetTeamInfo(m) == null) { return false; } var zdiff = myLoc.Z - m.Z; return zdiff >= 0 && (zdiff < 12 || zdiff < 16 && Utility.RandomBool()); } private void DoAnim(Point3D start, Point3D end, Map map) { Effects.SendMovingEffect( new Entity(Serial.Zero, start, map), new Entity(Serial.Zero, end, map), ItemID, 15, 0, false, false, Hue ); } private void DoCatch(Mobile m) { m_Flying = false; Visible = true; if (m?.Alive != true || !m.Player || m_Game == null) { return; } var useTeam = m_Game.GetTeamInfo(m); if (useTeam == null) { return; } DoAnim(GetWorldLocation(), m.Location, m.Map); var verb = "caught"; if (Thrower != null && m_Game.GetTeamInfo(Thrower) != useTeam) { verb = "intercepted"; } if (!TakeBomb(m, useTeam, verb)) { MoveToWorld(m.Location, m.Map); } } private void BeginFlight(Point3D dest) { var org = GetWorldLocation(); org.Z += 10; // always add 10 at the start cause we're coming from a mobile's eye level /*if (org.X > dest.X || ( org.X == dest.X && org.Y > dest.Y ) || ( org.X == dest.X && org.Y == dest.Y && org.Z > dest.Z )) { Point3D swap = org; org = dest; dest = swap; }*/ var list = new List(); var xd = dest.X - org.X; var yd = dest.Y - org.Y; var zd = dest.Z - org.Z; var dist2d = Math.Sqrt(xd * xd + yd * yd); var dist3d = zd == 0 ? dist2d : Math.Sqrt(dist2d * dist2d + zd * zd); var rise = yd / dist3d; var run = xd / dist3d; var zslp = zd / dist3d; double x = org.X; double y = org.Y; double z = org.Z; while (Utility.NumberBetween(x, dest.X, org.X, 0.5) && Utility.NumberBetween(y, dest.Y, org.Y, 0.5) && Utility.NumberBetween(z, dest.Z, org.Z, 0.5)) { var ix = (int)Math.Round(x); var iy = (int)Math.Round(y); var iz = (int)Math.Round(z); if (list.Count > 0) { var p = list[^1]; if (p.X != ix || p.Y != iy || p.Z != iz) { list.Add(new Point3D(ix, iy, iz)); } } else { list.Add(new Point3D(ix, iy, iz)); } x += run; y += rise; z += zslp; } if (list.Count > 0 && list[^1] != dest) { list.Add(dest); } /*if (dist3d > 4 && ( dest.X != org.X || dest.Y != org.Y )) { int count = list.Count; int i; int climb = count / 2; if (climb > 3) climb = 3; for ( i = 0; i < climb; i++ ) { p = ((Point3D)list[i]); p.Z += (i+1) * 4; list[i] = p; } for ( ; i < count - climb; i++ ) { p = ((Point3D)list[i]); p.Z += 16; list[i] = p; } for ( i = climb; i > 0; i-- ) { p = ((Point3D)list[i]); p.Z += i * 4; list[i] = p; } }*/ if (dist2d > 1) { var count = list.Count; var height = count * 2 * (Utility.RandomDouble() * 0.40 + 0.10); // 10 - 50% var coeff = -height / (count * count / 4.0); for (var i = 0; i < count; i++) { var p = list[i]; var xp = i - count / 2; p.Z += (int)Math.Ceiling(coeff * xp * xp + height); list[i] = p; } } m_Path.Clear(); for (var i = 0; i < list.Count; i++) { m_Path.Add(list[i]); } m_PathIdx = 0; ContinueFlight(); } private void ContinueFlight() { int height; var found = false; if (m_PathIdx < m_Path.Count && Map?.Tiles != null && Map != Map.Internal) { var pathCheckEnd = m_PathIdx + 5; if (m_Path.Count < pathCheckEnd) { pathCheckEnd = m_Path.Count; } Visible = false; if (m_PathIdx > 0) // move to the next location { MoveToWorld(m_Path[m_PathIdx - 1]); } Point3D pTop = new(GetWorldLocation()), pBottom = new(m_Path[pathCheckEnd - 1]); Utility.FixPoints(ref pTop, ref pBottom); for (var i = m_PathIdx; i < pathCheckEnd; i++) { var point = m_Path[i]; var landTile = Map.Tiles.GetLandTile(point.X, point.Y); Map.GetAverageZ(point.X, point.Y, out var landZ, out _, out var landTop); if (landZ <= point.Z && landTop >= point.Z && !landTile.Ignored) { HitObject(point, landTop, 0); return; } var foundStatics = false; foreach (var t in Map.Tiles.GetStaticAndMultiTiles(point.X, point.Y)) { foundStatics = true; var id = TileData.ItemTable[t.ID & TileData.MaxItemValue]; height = id.CalcHeight; if (t.Z <= point.Z && t.Z + height >= point.Z && (id.Flags & (TileFlag.Impassable | TileFlag.Wall | TileFlag.NoShoot)) != 0) { if (i > m_PathIdx) { point = m_Path[i - 1]; } else { point = GetWorldLocation(); } HitObject(point, t.Z, height); return; } } if (landTile.ID == 0x244 && foundStatics) // 0x244 = invalid land tile { var empty = true; foreach (var item in Map.GetItemsAt(point)) { if (item != this) { empty = false; break; } } if (empty) { HitObject(point, landTop, 0); return; } } } var rect = new Rectangle2D(pTop.X, pTop.Y, pBottom.X - pTop.X + 1, pBottom.Y - pTop.Y + 1); foreach (var i in Map.GetItemsInBounds(rect)) { if (i == this || i.ItemID >= 0x4000) { continue; } if (i is BRGoal) { height = 17; } else if (i is Blocker) { height = 20; } else { var id = i.ItemData; if ((id.Flags & (TileFlag.Impassable | TileFlag.Wall | TileFlag.NoShoot)) == 0) { continue; } height = id.CalcHeight; } var point = i.Location; var loc = i.Location; for (var j = m_PathIdx; j < pathCheckEnd; j++) { point = m_Path[j]; if (loc.X == point.X && loc.Y == point.Y && (i is Blocker || loc.Z <= point.Z && loc.Z + height >= point.Z)) { found = true; if (j > m_PathIdx) { point = m_Path[j - 1]; } else { point = GetWorldLocation(); } break; } } if (!found) { continue; } if (i is BRGoal goal) { var oldLoc = new Point3D(GetWorldLocation()); if (CheckScore(goal, Thrower, 3)) { DoAnim(oldLoc, point, Map); } else { HitObject(point, loc.Z, height); } } else { HitObject(point, loc.Z, height); } return; } foreach (var ns in Map.GetClientsInBounds(rect)) { var m = ns.Mobile; if (m == null || m == Thrower) { continue; } Point3D point; var loc = m.Location; for (var j = m_PathIdx; j < pathCheckEnd && !found; j++) { point = m_Path[j]; if (loc.X == point.X && loc.Y == point.Y && loc.Z <= point.Z && loc.Z + 16 >= point.Z) { found = CheckCatch(m, point); } } if (!found) { continue; } // TODO: probably need to change this a lot... DoCatch(m); return; } m_PathIdx = pathCheckEnd; if (m_PathIdx > 0 && m_PathIdx - 1 < m_Path.Count) { DoAnim(GetWorldLocation(), m_Path[m_PathIdx - 1], Map); } Timer.StartTimer(TimeSpan.FromSeconds(0.1), ContinueFlight); } else { if (m_PathIdx > 0 && m_PathIdx - 1 < m_Path.Count) { MoveToWorld(m_Path[m_PathIdx - 1]); } else if (m_Path.Count > 0) { MoveToWorld(m_Path.Last); } var myZ = Map?.GetAverageZ(X, Y) ?? 0; if (Map?.Tiles != null) { foreach (var t in Map.Tiles.GetStaticAndMultiTiles(X, Y)) { var id = TileData.ItemTable[t.ID & TileData.MaxItemValue]; height = id.CalcHeight; if (t.Z + height > myZ && t.Z + height <= Z) { myZ = t.Z + height; } } } foreach (var item in GetItemsAt()) { if (item.Visible && item != this) { height = item.ItemData.CalcHeight; if (item.Z + height > myZ && item.Z + height <= Z) { myZ = item.Z + height; } } } Z = myZ; m_Flying = false; Visible = true; m_Path.Clear(); m_PathIdx = 0; } } public bool CheckScore(BRGoal goal, Mobile m, int points) { if (m_Game == null || m == null || goal == null) { return false; } var team = m_Game.GetTeamInfo(m); if (team == null || goal.Team == null || team == goal.Team) { return false; } if (points > 3) { m_Game.Alert($"Touchdown {team.Name} ({m.Name})!"); } else { m_Game.Alert($"Field goal {team.Name} ({m.Name})!"); } for (var i = m_Helpers.Count - 1; i >= 0; i--) { var mob = m_Helpers[i]; var pi = team[mob]; if (pi != null) { if (mob == m) { pi.Captures += points; } pi.Score += points + 1; points /= 2; } } m_Game.ReturnBomb(); m_Flying = false; Visible = true; m_Path.Clear(); m_PathIdx = 0; Target.Cancel(m); return true; } private bool TakeBomb(Mobile m, BRTeamInfo team, string verb) { if (!m.Player || !m.Alive || m.NetState == null) { return false; } if (m.PlaceInBackpack(this)) { m.RevealingAction(); m.LocalOverheadMessage(MessageType.Regular, 0x59, false, "You got the bomb!"); m_Game.Alert($"{verb} ({m.Name}) {team.Name} the bomb!"); m.Target = new BombTarget(this, m); m_Helpers.Remove(m); if (m_Helpers.Count > 0) { var last = m_Helpers[0]; if (m_Game.GetTeamInfo(last) != team) { m_Helpers.Clear(); } } m_Helpers.Add(m); return true; } return false; } private class EffectTimer : Timer { private readonly BRBomb m_Bomb; private int m_Count; public EffectTimer(BRBomb bomb) : base(TimeSpan.Zero, TimeSpan.FromSeconds(1.0)) { m_Bomb = bomb; m_Count = 0; } protected override void OnTick() { if (m_Bomb.Parent == null && m_Bomb.m_Game?.Controller != null) { if (!m_Bomb.m_Flying && m_Bomb.Map != Map.Internal) { Effects.SendLocationEffect(m_Bomb.GetWorldLocation(), m_Bomb.Map, 0x377A, 16, 10, m_Bomb.Hue); } if (m_Bomb.Location != m_Bomb.m_Game.Controller.BombHome) { if (++m_Count >= 30) { m_Bomb.m_Game.ReturnBomb(); m_Bomb.m_Game.Alert("The bomb has been returned to it's starting point."); m_Count = 0; m_Bomb.m_Helpers.Clear(); } } else { m_Count = 0; } } else { m_Count = 0; } } } private class BombTarget : Target { private readonly BRBomb m_Bomb; private readonly Mobile m_Mob; private bool m_Resend = true; public BombTarget(BRBomb bomb, Mobile from) : base(10, true, TargetFlags.None) { CheckLOS = false; m_Bomb = bomb; m_Mob = from; m_Mob.SendMessage(0x26, "Where do you want to throw it?"); } protected override void OnTarget(Mobile from, object targeted) { m_Resend = !m_Bomb.OnBombTarget(from, targeted); } protected override void OnTargetUntargetable(Mobile from, object targeted) { m_Resend = !m_Bomb.OnBombTarget(from, targeted); } protected override void OnTargetFinish(Mobile from) { base.OnTargetFinish(from); // has to be delayed in case some other target canceled us... if (m_Resend) { Timer.StartTimer(ResendBombTarget); } } private void ResendBombTarget() { // Make sure they still have the bomb, then give them the target back if (m_Bomb?.Deleted == false && m_Mob?.Deleted == false && m_Mob.Alive && m_Bomb.IsChildOf(m_Mob)) { m_Mob.Target = new BombTarget(m_Bomb, m_Mob); } } } } [SerializationGenerator(0, false)] public partial class BRGoal : BaseAddon { [SerializableField(0, fieldChanged: nameof(OnNorthChanged))] private bool _north; [MethodImpl(MethodImplOptions.AggressiveInlining)] private void OnNorthChanged(bool oldValue, bool newValue) => Remake(); private BRTeamInfo m_Team; [Constructible] public BRGoal() { ItemID = 0x51D; Hue = 0x84C; Visible = true; Remake(); } public override string DefaultName => "Bombing Run Goal"; public override bool ShareHue => false; public BRTeamInfo Team { get => m_Team; set { m_Team = value; if (m_Team != null && m_Team.Color != 0) { Hue = m_Team.Color; } else { Hue = 0x84C; } } } private static AddonComponent SetHue(AddonComponent ac, int hue) { ac.Hue = hue; return ac; } private void Remake() { for (var i = 0; i < Components.Count; i++) { var ac = Components[i]; ac.Addon = null; ac.Delete(); } ClearComponents(); // stairs AddComponent(new AddonComponent(0x74D), -1, +1, -5); AddComponent(new AddonComponent(0x71F), 0, +1, -5); AddComponent(new AddonComponent(0x74B), +1, +1, -5); AddComponent(new AddonComponent(0x736), +1, 0, -5); AddComponent(new AddonComponent(0x74C), +1, -1, -5); AddComponent(new AddonComponent(0x737), 0, -1, -5); AddComponent(new AddonComponent(0x74A), -1, -1, -5); AddComponent(new AddonComponent(0x749), -1, 0, -5); // Center Sparkle AddComponent(new AddonComponent(0x375A), 0, 0, -1); if (!_north) { // Pillars AddComponent(new AddonComponent(0x0CE), 0, +1, -2); AddComponent(new AddonComponent(0x0CC), 0, -1, -2); AddComponent(new AddonComponent(0x0D0), 0, 0, -2); // Yellow parts AddComponent(SetHue(new AddonComponent(0x0DF), 0x499), 0, +1, 7); AddComponent(SetHue(new AddonComponent(0x0DF), 0x499), 0, 0, 16); AddComponent(SetHue(new AddonComponent(0x0DF), 0x499), 0, -1, 7); // Blue Sparkles AddComponent(SetHue(new AddonComponent(0x377A), 0x84C), 0, +1, 12); AddComponent(SetHue(new AddonComponent(0x377A), 0x84C), 0, +1, -1); AddComponent(SetHue(new AddonComponent(0x377A), 0x84C), 0, -1, 12); AddComponent(SetHue(new AddonComponent(0x377A), 0x84C), 0, -1, -1); } else { // Pillars AddComponent(new AddonComponent(0x0CF), +1, 0, -2); AddComponent(new AddonComponent(0x0CC), -1, 0, -2); AddComponent(new AddonComponent(0x0D1), 0, 0, -2); // Yellow parts AddComponent(SetHue(new AddonComponent(0x0DF), 0x499), +1, 0, 7); AddComponent(SetHue(new AddonComponent(0x0DF), 0x499), 0, 0, 16); AddComponent(SetHue(new AddonComponent(0x0DF), 0x499), -1, 0, 7); // Blue Sparkles AddComponent(SetHue(new AddonComponent(0x377A), 0x84C), +1, 0, 12); AddComponent(SetHue(new AddonComponent(0x377A), 0x84C), +1, 0, -1); AddComponent(SetHue(new AddonComponent(0x377A), 0x84C), -1, 0, 12); AddComponent(SetHue(new AddonComponent(0x377A), 0x84C), -1, 0, -1); } } public override bool OnMoveOver(Mobile m) { if (!Visible) { return true; } if (m?.Player != true || !m.Alive || m.Backpack == null || m_Team?.Game == null) { return true; } if (!base.OnMoveOver(m)) { return false; } if (m_Team != null && m_Team.Color != 0) { Hue = m_Team.Color; } else { Hue = 0x84C; } m.Backpack.FindItemByType()?.CheckScore(this, m, 7); return true; } } [SerializationGenerator(0, false)] public sealed partial class BRBoard : Item { public BRTeamInfo m_TeamInfo; [Constructible] public BRBoard() : base(7774) => Movable = false; public override string DefaultName => "Scoreboard"; public override void OnDoubleClick(Mobile from) { if (m_TeamInfo?.Game != null) { BRBoardGump.DisplayTo(from, m_TeamInfo.Game); } } } public class BRBoardGump : DynamicGump { private const int LabelColor32 = 0xFFFFFF; private const int BlackColor32 = 0x000000; private readonly Mobile _mob; private readonly BRGame _game; private readonly BRTeamInfo _section; public override bool Singleton => true; private BRBoardGump(Mobile mob, BRGame game, BRTeamInfo section) : base(60, 60) { _mob = mob; _game = game; _section = section; } public static void DisplayTo(Mobile mob, BRGame game, BRTeamInfo section = null) { if (mob?.NetState == null || game == null) { return; } mob.SendGump(new BRBoardGump(mob, game, section)); } protected override void BuildLayout(ref DynamicGumpBuilder builder) { var ourTeam = _game.GetTeamInfo(_mob); var entries = new List(); var total = 0; if (_section == null) { for (var i = 0; i < _game.Context.Participants.Count; ++i) { var teamInfo = _game.Controller.TeamInfo[i % _game.Controller.TeamInfo.Length]; if (teamInfo == null) { continue; } entries.Add(teamInfo); } total = entries.Count; } else { foreach (var player in _section.Players.Values) { if (player.Score > 0) { total++; } } } entries.Sort(); var height = 0; if (_section == null) { height = 73 + entries.Count * 75 + 28; } builder.SetNoClose(); builder.AddPage(); builder.AddBackground(1, 1, 398, height, 3600); builder.AddImageTiled(16, 15, 369, height - 29, 3604); for (var i = 0; i < total; i += 1) { builder.AddImageTiled(22, 58 + i * 75, 357, 70, 0x2430); } builder.AddAlphaRegion(16, 15, 369, height - 29); builder.AddImage(215, -45, 0xEE40); AddBorderedText(ref builder, 22, 22, 294, 20, "BR Scoreboard".Center(), LabelColor32, BlackColor32); builder.AddImageTiled(32, 50, 264, 1, 9107); builder.AddImageTiled(42, 52, 264, 1, 9157); if (_section == null) { for (var i = 0; i < entries.Count; ++i) { var teamInfo = entries[i]; builder.AddImage(30, 70 + i * 75, 10152); builder.AddImage(30, 85 + i * 75, 10151); builder.AddImage(30, 100 + i * 75, 10151); builder.AddImage(30, 106 + i * 75, 10154); builder.AddImage(24, 60 + i * 75, teamInfo == ourTeam ? 9730 : 9727, teamInfo.Color - 1); var borderColor = teamInfo.Color == 0x455 ? LabelColor32 : BlackColor32; var nameColor = teamInfo.Color switch { 0x47E => 0xFFFFFF, 0x4F2 => 0x3399FF, 0x4F7 => 0x33FF33, 0x4FC => 0xFF00FF, 0x021 => 0xFF3333, 0x01A => 0xFF66FF, 0x455 => 0x333333, _ => LabelColor32 }; AddBorderedText( ref builder, 60, 65 + i * 75, 250, 20, $"{LadderGump.Rank(1 + i)}: {teamInfo.Name}", nameColor, borderColor ); AddBorderedText(ref builder, 50 + 10, 85 + i * 75, 100, 20, "Score:", 0xFFC000, BlackColor32); AddBorderedText(ref builder, 50 + 15, 105 + i * 75, 100, 20, $"{teamInfo.Score:N0}", 0xFFC000, BlackColor32); AddBorderedText(ref builder, 110 + 10, 85 + i * 75, 100, 20, "Kills:", 0xFFC000, BlackColor32); AddBorderedText(ref builder, 110 + 15, 105 + i * 75, 100, 20, $"{teamInfo.Kills:N0}", 0xFFC000, BlackColor32); AddBorderedText(ref builder, 160 + 10, 85 + i * 75, 100, 20, "Points:", 0xFFC000, BlackColor32); AddBorderedText( ref builder, 160 + 15, 105 + i * 75, 100, 20, $"{teamInfo.Captures:N0}", 0xFFC000, BlackColor32 ); var pl = teamInfo.Leader; AddBorderedText(ref builder, 235 + 10, 85 + i * 75, 250, 20, "Leader:", 0xFFC000, BlackColor32); if (pl != null) { AddBorderedText(ref builder, 235 + 15, 105 + i * 75, 250, 20, pl.Player.Name, 0xFFC000, BlackColor32); } } } builder.AddButton(314, height - 42, 247, 248, 1); } private static void AddBorderedText( ref DynamicGumpBuilder builder, int x, int y, int width, int height, string text, int color, int borderColor ) { AddColoredText(ref builder, x - 1, y - 1, width, height, text, borderColor); AddColoredText(ref builder, x - 1, y + 1, width, height, text, borderColor); AddColoredText(ref builder, x + 1, y - 1, width, height, text, borderColor); AddColoredText(ref builder, x + 1, y + 1, width, height, text, borderColor); AddColoredText(ref builder, x, y, width, height, text, color); } private static void AddColoredText( ref DynamicGumpBuilder builder, int x, int y, int width, int height, string text, int color ) => builder.AddHtml(x, y, width, height, color == 0 ? text : text.Color(color)); } public sealed class BRPlayerInfo : IRankedCTF, IComparable { private readonly BRTeamInfo m_TeamInfo; private int m_Captures; private int m_Kills; private int m_Score; public BRPlayerInfo(BRTeamInfo teamInfo, Mobile player) { m_TeamInfo = teamInfo; Player = player; } public Mobile Player { get; } public int CompareTo(BRPlayerInfo pi) { var res = pi.Captures.CompareTo(Captures); if (res != 0) { return res; } res = pi.Score.CompareTo(Score); if (res == 0) { res = pi.Kills.CompareTo(Kills); } return res; } public string Name => Player.Name; public int Kills { get => m_Kills; set { m_TeamInfo.Kills += value - m_Kills; m_Kills = value; } } public int Captures { get => m_Captures; set { m_TeamInfo.Captures += value - m_Captures; m_Captures = value; } } public int Score { get => m_Score; set { m_TeamInfo.Score += value - m_Score; m_Score = value; if (m_TeamInfo.Leader == null || m_Score > m_TeamInfo.Leader.Score) { m_TeamInfo.Leader = this; } } } } [PropertyObject] public sealed class BRTeamInfo : IRankedCTF, IComparable { private BRGoal m_Goal; public BRTeamInfo(int teamID) { TeamID = teamID; Players = new Dictionary(); } public BRTeamInfo(int teamID, IGenericReader ip) { TeamID = teamID; Players = new Dictionary(); var version = ip.ReadEncodedInt(); switch (version) { case 0: { Board = ip.ReadEntity(); TeamName = ip.ReadString(); Color = ip.ReadEncodedInt(); m_Goal = ip.ReadEntity(); break; } } } public BRGame Game { get; set; } public int TeamID { get; } public BRPlayerInfo Leader { get; set; } [CommandProperty(AccessLevel.GameMaster)] public BRBoard Board { get; set; } public Dictionary Players { get; } public BRPlayerInfo this[Mobile mob] { get { if (mob == null) { return null; } if (!Players.TryGetValue(mob, out var val)) { Players[mob] = val = new BRPlayerInfo(this, mob); } return val; } } [CommandProperty(AccessLevel.GameMaster)] public int Color { get; set; } [CommandProperty(AccessLevel.GameMaster)] public string TeamName { get; set; } [CommandProperty(AccessLevel.GameMaster)] public BRGoal Goal { get => m_Goal; set { m_Goal = value; m_Goal?.Team = this; } } public int CompareTo(BRTeamInfo ti) { var res = ti.Captures.CompareTo(Captures); if (res == 0) { res = ti.Score.CompareTo(Score); if (res == 0) { res = ti.Kills.CompareTo(Kills); } } return res; } public string Name => $"{TeamName} Team"; public int Kills { get; set; } public int Captures { get; set; } public int Score { get; set; } public void Reset() { Kills = 0; Captures = 0; Score = 0; Leader = null; Players.Clear(); Board?.m_TeamInfo = this; m_Goal?.Team = this; } public void Serialize(IGenericWriter op) { op.WriteEncodedInt(0); // version op.Write(Board); op.Write(TeamName); op.WriteEncodedInt(Color); op.Write(m_Goal); } public override string ToString() => TeamName != null ? $"({Name}) ..." : "..."; } public sealed class BRController : EventController { [Constructible] public BRController() { Visible = false; Movable = false; Duration = TimeSpan.FromMinutes(30.0); BombHome = Point3D.Zero; TeamInfo = new BRTeamInfo[4]; for (var i = 0; i < TeamInfo.Length; ++i) { TeamInfo[i] = new BRTeamInfo(i); } } public BRController(Serial serial) : base(serial) { } public BRTeamInfo[] TeamInfo { get; private set; } [CommandProperty(AccessLevel.GameMaster)] public BRTeamInfo Team1 => TeamInfo[0]; [CommandProperty(AccessLevel.GameMaster)] public BRTeamInfo Team2 => TeamInfo[1]; [CommandProperty(AccessLevel.GameMaster)] public BRTeamInfo Team3 => TeamInfo[2]; [CommandProperty(AccessLevel.GameMaster)] public BRTeamInfo Team4 => TeamInfo[3]; [CommandProperty(AccessLevel.GameMaster)] public TimeSpan Duration { get; set; } [CommandProperty(AccessLevel.GameMaster)] public Point3D BombHome { get; set; } public override string Title => "Bombing Run"; public override string DefaultName => "Bombing Run Controller"; public override string GetTeamName(int teamID) => TeamInfo[teamID % TeamInfo.Length].Name; public override EventGame Construct(DuelContext context) => new BRGame(this, context); public override void Serialize(IGenericWriter writer) { base.Serialize(writer); writer.Write(0); writer.Write(BombHome); writer.Write(Duration); writer.WriteEncodedInt(TeamInfo.Length); for (var i = 0; i < TeamInfo.Length; ++i) { TeamInfo[i].Serialize(writer); } } public override void Deserialize(IGenericReader reader) { base.Deserialize(reader); var version = reader.ReadInt(); switch (version) { case 0: { BombHome = reader.ReadPoint3D(); Duration = reader.ReadTimeSpan(); TeamInfo = new BRTeamInfo[reader.ReadEncodedInt()]; for (var i = 0; i < TeamInfo.Length; ++i) { TeamInfo[i] = new BRTeamInfo(i, reader); } break; } } } } public sealed class BRGame : EventGame { private BRBomb m_Bomb; private TimerExecutionToken _finishTimerToken; public BRGame(BRController controller, DuelContext context) : base(context) => Controller = controller; public BRController Controller { get; } public Map Facet => m_Context.Arena != null ? m_Context.Arena.Facet : Controller.Map; public override bool CantDoAnything(Mobile mob) => mob.Backpack?.FindItemByType() != null && GetTeamInfo(mob) != null; public void ReturnBomb() { if (m_Bomb != null && Controller != null) { m_Bomb.Visible = false; m_Bomb.MoveToWorld(Controller.BombHome, Controller.Map); Timer.StartTimer(TimeSpan.FromSeconds(Utility.RandomMinMax(5, 15)), UnhideBomb); } } private void UnhideBomb() { if (m_Bomb != null) { m_Bomb.Visible = true; m_Bomb.OnLocationChange(m_Bomb.Location); } } public void Alert(string text) { m_Context.m_Tournament?.Alert(text); for (var i = 0; i < m_Context.Participants.Count; ++i) { var p = m_Context.Participants[i]; for (var j = 0; j < p.Players.Length; ++j) { if (p.Players[j] != null) { p.Players[j].Mobile.SendMessage(0x35, text); } } } } public BRTeamInfo GetTeamInfo(Mobile mob) { var teamID = GetTeamID(mob); if (teamID >= 0) { return Controller.TeamInfo[teamID % Controller.TeamInfo.Length]; } return null; } public int GetTeamID(Mobile mob) { if (mob is not PlayerMobile pm) { return mob is BaseCreature creature ? creature.Team - 1 : -1; } if (pm.DuelContext == null || pm.DuelContext != m_Context) { return -1; } if (pm.DuelPlayer?.Eliminated != false) { return -1; } return pm.DuelContext.Participants.IndexOf(pm.DuelPlayer.Participant); } public int GetColor(Mobile mob) => GetTeamInfo(mob)?.Color ?? -1; private void ApplyHues(Participant p, int hueOverride) { for (var i = 0; i < p.Players.Length; ++i) { if (p.Players[i] != null) { p.Players[i].Mobile.SolidHueOverride = hueOverride; } } } public void DelayBounce(TimeSpan ts, Mobile mob, Container corpse) { Timer.StartTimer(ts, () => DelayBounce_Callback(mob, corpse)); } private void DelayBounce_Callback(Mobile mob, Container corpse) { var dp = (mob as PlayerMobile)?.DuelPlayer; m_Context.RemoveAggressions(mob); if (dp?.Eliminated == false) { mob.MoveToWorld(m_Context.Arena.GetBaseStartPoint(GetTeamID(mob)), Facet); } else { m_Context.SendOutside(mob); } m_Context.Refresh(mob, corpse); DuelContext.Debuff(mob); DuelContext.CancelSpell(mob); mob.Frozen = false; } public override bool OnDeath(Mobile mob, Container corpse) { var killer = mob.FindMostRecentDamager(false); var hadBomb = false; using (var queue = corpse.EnumerateItemsByType(false)) { foreach (var bomb in queue) { hadBomb = true; bomb.DropTo(mob, killer); } } if (mob.Backpack != null) { using var queue = mob.Backpack.EnumerateItemsByType(false); foreach (var bomb in queue) { hadBomb = true; bomb.DropTo(mob, killer); } } if (killer?.Player == true) { var teamInfo = GetTeamInfo(killer); var victInfo = GetTeamInfo(mob); if (teamInfo != null && teamInfo != victInfo) { var playerInfo = teamInfo[killer]; if (playerInfo != null) { playerInfo.Kills += 1; playerInfo.Score += 1; // base frag if (hadBomb) { playerInfo.Score += 4; // fragged bomb carrier } } } } BRBoardGump.DisplayTo(mob, this); m_Context.Requip(mob, corpse); DelayBounce(TimeSpan.FromSeconds(30.0), mob, corpse); return false; } public override void OnStart() { for (var i = 0; i < Controller.TeamInfo.Length; ++i) { var teamInfo = Controller.TeamInfo[i]; teamInfo.Game = this; teamInfo.Reset(); } for (var i = 0; i < m_Context.Participants.Count; ++i) { ApplyHues( m_Context.Participants[i], Controller.TeamInfo[i % Controller.TeamInfo.Length].Color ); } _finishTimerToken.Cancel(); m_Bomb = new BRBomb(this); ReturnBomb(); Timer.StartTimer(Controller.Duration, Finish_Callback, out _finishTimerToken); } private void Finish_Callback() { var teams = new List(); for (var i = 0; i < m_Context.Participants.Count; ++i) { var teamInfo = Controller.TeamInfo[i % Controller.TeamInfo.Length]; if (teamInfo != null) { teams.Add(teamInfo); } } teams.Sort(); var tourney = m_Context.m_Tournament; using var sb = ValueStringBuilder.Create(256); if (tourney != null) { if (tourney.TourneyType == TourneyType.FreeForAll) { sb.Append(m_Context.Participants.Count * tourney.PlayersPerParticipant); sb.Append("-man 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 (var i = 0; i < tourney.ParticipantsPerMatch; ++i) { if (sb.Length > 0) { sb.Append('v'); } sb.Append(tourney.PlayersPerParticipant); } } } if (Controller != null) { sb.Append(' '); sb.Append(Controller.Title); } var title = sb.ToString(); var winner = teams.Count > 0 ? teams[0] : null; for (var i = 0; i < teams.Count; ++i) { var rank = i switch { 0 => TrophyRank.Gold, 1 => TrophyRank.Silver, _ => TrophyRank.Bronze }; var leader = teams[i].Leader; foreach (var pl in teams[i].Players.Values) { var mob = pl.Player; if (mob == null) { continue; } sb.Reset(); sb.Append(title); if (pl == leader) { sb.Append(" Leader"); } if (pl.Score > 0) { sb.Append(": "); // sb.Append( pl.Score.ToString( "N0" ) ); // sb.Append( pl.Score == 1 ? " point" : " points" ); sb.Append(pl.Kills.ToString("N0")); sb.Append(pl.Kills == 1 ? " kill" : " kills"); if (pl.Captures > 0) { sb.Append(", "); sb.Append(pl.Captures.ToString("N0")); sb.Append(pl.Captures == 1 ? " point" : " points"); } } Item item = new Trophy(sb.ToString(), rank); if (pl == leader) { item.ItemID = 4810; } item.Name = $"{item.Name}, {teams[i].Name.ToLower()} team"; if (!mob.PlaceInBackpack(item)) { mob.BankBox.DropItem(item); } var cash = pl.Score * 250; if (cash > 0) { item = new BankCheck(cash); if (!mob.PlaceInBackpack(item)) { mob.BankBox.DropItem(item); } mob.SendMessage( //There is no formatting flag for Lowercase, we may need a custom interface to get rid of it $"You have been awarded a {rank.LowerName()} trophy and {cash:N0}gp for your participation in this tournament." ); } else { mob.SendMessage( $"You have been awarded a {rank.LowerName()} trophy for your participation in this tournament." ); } } } for (var i = 0; i < m_Context.Participants.Count; ++i) { var p = m_Context.Participants[i]; if (p?.Players == null) { continue; } for (var j = 0; j < p.Players.Length; ++j) { var dp = p.Players[j]; if (dp?.Mobile != null) { BRBoardGump.DisplayTo(dp.Mobile, this); } } if (i == winner?.TeamID) { continue; } if (p.Players != null) { for (var j = 0; j < p.Players.Length; ++j) { p.Players[j]?.Eliminated = true; } } } if (winner != null) { m_Context.Finish(m_Context.Participants[winner.TeamID]); } } public override void OnStop() { for (var i = 0; i < Controller.TeamInfo.Length; ++i) { var teamInfo = Controller.TeamInfo[i]; teamInfo.Board?.m_TeamInfo = null; teamInfo.Game = null; } ReturnBomb(); m_Bomb?.Delete(); for (var i = 0; i < m_Context.Participants.Count; ++i) { ApplyHues(m_Context.Participants[i], -1); } _finishTimerToken.Cancel(); } }