diff --git a/Projects/UOContent/Engines/ConPVP/DuelContext.cs b/Projects/UOContent/Engines/ConPVP/DuelContext.cs index ef7fb5b89..f30e89bcf 100644 --- a/Projects/UOContent/Engines/ConPVP/DuelContext.cs +++ b/Projects/UOContent/Engines/ConPVP/DuelContext.cs @@ -2,6 +2,7 @@ using System; using System.Collections; using System.Collections.Generic; using ModernUO.CodeGeneratedEvents; +using ModernUO.Serialization; using Server.Engines.PartySystem; using Server.Factions; using Server.Gumps; @@ -19,2249 +20,1910 @@ using Server.Spells.Seventh; using Server.Spells.Spellweaving; using Server.Targeting; -namespace Server.Engines.ConPVP +namespace Server.Engines.ConPVP; + +public delegate void CountdownCallback(int count); + +public partial class DuelContext { - public delegate void CountdownCallback(int count); + private static readonly TimeSpan CombatDelay = TimeSpan.FromSeconds(30.0); + private static readonly TimeSpan AutoTieDelay = TimeSpan.FromMinutes(15.0); - public class DuelContext + private readonly List m_Walls = new(); + + private TimerExecutionToken _autoTieTimerToken; + private TimerExecutionToken _countdownTimerToken; + private TimerExecutionToken _SdWarnTimerToken; + private TimerExecutionToken _SdActivateTimerToken; + + public EventGame m_EventGame; + private Map m_GateFacet; + + private Point3D m_GatePoint; + public TourneyMatch m_Match; + + public Arena m_OverrideArena; + + public Tournament m_Tournament; + + private bool m_Yielding; + + public DuelContext(Mobile initiator, RulesetLayout layout, bool addNew = true) { - private static readonly TimeSpan CombatDelay = TimeSpan.FromSeconds(30.0); - private static readonly TimeSpan AutoTieDelay = TimeSpan.FromMinutes(15.0); + Initiator = initiator; + Participants = new List(); + Ruleset = new Ruleset(layout); + Ruleset.ApplyDefault(layout.Defaults[0]); - private readonly List m_Walls = new(); - - private TimerExecutionToken _autoTieTimerToken; - private TimerExecutionToken _countdownTimerToken; - private TimerExecutionToken _SdWarnTimerToken; - private TimerExecutionToken _SdActivateTimerToken; - - public EventGame m_EventGame; - private Map m_GateFacet; - - private Point3D m_GatePoint; - public TourneyMatch m_Match; - - public Arena m_OverrideArena; - - public Tournament m_Tournament; - - private bool m_Yielding; - - public DuelContext(Mobile initiator, RulesetLayout layout, bool addNew = true) + if (addNew) { - Initiator = initiator; - Participants = new List(); - Ruleset = new Ruleset(layout); - Ruleset.ApplyDefault(layout.Defaults[0]); - - if (addNew) - { - Participants.Add(new Participant(this, 1)); - Participants.Add(new Participant(this, 1)); - Participants[0].Add(initiator); - } + Participants.Add(new Participant(this, 1)); + Participants.Add(new Participant(this, 1)); + Participants[0].Add(initiator); } + } - public bool Rematch { get; private set; } + public bool Rematch { get; private set; } - public bool ReadyWait { get; private set; } + public bool ReadyWait { get; private set; } - public int ReadyCount { get; private set; } + public int ReadyCount { get; private set; } - public bool Registered { get; private set; } = true; + public bool Registered { get; private set; } = true; - public bool Finished { get; private set; } + public bool Finished { get; private set; } - public bool Started { get; private set; } + public bool Started { get; private set; } - public Mobile Initiator { get; } + public Mobile Initiator { get; } - public List Participants { get; } + public List Participants { get; } - public Ruleset Ruleset { get; private set; } + public Ruleset Ruleset { get; private set; } - public Arena Arena { get; private set; } + public Arena Arena { get; private set; } - public bool Tied { get; private set; } + public bool Tied { get; private set; } - public bool IsSuddenDeath { get; set; } + public bool IsSuddenDeath { get; set; } - public bool IsOneVsOne => Participants.Count == 2 && Participants[0].Players.Length == 1 && - Participants[1].Players.Length == 1; + public bool IsOneVsOne => Participants.Count == 2 && Participants[0].Players.Length == 1 && + Participants[1].Players.Length == 1; - public bool StartedBeginCountdown { get; private set; } + public bool StartedBeginCountdown { get; private set; } - public bool StartedReadyCountdown { get; private set; } + public bool StartedReadyCountdown { get; private set; } - public Tournament Tournament => m_Tournament; + public Tournament Tournament => m_Tournament; - private bool CantDoAnything(Mobile mob) => m_EventGame?.CantDoAnything(mob) == true; + private bool CantDoAnything(Mobile mob) => m_EventGame?.CantDoAnything(mob) == true; - public static bool IsFreeConsume(Mobile mob) + public static bool IsFreeConsume(Mobile mob) + { + if (mob is not PlayerMobile pm || pm.DuelContext?.m_EventGame == null) { - if (mob is not PlayerMobile pm || pm.DuelContext?.m_EventGame == null) - { - return false; - } - - return pm.DuelContext.m_EventGame.FreeConsume; - } - - public void DelayBounce(TimeSpan ts, Mobile mob, Container corpse) - { - Timer.StartTimer(ts, () => DelayBounce_Callback(mob, corpse)); - } - - public static bool AllowSpecialMove(Mobile from, string name, SpecialMove move) => - (from as PlayerMobile)?.DuelContext?.InstAllowSpecialMove(from, name, move) != false; - - public bool InstAllowSpecialMove(Mobile from, string name, SpecialMove move) - { - if (!StartedBeginCountdown) - { - return true; - } - - var pl = Find(from); - - if (pl?.Eliminated != false) - { - return true; - } - - if (CantDoAnything(from)) - { - return false; - } - - var title = move switch - { - NinjaMove => "Bushido", - SamuraiMove => "Ninjitsu", - _ => null - }; - - if (title == null || name == null || Ruleset.GetOption(title, name)) - { - return true; - } - - from.SendMessage("The dueling ruleset prevents you from using this move."); return false; } - public bool AllowSpellCast(Mobile from, Spell spell) + return pm.DuelContext.m_EventGame.FreeConsume; + } + + public void DelayBounce(TimeSpan ts, Mobile mob, Container corpse) + { + Timer.StartTimer(ts, () => DelayBounce_Callback(mob, corpse)); + } + + public static bool AllowSpecialMove(Mobile from, string name, SpecialMove move) => + (from as PlayerMobile)?.DuelContext?.InstAllowSpecialMove(from, name, move) != false; + + public bool InstAllowSpecialMove(Mobile from, string name, SpecialMove move) + { + if (!StartedBeginCountdown) { - if (!StartedBeginCountdown) - { - return true; - } + return true; + } - if (Find(from)?.Eliminated != false) - { - return true; - } + var pl = Find(from); - if (CantDoAnything(from)) + if (pl?.Eliminated != false) + { + return true; + } + + if (CantDoAnything(from)) + { + return false; + } + + var title = move switch + { + NinjaMove => "Bushido", + SamuraiMove => "Ninjitsu", + _ => null + }; + + if (title == null || name == null || Ruleset.GetOption(title, name)) + { + return true; + } + + from.SendMessage("The dueling ruleset prevents you from using this move."); + return false; + } + + public bool AllowSpellCast(Mobile from, Spell spell) + { + if (!StartedBeginCountdown) + { + return true; + } + + if (Find(from)?.Eliminated != false) + { + return true; + } + + if (CantDoAnything(from)) + { + return false; + } + + if (spell is RecallSpell) + { + from.SendMessage("You may not cast this spell."); + } + + string title; + string option; + + switch (spell) + { + case ArcanistSpell _: + title = "Spellweaving"; + option = spell.Name; + break; + case PaladinSpell _: + title = "Chivalry"; + option = spell.Name; + break; + case NecromancerSpell _: + title = "Necromancy"; + option = spell.Name; + break; + case NinjaSpell _: + title = "Ninjitsu"; + option = spell.Name; + break; + case SamuraiSpell _: + title = "Bushido"; + option = spell.Name; + break; + case MagerySpell magerySpell: + title = magerySpell.Circle switch + { + SpellCircle.First => "1st Circle", + SpellCircle.Second => "2nd Circle", + SpellCircle.Third => "3rd Circle", + SpellCircle.Fourth => "4th Circle", + SpellCircle.Fifth => "5th Circle", + SpellCircle.Sixth => "6th Circle", + SpellCircle.Seventh => "7th Circle", + SpellCircle.Eighth => "8th Circle", + _ => null + }; + + option = magerySpell.Name; + break; + default: + title = "Other Spell"; + option = spell.Name; + break; + } + + if (title == null || option == null || Ruleset.GetOption(title, option)) + { + return true; + } + + from.SendMessage("The dueling ruleset prevents you from casting this spell."); + return false; + } + + public bool AllowItemEquip(Mobile from, Item item) + { + if (!StartedBeginCountdown) + { + return true; + } + + var pl = Find(from); + + if (pl?.Eliminated != false) + { + return true; + } + + if (item is Dagger || CheckItemEquip(from, item)) + { + return true; + } + + from.SendMessage("The dueling ruleset prevents you from equipping this item."); + return false; + } + + public static bool AllowSpecialAbility(Mobile from, string name, bool message) + { + if (from is not PlayerMobile pm) + { + return true; + } + + var dc = pm.DuelContext; + + // No DuelContext or InstAllowSpecialAbility + return dc?.InstAllowSpecialAbility(from, name, message) != false; + } + + public bool InstAllowSpecialAbility(Mobile from, string name, bool message) + { + if (!StartedBeginCountdown) + { + return true; + } + + var pl = Find(from); + + if (pl?.Eliminated != false) + { + return true; + } + + if (CantDoAnything(from)) + { + return false; + } + + if (Ruleset.GetOption("Combat Abilities", name)) + { + return true; + } + + if (message) + { + from.SendMessage("The dueling ruleset prevents you from using this combat ability."); + } + + return false; + } + + public bool CheckItemEquip(Mobile from, Item item) + { + if (item is Fists) + { + if (!Ruleset.GetOption("Weapons", "Wrestling")) + { + return false; + } + } + else if (item is BaseArmor armor) + { + if (armor.ProtectionLevel > ArmorProtectionLevel.Regular && !Ruleset.GetOption("Armor", "Magical")) { return false; } - if (spell is RecallSpell) + if (!Core.AOS && armor.Resource != armor.DefaultResource && !Ruleset.GetOption("Armor", "Colored")) { - from.SendMessage("You may not cast this spell."); + return false; } - string title; - string option; - - switch (spell) + if (armor is BaseShield && !Ruleset.GetOption("Armor", "Shields")) { - case ArcanistSpell _: - title = "Spellweaving"; - option = spell.Name; - break; - case PaladinSpell _: - title = "Chivalry"; - option = spell.Name; - break; - case NecromancerSpell _: - title = "Necromancy"; - option = spell.Name; - break; - case NinjaSpell _: - title = "Ninjitsu"; - option = spell.Name; - break; - case SamuraiSpell _: - title = "Bushido"; - option = spell.Name; - break; - case MagerySpell magerySpell: - title = magerySpell.Circle switch + return false; + } + } + else if (item is BaseWeapon weapon) + { + if ((weapon.DamageLevel > WeaponDamageLevel.Regular || weapon.AccuracyLevel > WeaponAccuracyLevel.Regular) && + !Ruleset.GetOption("Weapons", "Magical")) + { + return false; + } + + if (!Core.AOS && weapon.Resource != CraftResource.Iron && weapon.Resource != CraftResource.None && + !Ruleset.GetOption("Weapons", "Runics")) + { + return false; + } + + if (weapon is BaseRanged && !Ruleset.GetOption("Weapons", "Ranged")) + { + return false; + } + + if (weapon is not BaseRanged && !Ruleset.GetOption("Weapons", "Melee")) + { + return false; + } + + if (weapon.PoisonCharges > 0 && weapon.Poison != null && !Ruleset.GetOption("Weapons", "Poisoned")) + { + return false; + } + + if (weapon is BaseWand && !Ruleset.GetOption("Items", "Wands")) + { + return false; + } + } + + return true; + } + + public bool AllowSkillUse(Mobile from, SkillName skill) + { + if (!StartedBeginCountdown) + { + return true; + } + + var pl = Find(from); + + if (pl?.Eliminated != false) + { + return true; + } + + if (CantDoAnything(from)) + { + return false; + } + + var id = (int)skill; + + if (id >= 0 && id < SkillInfo.Table.Length) + { + if (Ruleset.GetOption("Skills", SkillInfo.Table[id].Name)) + { + return true; + } + } + + from.SendMessage("The dueling ruleset prevents you from using this skill."); + return false; + } + + public bool AllowItemUse(Mobile from, Item item) + { + if (!StartedBeginCountdown) + { + return true; + } + + var pl = Find(from); + + if (pl?.Eliminated != false) + { + return true; + } + + if (item is not BaseRefreshPotion) + { + if (CantDoAnything(from)) + { + return false; + } + } + + string title = null, option = null; + + if (item is BasePotion) + { + title = "Potions"; + + if (item is BaseAgilityPotion) + { + option = "Agility"; + } + else if (item is BaseCurePotion) + { + option = "Cure"; + } + else if (item is BaseHealPotion) + { + option = "Heal"; + } + else if (item is NightSightPotion) + { + option = "Nightsight"; + } + else if (item is BasePoisonPotion) + { + option = "Poison"; + } + else if (item is BaseStrengthPotion) + { + option = "Strength"; + } + else if (item is BaseExplosionPotion) + { + option = "Explosion"; + } + else if (item is BaseRefreshPotion) + { + option = "Refresh"; + } + } + else if (item is Bandage) + { + title = "Items"; + option = "Bandages"; + } + else if (item is TrappableContainer container) + { + if (container.TrapType != TrapType.None) + { + title = "Items"; + option = "Trapped Containers"; + } + } + else if (item is Bola) + { + title = "Items"; + option = "Bolas"; + } + else if (item is OrangePetals) + { + title = "Items"; + option = "Orange Petals"; + } + else if (item is EtherealMount || item.Layer == Layer.Mount) + { + title = "Items"; + option = "Mounts"; + } + else if (item is LeatherNinjaBelt) + { + title = "Items"; + option = "Shurikens"; + } + else if (item is Fukiya) + { + title = "Items"; + option = "Fukiya Darts"; + } + else if (item is FireHorn) + { + title = "Items"; + option = "Fire Horns"; + } + else if (item is BaseWand) + { + title = "Items"; + option = "Wands"; + } + + if (title != null && option != null && StartedBeginCountdown && !Started) + { + from.SendMessage("You may not use this item before the duel begins."); + return false; + } + + if (item is BasePotion && item is not BaseExplosionPotion && item is not BaseRefreshPotion && IsSuddenDeath) + { + from.SendMessage(0x22, "You may not drink potions in sudden death."); + return false; + } + + if (item is Bandage && IsSuddenDeath) + { + from.SendMessage(0x22, "You may not use bandages in sudden death."); + return false; + } + + if (title == null || option == null || Ruleset.GetOption(title, option)) + { + return true; + } + + from.SendMessage("The dueling ruleset prevents you from using this item."); + return false; + } + + private void DelayBounce_Callback(Mobile mob, Container corpse) + { + RemoveAggressions(mob); + SendOutside(mob); + Refresh(mob, corpse); + Debuff(mob); + CancelSpell(mob); + mob.Frozen = false; + } + + public void OnMapChanged(Mobile mob) + { + OnLocationChanged(mob); + } + + public void OnLocationChanged(Mobile mob) + { + if (!Registered || !StartedBeginCountdown || Finished) + { + return; + } + + var arena = Arena; + + if (arena == null) + { + return; + } + + if (mob.Map == arena.Facet && arena.Bounds.Contains(mob.Location)) + { + return; + } + + var pl = Find(mob); + + if (pl?.Eliminated != false) + { + return; + } + + if (mob.Map == Map.Internal) + { + if (mob.LogoutMap == arena.Facet && arena.Bounds.Contains(mob.LogoutLocation)) + { + mob.LogoutLocation = arena.Outside; + } + } + + pl.Eliminated = true; + + mob.LocalOverheadMessage(MessageType.Regular, 0x22, false, "You have forfeited your position in the duel."); + mob.NonlocalOverheadMessage( + MessageType.Regular, + 0x22, + false, + $"{mob.Name} has forfeited by leaving the dueling arena." + ); + + var winner = CheckCompletion(); + + if (winner != null) + { + Finish(winner); + } + } + + [OnEvent(nameof(PlayerMobile.PlayerDeathEvent))] + public static void OnPlayerDeath(PlayerMobile m) => m.DuelContext?.OnDeath(m, m.Corpse); + + public void OnDeath(Mobile mob, Container corpse) + { + if (!Registered || !Started) + { + return; + } + + var pl = Find(mob); + + if (pl?.Eliminated != false || m_EventGame?.OnDeath(mob, corpse) == false) + { + return; + } + + pl.Eliminated = true; + + if (mob.Poison != null) + { + mob.Poison = null; + } + + Requip(mob, corpse); + DelayBounce(TimeSpan.FromSeconds(4.0), mob, corpse); + + var winner = CheckCompletion(); + + if (winner != null) + { + Finish(winner); + } + else if (!m_Yielding) + { + mob.LocalOverheadMessage(MessageType.Regular, 0x22, false, "You have been defeated."); + mob.NonlocalOverheadMessage(MessageType.Regular, 0x22, false, $"{mob.Name} has been defeated."); + } + } + + public bool CheckFull() + { + for (var i = 0; i < Participants.Count; ++i) + { + var p = Participants[i]; + + if (p.HasOpenSlot) + { + return false; + } + } + + return true; + } + + public void Requip(Mobile from, Container cont) + { + if (cont is not Corpse corpse) + { + return; + } + + var items = new List(corpse.Items); + + var didntFit = false; + + var pack = from.Backpack; + + for (var i = 0; !didntFit && i < items.Count; ++i) + { + var item = items[i]; + + if (item.Layer is Layer.Hair or Layer.FacialHair || !item.Movable) + { + continue; + } + + if (pack != null) + { + pack.DropItem(item); + } + else + { + didntFit = true; + } + } + + corpse.Carved = true; + + if (corpse.ItemID == 0x2006) + { + corpse.ProcessDelta(); + corpse.SendRemovePacket(); + corpse.ItemID = Utility.Random(0xECA, 9); // bone graphic + corpse.Hue = 0; + corpse.ProcessDelta(); + + var killer = from.FindMostRecentDamager(false); + + if (killer?.Player == true) + { + killer.AddToBackpack(new Head(m_Tournament == null ? HeadType.Duel : HeadType.Tournament, from.Name)); + } + } + + from.PlaySound(0x3E3); + + if (didntFit) + { + from.SendLocalizedMessage(1062472); // You gather some of your belongings. The rest remain on the corpse. + } + else + { + from.SendLocalizedMessage(1062471); // You quickly gather all of your belongings. + } + } + + public void Refresh(Mobile mob, Container cont) + { + if (!mob.Alive) + { + mob.Resurrect(); + + mob.FindItemOnLayer(Layer.OuterTorso)?.Delete(); + + if (cont is Corpse corpse) + { + for (var i = 0; i < corpse.EquipItems.Count; ++i) + { + var item = corpse.EquipItems[i]; + + if (item.Movable && item.Layer != Layer.Hair && item.Layer != Layer.FacialHair && + item.IsChildOf(mob.Backpack)) { - SpellCircle.First => "1st Circle", - SpellCircle.Second => "2nd Circle", - SpellCircle.Third => "3rd Circle", - SpellCircle.Fourth => "4th Circle", - SpellCircle.Fifth => "5th Circle", - SpellCircle.Sixth => "6th Circle", - SpellCircle.Seventh => "7th Circle", - SpellCircle.Eighth => "8th Circle", - _ => null - }; - - option = magerySpell.Name; - break; - default: - title = "Other Spell"; - option = spell.Name; - break; - } - - if (title == null || option == null || Ruleset.GetOption(title, option)) - { - return true; - } - - from.SendMessage("The dueling ruleset prevents you from casting this spell."); - return false; - } - - public bool AllowItemEquip(Mobile from, Item item) - { - if (!StartedBeginCountdown) - { - return true; - } - - var pl = Find(from); - - if (pl?.Eliminated != false) - { - return true; - } - - if (item is Dagger || CheckItemEquip(from, item)) - { - return true; - } - - from.SendMessage("The dueling ruleset prevents you from equipping this item."); - return false; - } - - public static bool AllowSpecialAbility(Mobile from, string name, bool message) - { - if (from is not PlayerMobile pm) - { - return true; - } - - var dc = pm.DuelContext; - - // No DuelContext or InstAllowSpecialAbility - return dc?.InstAllowSpecialAbility(from, name, message) != false; - } - - public bool InstAllowSpecialAbility(Mobile from, string name, bool message) - { - if (!StartedBeginCountdown) - { - return true; - } - - var pl = Find(from); - - if (pl?.Eliminated != false) - { - return true; - } - - if (CantDoAnything(from)) - { - return false; - } - - if (Ruleset.GetOption("Combat Abilities", name)) - { - return true; - } - - if (message) - { - from.SendMessage("The dueling ruleset prevents you from using this combat ability."); - } - - return false; - } - - public bool CheckItemEquip(Mobile from, Item item) - { - if (item is Fists) - { - if (!Ruleset.GetOption("Weapons", "Wrestling")) - { - return false; + mob.EquipItem(item); + } } } - else if (item is BaseArmor armor) - { - if (armor.ProtectionLevel > ArmorProtectionLevel.Regular && !Ruleset.GetOption("Armor", "Magical")) - { - return false; - } - - if (!Core.AOS && armor.Resource != armor.DefaultResource && !Ruleset.GetOption("Armor", "Colored")) - { - return false; - } - - if (armor is BaseShield && !Ruleset.GetOption("Armor", "Shields")) - { - return false; - } - } - else if (item is BaseWeapon weapon) - { - if ((weapon.DamageLevel > WeaponDamageLevel.Regular || weapon.AccuracyLevel > WeaponAccuracyLevel.Regular) && - !Ruleset.GetOption("Weapons", "Magical")) - { - return false; - } - - if (!Core.AOS && weapon.Resource != CraftResource.Iron && weapon.Resource != CraftResource.None && - !Ruleset.GetOption("Weapons", "Runics")) - { - return false; - } - - if (weapon is BaseRanged && !Ruleset.GetOption("Weapons", "Ranged")) - { - return false; - } - - if (weapon is not BaseRanged && !Ruleset.GetOption("Weapons", "Melee")) - { - return false; - } - - if (weapon.PoisonCharges > 0 && weapon.Poison != null && !Ruleset.GetOption("Weapons", "Poisoned")) - { - return false; - } - - if (weapon is BaseWand && !Ruleset.GetOption("Items", "Wands")) - { - return false; - } - } - - return true; - } - - public bool AllowSkillUse(Mobile from, SkillName skill) - { - if (!StartedBeginCountdown) - { - return true; - } - - var pl = Find(from); - - if (pl?.Eliminated != false) - { - return true; - } - - if (CantDoAnything(from)) - { - return false; - } - - var id = (int)skill; - - if (id >= 0 && id < SkillInfo.Table.Length) - { - if (Ruleset.GetOption("Skills", SkillInfo.Table[id].Name)) - { - return true; - } - } - - from.SendMessage("The dueling ruleset prevents you from using this skill."); - return false; - } - - public bool AllowItemUse(Mobile from, Item item) - { - if (!StartedBeginCountdown) - { - return true; - } - - var pl = Find(from); - - if (pl?.Eliminated != false) - { - return true; - } - - if (item is not BaseRefreshPotion) - { - if (CantDoAnything(from)) - { - return false; - } - } - - string title = null, option = null; - - if (item is BasePotion) - { - title = "Potions"; - - if (item is BaseAgilityPotion) - { - option = "Agility"; - } - else if (item is BaseCurePotion) - { - option = "Cure"; - } - else if (item is BaseHealPotion) - { - option = "Heal"; - } - else if (item is NightSightPotion) - { - option = "Nightsight"; - } - else if (item is BasePoisonPotion) - { - option = "Poison"; - } - else if (item is BaseStrengthPotion) - { - option = "Strength"; - } - else if (item is BaseExplosionPotion) - { - option = "Explosion"; - } - else if (item is BaseRefreshPotion) - { - option = "Refresh"; - } - } - else if (item is Bandage) - { - title = "Items"; - option = "Bandages"; - } - else if (item is TrappableContainer container) - { - if (container.TrapType != TrapType.None) - { - title = "Items"; - option = "Trapped Containers"; - } - } - else if (item is Bola) - { - title = "Items"; - option = "Bolas"; - } - else if (item is OrangePetals) - { - title = "Items"; - option = "Orange Petals"; - } - else if (item is EtherealMount || item.Layer == Layer.Mount) - { - title = "Items"; - option = "Mounts"; - } - else if (item is LeatherNinjaBelt) - { - title = "Items"; - option = "Shurikens"; - } - else if (item is Fukiya) - { - title = "Items"; - option = "Fukiya Darts"; - } - else if (item is FireHorn) - { - title = "Items"; - option = "Fire Horns"; - } - else if (item is BaseWand) - { - title = "Items"; - option = "Wands"; - } - - if (title != null && option != null && StartedBeginCountdown && !Started) - { - from.SendMessage("You may not use this item before the duel begins."); - return false; - } - - if (item is BasePotion && item is not BaseExplosionPotion && item is not BaseRefreshPotion && IsSuddenDeath) - { - from.SendMessage(0x22, "You may not drink potions in sudden death."); - return false; - } - - if (item is Bandage && IsSuddenDeath) - { - from.SendMessage(0x22, "You may not use bandages in sudden death."); - return false; - } - - if (title == null || option == null || Ruleset.GetOption(title, option)) - { - return true; - } - - from.SendMessage("The dueling ruleset prevents you from using this item."); - return false; - } - - private void DelayBounce_Callback(Mobile mob, Container corpse) - { - RemoveAggressions(mob); - SendOutside(mob); - Refresh(mob, corpse); - Debuff(mob); - CancelSpell(mob); - mob.Frozen = false; - } - - public void OnMapChanged(Mobile mob) - { - OnLocationChanged(mob); - } - - public void OnLocationChanged(Mobile mob) - { - if (!Registered || !StartedBeginCountdown || Finished) - { - return; - } - - var arena = Arena; - - if (arena == null) - { - return; - } - - if (mob.Map == arena.Facet && arena.Bounds.Contains(mob.Location)) - { - return; - } - - var pl = Find(mob); - - if (pl?.Eliminated != false) - { - return; - } - - if (mob.Map == Map.Internal) - { - if (mob.LogoutMap == arena.Facet && arena.Bounds.Contains(mob.LogoutLocation)) - { - mob.LogoutLocation = arena.Outside; - } - } - - pl.Eliminated = true; - - mob.LocalOverheadMessage(MessageType.Regular, 0x22, false, "You have forfeited your position in the duel."); - mob.NonlocalOverheadMessage( - MessageType.Regular, - 0x22, - false, - $"{mob.Name} has forfeited by leaving the dueling arena." - ); - - var winner = CheckCompletion(); - - if (winner != null) - { - Finish(winner); - } } - [OnEvent(nameof(PlayerMobile.PlayerDeathEvent))] - public static void OnPlayerDeath(PlayerMobile m) => m.DuelContext?.OnDeath(m, m.Corpse); + mob.Hits = mob.HitsMax; + mob.Stam = mob.StamMax; + mob.Mana = mob.ManaMax; - public void OnDeath(Mobile mob, Container corpse) + mob.Poison = null; + } + + public void SendOutside(Mobile mob) + { + if (Arena == null) { - if (!Registered || !Started) + return; + } + + mob.Combatant = null; + mob.MoveToWorld(Arena.Outside, Arena.Facet); + } + + public void Finish(Participant winner) + { + if (Finished) + { + return; + } + + _autoTieTimerToken.Cancel(); + StopSDTimers(); + + Finished = true; + + for (var i = 0; i < winner.Players.Length; ++i) + { + var pl = winner.Players[i]; + + if (pl?.Eliminated == false) { - return; - } - - var pl = Find(mob); - - if (pl?.Eliminated != false || m_EventGame?.OnDeath(mob, corpse) == false) - { - return; - } - - pl.Eliminated = true; - - if (mob.Poison != null) - { - mob.Poison = null; - } - - Requip(mob, corpse); - DelayBounce(TimeSpan.FromSeconds(4.0), mob, corpse); - - var winner = CheckCompletion(); - - if (winner != null) - { - Finish(winner); - } - else if (!m_Yielding) - { - mob.LocalOverheadMessage(MessageType.Regular, 0x22, false, "You have been defeated."); - mob.NonlocalOverheadMessage(MessageType.Regular, 0x22, false, $"{mob.Name} has been defeated."); + DelayBounce(TimeSpan.FromSeconds(8.0), pl.Mobile, null); } } - public bool CheckFull() - { - for (var i = 0; i < Participants.Count; ++i) - { - var p = Participants[i]; + winner.Broadcast( + 0x59, + null, + winner.Players.Length == 1 ? "{0} has won the duel." : "{0} and {1} team have won the duel.", + winner.Players.Length == 1 ? "You have won the duel." : "Your team has won the duel." + ); - if (p.HasOpenSlot) + if (m_Tournament != null && winner.TourneyPart != null) + { + m_Match.Winner = winner.TourneyPart; + winner.TourneyPart.WonMatch(m_Match); + m_Tournament.HandleWon(Arena, m_Match, winner.TourneyPart); + } + + for (var i = 0; i < Participants.Count; ++i) + { + var loser = Participants[i]; + + if (loser != winner) + { + loser.Broadcast( + 0x22, + null, + loser.Players.Length == 1 ? "{0} has lost the duel." : "{0} and {1} team have lost the duel.", + loser.Players.Length == 1 ? "You have lost the duel." : "Your team has lost the duel." + ); + + if (m_Tournament != null) { - return false; + loser.TourneyPart?.LostMatch(m_Match); } } - return true; + for (var j = 0; j < loser.Players.Length; ++j) + { + if (loser.Players[j] != null) + { + RemoveAggressions(loser.Players[j].Mobile); + loser.Players[j].Mobile.Delta(MobileDelta.Noto); + loser.Players[j].Mobile.CloseGump(); + + if (m_Tournament != null) + { + loser.Players[j].Mobile.SendEverything(); + } + } + } } - public void Requip(Mobile from, Container cont) + if (IsOneVsOne) { - if (cont is not Corpse corpse) + var dp1 = Participants[0].Players[0]; + var dp2 = Participants[1].Players[0]; + + if (dp1 != null && dp2 != null) { - return; + Award(dp1.Mobile, dp2.Mobile, dp1.Participant == winner); + Award(dp2.Mobile, dp1.Mobile, dp2.Participant == winner); } + } - var items = new List(corpse.Items); + m_EventGame?.OnStop(); - var didntFit = false; + Timer.StartTimer(TimeSpan.FromSeconds(9.0), UnregisterRematch); + } - var pack = from.Backpack; + public void Award(Mobile us, Mobile them, bool won) + { + var ladder = Arena == null ? Ladder.Instance : Arena.AcquireLadder(); - for (var i = 0; !didntFit && i < items.Count; ++i) + if (ladder == null) + { + return; + } + + var ourEntry = ladder.Find(us); + var theirEntry = ladder.Find(them); + + if (ourEntry == null || theirEntry == null) + { + return; + } + + var xpGain = Ladder.GetExperienceGain(ourEntry, theirEntry, won); + + if (xpGain == 0) + { + return; + } + + if (m_Tournament != null) + { + xpGain *= xpGain > 0 ? 5 : 2; + } + + if (won) + { + ++ourEntry.Wins; + } + else + { + ++ourEntry.Losses; + } + + var oldLevel = Ladder.GetLevel(ourEntry.Experience); + + ourEntry.Experience += xpGain; + + if (ourEntry.Experience < 0) + { + ourEntry.Experience = 0; + } + + ladder.UpdateEntry(ourEntry); + + var newLevel = Ladder.GetLevel(ourEntry.Experience); + + if (newLevel > oldLevel) + { + us.SendMessage(0x59, $"You have achieved level {newLevel}!"); + } + else if (newLevel < oldLevel) + { + us.SendMessage(0x22, $"You have lost a level. You are now at {newLevel}."); + } + } + + public void UnregisterRematch() + { + Unregister(true); + } + + public void Unregister() + { + Unregister(false); + } + + public void Unregister(bool queryRematch) + { + DestroyWall(); + + if (!Registered) + { + return; + } + + Registered = false; + + Arena?.Evict(); + + StopSDTimers(); + + for (var i = 0; i < Participants.Count; ++i) + { + var p = Participants[i]; + + for (var j = 0; j < p.Players.Length; ++j) { - var item = items[i]; + var pl = p.Players[j]; - if (item.Layer is Layer.Hair or Layer.FacialHair || !item.Movable) + if (pl == null) { continue; } - if (pack != null) + if (pl.Mobile is PlayerMobile mobile) { - pack.DropItem(item); + mobile.DuelPlayer = null; } - else + + CloseAllGumps(pl); + } + } + + if (queryRematch && m_Tournament == null) + { + QueryRematch(); + } + } + + public void QueryRematch() + { + var dc = new DuelContext(Initiator, Ruleset.Layout, false); + + dc.Ruleset = Ruleset; + dc.Rematch = true; + + dc.Participants.Clear(); + + for (var i = 0; i < Participants.Count; ++i) + { + var oldPart = Participants[i]; + var newPart = new Participant(dc, oldPart.Players.Length); + + for (var j = 0; j < oldPart.Players.Length; ++j) + { + var oldPlayer = oldPart.Players[j]; + + if (oldPlayer != null) { - didntFit = true; + newPart.Players[j] = new DuelPlayer(oldPlayer.Mobile, newPart); } } - corpse.Carved = true; + dc.Participants.Add(newPart); + } - if (corpse.ItemID == 0x2006) + dc.CloseAllGumps(); + dc.SendReadyUpGump(); + } + + public DuelPlayer Find(Mobile mob) + { + if (mob is PlayerMobile pm) + { + if (pm.DuelContext == this) { - corpse.ProcessDelta(); - corpse.SendRemovePacket(); - corpse.ItemID = Utility.Random(0xECA, 9); // bone graphic - corpse.Hue = 0; - corpse.ProcessDelta(); - - var killer = from.FindMostRecentDamager(false); - - if (killer?.Player == true) - { - killer.AddToBackpack(new Head(m_Tournament == null ? HeadType.Duel : HeadType.Tournament, from.Name)); - } + return pm.DuelPlayer; } - from.PlaySound(0x3E3); + return null; + } - if (didntFit) + for (var i = 0; i < Participants.Count; ++i) + { + var p = Participants[i]; + var pl = p.Find(mob); + + if (pl != null) { - from.SendLocalizedMessage(1062472); // You gather some of your belongings. The rest remain on the corpse. + return pl; + } + } + + return null; + } + + public bool IsAlly(Mobile m1, Mobile m2) + { + var pl1 = Find(m1); + var pl2 = Find(m2); + + return pl1 != null && pl1.Participant == pl2?.Participant; + } + + public Participant CheckCompletion() + { + Participant winner = null; + + var hasWinner = false; + var eliminated = 0; + + for (var i = 0; i < Participants.Count; ++i) + { + var p = Participants[i]; + + if (p.Eliminated) + { + ++eliminated; + + if (eliminated == Participants.Count - 1) + { + hasWinner = true; + } } else { - from.SendLocalizedMessage(1062471); // You quickly gather all of your belongings. + winner = p; } } - public void Refresh(Mobile mob, Container cont) + return hasWinner ? winner ?? Participants[0] : null; + } + + public void StartCountdown(int count, CountdownCallback cb) + { + Timer.StartTimer( + TimeSpan.FromSeconds(1.0), + count, + () => Countdown_Callback(cb), + out _countdownTimerToken + ); + } + + public void StopCountdown() => _countdownTimerToken.Cancel(); + + private void Countdown_Callback(CountdownCallback cb) + { + var count = _countdownTimerToken.RemainingCount; + + if (count == 0) { - if (!mob.Alive) + StopCountdown(); + } + + cb(count); + } + + public void StopSDTimers() + { + _SdWarnTimerToken.Cancel(); + _SdActivateTimerToken.Cancel(); + } + + public void StartSuddenDeath(TimeSpan timeUntilActive) + { + _SdWarnTimerToken.Cancel(); + Timer.StartTimer(TimeSpan.FromMinutes(timeUntilActive.TotalMinutes * 0.9), WarnSuddenDeath, out _SdWarnTimerToken); + + _SdActivateTimerToken.Cancel(); + Timer.StartTimer(timeUntilActive, ActivateSuddenDeath, out _SdActivateTimerToken); + } + + public void WarnSuddenDeath() + { + for (var i = 0; i < Participants.Count; ++i) + { + var p = Participants[i]; + + for (var j = 0; j < p.Players.Length; ++j) { - mob.Resurrect(); + var pl = p.Players[j]; - mob.FindItemOnLayer(Layer.OuterTorso)?.Delete(); - - if (cont is Corpse corpse) + if (pl?.Eliminated != false) { - for (var i = 0; i < corpse.EquipItems.Count; ++i) - { - var item = corpse.EquipItems[i]; - - if (item.Movable && item.Layer != Layer.Hair && item.Layer != Layer.FacialHair && - item.IsChildOf(mob.Backpack)) - { - mob.EquipItem(item); - } - } + continue; } + + pl.Mobile.SendSound(0x1E1); + pl.Mobile.SendMessage(0x22, "Warning! Warning! Warning!"); + pl.Mobile.SendMessage(0x22, "Sudden death will be active soon!"); } - - mob.Hits = mob.HitsMax; - mob.Stam = mob.StamMax; - mob.Mana = mob.ManaMax; - - mob.Poison = null; } - public void SendOutside(Mobile mob) + m_Tournament?.Alert(Arena, "Sudden death will be active soon!"); + + _SdWarnTimerToken.Cancel(); + } + + public static bool CheckSuddenDeath(Mobile mob) => mob is PlayerMobile pm && pm.DuelPlayer?.Eliminated == false && + pm.DuelContext?.IsSuddenDeath == true; + + public void ActivateSuddenDeath() + { + for (var i = 0; i < Participants.Count; ++i) { - if (Arena == null) + var p = Participants[i]; + + for (var j = 0; j < p.Players.Length; ++j) { - return; - } + var pl = p.Players[j]; - mob.Combatant = null; - mob.MoveToWorld(Arena.Outside, Arena.Facet); - } - - public void Finish(Participant winner) - { - if (Finished) - { - return; - } - - _autoTieTimerToken.Cancel(); - StopSDTimers(); - - Finished = true; - - for (var i = 0; i < winner.Players.Length; ++i) - { - var pl = winner.Players[i]; - - if (pl?.Eliminated == false) + if (pl?.Eliminated != false) { - DelayBounce(TimeSpan.FromSeconds(8.0), pl.Mobile, null); + continue; } + + pl.Mobile.SendSound(0x1E1); + pl.Mobile.SendMessage(0x22, "Warning! Warning! Warning!"); + pl.Mobile.SendMessage( + 0x22, + "Sudden death has ACTIVATED. You are now unable to perform any beneficial actions." + ); } + } - winner.Broadcast( - 0x59, - null, - winner.Players.Length == 1 ? "{0} has won the duel." : "{0} and {1} team have won the duel.", - winner.Players.Length == 1 ? "You have won the duel." : "Your team has won the duel." - ); + m_Tournament?.Alert(Arena, "Sudden death has been activated!"); - if (m_Tournament != null && winner.TourneyPart != null) + IsSuddenDeath = true; + + _SdActivateTimerToken.Cancel(); + } + + public void BeginAutoTie() + { + var ts = m_Tournament == null || m_Tournament.TourneyType == TourneyType.Standard + ? AutoTieDelay + : TimeSpan.FromMinutes(90.0); + + _autoTieTimerToken.Cancel(); + Timer.StartTimer(ts, InvokeAutoTie, out _autoTieTimerToken); + } + + public void InvokeAutoTie() + { + _autoTieTimerToken.Cancel(); + + if (!Started || Finished) + { + return; + } + + Tied = true; + Finished = true; + + StopSDTimers(); + + var remaining = new List(); + + for (var i = 0; i < Participants.Count; ++i) + { + var p = Participants[i]; + + if (p.Eliminated) { - m_Match.Winner = winner.TourneyPart; - winner.TourneyPart.WonMatch(m_Match); - m_Tournament.HandleWon(Arena, m_Match, winner.TourneyPart); + p.Broadcast( + 0x22, + null, + p.Players.Length == 1 ? "{0} has lost the duel." : "{0} and {1} team have lost the duel.", + p.Players.Length == 1 ? "You have lost the duel." : "Your team has lost the duel." + ); } - - for (var i = 0; i < Participants.Count; ++i) + else { - var loser = Participants[i]; + p.Broadcast( + 0x59, + null, + p.Players.Length == 1 + ? "{0} has tied the duel due to time expiration." + : "{0} and {1} team have tied the duel due to time expiration.", + p.Players.Length == 1 + ? "You have tied the duel due to time expiration." + : "Your team has tied the duel due to time expiration." + ); - if (loser != winner) + for (var j = 0; j < p.Players.Length; ++j) { - loser.Broadcast( - 0x22, - null, - loser.Players.Length == 1 ? "{0} has lost the duel." : "{0} and {1} team have lost the duel.", - loser.Players.Length == 1 ? "You have lost the duel." : "Your team has lost the duel." - ); + var pl = p.Players[j]; - if (m_Tournament != null) + if (pl?.Eliminated == false) { - loser.TourneyPart?.LostMatch(m_Match); + DelayBounce(TimeSpan.FromSeconds(8.0), pl.Mobile, null); } } - for (var j = 0; j < loser.Players.Length; ++j) + if (p.TourneyPart != null) { - if (loser.Players[j] != null) - { - RemoveAggressions(loser.Players[j].Mobile); - loser.Players[j].Mobile.Delta(MobileDelta.Noto); - loser.Players[j].Mobile.CloseGump(); - - if (m_Tournament != null) - { - loser.Players[j].Mobile.SendEverything(); - } - } + remaining.Add(p.TourneyPart); } } - if (IsOneVsOne) + for (var j = 0; j < p.Players.Length; ++j) { - var dp1 = Participants[0].Players[0]; - var dp2 = Participants[1].Players[0]; + var pl = p.Players[j]; - if (dp1 != null && dp2 != null) + if (pl != null) { - Award(dp1.Mobile, dp2.Mobile, dp1.Participant == winner); - Award(dp2.Mobile, dp1.Mobile, dp2.Participant == winner); + pl.Mobile.Delta(MobileDelta.Noto); + pl.Mobile.SendEverything(); } } - - m_EventGame?.OnStop(); - - Timer.StartTimer(TimeSpan.FromSeconds(9.0), UnregisterRematch); } - public void Award(Mobile us, Mobile them, bool won) + m_Tournament?.HandleTie(Arena, m_Match, remaining); + + Timer.StartTimer(TimeSpan.FromSeconds(10.0), Unregister); + } + + public static void Configure() + { + EventSink.Speech += EventSink_Speech; + + CommandSystem.Register("vli", AccessLevel.GameMaster, vli_oc); + } + + [Usage("vli")] + [Aliases("ViewLadderInfo")] + [Description("View ladder information.")] + private static void vli_oc(CommandEventArgs e) + { + e.Mobile.BeginTarget(-1, false, TargetFlags.None, vli_ot); + } + + private static void vli_ot(Mobile from, object obj) + { + if (obj is PlayerMobile pm) { - var ladder = Arena == null ? Ladder.Instance : Arena.AcquireLadder(); + var ladder = Ladder.Instance; if (ladder == null) { return; } - var ourEntry = ladder.Find(us); - var theirEntry = ladder.Find(them); + var entry = ladder.Find(pm); - if (ourEntry == null || theirEntry == null) + if (entry != null) { - return; + from.SendGump(new PropertiesGump(from, entry)); } + } + } - var xpGain = Ladder.GetExperienceGain(ourEntry, theirEntry, won); - - if (xpGain == 0) + public static bool CheckCombat(Mobile m) + { + foreach (var info in m.Aggressed) + { + if (info.Defender.Player && Core.Now - info.LastCombatTime < CombatDelay) { - return; - } - - if (m_Tournament != null) - { - xpGain *= xpGain > 0 ? 5 : 2; - } - - if (won) - { - ++ourEntry.Wins; - } - else - { - ++ourEntry.Losses; - } - - var oldLevel = Ladder.GetLevel(ourEntry.Experience); - - ourEntry.Experience += xpGain; - - if (ourEntry.Experience < 0) - { - ourEntry.Experience = 0; - } - - ladder.UpdateEntry(ourEntry); - - var newLevel = Ladder.GetLevel(ourEntry.Experience); - - if (newLevel > oldLevel) - { - us.SendMessage(0x59, $"You have achieved level {newLevel}!"); - } - else if (newLevel < oldLevel) - { - us.SendMessage(0x22, $"You have lost a level. You are now at {newLevel}."); + return true; } } - public void UnregisterRematch() + foreach (var info in m.Aggressors) { - Unregister(true); - } - - public void Unregister() - { - Unregister(false); - } - - public void Unregister(bool queryRematch) - { - DestroyWall(); - - if (!Registered) + if (info.Attacker.Player && Core.Now - info.LastCombatTime < CombatDelay) { - return; - } - - Registered = false; - - Arena?.Evict(); - - StopSDTimers(); - - for (var i = 0; i < Participants.Count; ++i) - { - var p = Participants[i]; - - for (var j = 0; j < p.Players.Length; ++j) - { - var pl = p.Players[j]; - - if (pl == null) - { - continue; - } - - if (pl.Mobile is PlayerMobile mobile) - { - mobile.DuelPlayer = null; - } - - CloseAllGumps(pl); - } - } - - if (queryRematch && m_Tournament == null) - { - QueryRematch(); + return true; } } - public void QueryRematch() + return false; + } + + [OnEvent(nameof(PlayerMobile.PlayerLoginEvent))] + public static void OnLogin(PlayerMobile pm) + { + var dc = pm.DuelContext; + + if (dc == null) { - var dc = new DuelContext(Initiator, Ruleset.Layout, false); - - dc.Ruleset = Ruleset; - dc.Rematch = true; - - dc.Participants.Clear(); - - for (var i = 0; i < Participants.Count; ++i) - { - var oldPart = Participants[i]; - var newPart = new Participant(dc, oldPart.Players.Length); - - for (var j = 0; j < oldPart.Players.Length; ++j) - { - var oldPlayer = oldPart.Players[j]; - - if (oldPlayer != null) - { - newPart.Players[j] = new DuelPlayer(oldPlayer.Mobile, newPart); - } - } - - dc.Participants.Add(newPart); - } - - dc.CloseAllGumps(); - dc.SendReadyUpGump(); + return; } - public DuelPlayer Find(Mobile mob) + if (dc.ReadyWait && pm.DuelPlayer.Ready && !dc.Started && !dc.StartedBeginCountdown && !dc.Finished) { - if (mob is PlayerMobile pm) + if (dc.m_Tournament == null) { - if (pm.DuelContext == this) - { - return pm.DuelPlayer; - } + pm.SendGump(new ReadyGump(pm, dc, dc.ReadyCount)); + } + } + else if (dc.ReadyWait && !dc.StartedBeginCountdown && !dc.Started && !dc.Finished) + { + if (dc.m_Tournament == null) + { + pm.SendGump(new ReadyUpGump(pm, dc)); + } + } + else if (dc.Initiator == pm && !dc.ReadyWait && !dc.StartedBeginCountdown && !dc.Started && !dc.Finished) + { + pm.SendGump(new DuelContextGump(pm, dc)); + } + } - return null; + private static void ViewLadder_OnTarget(Mobile from, object obj, Ladder ladder) + { + if (obj is PlayerMobile pm) + { + var entry = ladder.Find(pm); + + if (entry == null) + { + return; // sanity } - for (var i = 0; i < Participants.Count; ++i) - { - var p = Participants[i]; - var pl = p.Find(mob); + var text = + $"{{0}} are ranked {LadderGump.Rank(entry.Index + 1)} at level {Ladder.GetLevel(entry.Experience)}."; - if (pl != null) - { - return pl; - } - } - - return null; - } - - public bool IsAlly(Mobile m1, Mobile m2) - { - var pl1 = Find(m1); - var pl2 = Find(m2); - - return pl1 != null && pl1.Participant == pl2?.Participant; - } - - public Participant CheckCompletion() - { - Participant winner = null; - - var hasWinner = false; - var eliminated = 0; - - for (var i = 0; i < Participants.Count; ++i) - { - var p = Participants[i]; - - if (p.Eliminated) - { - ++eliminated; - - if (eliminated == Participants.Count - 1) - { - hasWinner = true; - } - } - else - { - winner = p; - } - } - - return hasWinner ? winner ?? Participants[0] : null; - } - - public void StartCountdown(int count, CountdownCallback cb) - { - Timer.StartTimer( - TimeSpan.FromSeconds(1.0), - count, - () => Countdown_Callback(cb), - out _countdownTimerToken + pm.PrivateOverheadMessage( + MessageType.Regular, + pm.SpeechHue, + true, + string.Format(text, from == pm ? "You" : "They"), + from.NetState ); } - - public void StopCountdown() => _countdownTimerToken.Cancel(); - - private void Countdown_Callback(CountdownCallback cb) + else if (obj is Mobile mob) { - var count = _countdownTimerToken.RemainingCount; - - if (count == 0) + if (mob.Body.IsHuman) { - StopCountdown(); - } - - cb(count); - } - - public void StopSDTimers() - { - _SdWarnTimerToken.Cancel(); - _SdActivateTimerToken.Cancel(); - } - - public void StartSuddenDeath(TimeSpan timeUntilActive) - { - _SdWarnTimerToken.Cancel(); - Timer.StartTimer(TimeSpan.FromMinutes(timeUntilActive.TotalMinutes * 0.9), WarnSuddenDeath, out _SdWarnTimerToken); - - _SdActivateTimerToken.Cancel(); - Timer.StartTimer(timeUntilActive, ActivateSuddenDeath, out _SdActivateTimerToken); - } - - public void WarnSuddenDeath() - { - for (var i = 0; i < Participants.Count; ++i) - { - var p = Participants[i]; - - for (var j = 0; j < p.Players.Length; ++j) - { - var pl = p.Players[j]; - - if (pl?.Eliminated != false) - { - continue; - } - - pl.Mobile.SendSound(0x1E1); - pl.Mobile.SendMessage(0x22, "Warning! Warning! Warning!"); - pl.Mobile.SendMessage(0x22, "Sudden death will be active soon!"); - } - } - - m_Tournament?.Alert(Arena, "Sudden death will be active soon!"); - - _SdWarnTimerToken.Cancel(); - } - - public static bool CheckSuddenDeath(Mobile mob) => mob is PlayerMobile pm && pm.DuelPlayer?.Eliminated == false && - pm.DuelContext?.IsSuddenDeath == true; - - public void ActivateSuddenDeath() - { - for (var i = 0; i < Participants.Count; ++i) - { - var p = Participants[i]; - - for (var j = 0; j < p.Players.Length; ++j) - { - var pl = p.Players[j]; - - if (pl?.Eliminated != false) - { - continue; - } - - pl.Mobile.SendSound(0x1E1); - pl.Mobile.SendMessage(0x22, "Warning! Warning! Warning!"); - pl.Mobile.SendMessage( - 0x22, - "Sudden death has ACTIVATED. You are now unable to perform any beneficial actions." - ); - } - } - - m_Tournament?.Alert(Arena, "Sudden death has been activated!"); - - IsSuddenDeath = true; - - _SdActivateTimerToken.Cancel(); - } - - public void BeginAutoTie() - { - var ts = m_Tournament == null || m_Tournament.TourneyType == TourneyType.Standard - ? AutoTieDelay - : TimeSpan.FromMinutes(90.0); - - _autoTieTimerToken.Cancel(); - Timer.StartTimer(ts, InvokeAutoTie, out _autoTieTimerToken); - } - - public void InvokeAutoTie() - { - _autoTieTimerToken.Cancel(); - - if (!Started || Finished) - { - return; - } - - Tied = true; - Finished = true; - - StopSDTimers(); - - var remaining = new List(); - - for (var i = 0; i < Participants.Count; ++i) - { - var p = Participants[i]; - - if (p.Eliminated) - { - p.Broadcast( - 0x22, - null, - p.Players.Length == 1 ? "{0} has lost the duel." : "{0} and {1} team have lost the duel.", - p.Players.Length == 1 ? "You have lost the duel." : "Your team has lost the duel." - ); - } - else - { - p.Broadcast( - 0x59, - null, - p.Players.Length == 1 - ? "{0} has tied the duel due to time expiration." - : "{0} and {1} team have tied the duel due to time expiration.", - p.Players.Length == 1 - ? "You have tied the duel due to time expiration." - : "Your team has tied the duel due to time expiration." - ); - - for (var j = 0; j < p.Players.Length; ++j) - { - var pl = p.Players[j]; - - if (pl?.Eliminated == false) - { - DelayBounce(TimeSpan.FromSeconds(8.0), pl.Mobile, null); - } - } - - if (p.TourneyPart != null) - { - remaining.Add(p.TourneyPart); - } - } - - for (var j = 0; j < p.Players.Length; ++j) - { - var pl = p.Players[j]; - - if (pl != null) - { - pl.Mobile.Delta(MobileDelta.Noto); - pl.Mobile.SendEverything(); - } - } - } - - m_Tournament?.HandleTie(Arena, m_Match, remaining); - - Timer.StartTimer(TimeSpan.FromSeconds(10.0), Unregister); - } - - public static void Configure() - { - EventSink.Speech += EventSink_Speech; - - CommandSystem.Register("vli", AccessLevel.GameMaster, vli_oc); - } - - [Usage("vli")] - [Aliases("ViewLadderInfo")] - [Description("View ladder information.")] - private static void vli_oc(CommandEventArgs e) - { - e.Mobile.BeginTarget(-1, false, TargetFlags.None, vli_ot); - } - - private static void vli_ot(Mobile from, object obj) - { - if (obj is PlayerMobile pm) - { - var ladder = Ladder.Instance; - - if (ladder == null) - { - return; - } - - var entry = ladder.Find(pm); - - if (entry != null) - { - from.SendGump(new PropertiesGump(from, entry)); - } - } - } - - public static bool CheckCombat(Mobile m) - { - foreach (var info in m.Aggressed) - { - if (info.Defender.Player && Core.Now - info.LastCombatTime < CombatDelay) - { - return true; - } - } - - foreach (var info in m.Aggressors) - { - if (info.Attacker.Player && Core.Now - info.LastCombatTime < CombatDelay) - { - return true; - } - } - - return false; - } - - [OnEvent(nameof(PlayerMobile.PlayerLoginEvent))] - public static void OnLogin(PlayerMobile pm) - { - var dc = pm.DuelContext; - - if (dc == null) - { - return; - } - - if (dc.ReadyWait && pm.DuelPlayer.Ready && !dc.Started && !dc.StartedBeginCountdown && !dc.Finished) - { - if (dc.m_Tournament == null) - { - pm.SendGump(new ReadyGump(pm, dc, dc.ReadyCount)); - } - } - else if (dc.ReadyWait && !dc.StartedBeginCountdown && !dc.Started && !dc.Finished) - { - if (dc.m_Tournament == null) - { - pm.SendGump(new ReadyUpGump(pm, dc)); - } - } - else if (dc.Initiator == pm && !dc.ReadyWait && !dc.StartedBeginCountdown && !dc.Started && !dc.Finished) - { - pm.SendGump(new DuelContextGump(pm, dc)); - } - } - - private static void ViewLadder_OnTarget(Mobile from, object obj, Ladder ladder) - { - if (obj is PlayerMobile pm) - { - var entry = ladder.Find(pm); - - if (entry == null) - { - return; // sanity - } - - var text = - $"{{0}} are ranked {LadderGump.Rank(entry.Index + 1)} at level {Ladder.GetLevel(entry.Experience)}."; - - pm.PrivateOverheadMessage( + mob.PrivateOverheadMessage( MessageType.Regular, - pm.SpeechHue, - true, - string.Format(text, from == pm ? "You" : "They"), + mob.SpeechHue, + false, + "I'm not a duelist, and quite frankly, I resent the implication.", from.NetState ); } - else if (obj is Mobile mob) + else { - if (mob.Body.IsHuman) + mob.PrivateOverheadMessage( + MessageType.Regular, + 0x3B2, + true, + "It's probably better than you.", + from.NetState + ); + } + } + else + { + from.SendMessage("That's not a player."); + } + } + + private static void EventSink_Speech(SpeechEventArgs e) + { + if (e.Handled) + { + return; + } + + if (e.Mobile is not PlayerMobile pm) + { + return; + } + + if (e.Speech.InsensitiveContains("i wish to duel")) + { + if (!pm.CheckAlive()) + { + } + else if (pm.Region.IsPartOf()) + { + } + else if (CheckCombat(pm)) + { + e.Mobile.SendMessage( + 0x22, + "You have recently been in combat with another player and must wait before starting a duel." + ); + } + else if (pm.DuelContext != null) + { + if (pm.DuelContext.Initiator == pm) { - mob.PrivateOverheadMessage( - MessageType.Regular, - mob.SpeechHue, - false, - "I'm not a duelist, and quite frankly, I resent the implication.", - from.NetState - ); + e.Mobile.SendMessage(0x22, "You have already started a duel."); } else { - mob.PrivateOverheadMessage( - MessageType.Regular, - 0x3B2, - true, - "It's probably better than you.", - from.NetState - ); + e.Mobile.SendMessage(0x22, "You have already been challenged in a duel."); } } + else if (TournamentController.IsActive) + { + e.Mobile.SendMessage(0x22, "You may not start a duel while a tournament is active."); + } else { - from.SendMessage("That's not a player."); + pm.SendGump(new DuelContextGump(pm, new DuelContext(pm, RulesetLayout.Root))); + e.Handled = true; } } - - private static void EventSink_Speech(SpeechEventArgs e) + else if (e.Speech.InsensitiveEquals("change arena preferences")) { - if (e.Handled) + if (!pm.CheckAlive()) { - return; } - - if (e.Mobile is not PlayerMobile pm) + else { - return; + var prefs = Preferences.Instance; + + if (prefs != null) + { + e.Mobile.SendGump(new PreferencesGump(e.Mobile, prefs)); + } } - - if (e.Speech.InsensitiveContains("i wish to duel")) + } + else if (e.Speech.InsensitiveEquals("showladder")) + { + e.Blocked = true; + if (!pm.CheckAlive()) { - if (!pm.CheckAlive()) + } + else + { + var instance = Ladder.Instance; + + if (instance == null) { + // pm.SendMessage( "Ladder not yet initialized." ); } - else if (pm.Region.IsPartOf()) + else { - } - else if (CheckCombat(pm)) - { - e.Mobile.SendMessage( - 0x22, - "You have recently been in combat with another player and must wait before starting a duel." + var entry = instance.Find(pm); + + if (entry == null) + { + return; // sanity + } + + var text = + $"{{0}} {{1}} ranked {LadderGump.Rank(entry.Index + 1)} at level {Ladder.GetLevel(entry.Experience)}."; + + pm.LocalOverheadMessage(MessageType.Regular, pm.SpeechHue, true, string.Format(text, "You", "are")); + pm.NonlocalOverheadMessage( + MessageType.Regular, + pm.SpeechHue, + true, + string.Format(text, pm.Name, "is") ); + + // pm.PublicOverheadMessage( MessageType.Regular, pm.SpeechHue, true, String.Format( "Level {0} with {1} win{2} and {3} loss{4}.", Ladder.GetLevel( entry.Experience ), entry.Wins, entry.Wins==1?"":"s", entry.Losses, entry.Losses==1?"":"es" ) ); + // pm.PublicOverheadMessage( MessageType.Regular, pm.SpeechHue, true, String.Format( "Level {0} with {1} win{2} and {3} loss{4}.", Ladder.GetLevel( entry.Experience ), entry.Wins, entry.Wins==1?"":"s", entry.Losses, entry.Losses==1?"":"es" ) ); } - else if (pm.DuelContext != null) + } + } + else if (e.Speech.InsensitiveEquals("viewladder")) + { + e.Blocked = true; + + if (!pm.CheckAlive()) + { + } + else + { + var instance = Ladder.Instance; + + if (instance == null) { - if (pm.DuelContext.Initiator == pm) - { - e.Mobile.SendMessage(0x22, "You have already started a duel."); - } - else - { - e.Mobile.SendMessage(0x22, "You have already been challenged in a duel."); - } - } - else if (TournamentController.IsActive) - { - e.Mobile.SendMessage(0x22, "You may not start a duel while a tournament is active."); + // pm.SendMessage( "Ladder not yet initialized." ); } else { - pm.SendGump(new DuelContextGump(pm, new DuelContext(pm, RulesetLayout.Root))); - e.Handled = true; + pm.SendMessage("Target a player to view their ranking and level."); + pm.BeginTarget(16, false, TargetFlags.None, ViewLadder_OnTarget, instance); } } - else if (e.Speech.InsensitiveEquals("change arena preferences")) + } + else if (e.Speech.InsensitiveContains("i yield")) + { + if (!pm.CheckAlive()) { - if (!pm.CheckAlive()) - { - } - else - { - var prefs = Preferences.Instance; - - if (prefs != null) - { - e.Mobile.SendGump(new PreferencesGump(e.Mobile, prefs)); - } - } } - else if (e.Speech.InsensitiveEquals("showladder")) + else if (pm.DuelContext == null) { - e.Blocked = true; - if (!pm.CheckAlive()) - { - } - else - { - var instance = Ladder.Instance; - - if (instance == null) - { - // pm.SendMessage( "Ladder not yet initialized." ); - } - else - { - var entry = instance.Find(pm); - - if (entry == null) - { - return; // sanity - } - - var text = - $"{{0}} {{1}} ranked {LadderGump.Rank(entry.Index + 1)} at level {Ladder.GetLevel(entry.Experience)}."; - - pm.LocalOverheadMessage(MessageType.Regular, pm.SpeechHue, true, string.Format(text, "You", "are")); - pm.NonlocalOverheadMessage( - MessageType.Regular, - pm.SpeechHue, - true, - string.Format(text, pm.Name, "is") - ); - - // pm.PublicOverheadMessage( MessageType.Regular, pm.SpeechHue, true, String.Format( "Level {0} with {1} win{2} and {3} loss{4}.", Ladder.GetLevel( entry.Experience ), entry.Wins, entry.Wins==1?"":"s", entry.Losses, entry.Losses==1?"":"es" ) ); - // pm.PublicOverheadMessage( MessageType.Regular, pm.SpeechHue, true, String.Format( "Level {0} with {1} win{2} and {3} loss{4}.", Ladder.GetLevel( entry.Experience ), entry.Wins, entry.Wins==1?"":"s", entry.Losses, entry.Losses==1?"":"es" ) ); - } - } } - else if (e.Speech.InsensitiveEquals("viewladder")) + else if (pm.DuelContext.Finished) { - e.Blocked = true; - - if (!pm.CheckAlive()) - { - } - else - { - var instance = Ladder.Instance; - - if (instance == null) - { - // pm.SendMessage( "Ladder not yet initialized." ); - } - else - { - pm.SendMessage("Target a player to view their ranking and level."); - pm.BeginTarget(16, false, TargetFlags.None, ViewLadder_OnTarget, instance); - } - } + e.Mobile.SendMessage(0x22, "The duel is already finished."); } - else if (e.Speech.InsensitiveContains("i yield")) + else if (!pm.DuelContext.Started) { - if (!pm.CheckAlive()) + var dc = pm.DuelContext; + var init = dc.Initiator; + + if (pm.DuelContext.StartedBeginCountdown) { - } - else if (pm.DuelContext == null) - { - } - else if (pm.DuelContext.Finished) - { - e.Mobile.SendMessage(0x22, "The duel is already finished."); - } - else if (!pm.DuelContext.Started) - { - var dc = pm.DuelContext; - var init = dc.Initiator; - - if (pm.DuelContext.StartedBeginCountdown) - { - e.Mobile.SendMessage(0x22, "The duel has not yet started."); - } - else - { - var pl = pm.DuelContext.Find(pm); - - if (pl == null) - { - return; - } - - var p = pl.Participant; - - if (!pm.DuelContext.ReadyWait) // still setting stuff up - { - p.Broadcast(0x22, null, "{0} has yielded.", "You have yielded."); - - if (init == pm) - { - dc.Unregister(); - } - else - { - p.Nullify(pl); - pm.DuelPlayer = null; - - foreach (var g in init.GetGumps()) - { - if (g is ParticipantGump pg && pg.Participant == p) - { - init.SendGump(new ParticipantGump(init, dc, p)); - break; - } - - if (g is DuelContextGump dcg && dcg.Context == dc) - { - init.SendGump(new DuelContextGump(init, dc)); - break; - } - } - } - } - else if (!pm.DuelContext.StartedReadyCountdown) // at ready stage - { - p.Broadcast(0x22, null, "{0} has yielded.", "You have yielded."); - - dc.m_Yielding = true; - dc.RejectReady(pm, null); - dc.m_Yielding = false; - - if (init == pm) - { - dc.Unregister(); - } - else if (dc.Registered) - { - p.Nullify(pl); - pm.DuelPlayer = null; - - var send = true; - - foreach (var g in init.GetGumps()) - { - if (g is ParticipantGump pg && pg.Participant == p) - { - init.SendGump(new ParticipantGump(init, dc, p)); - send = false; - break; - } - - if (g is DuelContextGump dcg && dcg.Context == dc) - { - init.SendGump(new DuelContextGump(init, dc)); - send = false; - break; - } - } - - if (send) - { - init.SendGump(new DuelContextGump(init, dc)); - } - } - } - else - { - pm.DuelContext.StopCountdown(); - pm.DuelContext.StartedReadyCountdown = false; - p.Broadcast(0x22, null, "{0} has yielded.", "You have yielded."); - - dc.m_Yielding = true; - dc.RejectReady(pm, null); - dc.m_Yielding = false; - - if (init == pm) - { - dc.Unregister(); - } - else if (dc.Registered) - { - p.Nullify(pl); - pm.DuelPlayer = null; - - var send = true; - - foreach (var g in init.GetGumps()) - { - if (g is ParticipantGump pg && pg.Participant == p) - { - init.SendGump(new ParticipantGump(init, dc, p)); - send = false; - break; - } - - if (g is DuelContextGump dcg && dcg.Context == dc) - { - init.SendGump(new DuelContextGump(init, dc)); - send = false; - break; - } - } - - if (send) - { - init.SendGump(new DuelContextGump(init, dc)); - } - } - } - } + e.Mobile.SendMessage(0x22, "The duel has not yet started."); } else { var pl = pm.DuelContext.Find(pm); - if (pl != null) + if (pl == null) { - if (pm.DuelContext.IsOneVsOne) + return; + } + + var p = pl.Participant; + + if (!pm.DuelContext.ReadyWait) // still setting stuff up + { + p.Broadcast(0x22, null, "{0} has yielded.", "You have yielded."); + + if (init == pm) { - e.Mobile.SendMessage(0x22, "You may not yield a 1 on 1 match."); - } - else if (pl.Eliminated) - { - e.Mobile.SendMessage(0x22, "You have already been eliminated."); + dc.Unregister(); } else { - pm.LocalOverheadMessage(MessageType.Regular, 0x22, false, "You have yielded."); - pm.NonlocalOverheadMessage(MessageType.Regular, 0x22, false, $"{pm.Name} has yielded."); + p.Nullify(pl); + pm.DuelPlayer = null; - pm.DuelContext.m_Yielding = true; - pm.Kill(); - pm.DuelContext.m_Yielding = false; - - if (pm.Alive) // invul, ... + foreach (var g in init.GetGumps()) { - pl.Eliminated = true; - - pm.DuelContext.RemoveAggressions(pm); - pm.DuelContext.SendOutside(pm); - pm.DuelContext.Refresh(pm, null); - Debuff(pm); - CancelSpell(pm); - pm.Frozen = false; - - var winner = pm.DuelContext.CheckCompletion(); - - if (winner != null) + if (g is ParticipantGump pg && pg.Participant == p) { - pm.DuelContext.Finish(winner); + init.SendGump(new ParticipantGump(init, dc, p)); + break; } + + if (g is DuelContextGump dcg && dcg.Context == dc) + { + init.SendGump(new DuelContextGump(init, dc)); + break; + } + } + } + } + else if (!pm.DuelContext.StartedReadyCountdown) // at ready stage + { + p.Broadcast(0x22, null, "{0} has yielded.", "You have yielded."); + + dc.m_Yielding = true; + dc.RejectReady(pm, null); + dc.m_Yielding = false; + + if (init == pm) + { + dc.Unregister(); + } + else if (dc.Registered) + { + p.Nullify(pl); + pm.DuelPlayer = null; + + var send = true; + + foreach (var g in init.GetGumps()) + { + if (g is ParticipantGump pg && pg.Participant == p) + { + init.SendGump(new ParticipantGump(init, dc, p)); + send = false; + break; + } + + if (g is DuelContextGump dcg && dcg.Context == dc) + { + init.SendGump(new DuelContextGump(init, dc)); + send = false; + break; + } + } + + if (send) + { + init.SendGump(new DuelContextGump(init, dc)); } } } else { - e.Mobile.SendMessage(0x22, "BUG: Unable to find duel context."); - } - } - } - } + pm.DuelContext.StopCountdown(); + pm.DuelContext.StartedReadyCountdown = false; + p.Broadcast(0x22, null, "{0} has yielded.", "You have yielded."); - public void CloseAllGumps(DuelPlayer pl) - { - var gumps = pl.Mobile.GetGumps(); + dc.m_Yielding = true; + dc.RejectReady(pm, null); + dc.m_Yielding = false; - gumps.Close(); - gumps.Close(); - gumps.Close(); - gumps.Close(); - gumps.Close(); - gumps.Close(); - gumps.Close(); - } - - public void CloseAllGumps() - { - for (var i = 0; i < Participants.Count; ++i) - { - var p = Participants[i]; - - for (var j = 0; j < p.Players.Length; ++j) - { - var pl = p.Players[j]; - - if (pl != null) - { - CloseAllGumps(pl); - } - } - } - } - - public void RejectReady(Mobile rejector, string page) - { - if (StartedReadyCountdown) - { - return; // sanity - } - - for (var i = 0; i < Participants.Count; ++i) - { - var p = Participants[i]; - - for (var j = 0; j < p.Players.Length; ++j) - { - var pl = p.Players[j]; - - if (pl == null) - { - continue; - } - - pl.Ready = false; - - var mob = pl.Mobile; - - if (page == null) // yield - { - if (mob != rejector) + if (init == pm) { - mob.SendMessage(0x22, $"{rejector.Name} has yielded."); + dc.Unregister(); } - } - else - { - if (mob == rejector) + else if (dc.Registered) { - if (Rematch) + p.Nullify(pl); + pm.DuelPlayer = null; + + var send = true; + + foreach (var g in init.GetGumps()) { - mob.SendMessage(0x22, "You have rejected the rematch."); - } - else - { - mob.SendMessage(0x22, $"You have rejected the {page}."); - } - } - else - { - if (Rematch) - { - mob.SendMessage(0x22, $"{rejector.Name} has rejected the rematch."); - } - else - { - mob.SendMessage(0x22, $"{rejector.Name} has rejected the {page}."); - } - } - } - - // Close all of them? - var gumps = mob.GetGumps(); - - gumps.Close(); - gumps.Close(); - gumps.Close(); - } - } - - if (Rematch) - { - Unregister(); - } - else if (!m_Yielding) - { - Initiator.SendGump(new DuelContextGump(Initiator, this)); - } - - ReadyWait = false; - ReadyCount = 0; - } - - public void SendReadyGump() - { - SendReadyGump(-1); - } - - public static void Debuff(Mobile mob) - { - mob.RemoveStatMod("[Magic] Str Buff"); - mob.RemoveStatMod("[Magic] Dex Buff"); - mob.RemoveStatMod("[Magic] Int Buff"); - mob.RemoveStatMod("[Magic] Str Curse"); - mob.RemoveStatMod("[Magic] Dex Curse"); - mob.RemoveStatMod("[Magic] Int Curse"); - mob.RemoveStatMod("Concussion"); - mob.RemoveStatMod("blood-rose"); - mob.RemoveStatMod("clarity-potion"); - mob.RemoveStatMod("RoseOfTrinsicPetal"); - mob.RemoveStatMod("Holy Bless"); - mob.RemoveStatMod("Holy Curse"); - - OrangePetals.RemoveEffect(mob); - - mob.Paralyzed = false; - mob.Hidden = false; - - if (!Core.AOS) - { - mob.MagicDamageAbsorb = 0; - mob.MeleeDamageAbsorb = 0; - ProtectionSpell.Registry.Remove(mob); - - ArchProtectionSpell.RemoveEntry(mob); - - mob.EndAction(); - } - - TransformationSpellHelper.RemoveContext(mob, true); - AnimalForm.RemoveContext(mob); - DisguisePersistence.StopTimer(mob); - - if (!mob.CanBeginAction()) - { - mob.BodyMod = 0; - mob.HueMod = -1; - mob.EndAction(); - } - - BaseArmor.ValidateMobile(mob); - BaseClothing.ValidateMobile(mob); - - mob.Hits = mob.HitsMax; - mob.Stam = mob.StamMax; - mob.Mana = mob.ManaMax; - - mob.Poison = null; - } - - public static void CancelSpell(Mobile mob) - { - if (mob.Spell is Spell spell) - { - spell.Disturb(DisturbType.Kill); - } - - Target.Cancel(mob); - } - - public void DestroyWall() - { - for (var i = 0; i < m_Walls.Count; ++i) - { - m_Walls[i].Delete(); - } - - m_Walls.Clear(); - } - - public void CreateWall() - { - if (Arena == null) - { - return; - } - - var start = Arena.Points.EdgeWest; - var wall = Arena.Wall; - - var dx = start.X - wall.X; - var dy = start.Y - wall.Y; - var rx = dx - dy; - var ry = dx + dy; - - var eastToWest = rx == 0 && ry >= 0 || rx >= 0 && ry == 0; - - Effects.PlaySound(wall, Arena.Facet, 0x1F6); - - for (var i = -1; i <= 1; ++i) - { - var loc = new Point3D(eastToWest ? wall.X + i : wall.X, eastToWest ? wall.Y : wall.Y + i, wall.Z); - - var created = new InternalWall(); - - created.Appear(loc, Arena.Facet); - - m_Walls.Add(created); - } - } - - public void BuildParties() - { - for (var i = 0; i < Participants.Count; ++i) - { - var p = Participants[i]; - - if (p.Players.Length > 1) - { - var players = new List(); - - for (var j = 0; j < p.Players.Length; ++j) - { - var dp = p.Players[j]; - - if (dp == null) - { - continue; - } - - players.Add(dp.Mobile); - } - - if (players.Count > 1) - { - for (var leaderIndex = 0; leaderIndex + 1 < players.Count; leaderIndex += Party.Capacity) - { - var leader = players[leaderIndex]; - var party = Party.Get(leader); - - if (party == null) - { - leader.Party = party = new Party(leader); - } - else if (party.Leader != leader) - { - party.SendPublicMessage(leader, "I leave this party to fight in a duel."); - party.Remove(leader); - leader.Party = party = new Party(leader); - } - - for (var j = leaderIndex + 1; j < players.Count && j < leaderIndex + Party.Capacity; ++j) - { - var player = players[j]; - var existing = Party.Get(player); - - if (existing == party) + if (g is ParticipantGump pg && pg.Participant == p) { - continue; + init.SendGump(new ParticipantGump(init, dc, p)); + send = false; + break; } - if (party.Members.Count + party.Candidates.Count >= Party.Capacity) + if (g is DuelContextGump dcg && dcg.Context == dc) { - player.SendMessage( - "You could not be added to the team party because it is at full capacity." - ); - leader.SendMessage( - "{0} could not be added to the team party because it is at full capacity." - ); + init.SendGump(new DuelContextGump(init, dc)); + send = false; + break; } - else - { - if (existing != null) - { - existing.SendPublicMessage(player, "I leave this party to fight in a duel."); - existing.Remove(player); - } + } - party.OnAccept(player, true); - } + if (send) + { + init.SendGump(new DuelContextGump(init, dc)); } } } } } - } - - public void ClearIllegalItems() - { - for (var i = 0; i < Participants.Count; ++i) - { - var p = Participants[i]; - - for (var j = 0; j < p.Players.Length; ++j) - { - var pl = p.Players[j]; - - if (pl == null) - { - continue; - } - - ClearIllegalItems(pl.Mobile); - } - } - } - - public void ClearIllegalItems(Mobile mob) - { - if (mob.StunReady && !AllowSpecialAbility(mob, "Stun", false)) - { - mob.StunReady = false; - } - - if (mob.DisarmReady && !AllowSpecialAbility(mob, "Disarm", false)) - { - mob.DisarmReady = false; - } - - var pack = mob.Backpack; - - if (pack == null) - { - return; - } - - for (var i = mob.Items.Count - 1; i >= 0; --i) - { - if (i >= mob.Items.Count) - { - continue; // sanity - } - - var item = mob.Items[i]; - - if (!CheckItemEquip(mob, item)) - { - pack.DropItem(item); - - // TODO: Use Layer instead of Type - var number = item switch - { - BaseWeapon _ => 1062001, // You can no longer wield your ~1_WEAPON~ - not BaseShield when item is BaseArmor or BaseClothing => 1062002, // You can no longer wear your ~1_ARMOR~ - _ => 1062003 // You can no longer equip your ~1_SHIELD~ - }; - - mob.SendLocalizedMessage(number, item.Name ?? $"#{item.LabelNumber}"); - } - } - - var inHand = mob.Holding; - - if (inHand != null && !CheckItemEquip(mob, inHand)) - { - mob.Holding = null; - - var bi = inHand.GetBounce(); - - if (bi.Parent == mob) - { - pack.DropItem(inHand); - } - else - { - inHand.Bounce(mob); - } - - inHand.ClearBounce(); - } - } - - private void MessageRuleset(Mobile mob) - { - if (Ruleset == null) - { - return; - } - - var ruleset = Ruleset; - var basedef = ruleset.Base; - - mob.SendMessage($"Ruleset: {basedef.Title}"); - - BitArray defs; - - if (ruleset.Flavors.Count > 0) - { - defs = new BitArray(basedef.Options); - - for (var i = 0; i < ruleset.Flavors.Count; ++i) - { - defs.Or(ruleset.Flavors[i].Options); - - mob.SendMessage($" + {ruleset.Flavors[i].Title}"); - } - } else { - defs = basedef.Options; - } + var pl = pm.DuelContext.Find(pm); - var changes = 0; - - var opts = ruleset.Options; - - for (var i = 0; i < opts.Length; ++i) - { - if (defs[i] != opts[i]) + if (pl != null) { - var name = ruleset.Layout.FindByIndex(i); - - if (name != null) // sanity + if (pm.DuelContext.IsOneVsOne) { - ++changes; - - if (changes == 1) - { - mob.SendMessage("Modifications:"); - } - - if (opts[i]) - { - mob.SendMessage($"{name}: enabled"); - } - else - { - mob.SendMessage($"{name}: disabled"); - } + e.Mobile.SendMessage(0x22, "You may not yield a 1 on 1 match."); } - } - } - } - - public void SendBeginGump(int count) - { - if (!Registered || Finished) - { - return; - } - - if (count == 10) - { - CreateWall(); - BuildParties(); - ClearIllegalItems(); - } - else if (count == 0) - { - DestroyWall(); - } - - StartedBeginCountdown = true; - - if (count == 0) - { - Started = true; - BeginAutoTie(); - } - - for (var i = 0; i < Participants.Count; ++i) - { - var p = Participants[i]; - - for (var j = 0; j < p.Players.Length; ++j) - { - var pl = p.Players[j]; - - if (pl == null) + else if (pl.Eliminated) { - continue; - } - - var mob = pl.Mobile; - - if (count > 0) - { - if (count == 10) - { - var gumps = mob.GetGumps(); - - gumps.Close(); - gumps.Close(); - gumps.Close(); - gumps.Send(new BeginGump(count)); - } - - mob.Frozen = true; + e.Mobile.SendMessage(0x22, "You have already been eliminated."); } else { - mob.CloseGump(); - mob.Frozen = false; + pm.LocalOverheadMessage(MessageType.Regular, 0x22, false, "You have yielded."); + pm.NonlocalOverheadMessage(MessageType.Regular, 0x22, false, $"{pm.Name} has yielded."); + + pm.DuelContext.m_Yielding = true; + pm.Kill(); + pm.DuelContext.m_Yielding = false; + + if (pm.Alive) // invul, ... + { + pl.Eliminated = true; + + pm.DuelContext.RemoveAggressions(pm); + pm.DuelContext.SendOutside(pm); + pm.DuelContext.Refresh(pm, null); + Debuff(pm); + CancelSpell(pm); + pm.Frozen = false; + + var winner = pm.DuelContext.CheckCompletion(); + + if (winner != null) + { + pm.DuelContext.Finish(winner); + } + } } } - } - } - - public void RemoveAggressions(Mobile mob) - { - for (var i = 0; i < Participants.Count; ++i) - { - var p = Participants[i]; - - for (var j = 0; j < p.Players.Length; ++j) + else { - var dp = p.Players[j]; - - if (dp == null || dp.Mobile == mob) - { - continue; - } - - mob.RemoveAggressed(dp.Mobile); - mob.RemoveAggressor(dp.Mobile); - dp.Mobile.RemoveAggressed(mob); - dp.Mobile.RemoveAggressor(mob); + e.Mobile.SendMessage(0x22, "BUG: Unable to find duel context."); } } } + } - public void SendReadyUpGump() + public void CloseAllGumps(DuelPlayer pl) + { + var gumps = pl.Mobile.GetGumps(); + + gumps.Close(); + gumps.Close(); + gumps.Close(); + gumps.Close(); + gumps.Close(); + gumps.Close(); + gumps.Close(); + } + + public void CloseAllGumps() + { + for (var i = 0; i < Participants.Count; ++i) { - if (!Registered) + var p = Participants[i]; + + for (var j = 0; j < p.Players.Length; ++j) { - return; - } + var pl = p.Players[j]; - ReadyWait = true; - ReadyCount = -1; - - for (var i = 0; i < Participants.Count; ++i) - { - var p = Participants[i]; - - for (var j = 0; j < p.Players.Length; ++j) + if (pl != null) { - var pl = p.Players[j]; - - var mob = pl?.Mobile; - - if (mob != null && m_Tournament == null) - { - mob.SendGump(new ReadyUpGump(mob, this), true); - } + CloseAllGumps(pl); } } } + } - public string ValidateStart() + public void RejectReady(Mobile rejector, string page) + { + if (StartedReadyCountdown) { - if (m_Tournament == null && TournamentController.IsActive) - { - return "a tournament is active"; - } + return; // sanity + } - for (var i = 0; i < Participants.Count; ++i) + for (var i = 0; i < Participants.Count; ++i) + { + var p = Participants[i]; + + for (var j = 0; j < p.Players.Length; ++j) { - var p = Participants[i]; + var pl = p.Players[j]; + + if (pl == null) + { + continue; + } + + pl.Ready = false; + + var mob = pl.Mobile; + + if (page == null) // yield + { + if (mob != rejector) + { + mob.SendMessage(0x22, $"{rejector.Name} has yielded."); + } + } + else + { + if (mob == rejector) + { + if (Rematch) + { + mob.SendMessage(0x22, "You have rejected the rematch."); + } + else + { + mob.SendMessage(0x22, $"You have rejected the {page}."); + } + } + else + { + if (Rematch) + { + mob.SendMessage(0x22, $"{rejector.Name} has rejected the rematch."); + } + else + { + mob.SendMessage(0x22, $"{rejector.Name} has rejected the {page}."); + } + } + } + + // Close all of them? + var gumps = mob.GetGumps(); + + gumps.Close(); + gumps.Close(); + gumps.Close(); + } + } + + if (Rematch) + { + Unregister(); + } + else if (!m_Yielding) + { + Initiator.SendGump(new DuelContextGump(Initiator, this)); + } + + ReadyWait = false; + ReadyCount = 0; + } + + public void SendReadyGump() + { + SendReadyGump(-1); + } + + public static void Debuff(Mobile mob) + { + mob.RemoveStatMod("[Magic] Str Buff"); + mob.RemoveStatMod("[Magic] Dex Buff"); + mob.RemoveStatMod("[Magic] Int Buff"); + mob.RemoveStatMod("[Magic] Str Curse"); + mob.RemoveStatMod("[Magic] Dex Curse"); + mob.RemoveStatMod("[Magic] Int Curse"); + mob.RemoveStatMod("Concussion"); + mob.RemoveStatMod("blood-rose"); + mob.RemoveStatMod("clarity-potion"); + mob.RemoveStatMod("RoseOfTrinsicPetal"); + mob.RemoveStatMod("Holy Bless"); + mob.RemoveStatMod("Holy Curse"); + + OrangePetals.RemoveEffect(mob); + + mob.Paralyzed = false; + mob.Hidden = false; + + if (!Core.AOS) + { + mob.MagicDamageAbsorb = 0; + mob.MeleeDamageAbsorb = 0; + ProtectionSpell.Registry.Remove(mob); + + ArchProtectionSpell.RemoveEntry(mob); + + mob.EndAction(); + } + + TransformationSpellHelper.RemoveContext(mob, true); + AnimalForm.RemoveContext(mob); + DisguisePersistence.StopTimer(mob); + + if (!mob.CanBeginAction()) + { + mob.BodyMod = 0; + mob.HueMod = -1; + mob.EndAction(); + } + + BaseArmor.ValidateMobile(mob); + BaseClothing.ValidateMobile(mob); + + mob.Hits = mob.HitsMax; + mob.Stam = mob.StamMax; + mob.Mana = mob.ManaMax; + + mob.Poison = null; + } + + public static void CancelSpell(Mobile mob) + { + if (mob.Spell is Spell spell) + { + spell.Disturb(DisturbType.Kill); + } + + Target.Cancel(mob); + } + + public void DestroyWall() + { + for (var i = 0; i < m_Walls.Count; ++i) + { + m_Walls[i].Delete(); + } + + m_Walls.Clear(); + } + + public void CreateWall() + { + if (Arena == null) + { + return; + } + + var start = Arena.Points.EdgeWest; + var wall = Arena.Wall; + + var dx = start.X - wall.X; + var dy = start.Y - wall.Y; + var rx = dx - dy; + var ry = dx + dy; + + var eastToWest = rx == 0 && ry >= 0 || rx >= 0 && ry == 0; + + Effects.PlaySound(wall, Arena.Facet, 0x1F6); + + for (var i = -1; i <= 1; ++i) + { + var loc = new Point3D(eastToWest ? wall.X + i : wall.X, eastToWest ? wall.Y : wall.Y + i, wall.Z); + + var created = new InternalWall(); + + created.Appear(loc, Arena.Facet); + + m_Walls.Add(created); + } + } + + public void BuildParties() + { + for (var i = 0; i < Participants.Count; ++i) + { + var p = Participants[i]; + + if (p.Players.Length > 1) + { + var players = new List(); for (var j = 0; j < p.Players.Length; ++j) { @@ -2269,94 +1931,412 @@ namespace Server.Engines.ConPVP if (dp == null) { - return "a slot is empty"; + continue; } - if (dp.Mobile.Region.IsPartOf()) - { - return $"{dp.Mobile.Name} is in jail"; - } + players.Add(dp.Mobile); + } - if (Sigil.ExistsOn(dp.Mobile)) + if (players.Count > 1) + { + for (var leaderIndex = 0; leaderIndex + 1 < players.Count; leaderIndex += Party.Capacity) { - return $"{dp.Mobile.Name} is holding a sigil"; - } + var leader = players[leaderIndex]; + var party = Party.Get(leader); - if (!dp.Mobile.Alive) - { - if (m_Tournament == null) + if (party == null) { - return $"{dp.Mobile.Name} is dead"; + leader.Party = party = new Party(leader); + } + else if (party.Leader != leader) + { + party.SendPublicMessage(leader, "I leave this party to fight in a duel."); + party.Remove(leader); + leader.Party = party = new Party(leader); } - dp.Mobile.Resurrect(); - } - - if (m_Tournament == null && CheckCombat(dp.Mobile)) - { - return $"{dp.Mobile.Name} is in combat"; - } - - if (dp.Mobile.Mounted) - { - var mount = dp.Mobile.Mount; - - if (m_Tournament != null && mount != null) + for (var j = leaderIndex + 1; j < players.Count && j < leaderIndex + Party.Capacity; ++j) { - mount.Rider = null; - } - else - { - return $"{dp.Mobile.Name} is mounted"; + var player = players[j]; + var existing = Party.Get(player); + + if (existing == party) + { + continue; + } + + if (party.Members.Count + party.Candidates.Count >= Party.Capacity) + { + player.SendMessage( + "You could not be added to the team party because it is at full capacity." + ); + leader.SendMessage( + "{0} could not be added to the team party because it is at full capacity." + ); + } + else + { + if (existing != null) + { + existing.SendPublicMessage(player, "I leave this party to fight in a duel."); + existing.Remove(player); + } + + party.OnAccept(player, true); + } } } } } + } + } - return null; + public void ClearIllegalItems() + { + for (var i = 0; i < Participants.Count; ++i) + { + var p = Participants[i]; + + for (var j = 0; j < p.Players.Length; ++j) + { + var pl = p.Players[j]; + + if (pl == null) + { + continue; + } + + ClearIllegalItems(pl.Mobile); + } + } + } + + public void ClearIllegalItems(Mobile mob) + { + if (mob.StunReady && !AllowSpecialAbility(mob, "Stun", false)) + { + mob.StunReady = false; } - public void SendReadyGump(int count) + if (mob.DisarmReady && !AllowSpecialAbility(mob, "Disarm", false)) { - if (!Registered) + mob.DisarmReady = false; + } + + var pack = mob.Backpack; + + if (pack == null) + { + return; + } + + for (var i = mob.Items.Count - 1; i >= 0; --i) + { + if (i >= mob.Items.Count) { - return; + continue; // sanity } - if (count != -1) + var item = mob.Items[i]; + + if (!CheckItemEquip(mob, item)) { - StartedReadyCountdown = true; - } + pack.DropItem(item); - ReadyCount = count; - - if (count == 0) - { - var error = ValidateStart(); - - if (error != null) + // TODO: Use Layer instead of Type + var number = item switch { - for (var i = 0; i < Participants.Count; ++i) + BaseWeapon _ => 1062001, // You can no longer wield your ~1_WEAPON~ + not BaseShield when item is BaseArmor or BaseClothing => 1062002, // You can no longer wear your ~1_ARMOR~ + _ => 1062003 // You can no longer equip your ~1_SHIELD~ + }; + + mob.SendLocalizedMessage(number, item.Name ?? $"#{item.LabelNumber}"); + } + } + + var inHand = mob.Holding; + + if (inHand != null && !CheckItemEquip(mob, inHand)) + { + mob.Holding = null; + + var bi = inHand.GetBounce(); + + if (bi.Parent == mob) + { + pack.DropItem(inHand); + } + else + { + inHand.Bounce(mob); + } + + inHand.ClearBounce(); + } + } + + private void MessageRuleset(Mobile mob) + { + if (Ruleset == null) + { + return; + } + + var ruleset = Ruleset; + var basedef = ruleset.Base; + + mob.SendMessage($"Ruleset: {basedef.Title}"); + + BitArray defs; + + if (ruleset.Flavors.Count > 0) + { + defs = new BitArray(basedef.Options); + + for (var i = 0; i < ruleset.Flavors.Count; ++i) + { + defs.Or(ruleset.Flavors[i].Options); + + mob.SendMessage($" + {ruleset.Flavors[i].Title}"); + } + } + else + { + defs = basedef.Options; + } + + var changes = 0; + + var opts = ruleset.Options; + + for (var i = 0; i < opts.Length; ++i) + { + if (defs[i] != opts[i]) + { + var name = ruleset.Layout.FindByIndex(i); + + if (name != null) // sanity + { + ++changes; + + if (changes == 1) { - var p = Participants[i]; - - for (var j = 0; j < p.Players.Length; ++j) - { - var dp = p.Players[j]; - - dp?.Mobile.SendMessage($"The duel could not be started because {error}."); - } + mob.SendMessage("Modifications:"); } - StartCountdown(10, SendReadyGump); + if (opts[i]) + { + mob.SendMessage($"{name}: enabled"); + } + else + { + mob.SendMessage($"{name}: disabled"); + } + } + } + } + } - return; + public void SendBeginGump(int count) + { + if (!Registered || Finished) + { + return; + } + + if (count == 10) + { + CreateWall(); + BuildParties(); + ClearIllegalItems(); + } + else if (count == 0) + { + DestroyWall(); + } + + StartedBeginCountdown = true; + + if (count == 0) + { + Started = true; + BeginAutoTie(); + } + + for (var i = 0; i < Participants.Count; ++i) + { + var p = Participants[i]; + + for (var j = 0; j < p.Players.Length; ++j) + { + var pl = p.Players[j]; + + if (pl == null) + { + continue; } - ReadyWait = false; + var mob = pl.Mobile; - var players = new List(); + if (count > 0) + { + if (count == 10) + { + var gumps = mob.GetGumps(); + gumps.Close(); + gumps.Close(); + gumps.Close(); + gumps.Send(new BeginGump(count)); + } + + mob.Frozen = true; + } + else + { + mob.CloseGump(); + mob.Frozen = false; + } + } + } + } + + public void RemoveAggressions(Mobile mob) + { + for (var i = 0; i < Participants.Count; ++i) + { + var p = Participants[i]; + + for (var j = 0; j < p.Players.Length; ++j) + { + var dp = p.Players[j]; + + if (dp == null || dp.Mobile == mob) + { + continue; + } + + mob.RemoveAggressed(dp.Mobile); + mob.RemoveAggressor(dp.Mobile); + dp.Mobile.RemoveAggressed(mob); + dp.Mobile.RemoveAggressor(mob); + } + } + } + + public void SendReadyUpGump() + { + if (!Registered) + { + return; + } + + ReadyWait = true; + ReadyCount = -1; + + for (var i = 0; i < Participants.Count; ++i) + { + var p = Participants[i]; + + for (var j = 0; j < p.Players.Length; ++j) + { + var pl = p.Players[j]; + + var mob = pl?.Mobile; + + if (mob != null && m_Tournament == null) + { + mob.SendGump(new ReadyUpGump(mob, this), true); + } + } + } + } + + public string ValidateStart() + { + if (m_Tournament == null && TournamentController.IsActive) + { + return "a tournament is active"; + } + + for (var i = 0; i < Participants.Count; ++i) + { + var p = Participants[i]; + + for (var j = 0; j < p.Players.Length; ++j) + { + var dp = p.Players[j]; + + if (dp == null) + { + return "a slot is empty"; + } + + if (dp.Mobile.Region.IsPartOf()) + { + return $"{dp.Mobile.Name} is in jail"; + } + + if (Sigil.ExistsOn(dp.Mobile)) + { + return $"{dp.Mobile.Name} is holding a sigil"; + } + + if (!dp.Mobile.Alive) + { + if (m_Tournament == null) + { + return $"{dp.Mobile.Name} is dead"; + } + + dp.Mobile.Resurrect(); + } + + if (m_Tournament == null && CheckCombat(dp.Mobile)) + { + return $"{dp.Mobile.Name} is in combat"; + } + + if (dp.Mobile.Mounted) + { + var mount = dp.Mobile.Mount; + + if (m_Tournament != null && mount != null) + { + mount.Rider = null; + } + else + { + return $"{dp.Mobile.Name} is mounted"; + } + } + } + } + + return null; + } + + public void SendReadyGump(int count) + { + if (!Registered) + { + return; + } + + if (count != -1) + { + StartedReadyCountdown = true; + } + + ReadyCount = count; + + if (count == 0) + { + var error = ValidateStart(); + + if (error != null) + { for (var i = 0; i < Participants.Count; ++i) { var p = Participants[i]; @@ -2365,122 +2345,18 @@ namespace Server.Engines.ConPVP { var dp = p.Players[j]; - if (dp != null) - { - players.Add(dp.Mobile); - } + dp?.Mobile.SendMessage($"The duel could not be started because {error}."); } } - var arena = m_OverrideArena ?? Arena.FindArena(players); - - if (arena == null) - { - for (var i = 0; i < Participants.Count; ++i) - { - var p = Participants[i]; - - for (var j = 0; j < p.Players.Length; ++j) - { - var dp = p.Players[j]; - - dp?.Mobile.SendMessage( - "The duel could not be started because there are no arenas. If you want to stop waiting for a free arena, yield the duel." - ); - } - } - - StartCountdown(10, SendReadyGump); - return; - } - - if (!arena.IsOccupied) - { - Arena = arena; - - if (Initiator.Map == Map.Internal) - { - m_GatePoint = Initiator.LogoutLocation; - m_GateFacet = Initiator.LogoutMap; - } - else - { - m_GatePoint = Initiator.Location; - m_GateFacet = Initiator.Map; - } - - if (arena.Teleporter is not ExitTeleporter tp) - { - arena.Teleporter = tp = new ExitTeleporter(); - tp.MoveToWorld(arena.GateOut == Point3D.Zero ? arena.Outside : arena.GateOut, arena.Facet); - } - - var mg = new ArenaMoongate( - arena.GateIn == Point3D.Zero ? arena.Outside : arena.GateIn, - arena.Facet, - tp - ); - - StartedBeginCountdown = true; - - for (var i = 0; i < Participants.Count; ++i) - { - var p = Participants[i]; - - for (var j = 0; j < p.Players.Length; ++j) - { - var pl = p.Players[j]; - - if (pl == null) - { - continue; - } - - tp.Register(pl.Mobile); - - pl.Mobile.Frozen = false; // reset timer just in case - pl.Mobile.Frozen = true; - - Debuff(pl.Mobile); - CancelSpell(pl.Mobile); - - pl.Mobile.Delta(MobileDelta.Noto); - } - - arena.MoveInside(p.Players, i); - } - - m_EventGame?.OnStart(); - - StartCountdown(10, SendBeginGump); - - mg.Appear(m_GatePoint, m_GateFacet); - } - else - { - for (var i = 0; i < Participants.Count; ++i) - { - var p = Participants[i]; - - for (var j = 0; j < p.Players.Length; ++j) - { - var dp = p.Players[j]; - - dp?.Mobile.SendMessage( - "The duel could not be started because all arenas are full. If you want to stop waiting for a free arena, yield the duel." - ); - } - } - - StartCountdown(10, SendReadyGump); - } + StartCountdown(10, SendReadyGump); return; } - ReadyWait = true; + ReadyWait = false; - var isAllReady = true; + var players = new List(); for (var i = 0; i < Participants.Count; ++i) { @@ -2488,341 +2364,431 @@ namespace Server.Engines.ConPVP for (var j = 0; j < p.Players.Length; ++j) { - var pl = p.Players[j]; + var dp = p.Players[j]; - if (pl == null) + if (dp != null) { - continue; + players.Add(dp.Mobile); } + } + } - var mob = pl.Mobile; + var arena = m_OverrideArena ?? Arena.FindArena(players); - if (pl.Ready) + if (arena == null) + { + for (var i = 0; i < Participants.Count; ++i) + { + var p = Participants[i]; + + for (var j = 0; j < p.Players.Length; ++j) { - if (m_Tournament == null) + var dp = p.Players[j]; + + dp?.Mobile.SendMessage( + "The duel could not be started because there are no arenas. If you want to stop waiting for a free arena, yield the duel." + ); + } + } + + StartCountdown(10, SendReadyGump); + return; + } + + if (!arena.IsOccupied) + { + Arena = arena; + + if (Initiator.Map == Map.Internal) + { + m_GatePoint = Initiator.LogoutLocation; + m_GateFacet = Initiator.LogoutMap; + } + else + { + m_GatePoint = Initiator.Location; + m_GateFacet = Initiator.Map; + } + + if (arena.Teleporter is not ExitTeleporter tp) + { + arena.Teleporter = tp = new ExitTeleporter(); + tp.MoveToWorld(arena.GateOut == Point3D.Zero ? arena.Outside : arena.GateOut, arena.Facet); + } + + var mg = new ArenaMoongate( + arena.GateIn == Point3D.Zero ? arena.Outside : arena.GateIn, + arena.Facet, + tp + ); + + StartedBeginCountdown = true; + + for (var i = 0; i < Participants.Count; ++i) + { + var p = Participants[i]; + + for (var j = 0; j < p.Players.Length; ++j) + { + var pl = p.Players[j]; + + if (pl == null) { - mob.SendGump(new ReadyGump(mob, this, count), true); + continue; } + + tp.Register(pl.Mobile); + + pl.Mobile.Frozen = false; // reset timer just in case + pl.Mobile.Frozen = true; + + Debuff(pl.Mobile); + CancelSpell(pl.Mobile); + + pl.Mobile.Delta(MobileDelta.Noto); } - else + + arena.MoveInside(p.Players, i); + } + + m_EventGame?.OnStart(); + + StartCountdown(10, SendBeginGump); + + mg.Appear(m_GatePoint, m_GateFacet); + } + else + { + for (var i = 0; i < Participants.Count; ++i) + { + var p = Participants[i]; + + for (var j = 0; j < p.Players.Length; ++j) { - isAllReady = false; + var dp = p.Players[j]; + + dp?.Mobile.SendMessage( + "The duel could not be started because all arenas are full. If you want to stop waiting for a free arena, yield the duel." + ); } } + + StartCountdown(10, SendReadyGump); } - if (count == -1 && isAllReady) - { - StartCountdown(3, SendReadyGump); - } + return; } - private class InternalWall : Item + ReadyWait = true; + + var isAllReady = true; + + for (var i = 0; i < Participants.Count; ++i) { - public InternalWall() : base(0x80) => Movable = false; + var p = Participants[i]; - public InternalWall(Serial serial) : base(serial) + for (var j = 0; j < p.Players.Length; ++j) { - } + var pl = p.Players[j]; - public void Appear(Point3D loc, Map map) - { - MoveToWorld(loc, map); - - Effects.SendLocationParticles(this, 0x376A, 9, 10, 5025); - } - - public override void Serialize(IGenericWriter writer) - { - base.Serialize(writer); - - writer.Write(0); - } - - public override void Deserialize(IGenericReader reader) - { - base.Deserialize(reader); - - var version = reader.ReadInt(); - - Delete(); - } - } - - private class ReturnEntry - { - private DateTime m_Expire; - - public ReturnEntry(Mobile mob) - { - Mobile = mob; - - Update(); - } - - public ReturnEntry(Mobile mob, Point3D loc, Map facet) - { - Mobile = mob; - Location = loc; - Facet = facet; - m_Expire = Core.Now + TimeSpan.FromMinutes(30.0); - } - - public Mobile Mobile { get; } - - public Point3D Location { get; private set; } - - public Map Facet { get; private set; } - - public bool Expired => Core.Now >= m_Expire; - - public void Return() - { - if (Facet == Map.Internal || Facet == null) + if (pl == null) { - return; + continue; } - if (Mobile.Map == Map.Internal) + var mob = pl.Mobile; + + if (pl.Ready) { - Mobile.LogoutLocation = Location; - Mobile.LogoutMap = Facet; + if (m_Tournament == null) + { + mob.SendGump(new ReadyGump(mob, this, count), true); + } } else { - Mobile.Location = Location; - Mobile.Map = Facet; - } - } - - public void Update() - { - m_Expire = Core.Now + TimeSpan.FromMinutes(30.0); - - if (Mobile.Map == Map.Internal) - { - Facet = Mobile.LogoutMap; - Location = Mobile.LogoutLocation; - } - else - { - Facet = Mobile.Map; - Location = Mobile.Location; + isAllReady = false; } } } - private class ExitTeleporter : Item + if (count == -1 && isAllReady) { - private List m_Entries; + StartCountdown(3, SendReadyGump); + } + } - public ExitTeleporter() : base(0x1822) + [SerializationGenerator(0, false)] + private partial class InternalWall : Item + { + public InternalWall() : base(0x80) => Movable = false; + + public void Appear(Point3D loc, Map map) + { + MoveToWorld(loc, map); + + Effects.SendLocationParticles(this, 0x376A, 9, 10, 5025); + } + + public override bool SkipSerialization => true; + } + + private class ReturnEntry + { + private DateTime m_Expire; + + public ReturnEntry(Mobile mob) + { + Mobile = mob; + + Update(); + } + + public ReturnEntry(Mobile mob, Point3D loc, Map facet) + { + Mobile = mob; + Location = loc; + Facet = facet; + m_Expire = Core.Now + TimeSpan.FromMinutes(30.0); + } + + public Mobile Mobile { get; } + + public Point3D Location { get; private set; } + + public Map Facet { get; private set; } + + public bool Expired => Core.Now >= m_Expire; + + public void Return() + { + if (Facet == Map.Internal || Facet == null) { - m_Entries = new List(); - - Hue = 0x482; - Movable = false; + return; } - public ExitTeleporter(Serial serial) : base(serial) + if (Mobile.Map == Map.Internal) { + Mobile.LogoutLocation = Location; + Mobile.LogoutMap = Facet; } - - public override string DefaultName => "return teleporter"; - - public void Register(Mobile mob) + else { - var entry = Find(mob); - - if (entry != null) - { - entry.Update(); - return; - } - - m_Entries.Add(new ReturnEntry(mob)); - } - - private ReturnEntry Find(Mobile mob) - { - for (var i = 0; i < m_Entries.Count; ++i) - { - var entry = m_Entries[i]; - - if (entry.Mobile == mob) - { - return entry; - } - - if (entry.Expired) - { - m_Entries.RemoveAt(i--); - } - } - - return null; - } - - public override bool OnMoveOver(Mobile m) - { - if (!base.OnMoveOver(m)) - { - return false; - } - - var entry = Find(m); - - if (entry != null) - { - entry.Return(); - - Effects.PlaySound(GetWorldLocation(), Map, 0x1FE); - Effects.PlaySound(m.Location, m.Map, 0x1FE); - - m_Entries.Remove(entry); - - return false; - } - - m.SendLocalizedMessage(1049383); // The teleporter doesn't seem to work for you. - return true; - } - - public override void Serialize(IGenericWriter writer) - { - base.Serialize(writer); - - writer.Write(0); - - writer.WriteEncodedInt(m_Entries.Count); - - for (var i = 0; i < m_Entries.Count; ++i) - { - var entry = m_Entries[i]; - - writer.Write(entry.Mobile); - writer.Write(entry.Location); - writer.Write(entry.Facet); - - if (entry.Expired) - { - m_Entries.RemoveAt(i--); - } - } - } - - public override void Deserialize(IGenericReader reader) - { - base.Deserialize(reader); - - var version = reader.ReadInt(); - - switch (version) - { - case 0: - { - var count = reader.ReadEncodedInt(); - - m_Entries = new List(count); - - for (var i = 0; i < count; ++i) - { - var mob = reader.ReadEntity(); - var loc = reader.ReadPoint3D(); - var map = reader.ReadMap(); - - m_Entries.Add(new ReturnEntry(mob, loc, map)); - } - - break; - } - } + Mobile.Location = Location; + Mobile.Map = Facet; } } - private class ArenaMoongate : ConfirmationMoongate + public void Update() { - private readonly ExitTeleporter m_Teleporter; + m_Expire = Core.Now + TimeSpan.FromMinutes(30.0); - public ArenaMoongate(Point3D target, Map map, ExitTeleporter tp) : base(target, map) + if (Mobile.Map == Map.Internal) { - m_Teleporter = tp; - - ItemID = 0x1FD4; - Dispellable = false; - - GumpWidth = 300; - GumpHeight = 150; - MessageColor = 0xFFC000; - Message = "Are you sure you wish to spectate this duel?"; - TitleColor = 0x7800; - TitleNumber = 1062051; // Gate Warning - - Timer.StartTimer(TimeSpan.FromSeconds(10.0), Delete); + Facet = Mobile.LogoutMap; + Location = Mobile.LogoutLocation; } - - public ArenaMoongate(Serial serial) : base(serial) + else { - } - - public override string DefaultName => "spectator moongate"; - - public override void CheckGate(Mobile m, int range) - { - if (CheckCombat(m)) - { - m.SendMessage( - 0x22, - "You have recently been in combat with another player and cannot use this moongate." - ); - } - else - { - base.CheckGate(m, range); - } - } - - public override void UseGate(Mobile m) - { - if (CheckCombat(m)) - { - m.SendMessage( - 0x22, - "You have recently been in combat with another player and cannot use this moongate." - ); - } - else - { - if (m_Teleporter?.Deleted == false) - { - m_Teleporter.Register(m); - } - - base.UseGate(m); - } - } - - public void Appear(Point3D loc, Map map) - { - Effects.PlaySound(loc, map, 0x20E); - MoveToWorld(loc, map); - } - - public override void Serialize(IGenericWriter writer) - { - base.Serialize(writer); - - writer.Write(0); - } - - public override void Deserialize(IGenericReader reader) - { - base.Deserialize(reader); - - var version = reader.ReadInt(); - - Delete(); + Facet = Mobile.Map; + Location = Mobile.Location; } } } + + private class ExitTeleporter : Item + { + private List m_Entries; + + public ExitTeleporter() : base(0x1822) + { + m_Entries = new List(); + + Hue = 0x482; + Movable = false; + } + + public ExitTeleporter(Serial serial) : base(serial) + { + } + + public override string DefaultName => "return teleporter"; + + public void Register(Mobile mob) + { + var entry = Find(mob); + + if (entry != null) + { + entry.Update(); + return; + } + + m_Entries.Add(new ReturnEntry(mob)); + } + + private ReturnEntry Find(Mobile mob) + { + for (var i = 0; i < m_Entries.Count; ++i) + { + var entry = m_Entries[i]; + + if (entry.Mobile == mob) + { + return entry; + } + + if (entry.Expired) + { + m_Entries.RemoveAt(i--); + } + } + + return null; + } + + public override bool OnMoveOver(Mobile m) + { + if (!base.OnMoveOver(m)) + { + return false; + } + + var entry = Find(m); + + if (entry != null) + { + entry.Return(); + + Effects.PlaySound(GetWorldLocation(), Map, 0x1FE); + Effects.PlaySound(m.Location, m.Map, 0x1FE); + + m_Entries.Remove(entry); + + return false; + } + + m.SendLocalizedMessage(1049383); // The teleporter doesn't seem to work for you. + return true; + } + + public override void Serialize(IGenericWriter writer) + { + base.Serialize(writer); + + writer.Write(0); + + writer.WriteEncodedInt(m_Entries.Count); + + for (var i = 0; i < m_Entries.Count; ++i) + { + var entry = m_Entries[i]; + + writer.Write(entry.Mobile); + writer.Write(entry.Location); + writer.Write(entry.Facet); + + if (entry.Expired) + { + m_Entries.RemoveAt(i--); + } + } + } + + public override void Deserialize(IGenericReader reader) + { + base.Deserialize(reader); + + var version = reader.ReadInt(); + + switch (version) + { + case 0: + { + var count = reader.ReadEncodedInt(); + + m_Entries = new List(count); + + for (var i = 0; i < count; ++i) + { + var mob = reader.ReadEntity(); + var loc = reader.ReadPoint3D(); + var map = reader.ReadMap(); + + m_Entries.Add(new ReturnEntry(mob, loc, map)); + } + + break; + } + } + } + } + + [SerializationGenerator(0, false)] + private partial class ArenaMoongate : ConfirmationMoongate + { + private readonly ExitTeleporter m_Teleporter; + + public ArenaMoongate(Point3D target, Map map, ExitTeleporter tp) : base(target, map) + { + m_Teleporter = tp; + + ItemID = 0x1FD4; + Dispellable = false; + + GumpWidth = 300; + GumpHeight = 150; + MessageColor = 0xFFC000; + Message = "Are you sure you wish to spectate this duel?"; + TitleColor = 0x7800; + TitleNumber = 1062051; // Gate Warning + + Timer.StartTimer(TimeSpan.FromSeconds(10.0), Delete); + } + + public override bool SkipSerialization => true; + + public override string DefaultName => "spectator moongate"; + + public override void CheckGate(Mobile m, int range) + { + if (CheckCombat(m)) + { + m.SendMessage( + 0x22, + "You have recently been in combat with another player and cannot use this moongate." + ); + } + else + { + base.CheckGate(m, range); + } + } + + public override void UseGate(Mobile m) + { + if (CheckCombat(m)) + { + m.SendMessage( + 0x22, + "You have recently been in combat with another player and cannot use this moongate." + ); + } + else + { + if (m_Teleporter?.Deleted == false) + { + m_Teleporter.Register(m); + } + + base.UseGate(m); + } + } + + public void Appear(Point3D loc, Map map) + { + Effects.PlaySound(loc, map, 0x20E); + MoveToWorld(loc, map); + } + } } diff --git a/Projects/UOContent/Engines/ConPVP/DuelTeleporterAddon.cs b/Projects/UOContent/Engines/ConPVP/DuelTeleporterAddon.cs index a8761cd34..ca92d598b 100644 --- a/Projects/UOContent/Engines/ConPVP/DuelTeleporterAddon.cs +++ b/Projects/UOContent/Engines/ConPVP/DuelTeleporterAddon.cs @@ -1,93 +1,68 @@ +using ModernUO.Serialization; using Server.Items; -namespace Server.Engines.ConPVP +namespace Server.Engines.ConPVP; + +public enum DuelTeleporterType { - public enum DuelTeleporterType + Squares = 6095, + Buds = 6104, + Flowers = 6113, + Spikes = 6122, + Arrows = 6140, + Links = 6149 +} + +[SerializationGenerator(0, false)] +public partial class DuelTeleporterAddon : BaseAddon +{ + [Constructible] + public DuelTeleporterAddon(DuelTeleporterType type = DuelTeleporterType.Squares) { - Squares = 6095, - Buds = 6104, - Flowers = 6113, - Spikes = 6122, - Arrows = 6140, - Links = 6149 + var 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 class DuelTeleporterAddon : BaseAddon + [CommandProperty(AccessLevel.GameMaster)] + public DuelTeleporterType Type { - [Constructible] - public DuelTeleporterAddon(DuelTeleporterType type = DuelTeleporterType.Squares) + get => Components.Count > 0 ? (DuelTeleporterType)Components[0].ItemID : DuelTeleporterType.Squares; + set { - var 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 + for (var i = 0; i < Components.Count && i < 9; ++i) { - if (Components.Count > 0) - { - return (DuelTeleporterType)Components[0].ItemID; - } - - return DuelTeleporterType.Squares; + Components[i].ItemID = i + (int)value; } - set - { - for (var i = 0; i < Components.Count && i < 9; ++i) - { - Components[i].ItemID = i + (int)value; - } - } - } - - public override void Serialize(IGenericWriter writer) - { - base.Serialize(writer); - - writer.Write(0); // version - } - - public override void Deserialize(IGenericReader reader) - { - base.Deserialize(reader); - - var version = reader.ReadInt(); } } } diff --git a/Projects/UOContent/Engines/ConPVP/Games/BombingRun.cs b/Projects/UOContent/Engines/ConPVP/Games/BombingRun.cs index d1f848f0f..8a46bfa70 100644 --- a/Projects/UOContent/Engines/ConPVP/Games/BombingRun.cs +++ b/Projects/UOContent/Engines/ConPVP/Games/BombingRun.cs @@ -1,796 +1,776 @@ using System; using System.Collections.Generic; +using System.Runtime.CompilerServices; using System.Text; +using ModernUO.Serialization; using Server.Gumps; using Server.Items; using Server.Mobiles; using Server.Targeting; -namespace Server.Engines.ConPVP +namespace Server.Engines.ConPVP; + +[SerializationGenerator(0, false)] +public partial class BRBomb : Item { - public 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 { - private readonly BRGame m_Game; + Movable = false; + Hue = 0x35; - private readonly List m_Helpers; + m_Game = game; - private readonly Point3DList m_Path = new(); - private readonly EffectTimer m_Timer; - private bool m_Flying; - private int m_PathIdx; + m_Helpers = new List(); - public BRBomb(BRGame game) : - base(0x103C) // 0x103C = bread, 0x1042 = pie, 0x1364 = rock, 0x13a8 = pillow, 0x2256 = bagball + 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) { - Movable = false; - Hue = 0x35; - - m_Game = game; - - m_Helpers = new List(); - - m_Timer = new EffectTimer(this); - m_Timer.Start(); + return item.RootParent as Mobile; } - public BRBomb(Serial serial) : base(serial) + if (parent is Mobile mobile) { + return mobile; } - public override bool SkipSerialization => true; + return null; + } - public override string DefaultName => "da bomb"; + public override void OnAdded(IEntity parent) + { + base.OnAdded(parent); - public Mobile Thrower { get; private set; } + var mob = FindOwner(parent); - private static Mobile FindOwner(IEntity parent) + if (mob != null) { - if (parent is Item item) - { - return item.RootParent as Mobile; - } - - if (parent is Mobile mobile) - { - return mobile; - } - - return null; + mob.SolidHueOverride = 0x0499; } + } - public override void Deserialize(IGenericReader reader) + public override void OnRemoved(IEntity parent) + { + base.OnRemoved(parent); + + var mob = FindOwner(parent); + + if (mob != null && m_Game != null) { - base.Deserialize(reader); - - var version = reader.ReadInt(); + mob.SolidHueOverride = m_Game.GetColor(mob); } + } - public override void Serialize(IGenericWriter writer) + public void DropTo(Mobile mob, Mobile killer) + { + if (mob?.Deleted == false) { - base.Serialize(writer); - - writer.Write(0); // version + MoveToWorld(mob.Location, mob.Map); } - - public override void OnAdded(IEntity parent) + else if (killer?.Deleted == false) { - base.OnAdded(parent); - - var mob = FindOwner(parent); - - if (mob != null) - { - mob.SolidHueOverride = 0x0499; - } + MoveToWorld(killer.Location, killer.Map); } - - public override void OnRemoved(IEntity parent) + else { - base.OnRemoved(parent); - - var mob = FindOwner(parent); - - if (mob != null && m_Game != null) - { - mob.SolidHueOverride = m_Game.GetColor(mob); - } + m_Game?.ReturnBomb(); } + } - public void DropTo(Mobile mob, Mobile killer) + public override bool OnMoveOver(Mobile m) + { + if (m_Flying || !Visible || m_Game == null || m?.Alive != true) { - 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) + 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) { - DoAnim(GetWorldLocation(), ballLoc, Map); - MoveToWorld(ballLoc); - - m_Path.Clear(); - m_PathIdx = 0; - - Timer.StartTimer(TimeSpan.FromSeconds(0.05), ContinueFlight); + return; } - private bool CheckCatch(Mobile m, Point3D myLoc) + foreach (var ns in GetClientsAt()) { - if (m?.Alive != true || !m.Player || m_Game == null) + var m = ns.Mobile; + + if (m?.Player != true || !m.Alive) { - 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; + continue; } var useTeam = m_Game.GetTeamInfo(m); - - if (useTeam == null) + 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); + TakeBomb(m, useTeam, "got"); } } + } - private void BeginFlight(Point3D dest) + public override void OnAfterDelete() + { + base.OnAfterDelete(); + + m_Timer?.Stop(); + } + + public override void OnDoubleClick(Mobile m) + { + if (m_Game == null || !Visible || m?.Alive != true) { - var org = GetWorldLocation(); + return; + } - 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 )) + 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) { - 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 useTeam = m_Game.GetTeamInfo(m); + if (useTeam == null) { - var p = list[^1]; - - if (p.X != ix || p.Y != iy || p.Z != iz) - { - list.Add(new Point3D(ix, iy, iz)); - } + return; } - else + + 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)); } - - x += run; - y += rise; - z += zslp; + } + else + { + list.Add(new Point3D(ix, iy, iz)); } - 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(); + x += run; + y += rise; + z += zslp; } - private void ContinueFlight() + if (list.Count > 0 && list[^1] != dest) { - int height; - var found = false; + list.Add(dest); + } - if (m_PathIdx < m_Path.Count && Map?.Tiles != null && Map != Map.Internal) + /*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 pathCheckEnd = m_PathIdx + 5; + var p = list[i]; - if (m_Path.Count < pathCheckEnd) + 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) { - pathCheckEnd = m_Path.Count; + HitObject(point, landTop, 0); + return; } - Visible = false; - - if (m_PathIdx > 0) // move to the next location + var foundStatics = false; + foreach (var t in Map.Tiles.GetStaticAndMultiTiles(point.X, point.Y)) { - MoveToWorld(m_Path[m_PathIdx - 1]); - } + foundStatics = true; - Point3D pTop = new(GetWorldLocation()), pBottom = new(m_Path[pathCheckEnd - 1]); - Utility.FixPoints(ref pTop, ref pBottom); + var id = TileData.ItemTable[t.ID & TileData.MaxItemValue]; + height = id.CalcHeight; - 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) + if (t.Z <= point.Z && t.Z + height >= point.Z && + (id.Flags & (TileFlag.Impassable | TileFlag.Wall | TileFlag.NoShoot)) != 0) { - HitObject(point, landTop, 0); + if (i > m_PathIdx) + { + point = m_Path[i - 1]; + } + else + { + point = GetWorldLocation(); + } + + HitObject(point, t.Z, height); 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 (landTile.ID == 0x244 && foundStatics) // 0x244 = invalid land tile { - if (i == this || i.ItemID >= 0x4000) + var empty = true; + foreach (var item in Map.GetItemsAt(point)) { - 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) + if (item != this) { - 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(); - } - + empty = false; break; } } - if (!found) + 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; } - if (i is BRGoal goal) + 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)) { - var oldLoc = new Point3D(GetWorldLocation()); - if (CheckScore(goal, Thrower, 3)) + found = true; + if (j > m_PathIdx) { - DoAnim(oldLoc, point, Map); + point = m_Path[j - 1]; } else { - HitObject(point, loc.Z, height); + 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); } - - return; } - - foreach (var ns in Map.GetClientsInBounds(rect)) + else { - 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; + HitObject(point, loc.Z, height); } - 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); + return; } - else + + foreach (var ns in Map.GetClientsInBounds(rect)) { - if (m_PathIdx > 0 && m_PathIdx - 1 < m_Path.Count) + var m = ns.Mobile; + + if (m == null || m == Thrower) { - MoveToWorld(m_Path[m_PathIdx - 1]); - } - else if (m_Path.Count > 0) - { - MoveToWorld(m_Path.Last); + continue; } - var myZ = Map?.GetAverageZ(X, Y) ?? 0; + Point3D point; + var loc = m.Location; - if (Map?.Tiles != null) + for (var j = m_PathIdx; j < pathCheckEnd && !found; j++) { - foreach (var t in Map.Tiles.GetStaticAndMultiTiles(X, Y)) + point = m_Path[j]; + + if (loc.X == point.X && loc.Y == point.Y && + loc.Z <= point.Z && loc.Z + 16 >= point.Z) { - var id = TileData.ItemTable[t.ID & TileData.MaxItemValue]; - height = id.CalcHeight; - - if (t.Z + height > myZ && t.Z + height <= Z) - { - myZ = t.Z + height; - } + found = CheckCatch(m, point); } } - foreach (var item in GetItemsAt()) + if (!found) { - if (item.Visible && item != this) - { - height = item.ItemData.CalcHeight; - if (item.Z + height > myZ && item.Z + height <= Z) - { - myZ = item.Z + height; - } - } + continue; } - Z = myZ; - m_Flying = false; - Visible = true; + // TODO: probably need to change this a lot... + DoCatch(m); - m_Path.Clear(); - m_PathIdx = 0; + 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; } } - public bool CheckScore(BRGoal goal, Mobile m, int points) + 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) { - if (m_Game == null || m == null || goal == 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); + + if (m_Helpers.Contains(m)) { - return false; + m_Helpers.Remove(m); } - var team = m_Game.GetTeamInfo(m); - if (team == null || goal.Team == null || team == goal.Team) + if (m_Helpers.Count > 0) { - return false; - } + var last = m_Helpers[0]; - 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 (m_Game.GetTeamInfo(last) != team) { - if (mob == m) - { - pi.Captures += points; - } - - pi.Score += points + 1; - - points /= 2; + m_Helpers.Clear(); } } - m_Game.ReturnBomb(); - - m_Flying = false; - Visible = true; - m_Path.Clear(); - m_PathIdx = 0; - - Target.Cancel(m); + m_Helpers.Add(m); return true; } - private bool TakeBomb(Mobile m, BRTeamInfo team, string verb) + 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)) { - 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); - - if (m_Helpers.Contains(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; + m_Bomb = bomb; + m_Count = 0; } - private class EffectTimer : Timer + protected override void OnTick() { - private readonly BRBomb m_Bomb; - private int m_Count; - - public EffectTimer(BRBomb bomb) : base(TimeSpan.Zero, TimeSpan.FromSeconds(1.0)) + if (m_Bomb.Parent == null && m_Bomb.m_Game?.Controller != null) { - 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) { - if (!m_Bomb.m_Flying && m_Bomb.Map != Map.Internal) - { - Effects.SendLocationEffect(m_Bomb.GetWorldLocation(), m_Bomb.Map, 0x377A, 16, 10, m_Bomb.Hue); - } + 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_Bomb.Location != m_Bomb.m_Game.Controller.BombHome) + { + if (++m_Count >= 30) { - 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_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; + m_Bomb.m_Helpers.Clear(); } } else @@ -798,223 +778,93 @@ namespace Server.Engines.ConPVP 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) + else { - 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); - } + m_Count = 0; } } } - public class BRGoal : BaseAddon + private class BombTarget : Target { - private bool m_North; + private readonly BRBomb m_Bomb; + private readonly Mobile m_Mob; + private bool m_Resend = true; - private BRTeamInfo m_Team; - - [Constructible] - public BRGoal() + public BombTarget(BRBomb bomb, Mobile from) : base(10, true, TargetFlags.None) { - ItemID = 0x51D; - Hue = 0x84C; - Visible = true; + CheckLOS = false; - Remake(); + m_Bomb = bomb; + m_Mob = from; + + m_Mob.SendMessage(0x26, "Where do you want to throw it?"); } - public BRGoal(Serial serial) : base(serial) + protected override void OnTarget(Mobile from, object targeted) { + m_Resend = !m_Bomb.OnBombTarget(from, targeted); } - [CommandProperty(AccessLevel.GameMaster)] - public bool North + protected override void OnTargetUntargetable(Mobile from, object targeted) { - get => m_North; - set + 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) { - m_North = value; - Remake(); + Timer.StartTimer(ResendBombTarget); } } - public override string DefaultName => "Bombing Run Goal"; - - public override bool ShareHue => false; - - public BRTeamInfo Team + private void ResendBombTarget() { - get => m_Team; - set + // 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_Team = value; - if (m_Team != null && m_Team.Color != 0) - { - Hue = m_Team.Color; - } - else - { - Hue = 0x84C; - } + m_Mob.Target = new BombTarget(m_Bomb, m_Mob); } } + } +} - private static AddonComponent SetHue(AddonComponent ac, int hue) +[SerializationGenerator(0, false)] +public partial class BRGoal : BaseAddon +{ + [SerializableField(0)] + private bool _north; + + [SerializableFieldChanged(0)] + [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 { - ac.Hue = hue; - return ac; - } - - private void Remake() - { - foreach (var ac in Components) - { - 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 (!m_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 void Serialize(IGenericWriter writer) - { - base.Serialize(writer); - - writer.Write(1); // version - - writer.Write(m_North); - } - - public override void Deserialize(IGenericReader reader) - { - base.Deserialize(reader); - - var version = reader.ReadInt(); - - switch (version) - { - case 1: - { - m_North = reader.ReadBool(); - goto case 0; - } - case 0: - { - break; - } - } - - Hue = 0x84C; - } - - 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; - } - + m_Team = value; if (m_Team != null && m_Team.Color != 0) { Hue = m_Team.Color; @@ -1023,942 +873,1017 @@ namespace Server.Engines.ConPVP { Hue = 0x84C; } + } + } - m.Backpack.FindItemByType()?.CheckScore(this, m, 7); + 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; } +} - public sealed class BRBoard : Item +[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) { - public BRTeamInfo m_TeamInfo; - - [Constructible] - public BRBoard() - : base(7774) => - Movable = false; - - public BRBoard(Serial serial) - : base(serial) + if (m_TeamInfo?.Game != null) { - } - - public override string DefaultName => "Scoreboard"; - - public override void OnDoubleClick(Mobile from) - { - if (m_TeamInfo?.Game != null) - { - from.SendGump(new BRBoardGump(from, m_TeamInfo.Game)); - } - } - - public override void Serialize(IGenericWriter writer) - { - base.Serialize(writer); - - writer.Write(0); - } - - public override void Deserialize(IGenericReader reader) - { - base.Deserialize(reader); - - var version = reader.ReadInt(); + from.SendGump(new BRBoardGump(from, m_TeamInfo.Game)); } } +} - public class BRBoardGump : Gump +public class BRBoardGump : Gump +{ + private const int LabelColor32 = 0xFFFFFF; + private const int BlackColor32 = 0x000000; + + public override bool Singleton => true; + + public BRBoardGump(Mobile mob, BRGame game, BRTeamInfo section = null) : base(60, 60) { - private const int LabelColor32 = 0xFFFFFF; - private const int BlackColor32 = 0x000000; + // m_Game = game; - public override bool Singleton => true; + var ourTeam = game.GetTeamInfo(mob); - public BRBoardGump(Mobile mob, BRGame game, BRTeamInfo section = null) : base(60, 60) + var entries = new List(); + var total = 0; + + if (section == null) { - // m_Game = game; - - 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) { - for (var i = 0; i < game.Context.Participants.Count; ++i) + var teamInfo = game.Controller.TeamInfo[i % game.Controller.TeamInfo.Length]; + + if (teamInfo == null) { - var teamInfo = game.Controller.TeamInfo[i % game.Controller.TeamInfo.Length]; - - if (teamInfo == null) - { - continue; - } - - entries.Add(teamInfo); + continue; } - total = entries.Count; + entries.Add(teamInfo); } - else + + total = entries.Count; + } + else + { + foreach (var player in section.Players.Values) { - foreach (var player in section.Players.Values) + if (player.Score > 0) { - if (player.Score > 0) - { - total++; - } + total++; } } + } - entries.Sort(); + entries.Sort(); - var height = 0; + var height = 0; - if (section == null) + if (section == null) + { + height = 73 + entries.Count * 75 + 28; + } + + Closable = false; + + AddPage(0); + + AddBackground(1, 1, 398, height, 3600); + + AddImageTiled(16, 15, 369, height - 29, 3604); + + for (var i = 0; i < total; i += 1) + { + AddImageTiled(22, 58 + i * 75, 357, 70, 0x2430); + } + + AddAlphaRegion(16, 15, 369, height - 29); + + AddImage(215, -45, 0xEE40); + // AddImage( 330, 141, 0x8BA ); + + AddBorderedText(22, 22, 294, 20, "BR Scoreboard".Center(), LabelColor32, BlackColor32); + + AddImageTiled(32, 50, 264, 1, 9107); + AddImageTiled(42, 52, 264, 1, 9157); + + if (section == null) + { + for (var i = 0; i < entries.Count; ++i) { - height = 73 + entries.Count * 75 + 28; - } + var teamInfo = entries[i]; - Closable = false; + AddImage(30, 70 + i * 75, 10152); + AddImage(30, 85 + i * 75, 10151); + AddImage(30, 100 + i * 75, 10151); + AddImage(30, 106 + i * 75, 10154); - AddPage(0); + AddImage(24, 60 + i * 75, teamInfo == ourTeam ? 9730 : 9727, teamInfo.Color - 1); - AddBackground(1, 1, 398, height, 3600); - - AddImageTiled(16, 15, 369, height - 29, 3604); - - for (var i = 0; i < total; i += 1) - { - AddImageTiled(22, 58 + i * 75, 357, 70, 0x2430); - } - - AddAlphaRegion(16, 15, 369, height - 29); - - AddImage(215, -45, 0xEE40); - // AddImage( 330, 141, 0x8BA ); - - AddBorderedText(22, 22, 294, 20, "BR Scoreboard".Center(), LabelColor32, BlackColor32); - - AddImageTiled(32, 50, 264, 1, 9107); - AddImageTiled(42, 52, 264, 1, 9157); - - if (section == null) - { - for (var i = 0; i < entries.Count; ++i) + var borderColor = teamInfo.Color == 0x455 ? LabelColor32 : BlackColor32; + var nameColor = teamInfo.Color switch { - var teamInfo = entries[i]; + 0x47E => 0xFFFFFF, + 0x4F2 => 0x3399FF, + 0x4F7 => 0x33FF33, + 0x4FC => 0xFF00FF, + 0x021 => 0xFF3333, + 0x01A => 0xFF66FF, + 0x455 => 0x333333, + _ => LabelColor32 + }; - AddImage(30, 70 + i * 75, 10152); - AddImage(30, 85 + i * 75, 10151); - AddImage(30, 100 + i * 75, 10151); - AddImage(30, 106 + i * 75, 10154); + AddBorderedText( + 60, + 65 + i * 75, + 250, + 20, + $"{LadderGump.Rank(1 + i)}: {teamInfo.Name}", + nameColor, + borderColor + ); - AddImage(24, 60 + i * 75, teamInfo == ourTeam ? 9730 : 9727, teamInfo.Color - 1); + AddBorderedText(50 + 10, 85 + i * 75, 100, 20, "Score:", 0xFFC000, BlackColor32); + AddBorderedText(50 + 15, 105 + i * 75, 100, 20, teamInfo.Score.ToString("N0"), 0xFFC000, BlackColor32); - 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(110 + 10, 85 + i * 75, 100, 20, "Kills:", 0xFFC000, BlackColor32); + AddBorderedText(110 + 15, 105 + i * 75, 100, 20, teamInfo.Kills.ToString("N0"), 0xFFC000, BlackColor32); - AddBorderedText( - 60, - 65 + i * 75, - 250, - 20, - $"{LadderGump.Rank(1 + i)}: {teamInfo.Name}", - nameColor, - borderColor - ); + AddBorderedText(160 + 10, 85 + i * 75, 100, 20, "Points:", 0xFFC000, BlackColor32); + AddBorderedText( + 160 + 15, + 105 + i * 75, + 100, + 20, + teamInfo.Captures.ToString("N0"), + 0xFFC000, + BlackColor32 + ); - AddBorderedText(50 + 10, 85 + i * 75, 100, 20, "Score:", 0xFFC000, BlackColor32); - AddBorderedText(50 + 15, 105 + i * 75, 100, 20, teamInfo.Score.ToString("N0"), 0xFFC000, BlackColor32); + var pl = teamInfo.Leader; - AddBorderedText(110 + 10, 85 + i * 75, 100, 20, "Kills:", 0xFFC000, BlackColor32); - AddBorderedText(110 + 15, 105 + i * 75, 100, 20, teamInfo.Kills.ToString("N0"), 0xFFC000, BlackColor32); + AddBorderedText(235 + 10, 85 + i * 75, 250, 20, "Leader:", 0xFFC000, BlackColor32); - AddBorderedText(160 + 10, 85 + i * 75, 100, 20, "Points:", 0xFFC000, BlackColor32); - AddBorderedText( - 160 + 15, - 105 + i * 75, - 100, - 20, - teamInfo.Captures.ToString("N0"), - 0xFFC000, - BlackColor32 - ); - - var pl = teamInfo.Leader; - - AddBorderedText(235 + 10, 85 + i * 75, 250, 20, "Leader:", 0xFFC000, BlackColor32); - - if (pl != null) - { - AddBorderedText(235 + 15, 105 + i * 75, 250, 20, pl.Player.Name, 0xFFC000, BlackColor32); - } + if (pl != null) + { + AddBorderedText(235 + 15, 105 + i * 75, 250, 20, pl.Player.Name, 0xFFC000, BlackColor32); } } - - AddButton(314, height - 42, 247, 248, 1); } - 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) - { - AddHtml(x, y, width, height, color == 0 ? text : text.Color(color)); - } + AddButton(314, height - 42, 247, 248, 1); } - public sealed class BRPlayerInfo : IRankedCTF, IComparable + private void AddBorderedText(int x, int y, int width, int height, string text, int color, int borderColor) { - private readonly BRTeamInfo m_TeamInfo; - private int m_Captures; + 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 int m_Kills; + private void AddColoredText(int x, int y, int width, int height, string text, int color) + { + AddHtml(x, y, width, height, color == 0 ? text : text.Color(color)); + } +} - private int m_Score; +public sealed class BRPlayerInfo : IRankedCTF, IComparable +{ + private readonly BRTeamInfo m_TeamInfo; + private int m_Captures; - public BRPlayerInfo(BRTeamInfo teamInfo, Mobile player) + 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) { - 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; + res = pi.Score.CompareTo(Score); - public int Kills + if (res == 0) { - get => m_Kills; - set - { - m_TeamInfo.Kills += value - m_Kills; - m_Kills = value; - } + res = pi.Kills.CompareTo(Kills); } - public int Captures - { - get => m_Captures; - set - { - m_TeamInfo.Captures += value - m_Captures; - m_Captures = value; - } - } + return res; + } - public int Score - { - get => m_Score; - set - { - m_TeamInfo.Score += value - m_Score; - m_Score = value; + public string Name => Player.Name; - if (m_TeamInfo.Leader == null || m_Score > m_TeamInfo.Leader.Score) - { - m_TeamInfo.Leader = this; - } - } + public int Kills + { + get => m_Kills; + set + { + m_TeamInfo.Kills += value - m_Kills; + m_Kills = value; } } - [PropertyObject] - public sealed class BRTeamInfo : IRankedCTF, IComparable + public int Captures { - private BRGoal m_Goal; - - public BRTeamInfo(int teamID) + get => m_Captures; + set { - TeamID = teamID; - Players = new Dictionary(); + m_TeamInfo.Captures += value - m_Captures; + m_Captures = value; } + } - public BRTeamInfo(int teamID, IGenericReader ip) + public int Score + { + get => m_Score; + set { - TeamID = teamID; - Players = new Dictionary(); + m_TeamInfo.Score += value - m_Score; + m_Score = value; - var version = ip.ReadEncodedInt(); - - switch (version) + if (m_TeamInfo.Leader == null || m_Score > m_TeamInfo.Leader.Score) { - case 0: - { - Board = ip.ReadEntity(); - TeamName = ip.ReadString(); - Color = ip.ReadEncodedInt(); - m_Goal = ip.ReadEntity(); - break; - } + m_TeamInfo.Leader = this; } } + } +} - public BRGame Game { get; set; } +[PropertyObject] +public sealed class BRTeamInfo : IRankedCTF, IComparable +{ + private BRGoal m_Goal; - public int TeamID { get; } + public BRTeamInfo(int teamID) + { + TeamID = teamID; + Players = new Dictionary(); + } - public BRPlayerInfo Leader { get; set; } + public BRTeamInfo(int teamID, IGenericReader ip) + { + TeamID = teamID; + Players = new Dictionary(); - [CommandProperty(AccessLevel.GameMaster)] - public BRBoard Board { get; set; } + var version = ip.ReadEncodedInt(); - public Dictionary Players { get; } - - public BRPlayerInfo this[Mobile mob] + switch (version) { - get - { - if (mob == null) + case 0: { - return null; + Board = ip.ReadEntity(); + TeamName = ip.ReadString(); + Color = ip.ReadEncodedInt(); + m_Goal = ip.ReadEntity(); + break; } - - if (!Players.TryGetValue(mob, out var val)) - { - Players[mob] = val = new BRPlayerInfo(this, mob); - } - - return val; - } } + } - [CommandProperty(AccessLevel.GameMaster)] - public int Color { get; set; } + public BRGame Game { get; set; } - [CommandProperty(AccessLevel.GameMaster)] - public string TeamName { get; set; } + public int TeamID { get; } - [CommandProperty(AccessLevel.GameMaster)] - public BRGoal Goal + public BRPlayerInfo Leader { get; set; } + + [CommandProperty(AccessLevel.GameMaster)] + public BRBoard Board { get; set; } + + public Dictionary Players { get; } + + public BRPlayerInfo this[Mobile mob] + { + get { - get => m_Goal; - set + if (mob == null) { - m_Goal = value; - if (m_Goal != null) - { - m_Goal.Team = this; - } + return null; } + + if (!Players.TryGetValue(mob, out var val)) + { + Players[mob] = val = new BRPlayerInfo(this, mob); + } + + return val; } + } - public int CompareTo(BRTeamInfo ti) + [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 { - 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(); - - if (Board != null) - { - Board.m_TeamInfo = this; - } - + m_Goal = value; if (m_Goal != null) { m_Goal.Team = this; } } + } - public void Serialize(IGenericWriter op) + public int CompareTo(BRTeamInfo ti) + { + var res = ti.Captures.CompareTo(Captures); + if (res == 0) { - op.WriteEncodedInt(0); // version + res = ti.Score.CompareTo(Score); - op.Write(Board); - - op.Write(TeamName); - - op.WriteEncodedInt(Color); - - op.Write(m_Goal); + if (res == 0) + { + res = ti.Kills.CompareTo(Kills); + } } - public override string ToString() - { - if (TeamName != null) - { - return $"({Name}) ..."; - } + return res; + } - return "..."; + 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(); + + if (Board != null) + { + Board.m_TeamInfo = this; + } + + if (m_Goal != null) + { + m_Goal.Team = this; } } - public sealed class BRController : EventController + public void Serialize(IGenericWriter op) { - [Constructible] - public BRController() + op.WriteEncodedInt(0); // version + + op.Write(Board); + + op.Write(TeamName); + + op.WriteEncodedInt(Color); + + op.Write(m_Goal); + } + + public override string ToString() + { + if (TeamName != null) { - 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); - } + return $"({Name}) ..."; } - public BRController(Serial serial) - : base(serial) + return "..."; + } +} + +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 BRTeamInfo[] TeamInfo { get; private set; } + public BRController(Serial serial) + : base(serial) + { + } - [CommandProperty(AccessLevel.GameMaster)] - public BRTeamInfo Team1 => TeamInfo[0]; + public BRTeamInfo[] TeamInfo { get; private set; } - [CommandProperty(AccessLevel.GameMaster)] - public BRTeamInfo Team2 => TeamInfo[1]; + [CommandProperty(AccessLevel.GameMaster)] + public BRTeamInfo Team1 => TeamInfo[0]; - [CommandProperty(AccessLevel.GameMaster)] - public BRTeamInfo Team3 => TeamInfo[2]; + [CommandProperty(AccessLevel.GameMaster)] + public BRTeamInfo Team2 => TeamInfo[1]; - [CommandProperty(AccessLevel.GameMaster)] - public BRTeamInfo Team4 => TeamInfo[3]; + [CommandProperty(AccessLevel.GameMaster)] + public BRTeamInfo Team3 => TeamInfo[2]; - [CommandProperty(AccessLevel.GameMaster)] - public TimeSpan Duration { get; set; } + [CommandProperty(AccessLevel.GameMaster)] + public BRTeamInfo Team4 => TeamInfo[3]; - [CommandProperty(AccessLevel.GameMaster)] - public Point3D BombHome { get; set; } + [CommandProperty(AccessLevel.GameMaster)] + public TimeSpan Duration { get; set; } - public override string Title => "Bombing Run"; - public override string DefaultName => "Bombing Run Controller"; + [CommandProperty(AccessLevel.GameMaster)] + public Point3D BombHome { get; set; } - public override string GetTeamName(int teamID) => TeamInfo[teamID % TeamInfo.Length].Name; + public override string Title => "Bombing Run"; + public override string DefaultName => "Bombing Run Controller"; - public override EventGame Construct(DuelContext context) => new BRGame(this, context); + public override string GetTeamName(int teamID) => TeamInfo[teamID % TeamInfo.Length].Name; - public override void Serialize(IGenericWriter writer) + 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) { - 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); - } + TeamInfo[i].Serialize(writer); } + } - public override void Deserialize(IGenericReader reader) + public override void Deserialize(IGenericReader reader) + { + base.Deserialize(reader); + + var version = reader.ReadInt(); + + switch (version) { - base.Deserialize(reader); + case 0: + { + BombHome = reader.ReadPoint3D(); - var version = reader.ReadInt(); + Duration = reader.ReadTimeSpan(); - switch (version) - { - case 0: + TeamInfo = new BRTeamInfo[reader.ReadEncodedInt()]; + + for (var i = 0; i < TeamInfo.Length; ++i) { - 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; + 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 sealed class BRGame : EventGame + public BRTeamInfo GetTeamInfo(Mobile mob) { - private BRBomb m_Bomb; + var teamID = GetTeamID(mob); - 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 (teamID >= 0) { - 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); - } + return Controller.TeamInfo[teamID % Controller.TeamInfo.Length]; } - private void UnhideBomb() + return null; + } + + public int GetTeamID(Mobile mob) + { + if (mob is not PlayerMobile pm) { - if (m_Bomb != null) - { - m_Bomb.Visible = true; - m_Bomb.OnLocationChange(m_Bomb.Location); - } + return mob is BaseCreature creature ? creature.Team - 1 : -1; } - public void Alert(string text) + if (pm.DuelContext == null || pm.DuelContext != m_Context) { - 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); - } - } - } + return -1; } - public BRTeamInfo GetTeamInfo(Mobile mob) + if (pm.DuelPlayer?.Eliminated != false) { - var teamID = GetTeamID(mob); - - if (teamID >= 0) - { - return Controller.TeamInfo[teamID % Controller.TeamInfo.Length]; - } - - return null; + return -1; } - public int GetTeamID(Mobile mob) + 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 (mob is not PlayerMobile pm) + if (p.Players[i] != null) { - 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; - } + p.Players[i].Mobile.SolidHueOverride = hueOverride; } } + } - public void DelayBounce(TimeSpan ts, Mobile mob, Container corpse) + 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) { - Timer.StartTimer(ts, () => DelayBounce_Callback(mob, corpse)); + mob.MoveToWorld(m_Context.Arena.GetBaseStartPoint(GetTeamID(mob)), Facet); + } + else + { + m_Context.SendOutside(mob); } - private void DelayBounce_Callback(Mobile mob, Container corpse) + 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; + + foreach (var bomb in corpse.EnumerateItemsByType(false)) { - 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; + hadBomb = true; + bomb.DropTo(mob, killer); } - public override bool OnDeath(Mobile mob, Container corpse) + if (mob.Backpack != null) { - var killer = mob.FindMostRecentDamager(false); - - var hadBomb = false; - - foreach (var bomb in corpse.EnumerateItemsByType(false)) + foreach (var bomb in mob.Backpack.EnumerateItemsByType(false)) { hadBomb = true; bomb.DropTo(mob, killer); } - - if (mob.Backpack != null) - { - foreach (var bomb in mob.Backpack.EnumerateItemsByType(false)) - { - 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 - } - } - } - } - - mob.SendGump(new BRBoardGump(mob, this)); - - m_Context.Requip(mob, corpse); - DelayBounce(TimeSpan.FromSeconds(30.0), mob, corpse); - - return false; } - public override void OnStart() + if (killer?.Player == true) { - for (var i = 0; i < Controller.TeamInfo.Length; ++i) + var teamInfo = GetTeamInfo(killer); + var victInfo = GetTeamInfo(mob); + + if (teamInfo != null && teamInfo != victInfo) { - var teamInfo = Controller.TeamInfo[i]; + var playerInfo = teamInfo[killer]; - teamInfo.Game = this; - teamInfo.Reset(); + if (playerInfo != null) + { + playerInfo.Kills += 1; + playerInfo.Score += 1; // base frag + + if (hadBomb) + { + playerInfo.Score += 4; // fragged bomb carrier + } + } } - - 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() + mob.SendGump(new BRBoardGump(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 teams = new List(); + var teamInfo = Controller.TeamInfo[i]; - for (var i = 0; i < m_Context.Participants.Count; ++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) { - var teamInfo = Controller.TeamInfo[i % Controller.TeamInfo.Length]; + teams.Add(teamInfo); + } + } - if (teamInfo != null) + teams.Sort(); + + var tourney = m_Context.m_Tournament; + + var sb = new StringBuilder(); + + 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) { - teams.Add(teamInfo); + if (sb.Length > 0) + { + sb.Append('v'); + } + + sb.Append(tourney.PlayersPerParticipant); } } + } - teams.Sort(); + if (Controller != null) + { + sb.Append(' ').Append(Controller.Title); + } - var tourney = m_Context.m_Tournament; + var title = sb.ToString(); - var sb = new StringBuilder(); + var winner = teams.Count > 0 ? teams[0] : null; - if (tourney != null) + for (var i = 0; i < teams.Count; ++i) + { + var rank = i switch { - 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'); - } + 0 => TrophyRank.Gold, + 1 => TrophyRank.Silver, + _ => TrophyRank.Bronze + }; - sb.Append(tourney.PlayersPerParticipant); - } - } - } + var leader = teams[i].Leader; - if (Controller != null) + foreach (var pl in teams[i].Players.Values) { - sb.Append(' ').Append(Controller.Title); - } + var mob = pl.Player; - var title = sb.ToString(); - - var winner = teams.Count > 0 ? teams[0] : null; - - for (var i = 0; i < teams.Count; ++i) - { - var rank = i switch + if (mob == null) { - 0 => TrophyRank.Gold, - 1 => TrophyRank.Silver, - _ => TrophyRank.Bronze - }; + continue; + } - var leader = teams[i].Leader; + sb = new StringBuilder(); - foreach (var pl in teams[i].Players.Values) + sb.Append(title); + + if (pl == leader) { - var mob = pl.Player; + sb.Append(" Leader"); + } - if (mob == null) + 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) { - continue; + sb.Append(", "); + sb.Append(pl.Captures.ToString("N0")); + sb.Append(pl.Captures == 1 ? " point" : " points"); } + } - sb = new StringBuilder(); + Item item = new Trophy(sb.ToString(), rank); - sb.Append(title); + if (pl == leader) + { + item.ItemID = 4810; + } - if (pl == leader) - { - sb.Append(" Leader"); - } + item.Name = $"{item.Name}, {teams[i].Name.ToLower()} team"; - if (pl.Score > 0) - { - sb.Append(": "); + if (!mob.PlaceInBackpack(item)) + { + mob.BankBox.DropItem(item); + } - // sb.Append( pl.Score.ToString( "N0" ) ); - // sb.Append( pl.Score == 1 ? " point" : " points" ); + var cash = pl.Score * 250; - 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 (cash > 0) + { + item = new BankCheck(cash); if (!mob.PlaceInBackpack(item)) { mob.BankBox.DropItem(item); } - var cash = pl.Score * 250; + 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.ToString().ToLower()} trophy and {cash:N0}gp for your participation in this tournament." + ); + } + else + { + mob.SendMessage( + $"You have been awarded a {rank.ToString().ToLower()} trophy for your participation in this tournament." + ); + } + } + } - if (cash > 0) - { - item = new BankCheck(cash); + for (var i = 0; i < m_Context.Participants.Count; ++i) + { + var p = m_Context.Participants[i]; - if (!mob.PlaceInBackpack(item)) - { - mob.BankBox.DropItem(item); - } + if (p?.Players == null) + { + continue; + } - 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.ToString().ToLower()} trophy and {cash:N0}gp for your participation in this tournament." - ); - } - else - { - mob.SendMessage( - $"You have been awarded a {rank.ToString().ToLower()} trophy for your participation in this tournament." - ); - } + for (var j = 0; j < p.Players.Length; ++j) + { + var dp = p.Players[j]; + + if (dp?.Mobile != null) + { + dp.Mobile.SendGump(new BRBoardGump(dp.Mobile, this)); } } - for (var i = 0; i < m_Context.Participants.Count; ++i) + if (i == winner?.TeamID) { - var p = m_Context.Participants[i]; - - if (p?.Players == null) - { - continue; - } + continue; + } + if (p.Players != null) + { for (var j = 0; j < p.Players.Length; ++j) { - var dp = p.Players[j]; - - if (dp?.Mobile != null) + if (p.Players[j] != null) { - dp.Mobile.SendGump(new BRBoardGump(dp.Mobile, this)); + p.Players[j].Eliminated = true; } } - - if (i == winner?.TeamID) - { - continue; - } - - if (p.Players != null) - { - for (var j = 0; j < p.Players.Length; ++j) - { - if (p.Players[j] != null) - { - p.Players[j].Eliminated = true; - } - } - } - } - - if (winner != null) - { - m_Context.Finish(m_Context.Participants[winner.TeamID]); } } - public override void OnStop() + if (winner != null) { - for (var i = 0; i < Controller.TeamInfo.Length; ++i) - { - var teamInfo = Controller.TeamInfo[i]; - - if (teamInfo.Board != null) - { - 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(); + 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]; + + if (teamInfo.Board != null) + { + 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(); + } } diff --git a/Projects/UOContent/Engines/ConPVP/Games/CTF.cs b/Projects/UOContent/Engines/ConPVP/Games/CTF.cs index 033475c38..d4b4f0eef 100644 --- a/Projects/UOContent/Engines/ConPVP/Games/CTF.cs +++ b/Projects/UOContent/Engines/ConPVP/Games/CTF.cs @@ -1,1313 +1,1273 @@ using System; using System.Collections.Generic; using System.Text; +using ModernUO.Serialization; using Server.Gumps; using Server.Items; using Server.Mobiles; using Server.Network; using Server.Targeting; -namespace Server.Engines.ConPVP +namespace Server.Engines.ConPVP; + +[SerializationGenerator(0, false)] +public sealed partial class CTFBoard : Item { - public sealed class CTFBoard : Item + public CTFTeamInfo m_TeamInfo; + + [Constructible] + public CTFBoard() : base(7774) => Movable = false; + + public override string DefaultName => "scoreboard"; + + public override void OnDoubleClick(Mobile from) { - public CTFTeamInfo m_TeamInfo; - - [Constructible] - public CTFBoard() - : base(7774) => - Movable = false; - - public CTFBoard(Serial serial) - : base(serial) + if (m_TeamInfo?.Game != null) { - } - - public override string DefaultName => "scoreboard"; - - public override void OnDoubleClick(Mobile from) - { - if (m_TeamInfo?.Game != null) - { - from.SendGump(new CTFBoardGump(from, m_TeamInfo.Game)); - } - } - - public override void Serialize(IGenericWriter writer) - { - base.Serialize(writer); - - writer.Write(0); - } - - public override void Deserialize(IGenericReader reader) - { - base.Deserialize(reader); - - var version = reader.ReadInt(); + from.SendGump(new CTFBoardGump(from, m_TeamInfo.Game)); } } +} - public class CTFBoardGump : Gump +public class CTFBoardGump : Gump +{ + private const int LabelColor32 = 0xFFFFFF; + private const int BlackColor32 = 0x000000; + + private CTFGame m_Game; + + public override bool Singleton => true; + + public CTFBoardGump(Mobile mob, CTFGame game, CTFTeamInfo section = null) + : base(60, 60) { - private const int LabelColor32 = 0xFFFFFF; - private const int BlackColor32 = 0x000000; + m_Game = game; - private CTFGame m_Game; + var ourTeam = game.GetTeamInfo(mob); - public override bool Singleton => true; + var entries = new List(); - public CTFBoardGump(Mobile mob, CTFGame game, CTFTeamInfo section = null) - : base(60, 60) + if (section == null) { - m_Game = game; - - var ourTeam = game.GetTeamInfo(mob); - - var entries = new List(); - - if (section == null) + for (var i = 0; i < game.Context.Participants.Count; ++i) { - for (var i = 0; i < game.Context.Participants.Count; ++i) - { - var teamInfo = game.Controller.TeamInfo[i % 8]; - - if (teamInfo?.Flag == null) - { - continue; - } - - entries.Add(teamInfo); - } - } - else - { - foreach (var player in section.Players.Values) - { - if (player.Score > 0) - { - entries.Add(player); - } - } - } - - entries.Sort((a, b) => b.Score - a.Score); - - var height = 0; - - if (section == null) - { - height = 73 + entries.Count * 75 + 28; - } - - Closable = false; - - AddPage(0); - - AddBackground(1, 1, 398, height, 3600); - - AddImageTiled(16, 15, 369, height - 29, 3604); - - for (var i = 0; i < entries.Count; i += 1) - { - AddImageTiled(22, 58 + i * 75, 357, 70, 0x2430); - } - - AddAlphaRegion(16, 15, 369, height - 29); - - AddImage(215, -45, 0xEE40); - // AddImage( 330, 141, 0x8BA ); - - AddBorderedText(22, 22, 294, 20, "CTF Scoreboard".Center(), LabelColor32, BlackColor32); - - AddImageTiled(32, 50, 264, 1, 9107); - AddImageTiled(42, 52, 264, 1, 9157); - - if (section == null) - { - for (var i = 0; i < entries.Count; ++i) - { - var teamInfo = entries[i] as CTFTeamInfo; - - if (teamInfo == null) - { - continue; - } - - AddImage(30, 70 + i * 75, 10152); - AddImage(30, 85 + i * 75, 10151); - AddImage(30, 100 + i * 75, 10151); - AddImage(30, 106 + i * 75, 10154); - - 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( - 60, - 65 + i * 75, - 250, - 20, - $"{LadderGump.Rank(1 + i)}: {teamInfo.Name} Team", - nameColor, - borderColor - ); - - AddBorderedText(50 + 10, 85 + i * 75, 100, 20, "Score:", 0xFFC000, BlackColor32); - AddBorderedText(50 + 15, 105 + i * 75, 100, 20, teamInfo.Score.ToString("N0"), 0xFFC000, BlackColor32); - - AddBorderedText(110 + 10, 85 + i * 75, 100, 20, "Kills:", 0xFFC000, BlackColor32); - AddBorderedText(110 + 15, 105 + i * 75, 100, 20, teamInfo.Kills.ToString("N0"), 0xFFC000, BlackColor32); - - AddBorderedText(160 + 10, 85 + i * 75, 100, 20, "Captures:", 0xFFC000, BlackColor32); - AddBorderedText( - 160 + 15, - 105 + i * 75, - 100, - 20, - teamInfo.Captures.ToString("N0"), - 0xFFC000, - BlackColor32 - ); - - var pl = teamInfo.Leader; - - AddBorderedText(235 + 10, 85 + i * 75, 250, 20, "Leader:", 0xFFC000, BlackColor32); - - if (pl != null) - { - AddBorderedText(235 + 15, 105 + i * 75, 250, 20, pl.Player.Name, 0xFFC000, BlackColor32); - } - } - } - - AddButton(314, height - 42, 247, 248, 1); - } - - 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) - { - AddHtml(x, y, width, height, color == 0 ? text : text.Color(color)); - } - } - - public sealed class CTFFlag : Item - { - public Mobile m_Fragger; - public DateTime m_FragTime; - - private int m_ReturnCount; - - public Mobile m_Returner; - public DateTime m_ReturnTime; - private TimerExecutionToken _returnTimerToken; - public CTFTeamInfo m_TeamInfo; - - [Constructible] - public CTFFlag() - : base(5643) => - Movable = false; - - public CTFFlag(Serial serial) - : base(serial) - { - } - - public override string DefaultName => "old people cookies"; - - public override void OnDoubleClick(Mobile from) - { - if (m_TeamInfo?.Game != null) - { - var ourTeam = m_TeamInfo; - var useTeam = m_TeamInfo.Game.GetTeamInfo(from); - - if (ourTeam == null || useTeam == null) - { - return; - } - - if (IsChildOf(from.Backpack)) - { - from.BeginTarget(1, false, TargetFlags.None, Flag_OnTarget); - } - else if (!from.InRange(this, 1) || !from.InLOS(this)) - { - from.LocalOverheadMessage(MessageType.Regular, 0x26, 1019045); // I can't reach that - } - else if (ourTeam == useTeam) - { - if (Location == m_TeamInfo.Origin && Map == m_TeamInfo.Game.Facet) - { - from.NetState.SendMessage( - Serial, - ItemID, - MessageType.Regular, - 0x3B2, - 3, - false, - "ENU", - Name, - "Touch me not for I am chaste." - ); - } - else - { - var playerInfo = useTeam[from]; - - if (playerInfo != null) - { - playerInfo.Score += 4; // return - } - - m_Returner = from; - m_ReturnTime = Core.Now; - - SendHome(); - - from.LocalOverheadMessage(MessageType.Regular, 0x59, false, "You returned the cookies!"); - m_TeamInfo.Game.Alert($"The {from.Name} cookies have been returned by {ourTeam.Name}."); - } - } - else if (!from.PlaceInBackpack(this)) - { - from.LocalOverheadMessage(MessageType.Regular, 0x26, false, "I can't hold that."); - } - else - { - from.RevealingAction(); - - from.LocalOverheadMessage(MessageType.Regular, 0x59, false, "You stole the cookies!"); - m_TeamInfo.Game.Alert($"The {ourTeam.Name} cookies have been stolen by {from.Name} ({useTeam.Name})."); - - BeginCountdown(120); - } - } - } - - public override void Delete() - { - if (Parent != null) - { - SendHome(); - return; - } - - base.Delete(); - } - - public void DropTo(Mobile mob, Mobile killer) - { - m_Fragger = killer; - m_FragTime = Core.Now; - - if (mob != null) - { - MoveToWorld(new Point3D(mob.X, mob.Y, mob.Z + 2), mob.Map); - - m_ReturnCount = Math.Min(m_ReturnCount, 10); - } - else - { - SendHome(); - } - } - - private void BeginCountdown(int returnCount) - { - _returnTimerToken.Cancel(); - - Timer.StartTimer(TimeSpan.FromSeconds(1.0), TimeSpan.FromSeconds(1.0), Countdown_OnTick, out _returnTimerToken); - m_ReturnCount = returnCount; - } - - private void Countdown_OnTick() - { - var owner = RootParent as Mobile; - - switch (m_ReturnCount) - { - case 60: - case 30: - case 15: - case 10: - case 5: - case 4: - case 3: - case 2: - case 1: - { - if (m_ReturnCount == 1) - { - owner?.SendMessage(0x26, $"You have {m_ReturnCount} second to capture the cookies!"); - } - else - { - owner?.SendMessage(0x26, $"You have {m_ReturnCount} seconds to capture the cookies!"); - } - - break; - } - - case 0: - { - if (owner != null) - { - owner.SendMessage(0x26, "You have taken too long to capture the cookies!"); - owner.Kill(); - } - - SendHome(); - - m_TeamInfo?.Game?.Alert($"The {m_TeamInfo.Name} cookies have been returned."); - - return; - } - } - - --m_ReturnCount; - } - - private void Flag_OnTarget(Mobile from, object obj) - { - if (m_TeamInfo == null) - { - return; - } - - if (!IsChildOf(from.Backpack)) - { - return; - } - - var ourTeam = m_TeamInfo; - var useTeam = m_TeamInfo.Game.GetTeamInfo(from); - - if (obj is CTFFlag) - { - if (obj == useTeam.Flag) - { - from.LocalOverheadMessage(MessageType.Regular, 0x59, false, "You captured the cookies!"); - m_TeamInfo.Game.Alert($"{from.Name} captured the {ourTeam.Name} cookies!"); - - SendHome(); - - var playerInfo = useTeam[from]; - - if (playerInfo != null) - { - playerInfo.Captures += 1; - playerInfo.Score += 50; // capture - - var teamFlag = useTeam.Flag; - - if (teamFlag.m_Fragger != null && - Core.Now < teamFlag.m_FragTime + TimeSpan.FromSeconds(5.0) && - m_TeamInfo.Game.GetTeamInfo(teamFlag.m_Fragger) == useTeam) - { - var assistInfo = useTeam[teamFlag.m_Fragger]; - - if (assistInfo != null) - { - assistInfo.Score += 6; // frag assist - } - } - - if (teamFlag.m_Returner != null && - Core.Now < teamFlag.m_ReturnTime + TimeSpan.FromSeconds(5.0)) - { - var assistInfo = useTeam[teamFlag.m_Returner]; - - if (assistInfo != null) - { - assistInfo.Score += 4; // return assist - } - } - } - } - else - { - from.LocalOverheadMessage(MessageType.Regular, 0x26, false, "Those are not my cookies."); - } - } - else if (obj is Mobile passTo) - { - var passTeam = m_TeamInfo.Game.GetTeamInfo(passTo); - - if (passTo == from) - { - from.LocalOverheadMessage(MessageType.Regular, 0x26, false, "I can't pass to them."); - } - else if (passTeam == useTeam && passTo.PlaceInBackpack(this)) - { - passTo.LocalOverheadMessage( - MessageType.Regular, - 0x59, - false, - $"{from.Name} has passed you the cookies!" - ); - } - else - { - from.LocalOverheadMessage(MessageType.Regular, 0x26, false, "I can't pass to them."); - } - } - } - - public void SendHome() - { - _returnTimerToken.Cancel(); - - if (m_TeamInfo == null) - { - return; - } - - MoveToWorld(m_TeamInfo.Origin, m_TeamInfo.Game.Facet); - } - - private 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); - - if (mob != null) - { - mob.SolidHueOverride = 0x4001; - } - } - - public override void OnRemoved(IEntity parent) - { - base.OnRemoved(parent); - - var mob = FindOwner(parent); - - if (mob != null) - { - mob.SolidHueOverride = m_TeamInfo?.Game.GetColor(mob) ?? -1; - } - } - - public override void Serialize(IGenericWriter writer) - { - base.Serialize(writer); - - writer.Write(0); - } - - public override void Deserialize(IGenericReader reader) - { - base.Deserialize(reader); - - var version = reader.ReadInt(); - } - } - - public interface IRankedCTF - { - int Kills { get; } - int Captures { get; } - int Score { get; } - string Name { get; } - } - - public sealed class CTFPlayerInfo : IRankedCTF - { - private readonly CTFTeamInfo m_TeamInfo; - private int m_Captures; - - private int m_Kills; - - private int m_Score; - - public CTFPlayerInfo(CTFTeamInfo teamInfo, Mobile player) - { - m_TeamInfo = teamInfo; - Player = player; - } - - public Mobile Player { get; } - - string IRankedCTF.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 CTFTeamInfo : IRankedCTF - { - public CTFTeamInfo(int teamID) - { - TeamID = teamID; - Players = new Dictionary(); - } - - public CTFTeamInfo(int teamID, IGenericReader ip) - { - TeamID = teamID; - Players = new Dictionary(); - - var version = ip.ReadEncodedInt(); - - switch (version) - { - case 2: - { - Board = ip.ReadEntity(); - - goto case 1; - } - case 1: - { - Name = ip.ReadString(); - - goto case 0; - } - case 0: - { - Color = ip.ReadEncodedInt(); - - Flag = ip.ReadEntity(); - Origin = ip.ReadPoint3D(); - break; - } - } - } - - public CTFGame Game { get; set; } - - public int TeamID { get; } - - public CTFPlayerInfo Leader { get; set; } - - [CommandProperty(AccessLevel.GameMaster)] - public CTFBoard Board { get; set; } - - public Dictionary Players { get; } - - public CTFPlayerInfo this[Mobile mob] - { - get - { - if (mob == null) - { - return null; - } - - if (!Players.TryGetValue(mob, out var val)) - { - Players[mob] = val = new CTFPlayerInfo(this, mob); - } - - return val; - } - } - - [CommandProperty(AccessLevel.GameMaster)] - public int Color { get; set; } - - [CommandProperty(AccessLevel.GameMaster)] - public string Name { get; set; } - - [CommandProperty(AccessLevel.GameMaster)] - public CTFFlag Flag { get; set; } - - [CommandProperty(AccessLevel.GameMaster)] - public Point3D Origin { get; set; } - - string IRankedCTF.Name => $"{Name} 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(); - - if (Flag != null) - { - Flag.m_TeamInfo = this; - Flag.Hue = Color; - Flag.SendHome(); - } - - if (Board != null) - { - Board.m_TeamInfo = this; - } - } - - public void Serialize(IGenericWriter op) - { - op.WriteEncodedInt(2); // version - - op.Write(Board); - - op.Write(Name); - - op.WriteEncodedInt(Color); - - op.Write(Flag); - op.Write(Origin); - } - - public override string ToString() => "..."; - } - - public sealed class CTFController : EventController - { - [Constructible] - public CTFController() - { - Visible = false; - Movable = false; - - Duration = TimeSpan.FromMinutes(30.0); - - TeamInfo = new CTFTeamInfo[8]; - - for (var i = 0; i < TeamInfo.Length; ++i) - { - TeamInfo[i] = new CTFTeamInfo(i); - } - } - - public CTFController(Serial serial) - : base(serial) - { - } - - public CTFTeamInfo[] TeamInfo { get; private set; } - - [CommandProperty(AccessLevel.GameMaster)] - public CTFTeamInfo Team1 => TeamInfo[0]; - - [CommandProperty(AccessLevel.GameMaster)] - public CTFTeamInfo Team2 => TeamInfo[1]; - - [CommandProperty(AccessLevel.GameMaster)] - public CTFTeamInfo Team3 => TeamInfo[2]; - - [CommandProperty(AccessLevel.GameMaster)] - public CTFTeamInfo Team4 => TeamInfo[3]; - - [CommandProperty(AccessLevel.GameMaster)] - public CTFTeamInfo Team5 => TeamInfo[4]; - - [CommandProperty(AccessLevel.GameMaster)] - public CTFTeamInfo Team6 => TeamInfo[5]; - - [CommandProperty(AccessLevel.GameMaster)] - public CTFTeamInfo Team7 => TeamInfo[6]; - - [CommandProperty(AccessLevel.GameMaster)] - public CTFTeamInfo Team8 => TeamInfo[7]; - - [CommandProperty(AccessLevel.GameMaster)] - public TimeSpan Duration { get; set; } - - public override string Title => "CTF"; - - public override string GetTeamName(int teamID) => TeamInfo[teamID % TeamInfo.Length].Name; - - public override EventGame Construct(DuelContext context) => new CTFGame(this, context); - - public override void Serialize(IGenericWriter writer) - { - base.Serialize(writer); - - writer.Write(2); - - 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 2: - { - Duration = reader.ReadTimeSpan(); - - goto case 1; - } - case 1: - { - TeamInfo = new CTFTeamInfo[reader.ReadEncodedInt()]; - - for (var i = 0; i < TeamInfo.Length; ++i) - { - TeamInfo[i] = new CTFTeamInfo(i, reader); - } - - break; - } - case 0: - { - TeamInfo = new CTFTeamInfo[8]; - - for (var i = 0; i < TeamInfo.Length; ++i) - { - TeamInfo[i] = new CTFTeamInfo(i); - } - - break; - } - } - - if (version < 2) - { - Duration = TimeSpan.FromMinutes(30.0); - } - } - } - - public sealed class CTFGame : EventGame - { - private TimerExecutionToken _finishTimerToken; - - public CTFGame(CTFController controller, DuelContext context) : base(context) => Controller = controller; - - public CTFController Controller { get; } - - public Map Facet - { - get - { - if (m_Context.Arena != null) - { - return m_Context.Arena.Facet; - } - - return Controller.Map; - } - } - - public static void Initialize() - { - for (var i = 0x7C9; i <= 0x7D0; ++i) - { - TileData.ItemTable[i].Flags |= TileFlag.NoShoot; - } - } - - 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 CTFTeamInfo 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 -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) - { - var teamInfo = GetTeamInfo(mob); - - if (teamInfo != null) - { - return teamInfo.Color; - } - - return -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 hadFlag = false; - - foreach (var flag in corpse.EnumerateItemsByType(false)) - { - hadFlag = true; - flag.DropTo(mob, killer); - } - - if (mob.Backpack != null) - { - foreach (var flag in mob.Backpack.EnumerateItemsByType(false)) - { - hadFlag = true; - flag.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 (hadFlag) - { - playerInfo.Score += 4; // fragged flag carrier - } - - if (mob.InRange(teamInfo.Origin, 24) && mob.Map == Facet) - { - playerInfo.Score += 1; // fragged in base -- guarding - } - - for (var i = 0; i < Controller.TeamInfo.Length; ++i) - { - if (Controller.TeamInfo[i] == teamInfo) - { - continue; - } - - Mobile ourFlagCarrier = null; - - if (Controller.TeamInfo[i].Flag != null) - { - ourFlagCarrier = Controller.TeamInfo[i].Flag.RootParent as Mobile; - } - - if (ourFlagCarrier != null && GetTeamInfo(ourFlagCarrier) == teamInfo) - { - foreach (var aggr in ourFlagCarrier.Aggressors) - { - if (aggr.Defender == ourFlagCarrier && aggr.Attacker == mob) - { - playerInfo.Score += 2; // helped defend guy capturing enemy flag - break; - } - } - - if (mob.Map == ourFlagCarrier.Map && ourFlagCarrier.InRange(mob, 12)) - { - playerInfo.Score += 1; // helped defend guy capturing enemy flag - } - } - } - } - } - } - - mob.SendGump(new CTFBoardGump(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 % 8].Color); - } - - _finishTimerToken.Cancel(); - 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 % 8]; + var teamInfo = game.Controller.TeamInfo[i % 8]; if (teamInfo?.Flag == null) { continue; } - teams.Add(teamInfo); + entries.Add(teamInfo); + } + } + else + { + foreach (var player in section.Players.Values) + { + if (player.Score > 0) + { + entries.Add(player); + } + } + } + + entries.Sort((a, b) => b.Score - a.Score); + + var height = 0; + + if (section == null) + { + height = 73 + entries.Count * 75 + 28; + } + + Closable = false; + + AddPage(0); + + AddBackground(1, 1, 398, height, 3600); + + AddImageTiled(16, 15, 369, height - 29, 3604); + + for (var i = 0; i < entries.Count; i += 1) + { + AddImageTiled(22, 58 + i * 75, 357, 70, 0x2430); + } + + AddAlphaRegion(16, 15, 369, height - 29); + + AddImage(215, -45, 0xEE40); + // AddImage( 330, 141, 0x8BA ); + + AddBorderedText(22, 22, 294, 20, "CTF Scoreboard".Center(), LabelColor32, BlackColor32); + + AddImageTiled(32, 50, 264, 1, 9107); + AddImageTiled(42, 52, 264, 1, 9157); + + if (section == null) + { + for (var i = 0; i < entries.Count; ++i) + { + var teamInfo = entries[i] as CTFTeamInfo; + + if (teamInfo == null) + { + continue; + } + + AddImage(30, 70 + i * 75, 10152); + AddImage(30, 85 + i * 75, 10151); + AddImage(30, 100 + i * 75, 10151); + AddImage(30, 106 + i * 75, 10154); + + 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( + 60, + 65 + i * 75, + 250, + 20, + $"{LadderGump.Rank(1 + i)}: {teamInfo.Name} Team", + nameColor, + borderColor + ); + + AddBorderedText(50 + 10, 85 + i * 75, 100, 20, "Score:", 0xFFC000, BlackColor32); + AddBorderedText(50 + 15, 105 + i * 75, 100, 20, teamInfo.Score.ToString("N0"), 0xFFC000, BlackColor32); + + AddBorderedText(110 + 10, 85 + i * 75, 100, 20, "Kills:", 0xFFC000, BlackColor32); + AddBorderedText(110 + 15, 105 + i * 75, 100, 20, teamInfo.Kills.ToString("N0"), 0xFFC000, BlackColor32); + + AddBorderedText(160 + 10, 85 + i * 75, 100, 20, "Captures:", 0xFFC000, BlackColor32); + AddBorderedText( + 160 + 15, + 105 + i * 75, + 100, + 20, + teamInfo.Captures.ToString("N0"), + 0xFFC000, + BlackColor32 + ); + + var pl = teamInfo.Leader; + + AddBorderedText(235 + 10, 85 + i * 75, 250, 20, "Leader:", 0xFFC000, BlackColor32); + + if (pl != null) + { + AddBorderedText(235 + 15, 105 + i * 75, 250, 20, pl.Player.Name, 0xFFC000, BlackColor32); + } + } + } + + AddButton(314, height - 42, 247, 248, 1); + } + + 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) + { + AddHtml(x, y, width, height, color == 0 ? text : text.Color(color)); + } +} + +[SerializationGenerator(0, false)] +public sealed partial class CTFFlag : Item +{ + public Mobile m_Fragger; + public DateTime m_FragTime; + + private int m_ReturnCount; + + public Mobile m_Returner; + public DateTime m_ReturnTime; + private TimerExecutionToken _returnTimerToken; + public CTFTeamInfo m_TeamInfo; + + [Constructible] + public CTFFlag() : base(5643) => Movable = false; + + public override string DefaultName => "old people cookies"; + + public override void OnDoubleClick(Mobile from) + { + if (m_TeamInfo?.Game != null) + { + var ourTeam = m_TeamInfo; + var useTeam = m_TeamInfo.Game.GetTeamInfo(from); + + if (ourTeam == null || useTeam == null) + { + return; } - teams.Sort((a, b) => b.Score - a.Score); - - var tourney = m_Context.m_Tournament; - - var sb = new StringBuilder(); - - if (tourney != null) + if (IsChildOf(from.Backpack)) { - if (tourney.TourneyType == TourneyType.FreeForAll) + from.BeginTarget(1, false, TargetFlags.None, Flag_OnTarget); + } + else if (!from.InRange(this, 1) || !from.InLOS(this)) + { + from.LocalOverheadMessage(MessageType.Regular, 0x26, 1019045); // I can't reach that + } + else if (ourTeam == useTeam) + { + if (Location == m_TeamInfo.Origin && Map == m_TeamInfo.Game.Facet) { - 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"); + from.NetState.SendMessage( + Serial, + ItemID, + MessageType.Regular, + 0x3B2, + 3, + false, + "ENU", + Name, + "Touch me not for I am chaste." + ); } else { - for (var i = 0; i < tourney.ParticipantsPerMatch; ++i) - { - if (sb.Length > 0) - { - sb.Append('v'); - } + var playerInfo = useTeam[from]; - sb.Append(tourney.PlayersPerParticipant); + if (playerInfo != null) + { + playerInfo.Score += 4; // return + } + + m_Returner = from; + m_ReturnTime = Core.Now; + + SendHome(); + + from.LocalOverheadMessage(MessageType.Regular, 0x59, false, "You returned the cookies!"); + m_TeamInfo.Game.Alert($"The {from.Name} cookies have been returned by {ourTeam.Name}."); + } + } + else if (!from.PlaceInBackpack(this)) + { + from.LocalOverheadMessage(MessageType.Regular, 0x26, false, "I can't hold that."); + } + else + { + from.RevealingAction(); + + from.LocalOverheadMessage(MessageType.Regular, 0x59, false, "You stole the cookies!"); + m_TeamInfo.Game.Alert($"The {ourTeam.Name} cookies have been stolen by {from.Name} ({useTeam.Name})."); + + BeginCountdown(120); + } + } + } + + public override void Delete() + { + if (Parent != null) + { + SendHome(); + return; + } + + base.Delete(); + } + + public void DropTo(Mobile mob, Mobile killer) + { + m_Fragger = killer; + m_FragTime = Core.Now; + + if (mob != null) + { + MoveToWorld(new Point3D(mob.X, mob.Y, mob.Z + 2), mob.Map); + + m_ReturnCount = Math.Min(m_ReturnCount, 10); + } + else + { + SendHome(); + } + } + + private void BeginCountdown(int returnCount) + { + _returnTimerToken.Cancel(); + + Timer.StartTimer(TimeSpan.FromSeconds(1.0), TimeSpan.FromSeconds(1.0), Countdown_OnTick, out _returnTimerToken); + m_ReturnCount = returnCount; + } + + private void Countdown_OnTick() + { + var owner = RootParent as Mobile; + + switch (m_ReturnCount) + { + case 60: + case 30: + case 15: + case 10: + case 5: + case 4: + case 3: + case 2: + case 1: + { + if (m_ReturnCount == 1) + { + owner?.SendMessage(0x26, $"You have {m_ReturnCount} second to capture the cookies!"); + } + else + { + owner?.SendMessage(0x26, $"You have {m_ReturnCount} seconds to capture the cookies!"); + } + + break; + } + + case 0: + { + if (owner != null) + { + owner.SendMessage(0x26, "You have taken too long to capture the cookies!"); + owner.Kill(); + } + + SendHome(); + + m_TeamInfo?.Game?.Alert($"The {m_TeamInfo.Name} cookies have been returned."); + + return; + } + } + + --m_ReturnCount; + } + + private void Flag_OnTarget(Mobile from, object obj) + { + if (m_TeamInfo == null) + { + return; + } + + if (!IsChildOf(from.Backpack)) + { + return; + } + + var ourTeam = m_TeamInfo; + var useTeam = m_TeamInfo.Game.GetTeamInfo(from); + + if (obj is CTFFlag) + { + if (obj == useTeam.Flag) + { + from.LocalOverheadMessage(MessageType.Regular, 0x59, false, "You captured the cookies!"); + m_TeamInfo.Game.Alert($"{from.Name} captured the {ourTeam.Name} cookies!"); + + SendHome(); + + var playerInfo = useTeam[from]; + + if (playerInfo != null) + { + playerInfo.Captures += 1; + playerInfo.Score += 50; // capture + + var teamFlag = useTeam.Flag; + + if (teamFlag.m_Fragger != null && + Core.Now < teamFlag.m_FragTime + TimeSpan.FromSeconds(5.0) && + m_TeamInfo.Game.GetTeamInfo(teamFlag.m_Fragger) == useTeam) + { + var assistInfo = useTeam[teamFlag.m_Fragger]; + + if (assistInfo != null) + { + assistInfo.Score += 6; // frag assist + } + } + + if (teamFlag.m_Returner != null && + Core.Now < teamFlag.m_ReturnTime + TimeSpan.FromSeconds(5.0)) + { + var assistInfo = useTeam[teamFlag.m_Returner]; + + if (assistInfo != null) + { + assistInfo.Score += 4; // return assist + } } } } - - if (Controller != null) + else { - sb.Append(' ').Append(Controller.Title); + from.LocalOverheadMessage(MessageType.Regular, 0x26, false, "Those are not my cookies."); + } + } + else if (obj is Mobile passTo) + { + var passTeam = m_TeamInfo.Game.GetTeamInfo(passTo); + + if (passTo == from) + { + from.LocalOverheadMessage(MessageType.Regular, 0x26, false, "I can't pass to them."); + } + else if (passTeam == useTeam && passTo.PlaceInBackpack(this)) + { + passTo.LocalOverheadMessage( + MessageType.Regular, + 0x59, + false, + $"{from.Name} has passed you the cookies!" + ); + } + else + { + from.LocalOverheadMessage(MessageType.Regular, 0x26, false, "I can't pass to them."); + } + } + } + + public void SendHome() + { + _returnTimerToken.Cancel(); + + if (m_TeamInfo == null) + { + return; + } + + MoveToWorld(m_TeamInfo.Origin, m_TeamInfo.Game.Facet); + } + + private 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); + + if (mob != null) + { + mob.SolidHueOverride = 0x4001; + } + } + + public override void OnRemoved(IEntity parent) + { + base.OnRemoved(parent); + + var mob = FindOwner(parent); + + if (mob != null) + { + mob.SolidHueOverride = m_TeamInfo?.Game.GetColor(mob) ?? -1; + } + } +} + +public interface IRankedCTF +{ + int Kills { get; } + int Captures { get; } + int Score { get; } + string Name { get; } +} + +public sealed class CTFPlayerInfo : IRankedCTF +{ + private readonly CTFTeamInfo m_TeamInfo; + private int m_Captures; + + private int m_Kills; + + private int m_Score; + + public CTFPlayerInfo(CTFTeamInfo teamInfo, Mobile player) + { + m_TeamInfo = teamInfo; + Player = player; + } + + public Mobile Player { get; } + + string IRankedCTF.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 CTFTeamInfo : IRankedCTF +{ + public CTFTeamInfo(int teamID) + { + TeamID = teamID; + Players = new Dictionary(); + } + + public CTFTeamInfo(int teamID, IGenericReader ip) + { + TeamID = teamID; + Players = new Dictionary(); + + var version = ip.ReadEncodedInt(); + + switch (version) + { + case 2: + { + Board = ip.ReadEntity(); + + goto case 1; + } + case 1: + { + Name = ip.ReadString(); + + goto case 0; + } + case 0: + { + Color = ip.ReadEncodedInt(); + + Flag = ip.ReadEntity(); + Origin = ip.ReadPoint3D(); + break; + } + } + } + + public CTFGame Game { get; set; } + + public int TeamID { get; } + + public CTFPlayerInfo Leader { get; set; } + + [CommandProperty(AccessLevel.GameMaster)] + public CTFBoard Board { get; set; } + + public Dictionary Players { get; } + + public CTFPlayerInfo this[Mobile mob] + { + get + { + if (mob == null) + { + return null; } - var title = sb.ToString(); - - var winner = teams.Count > 0 ? teams[0] : null; - - for (var i = 0; i < teams.Count; ++i) + if (!Players.TryGetValue(mob, out var val)) { - var rank = TrophyRank.Bronze; + Players[mob] = val = new CTFPlayerInfo(this, mob); + } - if (i == 0) + return val; + } + } + + [CommandProperty(AccessLevel.GameMaster)] + public int Color { get; set; } + + [CommandProperty(AccessLevel.GameMaster)] + public string Name { get; set; } + + [CommandProperty(AccessLevel.GameMaster)] + public CTFFlag Flag { get; set; } + + [CommandProperty(AccessLevel.GameMaster)] + public Point3D Origin { get; set; } + + string IRankedCTF.Name => $"{Name} 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(); + + if (Flag != null) + { + Flag.m_TeamInfo = this; + Flag.Hue = Color; + Flag.SendHome(); + } + + if (Board != null) + { + Board.m_TeamInfo = this; + } + } + + public void Serialize(IGenericWriter op) + { + op.WriteEncodedInt(2); // version + + op.Write(Board); + + op.Write(Name); + + op.WriteEncodedInt(Color); + + op.Write(Flag); + op.Write(Origin); + } + + public override string ToString() => "..."; +} + +public sealed class CTFController : EventController +{ + [Constructible] + public CTFController() + { + Visible = false; + Movable = false; + + Duration = TimeSpan.FromMinutes(30.0); + + TeamInfo = new CTFTeamInfo[8]; + + for (var i = 0; i < TeamInfo.Length; ++i) + { + TeamInfo[i] = new CTFTeamInfo(i); + } + } + + public CTFController(Serial serial) + : base(serial) + { + } + + public CTFTeamInfo[] TeamInfo { get; private set; } + + [CommandProperty(AccessLevel.GameMaster)] + public CTFTeamInfo Team1 => TeamInfo[0]; + + [CommandProperty(AccessLevel.GameMaster)] + public CTFTeamInfo Team2 => TeamInfo[1]; + + [CommandProperty(AccessLevel.GameMaster)] + public CTFTeamInfo Team3 => TeamInfo[2]; + + [CommandProperty(AccessLevel.GameMaster)] + public CTFTeamInfo Team4 => TeamInfo[3]; + + [CommandProperty(AccessLevel.GameMaster)] + public CTFTeamInfo Team5 => TeamInfo[4]; + + [CommandProperty(AccessLevel.GameMaster)] + public CTFTeamInfo Team6 => TeamInfo[5]; + + [CommandProperty(AccessLevel.GameMaster)] + public CTFTeamInfo Team7 => TeamInfo[6]; + + [CommandProperty(AccessLevel.GameMaster)] + public CTFTeamInfo Team8 => TeamInfo[7]; + + [CommandProperty(AccessLevel.GameMaster)] + public TimeSpan Duration { get; set; } + + public override string Title => "CTF"; + + public override string GetTeamName(int teamID) => TeamInfo[teamID % TeamInfo.Length].Name; + + public override EventGame Construct(DuelContext context) => new CTFGame(this, context); + + public override void Serialize(IGenericWriter writer) + { + base.Serialize(writer); + + writer.Write(2); + + 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 2: { - rank = TrophyRank.Gold; + Duration = reader.ReadTimeSpan(); + + goto case 1; } - else if (i == 1) + case 1: { - rank = TrophyRank.Silver; - } + TeamInfo = new CTFTeamInfo[reader.ReadEncodedInt()]; - var leader = teams[i].Leader; - - foreach (var pl in teams[i].Players.Values) - { - var mob = pl.Player; - - if (mob == null) + for (var i = 0; i < TeamInfo.Length; ++i) { - continue; + TeamInfo[i] = new CTFTeamInfo(i, reader); } - // "Red v Blue CTF Champion" + break; + } + case 0: + { + TeamInfo = new CTFTeamInfo[8]; - sb = new StringBuilder(); - - sb.Append(title); - - if (pl == leader) + for (var i = 0; i < TeamInfo.Length; ++i) { - sb.Append(" Leader"); + TeamInfo[i] = new CTFTeamInfo(i); } - if (pl.Score > 0) + break; + } + } + + if (version < 2) + { + Duration = TimeSpan.FromMinutes(30.0); + } + } +} + +public sealed class CTFGame : EventGame +{ + private TimerExecutionToken _finishTimerToken; + + public CTFGame(CTFController controller, DuelContext context) : base(context) => Controller = controller; + + public CTFController Controller { get; } + + public Map Facet + { + get + { + if (m_Context.Arena != null) + { + return m_Context.Arena.Facet; + } + + return Controller.Map; + } + } + + public static void Initialize() + { + for (var i = 0x7C9; i <= 0x7D0; ++i) + { + TileData.ItemTable[i].Flags |= TileFlag.NoShoot; + } + } + + 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 CTFTeamInfo 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 -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) + { + var teamInfo = GetTeamInfo(mob); + + if (teamInfo != null) + { + return teamInfo.Color; + } + + return -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 hadFlag = false; + + foreach (var flag in corpse.EnumerateItemsByType(false)) + { + hadFlag = true; + flag.DropTo(mob, killer); + } + + if (mob.Backpack != null) + { + foreach (var flag in mob.Backpack.EnumerateItemsByType(false)) + { + hadFlag = true; + flag.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 (hadFlag) { - sb.Append(": "); + playerInfo.Score += 4; // fragged flag carrier + } - sb.Append(pl.Score.ToString("N0")); - sb.Append(pl.Score == 1 ? " point" : " points"); + if (mob.InRange(teamInfo.Origin, 24) && mob.Map == Facet) + { + playerInfo.Score += 1; // fragged in base -- guarding + } - if (pl.Kills > 0) + for (var i = 0; i < Controller.TeamInfo.Length; ++i) + { + if (Controller.TeamInfo[i] == teamInfo) { - sb.Append(", "); - sb.Append(pl.Kills.ToString("N0")); - sb.Append(pl.Kills == 1 ? " kill" : " kills"); + continue; } - if (pl.Captures > 0) + Mobile ourFlagCarrier = null; + + if (Controller.TeamInfo[i].Flag != null) { - sb.Append(", "); - sb.Append(pl.Captures.ToString("N0")); - sb.Append(pl.Captures == 1 ? " capture" : " captures"); + ourFlagCarrier = Controller.TeamInfo[i].Flag.RootParent as Mobile; + } + + if (ourFlagCarrier != null && GetTeamInfo(ourFlagCarrier) == teamInfo) + { + foreach (var aggr in ourFlagCarrier.Aggressors) + { + if (aggr.Defender == ourFlagCarrier && aggr.Attacker == mob) + { + playerInfo.Score += 2; // helped defend guy capturing enemy flag + break; + } + } + + if (mob.Map == ourFlagCarrier.Map && ourFlagCarrier.InRange(mob, 12)) + { + playerInfo.Score += 1; // helped defend guy capturing enemy flag + } } } + } + } + } - Item item = new Trophy(sb.ToString(), rank); + mob.SendGump(new CTFBoardGump(mob, this)); - if (pl == leader) + 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 % 8].Color); + } + + _finishTimerToken.Cancel(); + 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 % 8]; + + if (teamInfo?.Flag == null) + { + continue; + } + + teams.Add(teamInfo); + } + + teams.Sort((a, b) => b.Score - a.Score); + + var tourney = m_Context.m_Tournament; + + var sb = new StringBuilder(); + + 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) { - item.ItemID = 4810; + sb.Append('v'); } - item.Name = $"{item.Name}, {teams[i].Name.ToLower()} team"; + sb.Append(tourney.PlayersPerParticipant); + } + } + } + + if (Controller != null) + { + sb.Append(' ').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 = TrophyRank.Bronze; + + if (i == 0) + { + rank = TrophyRank.Gold; + } + else if (i == 1) + { + rank = TrophyRank.Silver; + } + + var leader = teams[i].Leader; + + foreach (var pl in teams[i].Players.Values) + { + var mob = pl.Player; + + if (mob == null) + { + continue; + } + + // "Red v Blue CTF Champion" + + sb = new StringBuilder(); + + 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"); + + if (pl.Kills > 0) + { + sb.Append(", "); + 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 ? " capture" : " captures"); + } + } + + 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); } - var cash = pl.Score * 250; - - if (cash > 0) - { - item = new BankCheck(cash); - - if (!mob.PlaceInBackpack(item)) - { - mob.BankBox.DropItem(item); - } - - mob.SendMessage( - $"You have been awarded a {rank.ToString().ToLower()} trophy and {cash:N0}gp for your participation in this tournament." - ); - } - else - { - mob.SendMessage( - $"You have been awarded a {rank.ToString().ToLower()} trophy for your participation in this tournament." - ); - } + mob.SendMessage( + $"You have been awarded a {rank.ToString().ToLower()} trophy and {cash:N0}gp for your participation in this tournament." + ); } - } - - 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) + else { - var dp = p.Players[j]; - - if (dp?.Mobile != null) - { - dp.Mobile.SendGump(new CTFBoardGump(dp.Mobile, this)); - } + mob.SendMessage( + $"You have been awarded a {rank.ToString().ToLower()} trophy for your participation in this tournament." + ); } - - if (i == winner?.TeamID) - { - continue; - } - - for (var j = 0; j < p.Players.Length; ++j) - { - if (p.Players[j] != null) - { - 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 < m_Context.Participants.Count; ++i) { - for (var i = 0; i < Controller.TeamInfo.Length; ++i) + var p = m_Context.Participants[i]; + + for (var j = 0; j < p.Players.Length; ++j) { - var teamInfo = Controller.TeamInfo[i]; + var dp = p.Players[j]; - if (teamInfo.Flag != null) + if (dp?.Mobile != null) { - teamInfo.Flag.SendHome(); - teamInfo.Flag.m_TeamInfo = null; + dp.Mobile.SendGump(new CTFBoardGump(dp.Mobile, this)); } - - if (teamInfo.Board != null) - { - teamInfo.Board.m_TeamInfo = null; - } - - teamInfo.Game = null; } - for (var i = 0; i < m_Context.Participants.Count; ++i) + if (i == winner?.TeamID) { - ApplyHues(m_Context.Participants[i], -1); + continue; } - _finishTimerToken.Cancel(); + for (var j = 0; j < p.Players.Length; ++j) + { + if (p.Players[j] != null) + { + 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]; + + if (teamInfo.Flag != null) + { + teamInfo.Flag.SendHome(); + teamInfo.Flag.m_TeamInfo = null; + } + + if (teamInfo.Board != null) + { + teamInfo.Board.m_TeamInfo = null; + } + + teamInfo.Game = null; + } + + for (var i = 0; i < m_Context.Participants.Count; ++i) + { + ApplyHues(m_Context.Participants[i], -1); + } + + _finishTimerToken.Cancel(); + } } diff --git a/Projects/UOContent/Engines/ConPVP/Games/DoubleDom.cs b/Projects/UOContent/Engines/ConPVP/Games/DoubleDom.cs index e06bc2864..b37d4ab1d 100644 --- a/Projects/UOContent/Engines/ConPVP/Games/DoubleDom.cs +++ b/Projects/UOContent/Engines/ConPVP/Games/DoubleDom.cs @@ -1,1151 +1,1097 @@ using System; using System.Collections.Generic; using System.Text; +using ModernUO.Serialization; using Server.Gumps; using Server.Items; using Server.Mobiles; -namespace Server.Engines.ConPVP +namespace Server.Engines.ConPVP; + +[SerializationGenerator(0, false)] +public sealed partial class DDBoard : Item { - public sealed class DDBoard : Item + public DDTeamInfo m_TeamInfo; + + [Constructible] + public DDBoard() : base(7774) => Movable = false; + + public override string DefaultName => "scoreboard"; + + public override void OnDoubleClick(Mobile from) { - public DDTeamInfo m_TeamInfo; - - [Constructible] - public DDBoard() - : base(7774) => - Movable = false; - - public DDBoard(Serial serial) - : base(serial) + if (m_TeamInfo?.Game != null) { - } - - public override string DefaultName => "scoreboard"; - - public override void OnDoubleClick(Mobile from) - { - if (m_TeamInfo?.Game != null) - { - from.SendGump(new DDBoardGump(from, m_TeamInfo.Game)); - } - } - - public override void Serialize(IGenericWriter writer) - { - base.Serialize(writer); - - writer.Write(0); - } - - public override void Deserialize(IGenericReader reader) - { - base.Deserialize(reader); - - var version = reader.ReadInt(); + from.SendGump(new DDBoardGump(from, m_TeamInfo.Game)); } } +} - public class DDBoardGump : Gump +public class DDBoardGump : Gump +{ + private const int LabelColor32 = 0xFFFFFF; + private const int BlackColor32 = 0x000000; + + public override bool Singleton => true; + + public DDBoardGump(Mobile mob, DDGame game, DDTeamInfo section = null) + : base(60, 60) { - private const int LabelColor32 = 0xFFFFFF; - private const int BlackColor32 = 0x000000; + // m_Game = game; - public override bool Singleton => true; + var ourTeam = game.GetTeamInfo(mob); - public DDBoardGump(Mobile mob, DDGame game, DDTeamInfo section = null) - : base(60, 60) + var entries = new List(); + + if (section == null) { - // m_Game = game; - - var ourTeam = game.GetTeamInfo(mob); - - var entries = new List(); - - if (section == null) + for (var i = 0; i < game.Context.Participants.Count; ++i) { - for (var i = 0; i < game.Context.Participants.Count; ++i) - { - var teamInfo = game.Controller.TeamInfo[i % game.Controller.TeamInfo.Length]; - - if (teamInfo != null) - { - entries.Add(teamInfo); - } - } - } - else - { - foreach (var player in section.Players.Values) - { - if (player.Score > 0) - { - entries.Add(player); - } - } - } - - entries.Sort((a, b) => b.Score - a.Score); - - var height = 0; - - if (section == null) - { - height = 73 + entries.Count * 75 + 28; - } - - Closable = false; - - AddPage(0); - - AddBackground(1, 1, 398, height, 3600); - - AddImageTiled(16, 15, 369, height - 29, 3604); - - for (var i = 0; i < entries.Count; i += 1) - { - AddImageTiled(22, 58 + i * 75, 357, 70, 0x2430); - } - - AddAlphaRegion(16, 15, 369, height - 29); - - AddImage(215, -45, 0xEE40); - // AddImage( 330, 141, 0x8BA ); - - AddBorderedText(22, 22, 294, 20, "DD Scoreboard".Center(), LabelColor32, BlackColor32); - - AddImageTiled(32, 50, 264, 1, 9107); - AddImageTiled(42, 52, 264, 1, 9157); - - if (section == null) - { - for (var i = 0; i < entries.Count; ++i) - { - if (entries[i] is not DDTeamInfo teamInfo) - { - continue; - } - - AddImage(30, 70 + i * 75, 10152); - AddImage(30, 85 + i * 75, 10151); - AddImage(30, 100 + i * 75, 10151); - AddImage(30, 106 + i * 75, 10154); - - 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( - 60, - 65 + i * 75, - 250, - 20, - $"{LadderGump.Rank(1 + i)}: {teamInfo.Name}", - nameColor, - borderColor - ); - - AddBorderedText(50 + 10, 85 + i * 75, 100, 20, "Score:", 0xFFC000, BlackColor32); - AddBorderedText(50 + 15, 105 + i * 75, 100, 20, teamInfo.Score.ToString("N0"), 0xFFC000, BlackColor32); - - AddBorderedText(110 + 10, 85 + i * 75, 100, 20, "Kills:", 0xFFC000, BlackColor32); - AddBorderedText(110 + 15, 105 + i * 75, 100, 20, teamInfo.Kills.ToString("N0"), 0xFFC000, BlackColor32); - - AddBorderedText(160 + 10, 85 + i * 75, 100, 20, "Captures:", 0xFFC000, BlackColor32); - AddBorderedText( - 160 + 15, - 105 + i * 75, - 100, - 20, - teamInfo.Captures.ToString("N0"), - 0xFFC000, - BlackColor32 - ); - - var pl = teamInfo.Leader; - - AddBorderedText(235 + 10, 85 + i * 75, 250, 20, "Leader:", 0xFFC000, BlackColor32); - - if (pl != null) - { - AddBorderedText(235 + 15, 105 + i * 75, 250, 20, pl.Player.Name, 0xFFC000, BlackColor32); - } - } - } - - AddButton(314, height - 42, 247, 248, 1); - } - - 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) - { - AddHtml(x, y, width, height, color == 0 ? text : text.Color(color)); - } - } - - public sealed class DDPlayerInfo : IRankedCTF - { - private readonly DDTeamInfo m_TeamInfo; - private int m_Captures; - - private int m_Kills; - - private int m_Score; - - public DDPlayerInfo(DDTeamInfo teamInfo, Mobile player) - { - m_TeamInfo = teamInfo; - Player = player; - } - - public Mobile Player { get; } - - 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 DDTeamInfo : IRankedCTF - { - public DDTeamInfo(int teamID) - { - TeamID = teamID; - Players = new Dictionary(); - } - - public DDTeamInfo(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(); - Origin = ip.ReadPoint3D(); - break; - } - } - } - - public DDGame Game { get; set; } - - public int TeamID { get; } - - public DDPlayerInfo Leader { get; set; } - - [CommandProperty(AccessLevel.GameMaster)] - public DDBoard Board { get; set; } - - public Dictionary Players { get; } - - public DDPlayerInfo this[Mobile mob] - { - get - { - if (mob == null) - { - return null; - } - - if (!Players.TryGetValue(mob, out var val)) - { - Players[mob] = val = new DDPlayerInfo(this, mob); - } - - return val; - } - } - - [CommandProperty(AccessLevel.GameMaster)] - public int Color { get; set; } - - [CommandProperty(AccessLevel.GameMaster)] - public string TeamName { get; set; } - - [CommandProperty(AccessLevel.GameMaster)] - public Point3D Origin { get; set; } - - 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(); - - if (Board != null) - { - Board.m_TeamInfo = this; - } - } - - public void Serialize(IGenericWriter op) - { - op.WriteEncodedInt(0); // version - - op.Write(Board); - op.Write(TeamName); - op.WriteEncodedInt(Color); - op.Write(Origin); - } - - public override string ToString() => "..."; - } - - public sealed class DDController : EventController - { - [Constructible] - public DDController() - { - Visible = false; - Movable = false; - - Duration = TimeSpan.FromMinutes(30.0); - - TeamInfo = new DDTeamInfo[2]; - - for (var i = 0; i < TeamInfo.Length; ++i) - { - TeamInfo[i] = new DDTeamInfo(i); - } - } - - public DDController(Serial serial) - : base(serial) - { - } - - public DDTeamInfo[] TeamInfo { get; private set; } - - [CommandProperty(AccessLevel.GameMaster)] - public DDTeamInfo Team1 => TeamInfo[0]; - - [CommandProperty(AccessLevel.GameMaster)] - public DDTeamInfo Team2 => TeamInfo[1]; - - [CommandProperty(AccessLevel.GameMaster)] - public DDWayPoint PointA { get; set; } - - [CommandProperty(AccessLevel.GameMaster)] - public DDWayPoint PointB { get; set; } - - [CommandProperty(AccessLevel.GameMaster)] - public TimeSpan Duration { get; set; } - - public override string Title => "DoubleDom"; - - public override string DefaultName => "DD Controller"; - - public override string GetTeamName(int teamID) => TeamInfo[teamID % TeamInfo.Length].Name; - - public override EventGame Construct(DuelContext context) => new DDGame(this, context); - - public override void Serialize(IGenericWriter writer) - { - base.Serialize(writer); - - writer.Write(0); - - writer.Write(Duration); - - writer.WriteEncodedInt(TeamInfo.Length); - - for (var i = 0; i < TeamInfo.Length; ++i) - { - TeamInfo[i].Serialize(writer); - } - - writer.Write(PointA); - writer.Write(PointB); - } - - public override void Deserialize(IGenericReader reader) - { - base.Deserialize(reader); - - var version = reader.ReadInt(); - - switch (version) - { - case 0: - { - Duration = reader.ReadTimeSpan(); - TeamInfo = new DDTeamInfo[reader.ReadEncodedInt()]; - - for (var i = 0; i < TeamInfo.Length; ++i) - { - TeamInfo[i] = new DDTeamInfo(i, reader); - } - - PointA = reader.ReadEntity(); - PointB = reader.ReadEntity(); - - break; - } - } - } - } - - public sealed class DDGame : EventGame - { - private int m_CapStage; - - private bool m_Capturable = true; - private TimerExecutionToken _captureTimerToken; - private TimerExecutionToken _finishTimerToken; - private TimerExecutionToken _uncaptureTimerToken; - - public DDGame(DDController controller, DuelContext context) : base(context) => Controller = controller; - - public DDController Controller { get; } - - public Map Facet => m_Context.Arena?.Facet ?? Controller.Map; - - 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 DDTeamInfo 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 -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); - - 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 - - // extra points for killing someone on the waypoint - if (Controller.PointA != null) - { - if (mob.InRange(Controller.PointA, 2)) - { - playerInfo.Score += 1; - } - } - - if (Controller.PointB != null) - { - if (mob.InRange(Controller.PointB, 2)) - { - playerInfo.Score += 1; - } - } - } - - playerInfo = victInfo[mob]; - if (playerInfo != null) - { - playerInfo.Score -= 1; - } - } - } - - mob.SendGump(new DDBoardGump(mob, this)); - - m_Context.Requip(mob, corpse); - DelayBounce(TimeSpan.FromSeconds(30.0), mob, corpse); - - return false; - } - - public override void OnStart() - { - m_Capturable = true; - - _captureTimerToken.Cancel(); - _uncaptureTimerToken.Cancel(); - - for (var i = 0; i < Controller.TeamInfo.Length; ++i) - { - var teamInfo = Controller.TeamInfo[i]; - - teamInfo.Game = this; - teamInfo.Reset(); - } - - if (Controller.PointA != null) - { - Controller.PointA.Game = this; - } - - if (Controller.PointB != null) - { - Controller.PointB.Game = this; - } - - for (var i = 0; i < m_Context.Participants.Count; ++i) - { - ApplyHues( - m_Context.Participants[i], - Controller.TeamInfo[i % Controller.TeamInfo.Length].Color - ); - } - - _finishTimerToken.Cancel(); - 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]; + var teamInfo = game.Controller.TeamInfo[i % game.Controller.TeamInfo.Length]; if (teamInfo != null) { - teams.Add(teamInfo); + entries.Add(teamInfo); } } - - teams.Sort((a, b) => b.Score - a.Score); - - var tourney = m_Context.m_Tournament; - - var sb = new StringBuilder(); - - if (tourney != null) + } + else + { + foreach (var player in section.Players.Values) { - if (tourney.TourneyType == TourneyType.FreeForAll) + if (player.Score > 0) { - 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); - } + entries.Add(player); } } + } - if (Controller != null) + entries.Sort((a, b) => b.Score - a.Score); + + var height = 0; + + if (section == null) + { + height = 73 + entries.Count * 75 + 28; + } + + Closable = false; + + AddPage(0); + + AddBackground(1, 1, 398, height, 3600); + + AddImageTiled(16, 15, 369, height - 29, 3604); + + for (var i = 0; i < entries.Count; i += 1) + { + AddImageTiled(22, 58 + i * 75, 357, 70, 0x2430); + } + + AddAlphaRegion(16, 15, 369, height - 29); + + AddImage(215, -45, 0xEE40); + // AddImage( 330, 141, 0x8BA ); + + AddBorderedText(22, 22, 294, 20, "DD Scoreboard".Center(), LabelColor32, BlackColor32); + + AddImageTiled(32, 50, 264, 1, 9107); + AddImageTiled(42, 52, 264, 1, 9157); + + if (section == null) + { + for (var i = 0; i < entries.Count; ++i) { - sb.Append(' ').Append(Controller.Title); + if (entries[i] is not DDTeamInfo teamInfo) + { + continue; + } + + AddImage(30, 70 + i * 75, 10152); + AddImage(30, 85 + i * 75, 10151); + AddImage(30, 100 + i * 75, 10151); + AddImage(30, 106 + i * 75, 10154); + + 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( + 60, + 65 + i * 75, + 250, + 20, + $"{LadderGump.Rank(1 + i)}: {teamInfo.Name}", + nameColor, + borderColor + ); + + AddBorderedText(50 + 10, 85 + i * 75, 100, 20, "Score:", 0xFFC000, BlackColor32); + AddBorderedText(50 + 15, 105 + i * 75, 100, 20, teamInfo.Score.ToString("N0"), 0xFFC000, BlackColor32); + + AddBorderedText(110 + 10, 85 + i * 75, 100, 20, "Kills:", 0xFFC000, BlackColor32); + AddBorderedText(110 + 15, 105 + i * 75, 100, 20, teamInfo.Kills.ToString("N0"), 0xFFC000, BlackColor32); + + AddBorderedText(160 + 10, 85 + i * 75, 100, 20, "Captures:", 0xFFC000, BlackColor32); + AddBorderedText( + 160 + 15, + 105 + i * 75, + 100, + 20, + teamInfo.Captures.ToString("N0"), + 0xFFC000, + BlackColor32 + ); + + var pl = teamInfo.Leader; + + AddBorderedText(235 + 10, 85 + i * 75, 250, 20, "Leader:", 0xFFC000, BlackColor32); + + if (pl != null) + { + AddBorderedText(235 + 15, 105 + i * 75, 250, 20, pl.Player.Name, 0xFFC000, BlackColor32); + } + } + } + + AddButton(314, height - 42, 247, 248, 1); + } + + 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) + { + AddHtml(x, y, width, height, color == 0 ? text : text.Color(color)); + } +} + +public sealed class DDPlayerInfo : IRankedCTF +{ + private readonly DDTeamInfo m_TeamInfo; + private int m_Captures; + + private int m_Kills; + + private int m_Score; + + public DDPlayerInfo(DDTeamInfo teamInfo, Mobile player) + { + m_TeamInfo = teamInfo; + Player = player; + } + + public Mobile Player { get; } + + 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 DDTeamInfo : IRankedCTF +{ + public DDTeamInfo(int teamID) + { + TeamID = teamID; + Players = new Dictionary(); + } + + public DDTeamInfo(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(); + Origin = ip.ReadPoint3D(); + break; + } + } + } + + public DDGame Game { get; set; } + + public int TeamID { get; } + + public DDPlayerInfo Leader { get; set; } + + [CommandProperty(AccessLevel.GameMaster)] + public DDBoard Board { get; set; } + + public Dictionary Players { get; } + + public DDPlayerInfo this[Mobile mob] + { + get + { + if (mob == null) + { + return null; } - var title = sb.ToString(); - - var winner = teams.Count > 0 ? teams[0] : null; - - for (var i = 0; i < teams.Count; ++i) + if (!Players.TryGetValue(mob, out var val)) { - var rank = TrophyRank.Bronze; + Players[mob] = val = new DDPlayerInfo(this, mob); + } - if (i == 0) + return val; + } + } + + [CommandProperty(AccessLevel.GameMaster)] + public int Color { get; set; } + + [CommandProperty(AccessLevel.GameMaster)] + public string TeamName { get; set; } + + [CommandProperty(AccessLevel.GameMaster)] + public Point3D Origin { get; set; } + + 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(); + + if (Board != null) + { + Board.m_TeamInfo = this; + } + } + + public void Serialize(IGenericWriter op) + { + op.WriteEncodedInt(0); // version + + op.Write(Board); + op.Write(TeamName); + op.WriteEncodedInt(Color); + op.Write(Origin); + } + + public override string ToString() => "..."; +} + +public sealed class DDController : EventController +{ + [Constructible] + public DDController() + { + Visible = false; + Movable = false; + + Duration = TimeSpan.FromMinutes(30.0); + + TeamInfo = new DDTeamInfo[2]; + + for (var i = 0; i < TeamInfo.Length; ++i) + { + TeamInfo[i] = new DDTeamInfo(i); + } + } + + public DDController(Serial serial) + : base(serial) + { + } + + public DDTeamInfo[] TeamInfo { get; private set; } + + [CommandProperty(AccessLevel.GameMaster)] + public DDTeamInfo Team1 => TeamInfo[0]; + + [CommandProperty(AccessLevel.GameMaster)] + public DDTeamInfo Team2 => TeamInfo[1]; + + [CommandProperty(AccessLevel.GameMaster)] + public DDWayPoint PointA { get; set; } + + [CommandProperty(AccessLevel.GameMaster)] + public DDWayPoint PointB { get; set; } + + [CommandProperty(AccessLevel.GameMaster)] + public TimeSpan Duration { get; set; } + + public override string Title => "DoubleDom"; + + public override string DefaultName => "DD Controller"; + + public override string GetTeamName(int teamID) => TeamInfo[teamID % TeamInfo.Length].Name; + + public override EventGame Construct(DuelContext context) => new DDGame(this, context); + + public override void Serialize(IGenericWriter writer) + { + base.Serialize(writer); + + writer.Write(0); + + writer.Write(Duration); + + writer.WriteEncodedInt(TeamInfo.Length); + + for (var i = 0; i < TeamInfo.Length; ++i) + { + TeamInfo[i].Serialize(writer); + } + + writer.Write(PointA); + writer.Write(PointB); + } + + public override void Deserialize(IGenericReader reader) + { + base.Deserialize(reader); + + var version = reader.ReadInt(); + + switch (version) + { + case 0: { - rank = TrophyRank.Gold; - } - else if (i == 1) - { - rank = TrophyRank.Silver; - } + Duration = reader.ReadTimeSpan(); + TeamInfo = new DDTeamInfo[reader.ReadEncodedInt()]; - var leader = teams[i].Leader; - - foreach (var pl in teams[i].Players.Values) - { - var mob = pl.Player; - - if (mob == null) + for (var i = 0; i < TeamInfo.Length; ++i) { - continue; + TeamInfo[i] = new DDTeamInfo(i, reader); } - // "Red v Blue DD Champion" + PointA = reader.ReadEntity(); + PointB = reader.ReadEntity(); - sb = new StringBuilder(); + break; + } + } + } +} - sb.Append(title); +public sealed class DDGame : EventGame +{ + private int m_CapStage; - if (pl == leader) + private bool m_Capturable = true; + private TimerExecutionToken _captureTimerToken; + private TimerExecutionToken _finishTimerToken; + private TimerExecutionToken _uncaptureTimerToken; + + public DDGame(DDController controller, DuelContext context) : base(context) => Controller = controller; + + public DDController Controller { get; } + + public Map Facet => m_Context.Arena?.Facet ?? Controller.Map; + + 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 DDTeamInfo 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 -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); + + 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 + + // extra points for killing someone on the waypoint + if (Controller.PointA != null) { - sb.Append(" Leader"); - } - - if (pl.Score > 0) - { - sb.Append(": "); - - sb.Append(pl.Score.ToString("N0")); - sb.Append(pl.Score == 1 ? " point" : " points"); - - if (pl.Kills > 0) + if (mob.InRange(Controller.PointA, 2)) { - sb.Append(", "); - 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 ? " capture" : " captures"); + playerInfo.Score += 1; } } - Item item = new Trophy(sb.ToString(), rank); - - if (pl == leader) + if (Controller.PointB != null) { - item.ItemID = 4810; + if (mob.InRange(Controller.PointB, 2)) + { + playerInfo.Score += 1; + } + } + } + + playerInfo = victInfo[mob]; + if (playerInfo != null) + { + playerInfo.Score -= 1; + } + } + } + + mob.SendGump(new DDBoardGump(mob, this)); + + m_Context.Requip(mob, corpse); + DelayBounce(TimeSpan.FromSeconds(30.0), mob, corpse); + + return false; + } + + public override void OnStart() + { + m_Capturable = true; + + _captureTimerToken.Cancel(); + _uncaptureTimerToken.Cancel(); + + for (var i = 0; i < Controller.TeamInfo.Length; ++i) + { + var teamInfo = Controller.TeamInfo[i]; + + teamInfo.Game = this; + teamInfo.Reset(); + } + + if (Controller.PointA != null) + { + Controller.PointA.Game = this; + } + + if (Controller.PointB != null) + { + Controller.PointB.Game = this; + } + + for (var i = 0; i < m_Context.Participants.Count; ++i) + { + ApplyHues( + m_Context.Participants[i], + Controller.TeamInfo[i % Controller.TeamInfo.Length].Color + ); + } + + _finishTimerToken.Cancel(); + 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((a, b) => b.Score - a.Score); + + var tourney = m_Context.m_Tournament; + + var sb = new StringBuilder(); + + 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'); } - item.Name = $"{item.Name}, {teams[i].Name.ToLower()} team"; + sb.Append(tourney.PlayersPerParticipant); + } + } + } + + if (Controller != null) + { + sb.Append(' ').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 = TrophyRank.Bronze; + + if (i == 0) + { + rank = TrophyRank.Gold; + } + else if (i == 1) + { + rank = TrophyRank.Silver; + } + + var leader = teams[i].Leader; + + foreach (var pl in teams[i].Players.Values) + { + var mob = pl.Player; + + if (mob == null) + { + continue; + } + + // "Red v Blue DD Champion" + + sb = new StringBuilder(); + + 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"); + + if (pl.Kills > 0) + { + sb.Append(", "); + 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 ? " capture" : " captures"); + } + } + + 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); } - var cash = pl.Score * 250; - - if (cash > 0) - { - item = new BankCheck(cash); - - if (!mob.PlaceInBackpack(item)) - { - mob.BankBox.DropItem(item); - } - - mob.SendMessage( - $"You have been awarded a {rank.ToString().ToLower()} trophy and {cash:N0}gp for your participation in this tournament." - ); - } - else - { - mob.SendMessage( - $"You have been awarded a {rank.ToString().ToLower()} trophy for your participation in this tournament." - ); - } + mob.SendMessage( + $"You have been awarded a {rank.ToString().ToLower()} trophy and {cash:N0}gp for your participation in this tournament." + ); } - } - - 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) + else { - var dp = p.Players[j]; - - if (dp?.Mobile != null) - { - dp.Mobile.SendGump(new DDBoardGump(dp.Mobile, this)); - } + mob.SendMessage( + $"You have been awarded a {rank.ToString().ToLower()} trophy for your participation in this tournament." + ); } - - if (i == winner?.TeamID) - { - continue; - } - - for (var j = 0; j < p.Players.Length; ++j) - { - if (p.Players[j] != null) - { - 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 < m_Context.Participants.Count; ++i) { - for (var i = 0; i < Controller.TeamInfo.Length; ++i) - { - var teamInfo = Controller.TeamInfo[i]; + var p = m_Context.Participants[i]; - if (teamInfo.Board != null) + for (var j = 0; j < p.Players.Length; ++j) + { + var dp = p.Players[j]; + + if (dp?.Mobile != null) { - teamInfo.Board.m_TeamInfo = null; + dp.Mobile.SendGump(new DDBoardGump(dp.Mobile, this)); } - - teamInfo.Game = null; } - if (Controller.PointA != null) + if (i == winner?.TeamID) { - Controller.PointA.Game = null; + continue; } - if (Controller.PointB != null) + for (var j = 0; j < p.Players.Length; ++j) { - Controller.PointB.Game = null; + if (p.Players[j] != null) + { + 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]; + + if (teamInfo.Board != null) + { + teamInfo.Board.m_TeamInfo = null; } - m_Capturable = false; + teamInfo.Game = null; + } + if (Controller.PointA != null) + { + Controller.PointA.Game = null; + } + + if (Controller.PointB != null) + { + Controller.PointB.Game = null; + } + + m_Capturable = false; + + _captureTimerToken.Cancel(); + _uncaptureTimerToken.Cancel(); + + for (var i = 0; i < m_Context.Participants.Count; ++i) + { + ApplyHues(m_Context.Participants[i], -1); + } + + _finishTimerToken.Cancel(); + } + + public void Dominate(DDWayPoint point, Mobile from, DDTeamInfo team) + { + if (point == null || from == null || team == null || !m_Capturable) + { + return; + } + + var wasDom = Controller.PointA?.TeamOwner == Controller.PointB?.TeamOwner && + Controller.PointA?.TeamOwner != null; + + point.TeamOwner = team; + Alert($"{team.Name} has captured {point.Name}!"); + + var isDom = Controller.PointA?.TeamOwner == Controller.PointB?.TeamOwner && Controller.PointA?.TeamOwner != null; + + if (wasDom && !isDom) + { + Alert("Domination averted!"); + + Controller.PointA?.SetNonCaptureHue(); + Controller.PointB?.SetNonCaptureHue(); _captureTimerToken.Cancel(); - _uncaptureTimerToken.Cancel(); - - for (var i = 0; i < m_Context.Participants.Count; ++i) - { - ApplyHues(m_Context.Participants[i], -1); - } - - _finishTimerToken.Cancel(); } - public void Dominate(DDWayPoint point, Mobile from, DDTeamInfo team) + if (!wasDom && isDom) { - if (point == null || from == null || team == null || !m_Capturable) - { - return; - } - - var wasDom = Controller.PointA?.TeamOwner == Controller.PointB?.TeamOwner && - Controller.PointA?.TeamOwner != null; - - point.TeamOwner = team; - Alert($"{team.Name} has captured {point.Name}!"); - - var isDom = Controller.PointA?.TeamOwner == Controller.PointB?.TeamOwner && Controller.PointA?.TeamOwner != null; - - if (wasDom && !isDom) - { - Alert("Domination averted!"); - - Controller.PointA?.SetNonCaptureHue(); - Controller.PointB?.SetNonCaptureHue(); - _captureTimerToken.Cancel(); - } - - if (!wasDom && isDom) - { - m_CapStage = 0; - Timer.StartTimer(TimeSpan.Zero, TimeSpan.FromSeconds(1.0), CaptureTick, out _captureTimerToken); - } + m_CapStage = 0; + Timer.StartTimer(TimeSpan.Zero, TimeSpan.FromSeconds(1.0), CaptureTick, out _captureTimerToken); } + } - private void CaptureTick() - { - var team = Controller.PointA?.TeamOwner ?? Controller.PointB?.TeamOwner; + private void CaptureTick() + { + var team = Controller.PointA?.TeamOwner ?? Controller.PointB?.TeamOwner; - if (team == null) - { - m_Capturable = true; - _captureTimerToken.Cancel(); - return; - } - - if (++m_CapStage < 10) - { - Alert($"{team.Name} is dominating... {10 - m_CapStage}"); - - Controller.PointA?.SetCaptureHue(m_CapStage); - Controller.PointB?.SetCaptureHue(m_CapStage); - } - else - { - Alert($"{team.Name} has scored!"); - - team.Score += 100; - team.Captures += 1; - - m_Capturable = false; - m_CapStage = 0; - _captureTimerToken.Cancel(); - - if (Controller.PointA != null) - { - Controller.PointA.TeamOwner = null; - Controller.PointA.SetUncapturableHue(); - } - - if (Controller.PointB != null) - { - Controller.PointB.TeamOwner = null; - Controller.PointB.SetUncapturableHue(); - } - - Timer.StartTimer(TimeSpan.FromSeconds(30.0), UncaptureTick, out _uncaptureTimerToken); - } - } - - private void UncaptureTick() + if (team == null) { m_Capturable = true; - _captureTimerToken.Cancel(); - _uncaptureTimerToken.Cancel(); + return; + } + + if (++m_CapStage < 10) + { + Alert($"{team.Name} is dominating... {10 - m_CapStage}"); + + Controller.PointA?.SetCaptureHue(m_CapStage); + Controller.PointB?.SetCaptureHue(m_CapStage); + } + else + { + Alert($"{team.Name} has scored!"); + + team.Score += 100; + team.Captures += 1; + + m_Capturable = false; + m_CapStage = 0; + _captureTimerToken.Cancel(); if (Controller.PointA != null) { Controller.PointA.TeamOwner = null; - Controller.PointA.SetNonCaptureHue(); + Controller.PointA.SetUncapturableHue(); } if (Controller.PointB != null) { Controller.PointB.TeamOwner = null; - Controller.PointB.SetNonCaptureHue(); + Controller.PointB.SetUncapturableHue(); } + + Timer.StartTimer(TimeSpan.FromSeconds(30.0), UncaptureTick, out _uncaptureTimerToken); } } - public class DDWayPoint : BaseAddon + private void UncaptureTick() { - public const int UncapturableHue = 0x497; - public const int NonCapturedHue = 0x38A; - private DDGame m_Game; - private DDTeamInfo m_TeamOwner; + m_Capturable = true; - [Constructible] - public DDWayPoint() + _captureTimerToken.Cancel(); + _uncaptureTimerToken.Cancel(); + + if (Controller.PointA != null) { - ItemID = 0x519; - Visible = true; - Name = "SET MY NAME"; - - AddComponent(new DDStep(0x7A8), -1, -1, -5); - AddComponent(new DDStep(0x7A6), 0, -1, -5); - AddComponent(new DDStep(0x7AA), 1, -1, -5); - - AddComponent(new DDStep(0x7A5), 1, 0, -5); - - AddComponent(new DDStep(0x7A9), 1, 1, -5); - AddComponent(new DDStep(0x7A4), 0, 1, -5); - AddComponent(new DDStep(0x7AB), -1, 1, -5); - - AddComponent(new DDStep(0x7A7), -1, 0, -5); - - SetUncapturableHue(); + Controller.PointA.TeamOwner = null; + Controller.PointA.SetNonCaptureHue(); } - public DDWayPoint(Serial serial) : base(serial) + if (Controller.PointB != null) { - } - - public override bool ShareHue => false; - - public DDGame Game - { - get => m_Game; - set - { - m_Game = value; - m_TeamOwner = null; - - if (m_Game != null) - { - SetNonCaptureHue(); - } - else - { - SetUncapturableHue(); - } - } - } - - public DDTeamInfo TeamOwner - { - get => m_TeamOwner; - set - { - m_TeamOwner = value; - - SetNonCaptureHue(); - } - } - - public override void Serialize(IGenericWriter writer) - { - base.Serialize(writer); - - writer.Write(0); - } - - public override void Deserialize(IGenericReader reader) - { - base.Deserialize(reader); - - var version = reader.ReadInt(); - } - - public void SetUncapturableHue() - { - for (var i = 0; i < Components.Count; i++) - { - Components[i].Hue = UncapturableHue; - } - - Hue = UncapturableHue; - } - - public void SetNonCaptureHue() - { - for (var i = 0; i < Components.Count; i++) - { - Components[i].Hue = NonCapturedHue; - } - - if (m_TeamOwner != null) - { - Hue = m_TeamOwner.Color; - } - else - { - Hue = NonCapturedHue; - } - } - - public void SetCaptureHue(int stage) - { - if (m_TeamOwner == null) - { - return; - } - - Hue = m_TeamOwner.Color; - - for (var i = 0; i < Components.Count; i++) - { - if (i < stage) - { - Components[i].Hue = m_TeamOwner.Color; - } - else - { - Components[i].Hue = NonCapturedHue; - } - } - } - - public override bool OnMoveOver(Mobile from) - { - if (m_Game == null) - { - SetUncapturableHue(); - } - else if (from.Alive) - { - var team = m_Game.GetTeamInfo(from); - - if (team != null && team != TeamOwner) - { - m_Game.Dominate(this, from, team); - } - } - - return true; - } - - public class DDStep : AddonComponent - { - public DDStep(int itemID) : base(itemID) => Visible = true; - - public DDStep(Serial serial) : base(serial) - { - } - - public override void Deserialize(IGenericReader reader) - { - base.Deserialize(reader); - - var version = reader.ReadInt(); - } - - public override void Serialize(IGenericWriter writer) - { - base.Serialize(writer); - - writer.Write(0); // version - } - - public override bool OnMoveOver(Mobile m) => Addon.OnMoveOver(m); + Controller.PointB.TeamOwner = null; + Controller.PointB.SetNonCaptureHue(); } } } + +[SerializationGenerator(0, false)] +public partial class DDWayPoint : BaseAddon +{ + public const int UncapturableHue = 0x497; + public const int NonCapturedHue = 0x38A; + private DDGame m_Game; + private DDTeamInfo m_TeamOwner; + + [Constructible] + public DDWayPoint() + { + ItemID = 0x519; + Visible = true; + Name = "SET MY NAME"; + + AddComponent(new DDStep(0x7A8), -1, -1, -5); + AddComponent(new DDStep(0x7A6), 0, -1, -5); + AddComponent(new DDStep(0x7AA), 1, -1, -5); + + AddComponent(new DDStep(0x7A5), 1, 0, -5); + + AddComponent(new DDStep(0x7A9), 1, 1, -5); + AddComponent(new DDStep(0x7A4), 0, 1, -5); + AddComponent(new DDStep(0x7AB), -1, 1, -5); + + AddComponent(new DDStep(0x7A7), -1, 0, -5); + + SetUncapturableHue(); + } + + public override bool ShareHue => false; + + public DDGame Game + { + get => m_Game; + set + { + m_Game = value; + m_TeamOwner = null; + + if (m_Game != null) + { + SetNonCaptureHue(); + } + else + { + SetUncapturableHue(); + } + } + } + + public DDTeamInfo TeamOwner + { + get => m_TeamOwner; + set + { + m_TeamOwner = value; + + SetNonCaptureHue(); + } + } + + public void SetUncapturableHue() + { + for (var i = 0; i < Components.Count; i++) + { + Components[i].Hue = UncapturableHue; + } + + Hue = UncapturableHue; + } + + public void SetNonCaptureHue() + { + for (var i = 0; i < Components.Count; i++) + { + Components[i].Hue = NonCapturedHue; + } + + if (m_TeamOwner != null) + { + Hue = m_TeamOwner.Color; + } + else + { + Hue = NonCapturedHue; + } + } + + public void SetCaptureHue(int stage) + { + if (m_TeamOwner == null) + { + return; + } + + Hue = m_TeamOwner.Color; + + for (var i = 0; i < Components.Count; i++) + { + if (i < stage) + { + Components[i].Hue = m_TeamOwner.Color; + } + else + { + Components[i].Hue = NonCapturedHue; + } + } + } + + public override bool OnMoveOver(Mobile from) + { + if (m_Game == null) + { + SetUncapturableHue(); + } + else if (from.Alive) + { + var team = m_Game.GetTeamInfo(from); + + if (team != null && team != TeamOwner) + { + m_Game.Dominate(this, from, team); + } + } + + return true; + } + + [SerializationGenerator(0, false)] + public partial class DDStep : AddonComponent + { + public DDStep(int itemID) : base(itemID) => Visible = true; + + public override bool OnMoveOver(Mobile m) => Addon.OnMoveOver(m); + } +} diff --git a/Projects/UOContent/Engines/ConPVP/Games/EventGame.cs b/Projects/UOContent/Engines/ConPVP/Games/EventGame.cs index fecc2fdd4..623a25f72 100644 --- a/Projects/UOContent/Engines/ConPVP/Games/EventGame.cs +++ b/Projects/UOContent/Engines/ConPVP/Games/EventGame.cs @@ -1,70 +1,51 @@ +using ModernUO.Serialization; using Server.Gumps; using Server.Items; -namespace Server.Engines.ConPVP +namespace Server.Engines.ConPVP; + +[SerializationGenerator(0, false)] +public abstract partial class EventController : Item { - public abstract class EventController : Item + public EventController() : base(0x1B7A) { - 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(IGenericWriter writer) - { - base.Serialize(writer); - - writer.Write(0); - } - - public override void Deserialize(IGenericReader reader) - { - base.Deserialize(reader); - - var version = reader.ReadInt(); - } - - public override void OnDoubleClick(Mobile from) - { - if (from.AccessLevel >= AccessLevel.GameMaster) - { - from.SendGump(new PropertiesGump(from, this)); - } - } + Visible = false; + Movable = false; } - public abstract class EventGame + public abstract string Title { get; } + public abstract EventGame Construct(DuelContext dc); + + public abstract string GetTeamName(int teamID); + + public override void OnDoubleClick(Mobile from) { - 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) => true; - - public virtual bool CantDoAnything(Mobile mob) => false; - - public virtual void OnStart() - { - } - - public virtual void OnStop() + 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) => true; + + public virtual bool CantDoAnything(Mobile mob) => false; + + public virtual void OnStart() + { + } + + public virtual void OnStop() + { + } +} diff --git a/Projects/UOContent/Engines/ConPVP/Games/KingOfTheHill.cs b/Projects/UOContent/Engines/ConPVP/Games/KingOfTheHill.cs index f1d20e90f..e570c4f22 100644 --- a/Projects/UOContent/Engines/ConPVP/Games/KingOfTheHill.cs +++ b/Projects/UOContent/Engines/ConPVP/Games/KingOfTheHill.cs @@ -1,1260 +1,1198 @@ using System; using System.Collections.Generic; using System.Text; +using ModernUO.Serialization; using Server.Gumps; using Server.Items; using Server.Mobiles; -namespace Server.Engines.ConPVP +namespace Server.Engines.ConPVP; + +[SerializationGenerator(0, false)] +public partial class HillOfTheKing : Item { - public class HillOfTheKing : Item + private KHGame m_Game; + + [SerializedCommandProperty(AccessLevel.GameMaster)] + private KingTimer m_KingTimer; + + [EncodedInt] + [SerializableField(0)] + private int _scoreInterval; + + [Constructible] + public HillOfTheKing() : base(0x520) { - private KHGame m_Game; - private KingTimer m_KingTimer; + m_Game = null; + King = null; + Movable = false; - [Constructible] - public HillOfTheKing() - : base(0x520) + Name = "the hill"; + } + + public Mobile King { get; private set; } + + public KHGame Game + { + get => m_Game; + set { - ScoreInterval = 10; - m_Game = null; - King = null; - Movable = false; - - Name = "the hill"; - } - - public HillOfTheKing(Serial s) : base(s) - { - } - - public Mobile King { get; private set; } - - public KHGame Game - { - get => m_Game; - set + if (m_Game != value) { - if (m_Game != value) - { - m_KingTimer?.Stop(); - m_Game = value; - King = null; - } + m_KingTimer?.Stop(); + m_Game = value; + King = null; } } + } - [CommandProperty(AccessLevel.GameMaster)] - public int ScoreInterval { get; set; } + public int CapturesSoFar => m_KingTimer?.Captures ?? 0; - public int CapturesSoFar => m_KingTimer?.Captures ?? 0; - - public override void Deserialize(IGenericReader reader) + private bool CanBeKing(Mobile m) + { + // Game running? + if (m_Game == null) { - base.Deserialize(reader); - - var version = reader.ReadInt(); - - switch (version) - { - case 0: - { - ScoreInterval = reader.ReadEncodedInt(); - break; - } - } - } - - public override void Serialize(IGenericWriter writer) - { - base.Serialize(writer); - - writer.Write(0); // version - - writer.WriteEncodedInt(ScoreInterval); - } - - private bool CanBeKing(Mobile m) - { - // Game running? - if (m_Game == null) - { - return false; - } - - // Mobile exists and is alive and is a player? - if (m?.Deleted != false || !m.Alive || !m.Player) - { - return false; - } - - // Not current king (or they are the current king) - if (King != null && King != m) - { - return false; - } - - // They are on a team - return m_Game.GetTeamInfo(m) != null; - } - - public override bool OnMoveOver(Mobile m) - { - if (m_Game == null || m?.Alive != true) - { - return base.OnMoveOver(m); - } - - if (CanBeKing(m)) - { - if (base.OnMoveOver(m)) - { - ReKingify(m); - return true; - } - } - else - { - // Decrease their stam a little so they don't keep pushing someone out of the way - if (m.AccessLevel == AccessLevel.Player && m.Stam >= m.StamMax) - { - m.Stam -= 5; - } - } - return false; } - public override bool OnMoveOff(Mobile m) + // Mobile exists and is alive and is a player? + if (m?.Deleted != false || !m.Alive || !m.Player) { - if (base.OnMoveOff(m)) - { - if (King == m) - { - DeKingify(); - } + return false; + } + // Not current king (or they are the current king) + if (King != null && King != m) + { + return false; + } + + // They are on a team + return m_Game.GetTeamInfo(m) != null; + } + + public override bool OnMoveOver(Mobile m) + { + if (m_Game == null || m?.Alive != true) + { + return base.OnMoveOver(m); + } + + if (CanBeKing(m)) + { + if (base.OnMoveOver(m)) + { + ReKingify(m); return true; } - - return false; } - - public virtual void OnKingDied(Mobile king, KHTeamInfo kingTeam, Mobile killer, KHTeamInfo killerTeam) + else { - if (m_Game != null && CapturesSoFar > 0 && killer != null && king != null && kingTeam != null && - killerTeam != null) + // Decrease their stam a little so they don't keep pushing someone out of the way + if (m.AccessLevel == AccessLevel.Player && m.Stam >= m.StamMax) { - var kingName = king.Name ?? ""; - var killerName = killer.Name ?? ""; - - m_Game.Alert($"{kingName} ({kingTeam.Name}) was dethroned by {killerName} ({killerTeam.Name})!"); - } - - DeKingify(); - } - - private void DeKingify() - { - PublicOverheadMessage(MessageType.Regular, 0x0481, false, "Free!"); - - m_KingTimer?.Stop(); - - King = null; - } - - private void ReKingify(Mobile m) - { - if (m_Game == null || m == null) - { - return; - } - - if (m_Game.GetTeamInfo(m) == null) - { - return; - } - - King = m; - - m_KingTimer ??= new KingTimer(this); - m_KingTimer.Stop(); - m_KingTimer.StartHillTicker(); - - if (King.Name != null) - { - PublicOverheadMessage(MessageType.Regular, 0x0481, false, $"Taken by {King.Name}!"); + m.Stam -= 5; } } - private class KingTimer : Timer + return false; + } + + public override bool OnMoveOff(Mobile m) + { + if (base.OnMoveOff(m)) { - private readonly HillOfTheKing m_Hill; - private int m_Counter; - - public KingTimer(HillOfTheKing hill) - : base(TimeSpan.FromSeconds(1.0), TimeSpan.FromSeconds(1.0)) + if (King == m) { - m_Hill = hill; - Captures = 0; - m_Counter = 0; + DeKingify(); } - public int Captures { get; private set; } + return true; + } - public void StartHillTicker() - { - Captures = 0; - m_Counter = 0; + return false; + } - Start(); - } + public virtual void OnKingDied(Mobile king, KHTeamInfo kingTeam, Mobile killer, KHTeamInfo killerTeam) + { + if (m_Game != null && CapturesSoFar > 0 && killer != null && king != null && kingTeam != null && + killerTeam != null) + { + var kingName = king.Name ?? ""; + var killerName = killer.Name ?? ""; - protected override void OnTick() - { - KHPlayerInfo pi = null; + m_Game.Alert($"{kingName} ({kingTeam.Name}) was dethroned by {killerName} ({killerTeam.Name})!"); + } - if (m_Hill?.Deleted != false || m_Hill.Game == null) - { - Stop(); - return; - } + DeKingify(); + } - if (m_Hill.King?.Deleted != false || !m_Hill.King.Alive) - { - m_Hill.DeKingify(); - Stop(); - return; - } + private void DeKingify() + { + PublicOverheadMessage(MessageType.Regular, 0x0481, false, "Free!"); - var ti = m_Hill.Game.GetTeamInfo(m_Hill.King); - if (ti != null) - { - pi = ti[m_Hill.King]; - } + m_KingTimer?.Stop(); - if (ti == null || pi == null) - { - // error, bail - m_Hill.DeKingify(); - Stop(); - return; - } + King = null; + } - m_Counter++; + private void ReKingify(Mobile m) + { + if (m_Game == null || m == null) + { + return; + } - m_Hill.King.RevealingAction(); + if (m_Game.GetTeamInfo(m) == null) + { + return; + } - if (m_Counter >= m_Hill.ScoreInterval) - { - var hill = m_Hill.Name.DefaultIfNullOrEmpty("the hill"); - var king = m_Hill.King.Name ?? ""; + King = m; - m_Hill.Game.Alert($"{king} ({ti.Name}) is king of {hill}!"); + m_KingTimer ??= new KingTimer(this); + m_KingTimer.Stop(); + m_KingTimer.StartHillTicker(); - m_Hill.PublicOverheadMessage(MessageType.Regular, 0x0481, false, "Capture!"); - - pi.Captures++; - Captures++; - - pi.Score += m_Counter; - - m_Counter = 0; - } - else - { - m_Hill.PublicOverheadMessage( - MessageType.Regular, - 0x0481, - false, - (m_Hill.ScoreInterval - m_Counter).ToString() - ); - } - } + if (King.Name != null) + { + PublicOverheadMessage(MessageType.Regular, 0x0481, false, $"Taken by {King.Name}!"); } } - public class KHBoard : Item + private class KingTimer : Timer { - private KHController m_Controller; - public KHGame m_Game; + private readonly HillOfTheKing m_Hill; + private int m_Counter; - [Constructible] - public KHBoard() - : base(7774) + public KingTimer(HillOfTheKing hill) + : base(TimeSpan.FromSeconds(1.0), TimeSpan.FromSeconds(1.0)) { - Name = "King of the Hill Scoreboard"; - Movable = false; + m_Hill = hill; + Captures = 0; + m_Counter = 0; } - public KHBoard(Serial serial) - : base(serial) + public int Captures { get; private set; } + + public void StartHillTicker() { + Captures = 0; + m_Counter = 0; + + Start(); } - [CommandProperty(AccessLevel.GameMaster)] - public KHController Controller + protected override void OnTick() { - get => m_Controller; - set + KHPlayerInfo pi = null; + + if (m_Hill?.Deleted != false || m_Hill.Game == null) { - if (m_Controller != value) - { - m_Controller?.RemoveBoard(this); - m_Controller = value; - m_Controller?.AddBoard(this); - } + Stop(); + return; } - } - public override void OnDoubleClick(Mobile from) - { - if (m_Game != null) + if (m_Hill.King?.Deleted != false || !m_Hill.King.Alive) { - from.SendGump(new KHBoardGump(from, m_Game)); + m_Hill.DeKingify(); + Stop(); + return; + } + + var ti = m_Hill.Game.GetTeamInfo(m_Hill.King); + if (ti != null) + { + pi = ti[m_Hill.King]; + } + + if (ti == null || pi == null) + { + // error, bail + m_Hill.DeKingify(); + Stop(); + return; + } + + m_Counter++; + + m_Hill.King.RevealingAction(); + + if (m_Counter >= m_Hill.ScoreInterval) + { + var hill = m_Hill.Name.DefaultIfNullOrEmpty("the hill"); + var king = m_Hill.King.Name ?? ""; + + m_Hill.Game.Alert($"{king} ({ti.Name}) is king of {hill}!"); + + m_Hill.PublicOverheadMessage(MessageType.Regular, 0x0481, false, "Capture!"); + + pi.Captures++; + Captures++; + + pi.Score += m_Counter; + + m_Counter = 0; } else { - from.SendMessage("There is no King of the Hill game in progress."); - } - } - - public override void Serialize(IGenericWriter writer) - { - base.Serialize(writer); - - writer.Write(0); - - writer.Write(m_Controller); - } - - public override void Deserialize(IGenericReader reader) - { - base.Deserialize(reader); - - var version = reader.ReadInt(); - - switch (version) - { - case 0: - { - m_Controller = reader.ReadEntity(); - break; - } - } - } - } - - public sealed class KHBoardGump : Gump - { - private const int LabelColor32 = 0xFFFFFF; - private const int BlackColor32 = 0x000000; - - private KHGame m_Game; - - public override bool Singleton => true; - - public KHBoardGump(Mobile mob, KHGame game) - : base(60, 60) - { - m_Game = game; - - var ourTeam = game.GetTeamInfo(mob); - - var entries = new List(); - - for (var i = 0; i < game.Context.Participants.Count; ++i) - { - var teamInfo = game.Controller.TeamInfo[i % game.Controller.TeamInfo.Length]; - - if (teamInfo != null) - { - entries.Add(teamInfo); - } - } - - entries.Sort(); - /* - delegate( IRankedCTF a, IRankedCTF b ) - { - return b.Score - a.Score; - } );*/ - - var height = 73 + entries.Count * 75 + 28; - - Closable = false; - - AddPage(0); - - AddBackground(1, 1, 398, height, 3600); - - AddImageTiled(16, 15, 369, height - 29, 3604); - - for (var i = 0; i < entries.Count; i += 1) - { - AddImageTiled(22, 58 + i * 75, 357, 70, 0x2430); - } - - AddAlphaRegion(16, 15, 369, height - 29); - - AddImage(215, -45, 0xEE40); - // AddImage( 330, 141, 0x8BA ); - - AddBorderedText(22, 22, 294, 20, "King of the Hill Scoreboard".Center(), LabelColor32, BlackColor32); - - AddImageTiled(32, 50, 264, 1, 9107); - AddImageTiled(42, 52, 264, 1, 9157); - - for (var i = 0; i < entries.Count; ++i) - { - var teamInfo = entries[i]; - - AddImage(30, 70 + i * 75, 10152); - AddImage(30, 85 + i * 75, 10151); - AddImage(30, 100 + i * 75, 10151); - AddImage(30, 106 + i * 75, 10154); - - 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( - 60, - 65 + i * 75, - 250, - 20, - $"{LadderGump.Rank(1 + i)}: {teamInfo.Name}", - nameColor, - borderColor + m_Hill.PublicOverheadMessage( + MessageType.Regular, + 0x0481, + false, + (m_Hill.ScoreInterval - m_Counter).ToString() ); - - AddBorderedText(50 + 10, 85 + i * 75, 100, 20, "Score:", 0xFFC000, BlackColor32); - AddBorderedText(50 + 15, 105 + i * 75, 100, 20, teamInfo.Score.ToString("N0"), 0xFFC000, BlackColor32); - - AddBorderedText(110 + 10, 85 + i * 75, 100, 20, "Kills:", 0xFFC000, BlackColor32); - AddBorderedText(110 + 15, 105 + i * 75, 100, 20, teamInfo.Kills.ToString("N0"), 0xFFC000, BlackColor32); - - AddBorderedText(160 + 10, 85 + i * 75, 100, 20, "Captures:", 0xFFC000, BlackColor32); - AddBorderedText(160 + 15, 105 + i * 75, 100, 20, teamInfo.Captures.ToString("N0"), 0xFFC000, BlackColor32); - - var leader = teamInfo.Leader?.Name ?? "(none)"; - - AddBorderedText(235 + 10, 85 + i * 75, 250, 20, "Leader:", 0xFFC000, BlackColor32); - AddBorderedText(235 + 15, 105 + i * 75, 250, 20, leader, 0xFFC000, BlackColor32); - } - - AddButton(314, height - 42, 247, 248, 1); - } - - 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) - { - AddHtml(x, y, width, height, color == 0 ? text : text.Color(color)); - } - } - - public sealed class KHPlayerInfo : IRankedCTF, IComparable - { - private readonly KHTeamInfo m_TeamInfo; - private int m_Captures; - - private int m_Kills; - private int m_Score; - - public KHPlayerInfo(KHTeamInfo teamInfo, Mobile player) - { - m_TeamInfo = teamInfo; - Player = player; - } - - public Mobile Player { get; } - - public int CompareTo(KHPlayerInfo pi) - { - var res = pi.Score.CompareTo(Score); - if (res != 0) - { - return res; - } - - res = pi.Captures.CompareTo(Captures); - - return res != 0 ? res : pi.Kills.CompareTo(Kills); - } - - 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 KHTeamInfo : IRankedCTF, IComparable +[SerializationGenerator(0, false)] +public partial class KHBoard : Item +{ + [SerializedCommandProperty(AccessLevel.GameMaster)] + [SerializableField(0)] + private KHController _controller; + + [SerializableFieldChanged(0)] + private void OnControllerChanged(KHController oldValue, KHController newValue) { - public KHTeamInfo(int teamID) + oldValue?.RemoveBoard(this); + newValue?.AddBoard(this); + } + + public KHGame m_Game; + + [Constructible] + public KHBoard() : base(7774) + { + Name = "King of the Hill Scoreboard"; + Movable = false; + } + + public override void OnDoubleClick(Mobile from) + { + if (m_Game != null) { - TeamID = teamID; - Players = new Dictionary(); + from.SendGump(new KHBoardGump(from, m_Game)); } - - public KHTeamInfo(int teamID, IGenericReader ip) + else { - TeamID = teamID; - Players = new Dictionary(); + from.SendMessage("There is no King of the Hill game in progress."); + } + } +} - var version = ip.ReadEncodedInt(); +public sealed class KHBoardGump : Gump +{ + private const int LabelColor32 = 0xFFFFFF; + private const int BlackColor32 = 0x000000; - switch (version) + private KHGame m_Game; + + public override bool Singleton => true; + + public KHBoardGump(Mobile mob, KHGame game) + : base(60, 60) + { + m_Game = game; + + var ourTeam = game.GetTeamInfo(mob); + + var entries = new List(); + + for (var i = 0; i < game.Context.Participants.Count; ++i) + { + var teamInfo = game.Controller.TeamInfo[i % game.Controller.TeamInfo.Length]; + + if (teamInfo != null) { - case 0: - { - TeamName = ip.ReadString(); - Color = ip.ReadEncodedInt(); - break; - } + entries.Add(teamInfo); } } - public KHGame Game { get; set; } - - public int TeamID { get; } - - public KHPlayerInfo Leader { get; set; } - - public Dictionary Players { get; } - - public KHPlayerInfo this[Mobile mob] + entries.Sort(); + /* + delegate( IRankedCTF a, IRankedCTF b ) { - get - { - if (mob == null) - { - return null; - } + return b.Score - a.Score; + } );*/ - if (!Players.TryGetValue(mob, out var val)) - { - Players[mob] = val = new KHPlayerInfo(this, mob); - } + var height = 73 + entries.Count * 75 + 28; - return val; - } + Closable = false; + + AddPage(0); + + AddBackground(1, 1, 398, height, 3600); + + AddImageTiled(16, 15, 369, height - 29, 3604); + + for (var i = 0; i < entries.Count; i += 1) + { + AddImageTiled(22, 58 + i * 75, 357, 70, 0x2430); } - [CommandProperty(AccessLevel.GameMaster)] - public int Color { get; set; } + AddAlphaRegion(16, 15, 369, height - 29); - [CommandProperty(AccessLevel.GameMaster)] - public string TeamName { get; set; } + AddImage(215, -45, 0xEE40); + // AddImage( 330, 141, 0x8BA ); - public int CompareTo(KHTeamInfo ti) + AddBorderedText(22, 22, 294, 20, "King of the Hill Scoreboard".Center(), LabelColor32, BlackColor32); + + AddImageTiled(32, 50, 264, 1, 9107); + AddImageTiled(42, 52, 264, 1, 9157); + + for (var i = 0; i < entries.Count; ++i) { - var res = ti.Score.CompareTo(Score); - if (res != 0) + var teamInfo = entries[i]; + + AddImage(30, 70 + i * 75, 10152); + AddImage(30, 85 + i * 75, 10151); + AddImage(30, 100 + i * 75, 10151); + AddImage(30, 106 + i * 75, 10154); + + AddImage(24, 60 + i * 75, teamInfo == ourTeam ? 9730 : 9727, teamInfo.Color - 1); + + var borderColor = teamInfo.Color == 0x455 ? LabelColor32 : BlackColor32; + var nameColor = teamInfo.Color switch { - return res; - } + 0x47E => 0xFFFFFF, + 0x4F2 => 0x3399FF, + 0x4F7 => 0x33FF33, + 0x4FC => 0xFF00FF, + 0x021 => 0xFF3333, + 0x01A => 0xFF66FF, + 0x455 => 0x333333, + _ => LabelColor32 + }; - res = ti.Captures.CompareTo(Captures); + AddBorderedText( + 60, + 65 + i * 75, + 250, + 20, + $"{LadderGump.Rank(1 + i)}: {teamInfo.Name}", + nameColor, + borderColor + ); - if (res == 0) - { - res = ti.Kills.CompareTo(Kills); - } + AddBorderedText(50 + 10, 85 + i * 75, 100, 20, "Score:", 0xFFC000, BlackColor32); + AddBorderedText(50 + 15, 105 + i * 75, 100, 20, teamInfo.Score.ToString("N0"), 0xFFC000, BlackColor32); + AddBorderedText(110 + 10, 85 + i * 75, 100, 20, "Kills:", 0xFFC000, BlackColor32); + AddBorderedText(110 + 15, 105 + i * 75, 100, 20, teamInfo.Kills.ToString("N0"), 0xFFC000, BlackColor32); + + AddBorderedText(160 + 10, 85 + i * 75, 100, 20, "Captures:", 0xFFC000, BlackColor32); + AddBorderedText(160 + 15, 105 + i * 75, 100, 20, teamInfo.Captures.ToString("N0"), 0xFFC000, BlackColor32); + + var leader = teamInfo.Leader?.Name ?? "(none)"; + + AddBorderedText(235 + 10, 85 + i * 75, 250, 20, "Leader:", 0xFFC000, BlackColor32); + AddBorderedText(235 + 15, 105 + i * 75, 250, 20, leader, 0xFFC000, BlackColor32); + } + + AddButton(314, height - 42, 247, 248, 1); + } + + 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) + { + AddHtml(x, y, width, height, color == 0 ? text : text.Color(color)); + } +} + +public sealed class KHPlayerInfo : IRankedCTF, IComparable +{ + private readonly KHTeamInfo m_TeamInfo; + private int m_Captures; + + private int m_Kills; + private int m_Score; + + public KHPlayerInfo(KHTeamInfo teamInfo, Mobile player) + { + m_TeamInfo = teamInfo; + Player = player; + } + + public Mobile Player { get; } + + public int CompareTo(KHPlayerInfo pi) + { + var res = pi.Score.CompareTo(Score); + if (res != 0) + { return res; } - public string Name => $"{TeamName ?? "(none)"} Team"; + res = pi.Captures.CompareTo(Captures); - 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(); - } - - public void Serialize(IGenericWriter op) - { - op.WriteEncodedInt(0); // version - - op.Write(TeamName); - op.WriteEncodedInt(Color); - } - - public override string ToString() => TeamName != null ? $"({Name}) ..." : "..."; + return res != 0 ? res : pi.Kills.CompareTo(Kills); } - public sealed class KHController : EventController + public string Name => Player.Name ?? ""; + + public int Kills { - private int m_ScoreInterval; - - [Constructible] - public KHController() + get => m_Kills; + set { - Visible = false; - Movable = false; + m_TeamInfo.Kills += value - m_Kills; + m_Kills = value; + } + } - Name = "King of the Hill Controller"; + public int Captures + { + get => m_Captures; + set + { + m_TeamInfo.Captures += value - m_Captures; + m_Captures = value; + } + } - Duration = TimeSpan.FromMinutes(30.0); - Boards = new List(); - Hills = new HillOfTheKing[4]; - TeamInfo = new KHTeamInfo[8]; + public int Score + { + get => m_Score; + set + { + m_TeamInfo.Score += value - m_Score; + m_Score = value; - for (var i = 0; i < TeamInfo.Length; ++i) + if (m_TeamInfo.Leader == null || m_Score > m_TeamInfo.Leader.Score) { - TeamInfo[i] = new KHTeamInfo(i); + m_TeamInfo.Leader = this; } } + } +} - public KHController(Serial serial) - : base(serial) +[PropertyObject] +public sealed class KHTeamInfo : IRankedCTF, IComparable +{ + public KHTeamInfo(int teamID) + { + TeamID = teamID; + Players = new Dictionary(); + } + + public KHTeamInfo(int teamID, IGenericReader ip) + { + TeamID = teamID; + Players = new Dictionary(); + + var version = ip.ReadEncodedInt(); + + switch (version) { + case 0: + { + TeamName = ip.ReadString(); + Color = ip.ReadEncodedInt(); + break; + } } + } - public KHTeamInfo[] TeamInfo { get; private set; } + public KHGame Game { get; set; } - [CommandProperty(AccessLevel.GameMaster)] - public KHTeamInfo Team1_W => TeamInfo[0]; + public int TeamID { get; } - [CommandProperty(AccessLevel.GameMaster)] - public KHTeamInfo Team2_E => TeamInfo[1]; + public KHPlayerInfo Leader { get; set; } - [CommandProperty(AccessLevel.GameMaster)] - public KHTeamInfo Team3_N => TeamInfo[2]; + public Dictionary Players { get; } - [CommandProperty(AccessLevel.GameMaster)] - public KHTeamInfo Team4_S => TeamInfo[3]; - - [CommandProperty(AccessLevel.GameMaster)] - public KHTeamInfo Team5_NW => TeamInfo[4]; - - [CommandProperty(AccessLevel.GameMaster)] - public KHTeamInfo Team6_SE => TeamInfo[5]; - - [CommandProperty(AccessLevel.GameMaster)] - public KHTeamInfo Team7_SW => TeamInfo[6]; - - [CommandProperty(AccessLevel.GameMaster)] - public KHTeamInfo Team8_NE => TeamInfo[7]; - - public HillOfTheKing[] Hills { get; private set; } - - [CommandProperty(AccessLevel.GameMaster)] - public HillOfTheKing Hill1 + public KHPlayerInfo this[Mobile mob] + { + get { - get => Hills[0]; - set => Hills[0] = value; - } - - [CommandProperty(AccessLevel.GameMaster)] - public HillOfTheKing Hill2 - { - get => Hills[1]; - set => Hills[1] = value; - } - - [CommandProperty(AccessLevel.GameMaster)] - public HillOfTheKing Hill3 - { - get => Hills[2]; - set => Hills[2] = value; - } - - [CommandProperty(AccessLevel.GameMaster)] - public HillOfTheKing Hill4 - { - get => Hills[3]; - set => Hills[3] = value; - } - - public List Boards { get; private set; } - - [CommandProperty(AccessLevel.GameMaster)] - public TimeSpan Duration { get; set; } - - public override string Title => "King of the Hill"; - - public override string GetTeamName(int teamID) => TeamInfo[teamID % TeamInfo.Length].Name; - - public override EventGame Construct(DuelContext context) => new KHGame(this, context); - - public void RemoveBoard(KHBoard b) - { - if (b != null) + if (mob == null) { - Boards.Remove(b); - b.m_Game = null; - } - } - - public void AddBoard(KHBoard b) - { - if (b != null) - { - Boards.Add(b); - } - } - - public override void Serialize(IGenericWriter writer) - { - base.Serialize(writer); - - writer.Write(0); - - writer.WriteEncodedInt(m_ScoreInterval); - writer.Write(Duration); - - Boards.Tidy(); - writer.Write(Boards); - - writer.WriteEncodedInt(Hills.Length); - for (var i = 0; i < Hills.Length; ++i) - { - writer.Write(Hills[i]); + return null; } - writer.WriteEncodedInt(TeamInfo.Length); - for (var i = 0; i < TeamInfo.Length; ++i) + if (!Players.TryGetValue(mob, out var val)) { - TeamInfo[i].Serialize(writer); + Players[mob] = val = new KHPlayerInfo(this, mob); } + + return val; + } + } + + [CommandProperty(AccessLevel.GameMaster)] + public int Color { get; set; } + + [CommandProperty(AccessLevel.GameMaster)] + public string TeamName { get; set; } + + public int CompareTo(KHTeamInfo ti) + { + var res = ti.Score.CompareTo(Score); + if (res != 0) + { + return res; } - public override void Deserialize(IGenericReader reader) + res = ti.Captures.CompareTo(Captures); + + if (res == 0) { - base.Deserialize(reader); + res = ti.Kills.CompareTo(Kills); + } - var version = reader.ReadInt(); + return res; + } - switch (version) - { - case 0: + public string Name => $"{TeamName ?? "(none)"} 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(); + } + + public void Serialize(IGenericWriter op) + { + op.WriteEncodedInt(0); // version + + op.Write(TeamName); + op.WriteEncodedInt(Color); + } + + public override string ToString() => TeamName != null ? $"({Name}) ..." : "..."; +} + +public sealed class KHController : EventController +{ + private int m_ScoreInterval; + + [Constructible] + public KHController() + { + Visible = false; + Movable = false; + + Name = "King of the Hill Controller"; + + Duration = TimeSpan.FromMinutes(30.0); + Boards = new List(); + Hills = new HillOfTheKing[4]; + TeamInfo = new KHTeamInfo[8]; + + for (var i = 0; i < TeamInfo.Length; ++i) + { + TeamInfo[i] = new KHTeamInfo(i); + } + } + + public KHController(Serial serial) + : base(serial) + { + } + + public KHTeamInfo[] TeamInfo { get; private set; } + + [CommandProperty(AccessLevel.GameMaster)] + public KHTeamInfo Team1_W => TeamInfo[0]; + + [CommandProperty(AccessLevel.GameMaster)] + public KHTeamInfo Team2_E => TeamInfo[1]; + + [CommandProperty(AccessLevel.GameMaster)] + public KHTeamInfo Team3_N => TeamInfo[2]; + + [CommandProperty(AccessLevel.GameMaster)] + public KHTeamInfo Team4_S => TeamInfo[3]; + + [CommandProperty(AccessLevel.GameMaster)] + public KHTeamInfo Team5_NW => TeamInfo[4]; + + [CommandProperty(AccessLevel.GameMaster)] + public KHTeamInfo Team6_SE => TeamInfo[5]; + + [CommandProperty(AccessLevel.GameMaster)] + public KHTeamInfo Team7_SW => TeamInfo[6]; + + [CommandProperty(AccessLevel.GameMaster)] + public KHTeamInfo Team8_NE => TeamInfo[7]; + + public HillOfTheKing[] Hills { get; private set; } + + [CommandProperty(AccessLevel.GameMaster)] + public HillOfTheKing Hill1 + { + get => Hills[0]; + set => Hills[0] = value; + } + + [CommandProperty(AccessLevel.GameMaster)] + public HillOfTheKing Hill2 + { + get => Hills[1]; + set => Hills[1] = value; + } + + [CommandProperty(AccessLevel.GameMaster)] + public HillOfTheKing Hill3 + { + get => Hills[2]; + set => Hills[2] = value; + } + + [CommandProperty(AccessLevel.GameMaster)] + public HillOfTheKing Hill4 + { + get => Hills[3]; + set => Hills[3] = value; + } + + public List Boards { get; private set; } + + [CommandProperty(AccessLevel.GameMaster)] + public TimeSpan Duration { get; set; } + + public override string Title => "King of the Hill"; + + public override string GetTeamName(int teamID) => TeamInfo[teamID % TeamInfo.Length].Name; + + public override EventGame Construct(DuelContext context) => new KHGame(this, context); + + public void RemoveBoard(KHBoard b) + { + if (b != null) + { + Boards.Remove(b); + b.m_Game = null; + } + } + + public void AddBoard(KHBoard b) + { + if (b != null) + { + Boards.Add(b); + } + } + + public override void Serialize(IGenericWriter writer) + { + base.Serialize(writer); + + writer.Write(0); + + writer.WriteEncodedInt(m_ScoreInterval); + writer.Write(Duration); + + Boards.Tidy(); + writer.Write(Boards); + + writer.WriteEncodedInt(Hills.Length); + for (var i = 0; i < Hills.Length; ++i) + { + writer.Write(Hills[i]); + } + + 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: + { + m_ScoreInterval = reader.ReadEncodedInt(); + + Duration = reader.ReadTimeSpan(); + + Boards = reader.ReadEntityList(); + + Hills = new HillOfTheKing[reader.ReadEncodedInt()]; + for (var i = 0; i < Hills.Length; ++i) { - m_ScoreInterval = reader.ReadEncodedInt(); - - Duration = reader.ReadTimeSpan(); - - Boards = reader.ReadEntityList(); - - Hills = new HillOfTheKing[reader.ReadEncodedInt()]; - for (var i = 0; i < Hills.Length; ++i) - { - Hills[i] = reader.ReadEntity(); - } - - TeamInfo = new KHTeamInfo[reader.ReadEncodedInt()]; - for (var i = 0; i < TeamInfo.Length; ++i) - { - TeamInfo[i] = new KHTeamInfo(i, reader); - } - - break; + Hills[i] = reader.ReadEntity(); } + + TeamInfo = new KHTeamInfo[reader.ReadEncodedInt()]; + for (var i = 0; i < TeamInfo.Length; ++i) + { + TeamInfo[i] = new KHTeamInfo(i, reader); + } + + break; + } + } + } +} + +public sealed class KHGame : EventGame +{ + private TimerExecutionToken _finishTimerToken; + + public KHGame(KHController controller, DuelContext context) : base(context) => Controller = controller; + + public KHController Controller { get; } + + public Map Facet + { + get + { + if (m_Context?.Arena != null) + { + return m_Context.Arena.Facet; + } + + return Controller.Map; + } + } + + public override bool CantDoAnything(Mobile mob) + { + if (mob != null && GetTeamInfo(mob) != null && Controller != null) + { + for (var i = 0; i < Controller.Hills.Length; i++) + { + if (Controller.Hills[i]?.King == mob) + { + return true; + } + } + } + + return false; + } + + 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 sealed class KHGame : EventGame + public KHTeamInfo GetTeamInfo(Mobile mob) { - private TimerExecutionToken _finishTimerToken; + var teamID = GetTeamID(mob); - public KHGame(KHController controller, DuelContext context) : base(context) => Controller = controller; - - public KHController Controller { get; } - - public Map Facet + if (teamID >= 0) { - get - { - if (m_Context?.Arena != null) - { - return m_Context.Arena.Facet; - } + return Controller.TeamInfo[teamID % Controller.TeamInfo.Length]; + } - return Controller.Map; + 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; + + if (corpse?.Deleted == false) + { + Timer.StartTimer(TimeSpan.FromSeconds(30), corpse.Delete); + } + } + + public override bool OnDeath(Mobile mob, Container corpse) + { + var killer = mob.FindMostRecentDamager(false); + KHTeamInfo teamInfo = null; + var victInfo = GetTeamInfo(mob); + var bonus = 0; + + if (killer?.Player == true) + { + teamInfo = GetTeamInfo(killer); + } + + for (var i = 0; i < Controller.Hills.Length; i++) + { + if (Controller.Hills[i] == null) + { + continue; + } + + if (Controller.Hills[i].King == mob) + { + bonus += Controller.Hills[i].CapturesSoFar; + Controller.Hills[i].OnKingDied(mob, victInfo, killer, teamInfo); + } + + if (Controller.Hills[i].King == killer) + { + bonus += 2; } } - public override bool CantDoAnything(Mobile mob) + if (teamInfo != null && teamInfo != victInfo) { - if (mob != null && GetTeamInfo(mob) != null && Controller != null) + var playerInfo = teamInfo[killer]; + + if (playerInfo != null) { - for (var i = 0; i < Controller.Hills.Length; i++) - { - if (Controller.Hills[i]?.King == mob) - { - return true; - } - } - } - - return false; - } - - 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); - } - } + playerInfo.Kills += 1; + playerInfo.Score += 1 + bonus; } } - public KHTeamInfo GetTeamInfo(Mobile mob) + mob.SendGump(new KHBoardGump(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 teamID = GetTeamID(mob); + var teamInfo = Controller.TeamInfo[i]; - if (teamID >= 0) - { - return Controller.TeamInfo[teamID % Controller.TeamInfo.Length]; - } - - return null; + teamInfo.Game = this; + teamInfo.Reset(); } - public int GetTeamID(Mobile mob) + for (var i = 0; i < m_Context.Participants.Count; ++i) { - 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); + ApplyHues( + m_Context.Participants[i], + Controller.TeamInfo[i % Controller.TeamInfo.Length].Color + ); } - public int GetColor(Mobile mob) => GetTeamInfo(mob)?.Color ?? -1; - - private void ApplyHues(Participant p, int hueOverride) + for (var i = 0; i < Controller.Hills.Length; i++) { - for (var i = 0; i < p.Players.Length; ++i) + if (Controller.Hills[i] != null) { - if (p.Players[i] != null) - { - p.Players[i].Mobile.SolidHueOverride = hueOverride; - } + Controller.Hills[i].Game = this; } } - public void DelayBounce(TimeSpan ts, Mobile mob, Container corpse) + foreach (var board in Controller.Boards) { - Timer.StartTimer(ts, () => DelayBounce_Callback(mob, corpse)); + if (board?.Deleted == false) + { + board.m_Game = this; + } } - private void DelayBounce_Callback(Mobile mob, Container corpse) + _finishTimerToken.Cancel(); + 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 dp = (mob as PlayerMobile)?.DuelPlayer; + var teamInfo = Controller.TeamInfo[i % Controller.TeamInfo.Length]; - m_Context.RemoveAggressions(mob); - - if (dp?.Eliminated == false) + if (teamInfo != null) { - mob.MoveToWorld(m_Context.Arena.GetBaseStartPoint(GetTeamID(mob)), Facet); + teams.Add(teamInfo); + } + } + + teams.Sort(); + + var tourney = m_Context.m_Tournament; + + var sb = new StringBuilder(); + + 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 { - m_Context.SendOutside(mob); - } + for (var i = 0; i < tourney.ParticipantsPerMatch; ++i) + { + if (sb.Length > 0) + { + sb.Append('v'); + } - m_Context.Refresh(mob, corpse); - DuelContext.Debuff(mob); - DuelContext.CancelSpell(mob); - mob.Frozen = false; - - if (corpse?.Deleted == false) - { - Timer.StartTimer(TimeSpan.FromSeconds(30), corpse.Delete); + sb.Append(tourney.PlayersPerParticipant); + } } } - public override bool OnDeath(Mobile mob, Container corpse) + if (Controller != null) { - var killer = mob.FindMostRecentDamager(false); - KHTeamInfo teamInfo = null; - var victInfo = GetTeamInfo(mob); - var bonus = 0; + sb.Append(' ').Append(Controller.Title); + } - if (killer?.Player == true) + var title = sb.ToString(); + + var winner = teams.Count > 0 ? teams[0] : null; + + for (var i = 0; i < teams.Count; ++i) + { + var rank = TrophyRank.Bronze; + + if (i == 0) { - teamInfo = GetTeamInfo(killer); + rank = TrophyRank.Gold; + } + else if (i == 1) + { + rank = TrophyRank.Silver; } - for (var i = 0; i < Controller.Hills.Length; i++) + var leader = teams[i].Leader; + + foreach (var pl in teams[i].Players.Values) { - if (Controller.Hills[i] == null) + var mob = pl.Player; + + if (mob == null) { continue; } - if (Controller.Hills[i].King == mob) + sb = new StringBuilder(); + + sb.Append(title); + + if (pl == leader) { - bonus += Controller.Hills[i].CapturesSoFar; - Controller.Hills[i].OnKingDied(mob, victInfo, killer, teamInfo); + sb.Append(" Leader"); } - if (Controller.Hills[i].King == killer) + if (pl.Score > 0) { - bonus += 2; - } - } + sb.Append(": "); - if (teamInfo != null && teamInfo != victInfo) - { - var playerInfo = teamInfo[killer]; + sb.Append(pl.Score.ToString("N0")); + sb.Append(pl.Score == 1 ? " point" : " points"); - if (playerInfo != null) - { - playerInfo.Kills += 1; - playerInfo.Score += 1 + bonus; - } - } + sb.Append(", "); + sb.Append(pl.Kills.ToString("N0")); + sb.Append(pl.Kills == 1 ? " kill" : " kills"); - mob.SendGump(new KHBoardGump(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 - ); - } - - for (var i = 0; i < Controller.Hills.Length; i++) - { - if (Controller.Hills[i] != null) - { - Controller.Hills[i].Game = this; - } - } - - foreach (var board in Controller.Boards) - { - if (board?.Deleted == false) - { - board.m_Game = this; - } - } - - _finishTimerToken.Cancel(); - 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; - - var sb = new StringBuilder(); - - 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 - { - for (var i = 0; i < tourney.ParticipantsPerMatch; ++i) + if (pl.Captures > 0) { - if (sb.Length > 0) - { - sb.Append('v'); - } - - sb.Append(tourney.PlayersPerParticipant); - } - } - } - - if (Controller != null) - { - sb.Append(' ').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 = TrophyRank.Bronze; - - if (i == 0) - { - rank = TrophyRank.Gold; - } - else if (i == 1) - { - rank = TrophyRank.Silver; - } - - var leader = teams[i].Leader; - - foreach (var pl in teams[i].Players.Values) - { - var mob = pl.Player; - - if (mob == null) - { - continue; - } - - sb = new StringBuilder(); - - 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(", "); - 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 ? " capture" : " captures"); - } + sb.Append(pl.Captures.ToString("N0")); + sb.Append(pl.Captures == 1 ? " capture" : " captures"); } + } - Item item = new Trophy(sb.ToString(), rank); + Item item = new Trophy(sb.ToString(), rank); - if (pl == leader) - { - item.ItemID = 4810; - } + if (pl == leader) + { + item.ItemID = 4810; + } - item.Name = $"{item.Name}, {teams[i].Name.ToLower()}"; + item.Name = $"{item.Name}, {teams[i].Name.ToLower()}"; + + 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); } - var cash = pl.Score * 250; + mob.SendMessage( + $"You have been awarded a {rank.ToString().ToLower()} trophy and {cash:N0}gp for your participation in this game." + ); + } + else + { + mob.SendMessage( + $"You have been awarded a {rank.ToString().ToLower()} trophy for your participation in this game." + ); + } + } + } - if (cash > 0) - { - item = new BankCheck(cash); + for (var i = 0; i < m_Context.Participants.Count; ++i) + { + var p = m_Context.Participants[i]; + if (p.Players == null) + { + continue; + } - if (!mob.PlaceInBackpack(item)) - { - mob.BankBox.DropItem(item); - } + for (var j = 0; j < p.Players.Length; ++j) + { + var dp = p.Players[j]; - mob.SendMessage( - $"You have been awarded a {rank.ToString().ToLower()} trophy and {cash:N0}gp for your participation in this game." - ); - } - else - { - mob.SendMessage( - $"You have been awarded a {rank.ToString().ToLower()} trophy for your participation in this game." - ); - } + if (dp?.Mobile != null) + { + dp.Mobile.SendGump(new KHBoardGump(dp.Mobile, this)); } } - for (var i = 0; i < m_Context.Participants.Count; ++i) + if (i == winner?.TeamID) { - var p = m_Context.Participants[i]; - if (p.Players == null) - { - continue; - } + continue; + } + if (p.Players != null) + { for (var j = 0; j < p.Players.Length; ++j) { - var dp = p.Players[j]; - - if (dp?.Mobile != null) + if (p.Players[j] != null) { - dp.Mobile.SendGump(new KHBoardGump(dp.Mobile, this)); + p.Players[j].Eliminated = true; } } - - if (i == winner?.TeamID) - { - continue; - } - - if (p.Players != null) - { - for (var j = 0; j < p.Players.Length; ++j) - { - if (p.Players[j] != null) - { - p.Players[j].Eliminated = true; - } - } - } - } - - if (winner != null) - { - m_Context.Finish(m_Context.Participants[winner.TeamID]); } } - public override void OnStop() + if (winner != null) { - for (var i = 0; i < Controller.TeamInfo.Length; ++i) - { - Controller.TeamInfo[i].Game = null; - } - - for (var i = 0; i < Controller.Hills.Length; ++i) - { - if (Controller.Hills[i] != null) - { - Controller.Hills[i].Game = null; - } - } - - foreach (var board in Controller.Boards) - { - if (board != null) - { - board.m_Game = null; - } - } - - for (var i = 0; i < m_Context.Participants.Count; ++i) - { - ApplyHues(m_Context.Participants[i], -1); - } - - _finishTimerToken.Cancel(); + m_Context.Finish(m_Context.Participants[winner.TeamID]); } } + + public override void OnStop() + { + for (var i = 0; i < Controller.TeamInfo.Length; ++i) + { + Controller.TeamInfo[i].Game = null; + } + + for (var i = 0; i < Controller.Hills.Length; ++i) + { + if (Controller.Hills[i] != null) + { + Controller.Hills[i].Game = null; + } + } + + foreach (var board in Controller.Boards) + { + if (board != null) + { + board.m_Game = null; + } + } + + for (var i = 0; i < m_Context.Participants.Count; ++i) + { + ApplyHues(m_Context.Participants[i], -1); + } + + _finishTimerToken.Cancel(); + } } diff --git a/Projects/UOContent/Engines/ConPVP/Gumps/ArenaGump.cs b/Projects/UOContent/Engines/ConPVP/Gumps/ArenaGump.cs index 98b95964d..814b2de0c 100644 --- a/Projects/UOContent/Engines/ConPVP/Gumps/ArenaGump.cs +++ b/Projects/UOContent/Engines/ConPVP/Gumps/ArenaGump.cs @@ -1,296 +1,278 @@ using System.Collections.Generic; using System.Text; +using ModernUO.Serialization; using Server.Gumps; using Server.Mobiles; using Server.Network; -namespace Server.Engines.ConPVP +namespace Server.Engines.ConPVP; + +[SerializationGenerator(0, false)] +public partial class ArenasMoongate : Item { - public class ArenasMoongate : Item + [Constructible] + public ArenasMoongate() : base(0x1FD4) { - [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(IGenericWriter writer) - { - base.Serialize(writer); - - writer.Write(0); - } - - public override void Deserialize(IGenericReader reader) - { - base.Deserialize(reader); - - var 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.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) => !m.Player || UseGate(m); + Movable = false; + Light = LightType.Circle300; } - public class ArenaGump : Gump + public override string DefaultName => "arena moongate"; + + public bool UseGate(Mobile from) { - private readonly List m_Arenas; - private readonly Mobile m_From; - private readonly ArenasMoongate m_Gate; - - private int m_ColumnX = 12; - - public override bool Singleton => true; - - public ArenaGump(Mobile from, ArenasMoongate gate) : base(50, 50) + if (DuelContext.CheckCombat(from)) { - m_From = from; - m_Gate = gate; - m_Arenas = Arena.Arenas; - - AddPage(0); - - var height = 12 + 20 + m_Arenas.Count * 31 + 24 + 12; - - AddBackground(0, 0, 499 + 40, height, 0x2436); - - var list = m_Arenas; - - for (var 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 (var i = 0; i < list.Count; ++i) - { - var ar = list[i]; - - var x = 12; - var y = 32 + i * 31; - - var 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; - - var sb = new StringBuilder(); - - if (ar.Players.Count > 0) - { - var ladder = Ladder.Instance; - - if (ladder == null) - { - continue; - } - - LadderEntry p1 = null, p2 = null, p3 = null, p4 = null; - - for (var j = 0; j < ar.Players.Count; ++j) - { - var mob = ar.Players[j]; - var 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, Html.Center($"{ar.Spectators}"), color, 0); - } + from.SendMessage(0x22, "You have recently been in combat with another player and cannot use this moongate."); + return false; } - private void Append(StringBuilder sb, LadderEntry le) + if (from.Spell != null) { - if (le == null) - { - return; - } - - if (sb.Length > 0) - { - sb.Append(", "); - } - - sb.Append(le.Mobile.Name); + from.SendLocalizedMessage(1049616); // You are too busy to do that at the moment. + return false; } - public override void OnResponse(NetState sender, in RelayInfo info) + from.SendGump(new ArenaGump(from, this)); + + if (!from.Hidden || from.AccessLevel == AccessLevel.Player) { - if (info.ButtonID != 1) - { - return; - } + Effects.PlaySound(from.Location, from.Map, 0x20E); + } - var switches = info.Switches; + return true; + } - if (switches.Length == 0) - { - return; - } + 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 + } + } - var opt = switches[0]; + public override bool OnMoveOver(Mobile m) => !m.Player || UseGate(m); +} - if (opt < 0 || opt >= m_Arenas.Count) - { - return; - } +public class ArenaGump : Gump +{ + private readonly List m_Arenas; + private readonly Mobile m_From; + private readonly ArenasMoongate m_Gate; - var arena = m_Arenas[opt]; + private int m_ColumnX = 12; - if (!m_From.InRange(m_Gate.GetWorldLocation(), 1) || m_From.Map != m_Gate.Map) + public override bool Singleton => true; + + public ArenaGump(Mobile from, ArenasMoongate gate) : base(50, 50) + { + m_From = from; + m_Gate = gate; + m_Arenas = Arena.Arenas; + + AddPage(0); + + var height = 12 + 20 + m_Arenas.Count * 31 + 24 + 12; + + AddBackground(0, 0, 499 + 40, height, 0x2436); + + var list = m_Arenas; + + for (var 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 (var i = 0; i < list.Count; ++i) + { + var ar = list[i]; + + var x = 12; + var y = 32 + i * 31; + + var 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; + + var sb = new StringBuilder(); + + if (ar.Players.Count > 0) { - 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.Location)) - { - m_From.SendLocalizedMessage(1019003); // You are already there. + var ladder = Ladder.Instance; + + if (ladder == null) + { + continue; + } + + LadderEntry p1 = null, p2 = null, p3 = null, p4 = null; + + for (var j = 0; j < ar.Players.Count; ++j) + { + var mob = ar.Players[j]; + var 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 { - 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); - } - } - - 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) - { - AddHtml(x, y, width, 20, color == 0 ? text : text.Color(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, name.Center(), 0xFFFFFF, 0); + sb.Append("Empty"); } - m_ColumnX += width; + AddBorderedText(x + 5, y + 5, 325 - 5, sb.ToString(), color, 0); + x += 325; + + AddBorderedText(x, y + 5, 40, Html.Center($"{ar.Spectators}"), 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, in RelayInfo info) + { + if (info.ButtonID != 1) + { + return; + } + + var switches = info.Switches; + + if (switches.Length == 0) + { + return; + } + + var opt = switches[0]; + + if (opt < 0 || opt >= m_Arenas.Count) + { + return; + } + + var 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.Location)) + { + 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); + } + } + + 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) + { + AddHtml(x, y, width, 20, color == 0 ? text : text.Color(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, name.Center(), 0xFFFFFF, 0); + } + + m_ColumnX += width; + } } diff --git a/Projects/UOContent/Engines/ConPVP/Gumps/LadderGump.cs b/Projects/UOContent/Engines/ConPVP/Gumps/LadderGump.cs index db6ed89d0..b31ff4812 100644 --- a/Projects/UOContent/Engines/ConPVP/Gumps/LadderGump.cs +++ b/Projects/UOContent/Engines/ConPVP/Gumps/LadderGump.cs @@ -1,259 +1,232 @@ using System; using System.Collections.Generic; +using ModernUO.Serialization; using Server.Gumps; using Server.Network; -namespace Server.Engines.ConPVP +namespace Server.Engines.ConPVP; + +[SerializationGenerator(0, false)] +public partial class LadderItem : Item { - public class LadderItem : Item + [SerializedCommandProperty(AccessLevel.GameMaster)] + [SerializableField(0)] + private LadderController _ladder; + + [Constructible] + public LadderItem() : base(0x117F) => Movable = false; + + public override string DefaultName => "1v1 leaderboard"; + + public override void OnDoubleClick(Mobile from) { - [Constructible] - public LadderItem() : base(0x117F) => Movable = false; - - public LadderItem(Serial serial) : base(serial) + if (from.InRange(GetWorldLocation(), 2)) { - } + var ladder = ConPVP.Ladder.Instance ?? Ladder.Ladder; - [CommandProperty(AccessLevel.GameMaster)] - public LadderController Ladder { get; set; } - - public override string DefaultName => "1v1 leaderboard"; - - public override void Serialize(IGenericWriter writer) - { - base.Serialize(writer); - - writer.Write(1); - - writer.Write(Ladder); - } - - public override void Deserialize(IGenericReader reader) - { - base.Deserialize(reader); - - var version = reader.ReadInt(); - - switch (version) + if (ladder != null) { - case 1: - { - Ladder = reader.ReadEntity(); - break; - } + from.SendGump(new LadderGump(ladder), true); } } - - public override void OnDoubleClick(Mobile from) + else { - if (from.InRange(GetWorldLocation(), 2)) - { - var ladder = ConPVP.Ladder.Instance ?? Ladder.Ladder; - - if (ladder != null) - { - from.SendGump(new LadderGump(ladder), true); - } - } - else - { - from.LocalOverheadMessage(MessageType.Regular, 0x3B2, 1019045); // I can't reach that - } - } - } - - public class LadderGump : Gump - { - private readonly Ladder m_Ladder; - - private readonly List m_List; - private readonly int m_Page; - private int m_ColumnX = 12; - - public LadderGump(Ladder ladder, int page = 0) : base(50, 50) - { - m_Ladder = ladder; - m_Page = page; - - AddPage(0); - - m_List = new List(ladder.Entries); - - var lc = Math.Min(m_List.Count, 150); - - var start = page * 15; - var end = start + 15; - - if (end > lc) - { - end = lc; - } - - var ct = end - start; - - var height = 12 + 20 + ct * 20 + 23 + 12; - - AddBackground(0, 0, 499, height, 0x2436); - - for (var 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, - Html.Color($"Top {lc} of {m_List.Count:N0} duelists, page {page + 1} of {(lc + 14) / 15}", 0xFFC000) - ); - - AddColumnHeader(75, "Rank"); - AddColumnHeader(115, "Level"); - AddColumnHeader(50, "Guild"); - AddColumnHeader(115, "Name"); - AddColumnHeader(60, "Wins"); - AddColumnHeader(60, "Losses"); - - for (var i = start; i < end && i < lc; ++i) - { - var entry = m_List[i]; - - var y = 32 + (i - start) * 20; - var x = 12; - - AddBorderedText(x, y, 75, Rank(i + 1).Center(), 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); - - var xp = entry.Experience; - var level = Ladder.GetLevel(xp); - - Ladder.GetLevelInfo(level, out var xpBase, out var xpAdvance); - - int width; - - var 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, Html.Center($"{level}"), 0xFFFFFF, 0); - x += 115; - - var mob = entry.Mobile; - - if (mob.Guild != null) - { - AddBorderedText(x, y, 50, mob.Guild.Abbreviation.Center(), 0xFFFFFF, 0); - } - - x += 50; - - AddBorderedText(x + 5, y, 115 - 5, mob.Name, 0xFFFFFF, 0); - x += 115; - - AddBorderedText(x, y, 60, Html.Center($"{entry.Wins}"), 0xFFFFFF, 0); - x += 60; - - AddBorderedText(x, y, 60, Html.Center($"{entry.Losses}"), 0xFFFFFF, 0); - x += 60; - - // AddBorderedText( 292 + 15, y, 115 - 30, String.Format( "{0}
/
{1}
", entry.Wins, entry.Losses ), 0xFFC000, 0 ); - } - } - - public static string Rank(int num) - { - var numStr = num.ToString("N0"); - - if (num % 100 > 10 && num % 100 < 20) - { - return $"{numStr}th"; - } - - return (num % 10) switch - { - 1 => $"{numStr}st", - 2 => $"{numStr}nd", - 3 => $"{numStr}rd", - _ => $"{numStr}th" - }; - } - - public override void OnResponse(NetState sender, in RelayInfo info) - { - var 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)); - } - } - - 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) - { - AddHtml(x, y, width, 20, color == 0 ? text : text.Color(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, name.Center(), 0xFFFFFF, 0); - - m_ColumnX += width; + from.LocalOverheadMessage(MessageType.Regular, 0x3B2, 1019045); // I can't reach that } } } + +public class LadderGump : Gump +{ + private readonly Ladder m_Ladder; + + private readonly List m_List; + private readonly int m_Page; + private int m_ColumnX = 12; + + public LadderGump(Ladder ladder, int page = 0) : base(50, 50) + { + m_Ladder = ladder; + m_Page = page; + + AddPage(0); + + m_List = new List(ladder.Entries); + + var lc = Math.Min(m_List.Count, 150); + + var start = page * 15; + var end = start + 15; + + if (end > lc) + { + end = lc; + } + + var ct = end - start; + + var height = 12 + 20 + ct * 20 + 23 + 12; + + AddBackground(0, 0, 499, height, 0x2436); + + for (var 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, + Html.Color($"Top {lc} of {m_List.Count:N0} duelists, page {page + 1} of {(lc + 14) / 15}", 0xFFC000) + ); + + AddColumnHeader(75, "Rank"); + AddColumnHeader(115, "Level"); + AddColumnHeader(50, "Guild"); + AddColumnHeader(115, "Name"); + AddColumnHeader(60, "Wins"); + AddColumnHeader(60, "Losses"); + + for (var i = start; i < end && i < lc; ++i) + { + var entry = m_List[i]; + + var y = 32 + (i - start) * 20; + var x = 12; + + AddBorderedText(x, y, 75, Rank(i + 1).Center(), 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); + + var xp = entry.Experience; + var level = Ladder.GetLevel(xp); + + Ladder.GetLevelInfo(level, out var xpBase, out var xpAdvance); + + int width; + + var 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, Html.Center($"{level}"), 0xFFFFFF, 0); + x += 115; + + var mob = entry.Mobile; + + if (mob.Guild != null) + { + AddBorderedText(x, y, 50, mob.Guild.Abbreviation.Center(), 0xFFFFFF, 0); + } + + x += 50; + + AddBorderedText(x + 5, y, 115 - 5, mob.Name, 0xFFFFFF, 0); + x += 115; + + AddBorderedText(x, y, 60, Html.Center($"{entry.Wins}"), 0xFFFFFF, 0); + x += 60; + + AddBorderedText(x, y, 60, Html.Center($"{entry.Losses}"), 0xFFFFFF, 0); + x += 60; + + // AddBorderedText( 292 + 15, y, 115 - 30, String.Format( "{0}
/
{1}
", entry.Wins, entry.Losses ), 0xFFC000, 0 ); + } + } + + public static string Rank(int num) + { + var numStr = num.ToString("N0"); + + if (num % 100 > 10 && num % 100 < 20) + { + return $"{numStr}th"; + } + + return (num % 10) switch + { + 1 => $"{numStr}st", + 2 => $"{numStr}nd", + 3 => $"{numStr}rd", + _ => $"{numStr}th" + }; + } + + public override void OnResponse(NetState sender, in RelayInfo info) + { + var 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)); + } + } + + 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) + { + AddHtml(x, y, width, 20, color == 0 ? text : text.Color(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, name.Center(), 0xFFFFFF, 0); + + m_ColumnX += width; + } +} diff --git a/Projects/UOContent/Engines/ConPVP/TournamentBracketItem.cs b/Projects/UOContent/Engines/ConPVP/TournamentBracketItem.cs index 5fc71cb29..f678b4060 100644 --- a/Projects/UOContent/Engines/ConPVP/TournamentBracketItem.cs +++ b/Projects/UOContent/Engines/ConPVP/TournamentBracketItem.cs @@ -1,60 +1,33 @@ +using ModernUO.Serialization; using Server.Gumps; -namespace Server.Engines.ConPVP +namespace Server.Engines.ConPVP; + +[SerializationGenerator(0, false)] +public partial class TournamentBracketItem : Item { - public class TournamentBracketItem : Item + [SerializableField(0)] + [SerializedCommandProperty(AccessLevel.GameMaster)] + private TournamentController _tournament; + + [Constructible] + public TournamentBracketItem() : base(3774) => Movable = false; + + public override string DefaultName => "tournament bracket"; + + public override void OnDoubleClick(Mobile from) { - [Constructible] - public TournamentBracketItem() : base(3774) => Movable = false; - - public TournamentBracketItem(Serial serial) : base(serial) + if (!from.InRange(GetWorldLocation(), 2)) { + from.LocalOverheadMessage(MessageType.Regular, 0x3B2, 1019045); // I can't reach that } - - [CommandProperty(AccessLevel.GameMaster)] - public TournamentController Tournament { get; set; } - - public override string DefaultName => "tournament bracket"; - - public override void OnDoubleClick(Mobile from) + else { - if (!from.InRange(GetWorldLocation(), 2)) + var tourney = Tournament?.Tournament; + + if (tourney != null) { - from.LocalOverheadMessage(MessageType.Regular, 0x3B2, 1019045); // I can't reach that - } - else - { - var tourney = Tournament?.Tournament; - - if (tourney != null) - { - from.SendGump(new TournamentBracketGump(from, tourney, TourneyBracketGumpType.Index), true); - } - } - } - - public override void Serialize(IGenericWriter writer) - { - base.Serialize(writer); - - writer.Write(0); - - writer.Write(Tournament); - } - - public override void Deserialize(IGenericReader reader) - { - base.Deserialize(reader); - - var version = reader.ReadInt(); - - switch (version) - { - case 0: - { - Tournament = reader.ReadEntity(); - break; - } + from.SendGump(new TournamentBracketGump(from, tourney, TourneyBracketGumpType.Index), true); } } } diff --git a/Projects/UOContent/Engines/ConPVP/TournamentRegistrar.cs b/Projects/UOContent/Engines/ConPVP/TournamentRegistrar.cs index c128e6ff2..edd92fb94 100644 --- a/Projects/UOContent/Engines/ConPVP/TournamentRegistrar.cs +++ b/Projects/UOContent/Engines/ConPVP/TournamentRegistrar.cs @@ -1,108 +1,83 @@ using System; +using ModernUO.Serialization; using Server.Factions; using Server.Mobiles; -namespace Server.Engines.ConPVP +namespace Server.Engines.ConPVP; + +[SerializationGenerator(0, false)] +public partial class TournamentRegistrar : Banker { - public class TournamentRegistrar : Banker + [SerializedCommandProperty(AccessLevel.GameMaster)] + [SerializableField(0)] + private TournamentController _tournament; + + [Constructible] + public TournamentRegistrar() => + Timer.DelayCall(TimeSpan.FromSeconds(30.0), TimeSpan.FromSeconds(30.0), Announce_Callback); + + private void Announce_Callback() { - [Constructible] - public TournamentRegistrar() + var tourney = Tournament?.Tournament; + + if (tourney?.Stage == TournamentStage.Signup) { - Timer.StartTimer(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() - { - var 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); - - var tourney = Tournament?.Tournament; - - if (InRange(m, 4) && !InRange(oldLocation, 4) && tourney?.Stage == TournamentStage.Signup && m.CanBeginAction(this)) - { - var ladder = Ladder.Instance; - - var 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.StartTimer(TimeSpan.FromSeconds(10.0), () => ReleaseLock_Callback(m)); - } - } - - public void ReleaseLock_Callback(Mobile m) - { - m.EndAction(this); - } - - public override void Serialize(IGenericWriter writer) - { - base.Serialize(writer); - - writer.Write(0); - - writer.Write(Tournament); - } - - public override void Deserialize(IGenericReader reader) - { - base.Deserialize(reader); - - var version = reader.ReadInt(); - - switch (version) - { - case 0: - { - Tournament = reader.ReadEntity(); - break; - } - } - - Timer.StartTimer(TimeSpan.FromSeconds(30.0), TimeSpan.FromSeconds(30.0), Announce_Callback); + 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); + + var tourney = Tournament?.Tournament; + + if (InRange(m, 4) && !InRange(oldLocation, 4) && tourney?.Stage == TournamentStage.Signup && m.CanBeginAction(this)) + { + var ladder = Ladder.Instance; + + var 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.StartTimer(TimeSpan.FromSeconds(10.0), () => ReleaseLock_Callback(m)); + } + } + + public void ReleaseLock_Callback(Mobile m) + { + m.EndAction(this); + } + + [AfterDeserialization] + private void AfterDeserialization() + { + Timer.DelayCall(TimeSpan.FromSeconds(30.0), TimeSpan.FromSeconds(30.0), Announce_Callback); + } } diff --git a/Projects/UOContent/Engines/ConPVP/TournamentSignupItem.cs b/Projects/UOContent/Engines/ConPVP/TournamentSignupItem.cs index 40733e15f..82d2bd4dd 100644 --- a/Projects/UOContent/Engines/ConPVP/TournamentSignupItem.cs +++ b/Projects/UOContent/Engines/ConPVP/TournamentSignupItem.cs @@ -1,193 +1,162 @@ using System.Collections.Generic; +using ModernUO.Serialization; using Server.Factions; using Server.Gumps; using Server.Mobiles; -namespace Server.Engines.ConPVP +namespace Server.Engines.ConPVP; + +[SerializationGenerator(0, false)] +public partial class TournamentSignupItem : Item { - public class TournamentSignupItem : Item + [SerializedCommandProperty(AccessLevel.GameMaster)] + [SerializableField(0)] + private TournamentController _tournament; + + [SerializedCommandProperty(AccessLevel.GameMaster)] + [SerializableField(1)] + private Mobile _registrar; + + [Constructible] + public TournamentSignupItem() : base(4029) => Movable = false; + + public override string DefaultName => "tournament signup book"; + + public override void OnDoubleClick(Mobile from) { - [Constructible] - public TournamentSignupItem() : base(4029) => Movable = false; - - public TournamentSignupItem(Serial serial) : base(serial) + if (!from.InRange(GetWorldLocation(), 2)) { + from.LocalOverheadMessage(MessageType.Regular, 0x3B2, 1019045); // I can't reach that } - - [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) + else { - if (!from.InRange(GetWorldLocation(), 2)) + var tourney = Tournament?.Tournament; + + if (tourney == null) { - from.LocalOverheadMessage(MessageType.Regular, 0x3B2, 1019045); // I can't reach that + return; } - else + + Registrar?.Direction = Registrar.GetDirectionTo(this); + + switch (tourney.Stage) { - var tourney = Tournament?.Tournament; - - if (tourney == null) - { - return; - } - - if (Registrar != null) - { - Registrar.Direction = Registrar.GetDirectionTo(this); - } - - switch (tourney.Stage) - { - case TournamentStage.Fighting: + case TournamentStage.Fighting: + { + if (Registrar != null) { - if (Registrar != null) + if (tourney.HasParticipant(from)) { - 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 - ); - } + 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: + + break; + } + case TournamentStage.Inactive: + { + Registrar?.PrivateOverheadMessage( + MessageType.Regular, + 0x35, + false, + "The tournament is closed.", + from.NetState + ); + + break; + } + case TournamentStage.Signup: + { + var ladder = Ladder.Instance; + var entry = ladder?.Find(from); + + if (entry != null && Ladder.GetLevel(entry.Experience) < tourney.LevelRequirement) { Registrar?.PrivateOverheadMessage( MessageType.Regular, 0x35, false, - "The tournament is closed.", + "You have not yet proven yourself a worthy dueler.", from.NetState ); break; } - case TournamentStage.Signup: + + if (tourney.IsFactionRestricted && Faction.Find(from) == null) { - var ladder = Ladder.Instance; - var 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()) - { - Registrar?.PrivateOverheadMessage( - MessageType.Regular, - 0x22, - false, - "You must first respond to the offer I've given you.", - from.NetState - ); - } - else if (from.HasGump()) - { - 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.SendGump(new ConfirmSignupGump(from, Registrar, tourney, new List { from })); - } - else - { - Registrar?.PrivateOverheadMessage( - MessageType.Regular, - 0x35, - false, - "You have already entered this tournament.", - from.NetState - ); - } + Registrar?.PrivateOverheadMessage( + MessageType.Regular, + 0x35, + false, + "Only those who have declared their faction allegiance may participate.", + from.NetState + ); break; } - } - } - } - public override void Serialize(IGenericWriter writer) - { - base.Serialize(writer); + if (from.HasGump()) + { + Registrar?.PrivateOverheadMessage( + MessageType.Regular, + 0x22, + false, + "You must first respond to the offer I've given you.", + from.NetState + ); + } + else if (from.HasGump()) + { + 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.SendGump(new ConfirmSignupGump(from, Registrar, tourney, new List { from })); + } + else + { + Registrar?.PrivateOverheadMessage( + MessageType.Regular, + 0x35, + false, + "You have already entered this tournament.", + from.NetState + ); + } - writer.Write(0); - - writer.Write(Tournament); - writer.Write(Registrar); - } - - public override void Deserialize(IGenericReader reader) - { - base.Deserialize(reader); - - var version = reader.ReadInt(); - - switch (version) - { - case 0: - { - Tournament = reader.ReadEntity(); - Registrar = reader.ReadEntity(); break; } } diff --git a/Projects/UOContent/Migrations/Server.Engines.ConPVP.ArenasMoongate.v0.json b/Projects/UOContent/Migrations/Server.Engines.ConPVP.ArenasMoongate.v0.json new file mode 100644 index 000000000..890daeeed --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Engines.ConPVP.ArenasMoongate.v0.json @@ -0,0 +1,4 @@ +{ + "version": 0, + "type": "Server.Engines.ConPVP.ArenasMoongate" +} \ No newline at end of file diff --git a/Projects/UOContent/Migrations/Server.Engines.ConPVP.BRBoard.v0.json b/Projects/UOContent/Migrations/Server.Engines.ConPVP.BRBoard.v0.json new file mode 100644 index 000000000..a8ed00ad6 --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Engines.ConPVP.BRBoard.v0.json @@ -0,0 +1,4 @@ +{ + "version": 0, + "type": "Server.Engines.ConPVP.BRBoard" +} \ No newline at end of file diff --git a/Projects/UOContent/Migrations/Server.Engines.ConPVP.BRBomb.v0.json b/Projects/UOContent/Migrations/Server.Engines.ConPVP.BRBomb.v0.json new file mode 100644 index 000000000..4b7ad8d8c --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Engines.ConPVP.BRBomb.v0.json @@ -0,0 +1,4 @@ +{ + "version": 0, + "type": "Server.Engines.ConPVP.BRBomb" +} \ No newline at end of file diff --git a/Projects/UOContent/Migrations/Server.Engines.ConPVP.BRGoal.v0.json b/Projects/UOContent/Migrations/Server.Engines.ConPVP.BRGoal.v0.json new file mode 100644 index 000000000..734d81f04 --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Engines.ConPVP.BRGoal.v0.json @@ -0,0 +1,14 @@ +{ + "version": 0, + "type": "Server.Engines.ConPVP.BRGoal", + "properties": [ + { + "name": "North", + "type": "bool", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + } + ] +} \ No newline at end of file diff --git a/Projects/UOContent/Migrations/Server.Engines.ConPVP.CTFBoard.v0.json b/Projects/UOContent/Migrations/Server.Engines.ConPVP.CTFBoard.v0.json new file mode 100644 index 000000000..e12470e8c --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Engines.ConPVP.CTFBoard.v0.json @@ -0,0 +1,4 @@ +{ + "version": 0, + "type": "Server.Engines.ConPVP.CTFBoard" +} \ No newline at end of file diff --git a/Projects/UOContent/Migrations/Server.Engines.ConPVP.CTFFlag.v0.json b/Projects/UOContent/Migrations/Server.Engines.ConPVP.CTFFlag.v0.json new file mode 100644 index 000000000..e2bc9a93a --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Engines.ConPVP.CTFFlag.v0.json @@ -0,0 +1,4 @@ +{ + "version": 0, + "type": "Server.Engines.ConPVP.CTFFlag" +} \ No newline at end of file diff --git a/Projects/UOContent/Migrations/Server.Engines.ConPVP.DDBoard.v0.json b/Projects/UOContent/Migrations/Server.Engines.ConPVP.DDBoard.v0.json new file mode 100644 index 000000000..33146ad57 --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Engines.ConPVP.DDBoard.v0.json @@ -0,0 +1,4 @@ +{ + "version": 0, + "type": "Server.Engines.ConPVP.DDBoard" +} \ No newline at end of file diff --git a/Projects/UOContent/Migrations/Server.Engines.ConPVP.DDWayPoint.DDStep.v0.json b/Projects/UOContent/Migrations/Server.Engines.ConPVP.DDWayPoint.DDStep.v0.json new file mode 100644 index 000000000..b5dd5737d --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Engines.ConPVP.DDWayPoint.DDStep.v0.json @@ -0,0 +1,4 @@ +{ + "version": 0, + "type": "Server.Engines.ConPVP.DDWayPoint.DDStep" +} \ No newline at end of file diff --git a/Projects/UOContent/Migrations/Server.Engines.ConPVP.DDWayPoint.v0.json b/Projects/UOContent/Migrations/Server.Engines.ConPVP.DDWayPoint.v0.json new file mode 100644 index 000000000..05fcfd6c9 --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Engines.ConPVP.DDWayPoint.v0.json @@ -0,0 +1,4 @@ +{ + "version": 0, + "type": "Server.Engines.ConPVP.DDWayPoint" +} \ No newline at end of file diff --git a/Projects/UOContent/Migrations/Server.Engines.ConPVP.DuelContext.ArenaMoongate.v0.json b/Projects/UOContent/Migrations/Server.Engines.ConPVP.DuelContext.ArenaMoongate.v0.json new file mode 100644 index 000000000..edb4336ba --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Engines.ConPVP.DuelContext.ArenaMoongate.v0.json @@ -0,0 +1,4 @@ +{ + "version": 0, + "type": "Server.Engines.ConPVP.DuelContext.ArenaMoongate" +} \ No newline at end of file diff --git a/Projects/UOContent/Migrations/Server.Engines.ConPVP.DuelContext.InternalWall.v0.json b/Projects/UOContent/Migrations/Server.Engines.ConPVP.DuelContext.InternalWall.v0.json new file mode 100644 index 000000000..90b2a1629 --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Engines.ConPVP.DuelContext.InternalWall.v0.json @@ -0,0 +1,4 @@ +{ + "version": 0, + "type": "Server.Engines.ConPVP.DuelContext.InternalWall" +} \ No newline at end of file diff --git a/Projects/UOContent/Migrations/Server.Engines.ConPVP.DuelTeleporterAddon.v0.json b/Projects/UOContent/Migrations/Server.Engines.ConPVP.DuelTeleporterAddon.v0.json new file mode 100644 index 000000000..e87eaeecb --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Engines.ConPVP.DuelTeleporterAddon.v0.json @@ -0,0 +1,4 @@ +{ + "version": 0, + "type": "Server.Engines.ConPVP.DuelTeleporterAddon" +} \ No newline at end of file diff --git a/Projects/UOContent/Migrations/Server.Engines.ConPVP.EventController.v0.json b/Projects/UOContent/Migrations/Server.Engines.ConPVP.EventController.v0.json new file mode 100644 index 000000000..763603e25 --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Engines.ConPVP.EventController.v0.json @@ -0,0 +1,4 @@ +{ + "version": 0, + "type": "Server.Engines.ConPVP.EventController" +} \ No newline at end of file diff --git a/Projects/UOContent/Migrations/Server.Engines.ConPVP.HillOfTheKing.v0.json b/Projects/UOContent/Migrations/Server.Engines.ConPVP.HillOfTheKing.v0.json new file mode 100644 index 000000000..bfccd2eb6 --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Engines.ConPVP.HillOfTheKing.v0.json @@ -0,0 +1,14 @@ +{ + "version": 0, + "type": "Server.Engines.ConPVP.HillOfTheKing", + "properties": [ + { + "name": "ScoreInterval", + "type": "int", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "EncodedInt" + ] + } + ] +} \ No newline at end of file diff --git a/Projects/UOContent/Migrations/Server.Engines.ConPVP.KHBoard.v0.json b/Projects/UOContent/Migrations/Server.Engines.ConPVP.KHBoard.v0.json new file mode 100644 index 000000000..246348a67 --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Engines.ConPVP.KHBoard.v0.json @@ -0,0 +1,11 @@ +{ + "version": 0, + "type": "Server.Engines.ConPVP.KHBoard", + "properties": [ + { + "name": "Controller", + "type": "Server.Engines.ConPVP.KHController", + "rule": "SerializableInterfaceMigrationRule" + } + ] +} \ No newline at end of file diff --git a/Projects/UOContent/Migrations/Server.Engines.ConPVP.LadderItem.v0.json b/Projects/UOContent/Migrations/Server.Engines.ConPVP.LadderItem.v0.json new file mode 100644 index 000000000..58fb7cfba --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Engines.ConPVP.LadderItem.v0.json @@ -0,0 +1,11 @@ +{ + "version": 0, + "type": "Server.Engines.ConPVP.LadderItem", + "properties": [ + { + "name": "Ladder", + "type": "Server.Engines.ConPVP.LadderController", + "rule": "SerializableInterfaceMigrationRule" + } + ] +} \ No newline at end of file diff --git a/Projects/UOContent/Migrations/Server.Engines.ConPVP.TournamentBracketItem.v0.json b/Projects/UOContent/Migrations/Server.Engines.ConPVP.TournamentBracketItem.v0.json new file mode 100644 index 000000000..93d812ff0 --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Engines.ConPVP.TournamentBracketItem.v0.json @@ -0,0 +1,11 @@ +{ + "version": 0, + "type": "Server.Engines.ConPVP.TournamentBracketItem", + "properties": [ + { + "name": "Tournament", + "type": "Server.Engines.ConPVP.TournamentController", + "rule": "SerializableInterfaceMigrationRule" + } + ] +} \ No newline at end of file diff --git a/Projects/UOContent/Migrations/Server.Engines.ConPVP.TournamentRegistrar.v0.json b/Projects/UOContent/Migrations/Server.Engines.ConPVP.TournamentRegistrar.v0.json new file mode 100644 index 000000000..a1f89eb40 --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Engines.ConPVP.TournamentRegistrar.v0.json @@ -0,0 +1,11 @@ +{ + "version": 0, + "type": "Server.Engines.ConPVP.TournamentRegistrar", + "properties": [ + { + "name": "Tournament", + "type": "Server.Engines.ConPVP.TournamentController", + "rule": "SerializableInterfaceMigrationRule" + } + ] +} \ No newline at end of file diff --git a/Projects/UOContent/Migrations/Server.Engines.ConPVP.TournamentSignupItem.v0.json b/Projects/UOContent/Migrations/Server.Engines.ConPVP.TournamentSignupItem.v0.json new file mode 100644 index 000000000..efa167ea5 --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Engines.ConPVP.TournamentSignupItem.v0.json @@ -0,0 +1,16 @@ +{ + "version": 0, + "type": "Server.Engines.ConPVP.TournamentSignupItem", + "properties": [ + { + "name": "Tournament", + "type": "Server.Engines.ConPVP.TournamentController", + "rule": "SerializableInterfaceMigrationRule" + }, + { + "name": "Registrar", + "type": "Server.Mobile", + "rule": "SerializableInterfaceMigrationRule" + } + ] +} \ No newline at end of file