2448 lines
64 KiB
C#
2448 lines
64 KiB
C#
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using Server.Engines.PartySystem;
|
|
using Server.Factions;
|
|
using Server.Gumps;
|
|
using Server.Items;
|
|
using Server.Mobiles;
|
|
using Server.Network;
|
|
using Server.Regions;
|
|
using Server.Spells;
|
|
using Server.Spells.Bushido;
|
|
using Server.Spells.Chivalry;
|
|
using Server.Spells.Fourth;
|
|
using Server.Spells.Necromancy;
|
|
using Server.Spells.Ninjitsu;
|
|
using Server.Spells.Second;
|
|
using Server.Spells.Seventh;
|
|
using Server.Spells.Spellweaving;
|
|
using Server.Targeting;
|
|
|
|
namespace Server.Engines.ConPVP
|
|
{
|
|
public delegate void CountdownCallback(int count);
|
|
|
|
public class DuelContext
|
|
{
|
|
private static readonly TimeSpan CombatDelay = TimeSpan.FromSeconds(30.0);
|
|
private static readonly TimeSpan AutoTieDelay = TimeSpan.FromMinutes(15.0);
|
|
|
|
private Timer m_AutoTieTimer;
|
|
|
|
private Timer m_Countdown;
|
|
|
|
public EventGame m_EventGame;
|
|
private Map m_GateFacet;
|
|
|
|
private Point3D m_GatePoint;
|
|
public TourneyMatch m_Match;
|
|
|
|
public Arena m_OverrideArena;
|
|
|
|
private Timer m_SDWarnTimer, m_SDActivateTimer;
|
|
public Tournament m_Tournament;
|
|
|
|
private readonly List<Item> m_Walls = new List<Item>();
|
|
|
|
private bool m_Yielding;
|
|
|
|
public DuelContext(Mobile initiator, RulesetLayout layout, bool addNew = true)
|
|
{
|
|
Initiator = initiator;
|
|
Participants = new List<Participant>();
|
|
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);
|
|
}
|
|
}
|
|
|
|
public bool Rematch { get; private set; }
|
|
|
|
public bool ReadyWait { get; private set; }
|
|
|
|
public int ReadyCount { get; private set; }
|
|
|
|
public bool Registered { get; private set; } = true;
|
|
|
|
public bool Finished { get; private set; }
|
|
|
|
public bool Started { get; private set; }
|
|
|
|
public Mobile Initiator { get; }
|
|
|
|
public List<Participant> Participants { get; }
|
|
|
|
public Ruleset Ruleset { get; private set; }
|
|
|
|
public Arena Arena { get; private set; }
|
|
|
|
public bool Tied { get; private set; }
|
|
|
|
public bool IsSuddenDeath { get; set; }
|
|
|
|
public bool IsOneVsOne => Participants.Count == 2 && Participants[0].Players.Length == 1 &&
|
|
Participants[1].Players.Length == 1;
|
|
|
|
public bool StartedBeginCountdown { get; private set; }
|
|
|
|
public bool StartedReadyCountdown { get; private set; }
|
|
|
|
public Tournament Tournament => m_Tournament;
|
|
|
|
private bool CantDoAnything(Mobile mob)
|
|
{
|
|
if (m_EventGame != null)
|
|
return m_EventGame.CantDoAnything(mob);
|
|
|
|
return false;
|
|
}
|
|
|
|
public static bool IsFreeConsume(Mobile mob)
|
|
{
|
|
if (!(mob is 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.DelayCall(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;
|
|
|
|
DuelPlayer pl = Find(from);
|
|
|
|
if (pl?.Eliminated != false)
|
|
return true;
|
|
|
|
if (CantDoAnything(from))
|
|
return false;
|
|
|
|
string title = null;
|
|
|
|
if (move is NinjaMove)
|
|
title = "Bushido";
|
|
else if (move is SamuraiMove)
|
|
title = "Ninjitsu";
|
|
|
|
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;
|
|
|
|
DuelPlayer 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 PlayerMobile pm))
|
|
return true;
|
|
|
|
DuelContext 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;
|
|
|
|
DuelPlayer 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 (!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 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;
|
|
|
|
DuelPlayer pl = Find(from);
|
|
|
|
if (pl?.Eliminated != false)
|
|
return true;
|
|
|
|
if (CantDoAnything(from))
|
|
return false;
|
|
|
|
int 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;
|
|
|
|
DuelPlayer pl = Find(from);
|
|
|
|
if (pl?.Eliminated != false)
|
|
return true;
|
|
|
|
if (!(item is 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 BaseExplosionPotion) && !(item is 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;
|
|
|
|
Arena arena = Arena;
|
|
|
|
if (arena == null)
|
|
return;
|
|
|
|
if (mob.Map == arena.Facet && arena.Bounds.Contains(mob.Location))
|
|
return;
|
|
|
|
DuelPlayer 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.");
|
|
|
|
Participant winner = CheckCompletion();
|
|
|
|
if (winner != null)
|
|
Finish(winner);
|
|
}
|
|
|
|
public void OnDeath(Mobile mob, Container corpse)
|
|
{
|
|
if (!Registered || !Started)
|
|
return;
|
|
|
|
DuelPlayer 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);
|
|
|
|
Participant 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 (int i = 0; i < Participants.Count; ++i)
|
|
{
|
|
Participant p = Participants[i];
|
|
|
|
if (p.HasOpenSlot)
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public void Requip(Mobile from, Container cont)
|
|
{
|
|
if (!(cont is Corpse corpse))
|
|
return;
|
|
|
|
List<Item> items = new List<Item>(corpse.Items);
|
|
|
|
bool didntFit = false;
|
|
|
|
Container pack = from.Backpack;
|
|
|
|
for (int i = 0; !didntFit && i < items.Count; ++i)
|
|
{
|
|
Item item = items[i];
|
|
|
|
if (item.Layer == Layer.Hair || item.Layer == 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();
|
|
|
|
Mobile 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();
|
|
|
|
if (mob.FindItemOnLayer(Layer.OuterTorso) is DeathRobe robe)
|
|
robe.Delete();
|
|
|
|
if (cont is Corpse corpse)
|
|
for (int i = 0; i < corpse.EquipItems.Count; ++i)
|
|
{
|
|
Item item = corpse.EquipItems[i];
|
|
|
|
if (item.Movable && item.Layer != Layer.Hair && item.Layer != Layer.FacialHair &&
|
|
item.IsChildOf(mob.Backpack))
|
|
mob.EquipItem(item);
|
|
}
|
|
}
|
|
|
|
mob.Hits = mob.HitsMax;
|
|
mob.Stam = mob.StamMax;
|
|
mob.Mana = mob.ManaMax;
|
|
|
|
mob.Poison = null;
|
|
}
|
|
|
|
public void SendOutside(Mobile mob)
|
|
{
|
|
if (Arena == null)
|
|
return;
|
|
|
|
mob.Combatant = null;
|
|
mob.MoveToWorld(Arena.Outside, Arena.Facet);
|
|
}
|
|
|
|
public void Finish(Participant winner)
|
|
{
|
|
if (Finished)
|
|
return;
|
|
|
|
EndAutoTie();
|
|
StopSDTimers();
|
|
|
|
Finished = true;
|
|
|
|
for (int i = 0; i < winner.Players.Length; ++i)
|
|
{
|
|
DuelPlayer pl = winner.Players[i];
|
|
|
|
if (pl?.Eliminated == false)
|
|
DelayBounce(TimeSpan.FromSeconds(8.0), pl.Mobile, null);
|
|
}
|
|
|
|
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 (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 (int i = 0; i < Participants.Count; ++i)
|
|
{
|
|
Participant 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)
|
|
loser.TourneyPart?.LostMatch(m_Match);
|
|
}
|
|
|
|
for (int 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<BeginGump>();
|
|
|
|
if (m_Tournament != null)
|
|
loser.Players[j].Mobile.SendEverything();
|
|
}
|
|
}
|
|
|
|
if (IsOneVsOne)
|
|
{
|
|
DuelPlayer dp1 = Participants[0].Players[0];
|
|
DuelPlayer dp2 = Participants[1].Players[0];
|
|
|
|
if (dp1 != null && dp2 != null)
|
|
{
|
|
Award(dp1.Mobile, dp2.Mobile, dp1.Participant == winner);
|
|
Award(dp2.Mobile, dp1.Mobile, dp2.Participant == winner);
|
|
}
|
|
}
|
|
|
|
m_EventGame?.OnStop();
|
|
|
|
Timer.DelayCall(TimeSpan.FromSeconds(9.0), UnregisterRematch);
|
|
}
|
|
|
|
public void Award(Mobile us, Mobile them, bool won)
|
|
{
|
|
Ladder ladder = Arena == null ? Ladder.Instance : Arena.AcquireLadder();
|
|
|
|
if (ladder == null)
|
|
return;
|
|
|
|
LadderEntry ourEntry = ladder.Find(us);
|
|
LadderEntry theirEntry = ladder.Find(them);
|
|
|
|
if (ourEntry == null || theirEntry == null)
|
|
return;
|
|
|
|
int 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;
|
|
|
|
int oldLevel = Ladder.GetLevel(ourEntry.Experience);
|
|
|
|
ourEntry.Experience += xpGain;
|
|
|
|
if (ourEntry.Experience < 0)
|
|
ourEntry.Experience = 0;
|
|
|
|
ladder.UpdateEntry(ourEntry);
|
|
|
|
int newLevel = Ladder.GetLevel(ourEntry.Experience);
|
|
|
|
if (newLevel > oldLevel)
|
|
us.SendMessage(0x59, "You have achieved level {0}!", newLevel);
|
|
else if (newLevel < oldLevel)
|
|
us.SendMessage(0x22, "You have lost a level. You are now at {0}.", 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 (int i = 0; i < Participants.Count; ++i)
|
|
{
|
|
Participant p = Participants[i];
|
|
|
|
for (int j = 0; j < p.Players.Length; ++j)
|
|
{
|
|
DuelPlayer 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();
|
|
}
|
|
|
|
public void QueryRematch()
|
|
{
|
|
DuelContext dc = new DuelContext(Initiator, Ruleset.Layout, false);
|
|
|
|
dc.Ruleset = Ruleset;
|
|
dc.Rematch = true;
|
|
|
|
dc.Participants.Clear();
|
|
|
|
for (int i = 0; i < Participants.Count; ++i)
|
|
{
|
|
Participant oldPart = Participants[i];
|
|
Participant newPart = new Participant(dc, oldPart.Players.Length);
|
|
|
|
for (int j = 0; j < oldPart.Players.Length; ++j)
|
|
{
|
|
DuelPlayer oldPlayer = oldPart.Players[j];
|
|
|
|
if (oldPlayer != null)
|
|
newPart.Players[j] = new DuelPlayer(oldPlayer.Mobile, newPart);
|
|
}
|
|
|
|
dc.Participants.Add(newPart);
|
|
}
|
|
|
|
dc.CloseAllGumps();
|
|
dc.SendReadyUpGump();
|
|
}
|
|
|
|
public DuelPlayer Find(Mobile mob)
|
|
{
|
|
if (mob is PlayerMobile pm)
|
|
{
|
|
if (pm.DuelContext == this)
|
|
return pm.DuelPlayer;
|
|
|
|
return null;
|
|
}
|
|
|
|
for (int i = 0; i < Participants.Count; ++i)
|
|
{
|
|
Participant p = Participants[i];
|
|
DuelPlayer pl = p.Find(mob);
|
|
|
|
if (pl != null)
|
|
return pl;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public bool IsAlly(Mobile m1, Mobile m2)
|
|
{
|
|
DuelPlayer pl1 = Find(m1);
|
|
DuelPlayer pl2 = Find(m2);
|
|
|
|
return pl1 != null && pl1.Participant == pl2?.Participant;
|
|
}
|
|
|
|
public Participant CheckCompletion()
|
|
{
|
|
Participant winner = null;
|
|
|
|
bool hasWinner = false;
|
|
int eliminated = 0;
|
|
|
|
for (int i = 0; i < Participants.Count; ++i)
|
|
{
|
|
Participant 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)
|
|
{
|
|
cb(count);
|
|
m_Countdown = Timer.DelayCall(TimeSpan.FromSeconds(1.0), TimeSpan.FromSeconds(1.0), count,
|
|
() => Countdown_Callback(--count, cb));
|
|
}
|
|
|
|
public void StopCountdown()
|
|
{
|
|
m_Countdown?.Stop();
|
|
m_Countdown = null;
|
|
}
|
|
|
|
private void Countdown_Callback(int count, CountdownCallback cb)
|
|
{
|
|
if (count == 0)
|
|
StopCountdown();
|
|
|
|
cb(count);
|
|
}
|
|
|
|
public void StopSDTimers()
|
|
{
|
|
m_SDWarnTimer?.Stop();
|
|
|
|
m_SDWarnTimer = null;
|
|
|
|
m_SDActivateTimer?.Stop();
|
|
|
|
m_SDActivateTimer = null;
|
|
}
|
|
|
|
public void StartSuddenDeath(TimeSpan timeUntilActive)
|
|
{
|
|
m_SDWarnTimer?.Stop();
|
|
|
|
m_SDWarnTimer = Timer.DelayCall(TimeSpan.FromMinutes(timeUntilActive.TotalMinutes * 0.9), WarnSuddenDeath);
|
|
|
|
m_SDActivateTimer?.Stop();
|
|
|
|
m_SDActivateTimer = Timer.DelayCall(timeUntilActive, ActivateSuddenDeath);
|
|
}
|
|
|
|
public void WarnSuddenDeath()
|
|
{
|
|
for (int i = 0; i < Participants.Count; ++i)
|
|
{
|
|
Participant p = Participants[i];
|
|
|
|
for (int j = 0; j < p.Players.Length; ++j)
|
|
{
|
|
DuelPlayer 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!");
|
|
|
|
m_SDWarnTimer?.Stop();
|
|
|
|
m_SDWarnTimer = null;
|
|
}
|
|
|
|
public static bool CheckSuddenDeath(Mobile mob) => mob is PlayerMobile pm && pm.DuelPlayer?.Eliminated == false && pm.DuelContext?.IsSuddenDeath == true;
|
|
|
|
public void ActivateSuddenDeath()
|
|
{
|
|
for (int i = 0; i < Participants.Count; ++i)
|
|
{
|
|
Participant p = Participants[i];
|
|
|
|
for (int j = 0; j < p.Players.Length; ++j)
|
|
{
|
|
DuelPlayer 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;
|
|
|
|
m_SDActivateTimer?.Stop();
|
|
|
|
m_SDActivateTimer = null;
|
|
}
|
|
|
|
public void BeginAutoTie()
|
|
{
|
|
m_AutoTieTimer?.Stop();
|
|
|
|
TimeSpan ts = m_Tournament == null || m_Tournament.TourneyType == TourneyType.Standard
|
|
? AutoTieDelay
|
|
: TimeSpan.FromMinutes(90.0);
|
|
|
|
m_AutoTieTimer = Timer.DelayCall(ts, InvokeAutoTie);
|
|
}
|
|
|
|
public void EndAutoTie()
|
|
{
|
|
m_AutoTieTimer?.Stop();
|
|
|
|
m_AutoTieTimer = null;
|
|
}
|
|
|
|
public void InvokeAutoTie()
|
|
{
|
|
m_AutoTieTimer = null;
|
|
|
|
if (!Started || Finished)
|
|
return;
|
|
|
|
Tied = true;
|
|
Finished = true;
|
|
|
|
StopSDTimers();
|
|
|
|
List<TourneyParticipant> remaining = new List<TourneyParticipant>();
|
|
|
|
for (int i = 0; i < Participants.Count; ++i)
|
|
{
|
|
Participant 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 (int j = 0; j < p.Players.Length; ++j)
|
|
{
|
|
DuelPlayer 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 (int j = 0; j < p.Players.Length; ++j)
|
|
{
|
|
DuelPlayer pl = p.Players[j];
|
|
|
|
if (pl != null)
|
|
{
|
|
pl.Mobile.Delta(MobileDelta.Noto);
|
|
pl.Mobile.SendEverything();
|
|
}
|
|
}
|
|
}
|
|
|
|
m_Tournament?.HandleTie(Arena, m_Match, remaining);
|
|
|
|
Timer.DelayCall(TimeSpan.FromSeconds(10.0), Unregister);
|
|
}
|
|
|
|
public static void Initialize()
|
|
{
|
|
EventSink.Speech += EventSink_Speech;
|
|
EventSink.Login += EventSink_Login;
|
|
|
|
CommandSystem.Register("vli", AccessLevel.GameMaster, vli_oc);
|
|
}
|
|
|
|
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)
|
|
{
|
|
Ladder ladder = Ladder.Instance;
|
|
|
|
if (ladder == null)
|
|
return;
|
|
|
|
LadderEntry entry = ladder.Find(pm);
|
|
|
|
if (entry != null)
|
|
from.SendGump(new PropertiesGump(from, entry));
|
|
}
|
|
}
|
|
|
|
public static bool CheckCombat(Mobile m) =>
|
|
m.Aggressed.Any(info => info.Defender.Player && DateTime.UtcNow - info.LastCombatTime < CombatDelay) ||
|
|
m.Aggressors.Any(info => info.Attacker.Player && DateTime.UtcNow - info.LastCombatTime < CombatDelay);
|
|
|
|
private static void EventSink_Login(Mobile m)
|
|
{
|
|
if (!(m is PlayerMobile pm))
|
|
return;
|
|
|
|
DuelContext 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)
|
|
{
|
|
LadderEntry entry = ladder.Find(pm);
|
|
|
|
if (entry == null)
|
|
return; // sanity
|
|
|
|
string text =
|
|
$"{{0}} are ranked {LadderGump.Rank(entry.Index + 1)} at level {Ladder.GetLevel(entry.Experience)}.";
|
|
|
|
pm.PrivateOverheadMessage(MessageType.Regular, pm.SpeechHue, true,
|
|
string.Format(text, from == pm ? "You" : "They"), from.NetState);
|
|
}
|
|
else if (obj is Mobile mob)
|
|
{
|
|
if (mob.Body.IsHuman)
|
|
mob.PrivateOverheadMessage(MessageType.Regular, mob.SpeechHue, false,
|
|
"I'm not a duelist, and quite frankly, I resent the implication.", from.NetState);
|
|
else
|
|
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 PlayerMobile pm))
|
|
return;
|
|
|
|
if (Insensitive.Contains(e.Speech, "i wish to duel"))
|
|
{
|
|
if (!pm.CheckAlive())
|
|
{
|
|
}
|
|
else if (pm.Region.IsPartOf<Jail>())
|
|
{
|
|
}
|
|
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)
|
|
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.");
|
|
}
|
|
else
|
|
{
|
|
pm.SendGump(new DuelContextGump(pm, new DuelContext(pm, RulesetLayout.Root)));
|
|
e.Handled = true;
|
|
}
|
|
}
|
|
else if (Insensitive.Equals(e.Speech, "change arena preferences"))
|
|
{
|
|
if (!pm.CheckAlive())
|
|
{
|
|
}
|
|
else
|
|
{
|
|
Preferences prefs = Preferences.Instance;
|
|
|
|
if (prefs != null)
|
|
{
|
|
e.Mobile.CloseGump<PreferencesGump>();
|
|
e.Mobile.SendGump(new PreferencesGump(e.Mobile, prefs));
|
|
}
|
|
}
|
|
}
|
|
else if (Insensitive.Equals(e.Speech, "showladder"))
|
|
{
|
|
e.Blocked = true;
|
|
if (!pm.CheckAlive())
|
|
{
|
|
}
|
|
else
|
|
{
|
|
Ladder instance = Ladder.Instance;
|
|
|
|
if (instance == null)
|
|
{
|
|
// pm.SendMessage( "Ladder not yet initialized." );
|
|
}
|
|
else
|
|
{
|
|
LadderEntry entry = instance.Find(pm);
|
|
|
|
if (entry == null)
|
|
return; // sanity
|
|
|
|
string 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 (Insensitive.Equals(e.Speech, "viewladder"))
|
|
{
|
|
e.Blocked = true;
|
|
|
|
if (!pm.CheckAlive())
|
|
{
|
|
}
|
|
else
|
|
{
|
|
Ladder 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);
|
|
}
|
|
}
|
|
}
|
|
else if (Insensitive.Contains(e.Speech, "i yield"))
|
|
{
|
|
if (!pm.CheckAlive())
|
|
{
|
|
}
|
|
else if (pm.DuelContext == null)
|
|
{
|
|
}
|
|
else if (pm.DuelContext.Finished)
|
|
{
|
|
e.Mobile.SendMessage(0x22, "The duel is already finished.");
|
|
}
|
|
else if (!pm.DuelContext.Started)
|
|
{
|
|
DuelContext dc = pm.DuelContext;
|
|
Mobile init = dc.Initiator;
|
|
|
|
if (pm.DuelContext.StartedBeginCountdown)
|
|
{
|
|
e.Mobile.SendMessage(0x22, "The duel has not yet started.");
|
|
}
|
|
else
|
|
{
|
|
DuelPlayer pl = pm.DuelContext.Find(pm);
|
|
|
|
if (pl == null)
|
|
return;
|
|
|
|
Participant 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;
|
|
|
|
NetState ns = init.NetState;
|
|
|
|
if (ns != null)
|
|
foreach (Gump g in ns.Gumps)
|
|
{
|
|
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;
|
|
|
|
NetState ns = init.NetState;
|
|
|
|
if (ns != null)
|
|
{
|
|
bool send = true;
|
|
|
|
foreach (Gump g in ns.Gumps)
|
|
{
|
|
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.m_Countdown?.Stop();
|
|
pm.DuelContext.m_Countdown = null;
|
|
|
|
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;
|
|
|
|
NetState ns = init.NetState;
|
|
|
|
if (ns != null)
|
|
{
|
|
bool send = true;
|
|
|
|
foreach (Gump g in ns.Gumps)
|
|
{
|
|
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
|
|
{
|
|
DuelPlayer pl = pm.DuelContext.Find(pm);
|
|
|
|
if (pl != null)
|
|
{
|
|
if (pm.DuelContext.IsOneVsOne)
|
|
{
|
|
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.");
|
|
}
|
|
else
|
|
{
|
|
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;
|
|
|
|
Participant winner = pm.DuelContext.CheckCompletion();
|
|
|
|
if (winner != null)
|
|
pm.DuelContext.Finish(winner);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
e.Mobile.SendMessage(0x22, "BUG: Unable to find duel context.");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public void CloseAllGumps(DuelPlayer pl)
|
|
{
|
|
pl.Mobile.CloseGump<BeginGump>();
|
|
pl.Mobile.CloseGump<DuelContextGump>();
|
|
pl.Mobile.CloseGump<ParticipantGump>();
|
|
pl.Mobile.CloseGump<PickRulesetGump>();
|
|
pl.Mobile.CloseGump<ReadyGump>();
|
|
pl.Mobile.CloseGump<ReadyUpGump>();
|
|
pl.Mobile.CloseGump<RulesetGump>();
|
|
}
|
|
|
|
public void CloseAllGumps()
|
|
{
|
|
for (int i = 0; i < Participants.Count; ++i)
|
|
{
|
|
Participant p = Participants[i];
|
|
|
|
for (int j = 0; j < p.Players.Length; ++j)
|
|
{
|
|
DuelPlayer pl = p.Players[j];
|
|
|
|
if (pl != null)
|
|
CloseAllGumps(pl);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void RejectReady(Mobile rejector, string page)
|
|
{
|
|
if (StartedReadyCountdown)
|
|
return; // sanity
|
|
|
|
for (int i = 0; i < Participants.Count; ++i)
|
|
{
|
|
Participant p = Participants[i];
|
|
|
|
for (int j = 0; j < p.Players.Length; ++j)
|
|
{
|
|
DuelPlayer pl = p.Players[j];
|
|
|
|
if (pl == null)
|
|
continue;
|
|
|
|
pl.Ready = false;
|
|
|
|
Mobile mob = pl.Mobile;
|
|
|
|
if (page == null) // yield
|
|
{
|
|
if (mob != rejector)
|
|
mob.SendMessage(0x22, "{0} has yielded.", rejector.Name);
|
|
}
|
|
else
|
|
{
|
|
if (mob == rejector)
|
|
mob.SendMessage(0x22, "You have rejected the {0}.", Rematch ? "rematch" : page);
|
|
else
|
|
mob.SendMessage(0x22, "{0} has rejected the {1}.", rejector.Name, Rematch ? "rematch" : page);
|
|
}
|
|
|
|
// Close all of them?
|
|
mob.CloseGump<DuelContextGump>();
|
|
mob.CloseGump<ReadyUpGump>();
|
|
mob.CloseGump<ReadyGump>();
|
|
}
|
|
}
|
|
|
|
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 Offset");
|
|
mob.RemoveStatMod("[Magic] Dex Offset");
|
|
mob.RemoveStatMod("[Magic] Int Offset");
|
|
mob.RemoveStatMod("Concussion");
|
|
mob.RemoveStatMod("blood-rose");
|
|
mob.RemoveStatMod("clarity-potion");
|
|
|
|
OrangePetals.RemoveContext(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<DefensiveSpell>();
|
|
}
|
|
|
|
TransformationSpellHelper.RemoveContext(mob, true);
|
|
AnimalForm.RemoveContext(mob, true);
|
|
|
|
if (DisguiseTimers.IsDisguised(mob))
|
|
DisguiseTimers.StopTimer(mob);
|
|
|
|
if (!mob.CanBeginAction<PolymorphSpell>())
|
|
{
|
|
mob.BodyMod = 0;
|
|
mob.HueMod = -1;
|
|
mob.EndAction<PolymorphSpell>();
|
|
}
|
|
|
|
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 (int i = 0; i < m_Walls.Count; ++i)
|
|
m_Walls[i].Delete();
|
|
|
|
m_Walls.Clear();
|
|
}
|
|
|
|
public void CreateWall()
|
|
{
|
|
if (Arena == null)
|
|
return;
|
|
|
|
Point3D start = Arena.Points.EdgeWest;
|
|
Point3D wall = Arena.Wall;
|
|
|
|
int dx = start.X - wall.X;
|
|
int dy = start.Y - wall.Y;
|
|
int rx = dx - dy;
|
|
int ry = dx + dy;
|
|
|
|
bool eastToWest;
|
|
|
|
if (rx >= 0 && ry >= 0)
|
|
eastToWest = false;
|
|
else if (rx >= 0)
|
|
eastToWest = true;
|
|
else if (ry >= 0)
|
|
eastToWest = true;
|
|
else
|
|
eastToWest = false;
|
|
|
|
Effects.PlaySound(wall, Arena.Facet, 0x1F6);
|
|
|
|
for (int i = -1; i <= 1; ++i)
|
|
{
|
|
Point3D loc = new Point3D(eastToWest ? wall.X + i : wall.X, eastToWest ? wall.Y : wall.Y + i, wall.Z);
|
|
|
|
InternalWall created = new InternalWall();
|
|
|
|
created.Appear(loc, Arena.Facet);
|
|
|
|
m_Walls.Add(created);
|
|
}
|
|
}
|
|
|
|
public void BuildParties()
|
|
{
|
|
for (int i = 0; i < Participants.Count; ++i)
|
|
{
|
|
Participant p = Participants[i];
|
|
|
|
if (p.Players.Length > 1)
|
|
{
|
|
List<Mobile> players = new List<Mobile>();
|
|
|
|
for (int j = 0; j < p.Players.Length; ++j)
|
|
{
|
|
DuelPlayer dp = p.Players[j];
|
|
|
|
if (dp == null)
|
|
continue;
|
|
|
|
players.Add(dp.Mobile);
|
|
}
|
|
|
|
if (players.Count > 1)
|
|
for (int leaderIndex = 0; leaderIndex + 1 < players.Count; leaderIndex += Party.Capacity)
|
|
{
|
|
Mobile leader = players[leaderIndex];
|
|
Party 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 (int j = leaderIndex + 1; j < players.Count && j < leaderIndex + Party.Capacity; ++j)
|
|
{
|
|
Mobile player = players[j];
|
|
Party 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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public void ClearIllegalItems()
|
|
{
|
|
for (int i = 0; i < Participants.Count; ++i)
|
|
{
|
|
Participant p = Participants[i];
|
|
|
|
for (int j = 0; j < p.Players.Length; ++j)
|
|
{
|
|
DuelPlayer 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;
|
|
|
|
Container pack = mob.Backpack;
|
|
|
|
if (pack == null)
|
|
return;
|
|
|
|
for (int i = mob.Items.Count - 1; i >= 0; --i)
|
|
{
|
|
if (i >= mob.Items.Count)
|
|
continue; // sanity
|
|
|
|
Item item = mob.Items[i];
|
|
|
|
if (!CheckItemEquip(mob, item))
|
|
{
|
|
pack.DropItem(item);
|
|
|
|
if (item is BaseWeapon)
|
|
mob.SendLocalizedMessage(1062001,
|
|
item.Name ?? $"#{item.LabelNumber}"); // You can no longer wield your ~1_WEAPON~
|
|
else if (item is BaseArmor && !(item is BaseShield))
|
|
mob.SendLocalizedMessage(1062002,
|
|
item.Name ?? $"#{item.LabelNumber}"); // You can no longer wear your ~1_ARMOR~
|
|
else
|
|
mob.SendLocalizedMessage(1062003,
|
|
item.Name ?? $"#{item.LabelNumber}"); // You can no longer equip your ~1_SHIELD~
|
|
}
|
|
}
|
|
|
|
Item inHand = mob.Holding;
|
|
|
|
if (inHand != null && !CheckItemEquip(mob, inHand))
|
|
{
|
|
mob.Holding = null;
|
|
|
|
BounceInfo 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;
|
|
|
|
Ruleset ruleset = Ruleset;
|
|
Ruleset basedef = ruleset.Base;
|
|
|
|
mob.SendMessage("Ruleset: {0}", basedef.Title);
|
|
|
|
BitArray defs;
|
|
|
|
if (ruleset.Flavors.Count > 0)
|
|
{
|
|
defs = new BitArray(basedef.Options);
|
|
|
|
for (int i = 0; i < ruleset.Flavors.Count; ++i)
|
|
{
|
|
defs.Or(ruleset.Flavors[i].Options);
|
|
|
|
mob.SendMessage(" + {0}", ruleset.Flavors[i].Title);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
defs = basedef.Options;
|
|
}
|
|
|
|
int changes = 0;
|
|
|
|
BitArray opts = ruleset.Options;
|
|
|
|
for (int i = 0; i < opts.Length; ++i)
|
|
if (defs[i] != opts[i])
|
|
{
|
|
string name = ruleset.Layout.FindByIndex(i);
|
|
|
|
if (name != null) // sanity
|
|
{
|
|
++changes;
|
|
|
|
if (changes == 1) mob.SendMessage("Modifications:");
|
|
|
|
mob.SendMessage("{0}: {1}", name, opts[i] ? "enabled" : "disabled");
|
|
}
|
|
}
|
|
}
|
|
|
|
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 (int i = 0; i < Participants.Count; ++i)
|
|
{
|
|
Participant p = Participants[i];
|
|
|
|
for (int j = 0; j < p.Players.Length; ++j)
|
|
{
|
|
DuelPlayer pl = p.Players[j];
|
|
|
|
if (pl == null)
|
|
continue;
|
|
|
|
Mobile mob = pl.Mobile;
|
|
|
|
if (count > 0)
|
|
{
|
|
if (count == 10)
|
|
{
|
|
mob.CloseGump<ReadyGump>();
|
|
mob.CloseGump<ReadyUpGump>();
|
|
mob.CloseGump<BeginGump>();
|
|
mob.SendGump(new BeginGump(count));
|
|
}
|
|
|
|
mob.Frozen = true;
|
|
}
|
|
else
|
|
{
|
|
mob.CloseGump<BeginGump>();
|
|
mob.Frozen = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public void RemoveAggressions(Mobile mob)
|
|
{
|
|
for (int i = 0; i < Participants.Count; ++i)
|
|
{
|
|
Participant p = Participants[i];
|
|
|
|
for (int j = 0; j < p.Players.Length; ++j)
|
|
{
|
|
DuelPlayer 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 (int i = 0; i < Participants.Count; ++i)
|
|
{
|
|
Participant p = Participants[i];
|
|
|
|
for (int j = 0; j < p.Players.Length; ++j)
|
|
{
|
|
DuelPlayer pl = p.Players[j];
|
|
|
|
Mobile mob = pl?.Mobile;
|
|
|
|
if (mob != null && m_Tournament == null)
|
|
{
|
|
mob.CloseGump<ReadyUpGump>();
|
|
mob.SendGump(new ReadyUpGump(mob, this));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public string ValidateStart()
|
|
{
|
|
if (m_Tournament == null && TournamentController.IsActive)
|
|
return "a tournament is active";
|
|
|
|
for (int i = 0; i < Participants.Count; ++i)
|
|
{
|
|
Participant p = Participants[i];
|
|
|
|
for (int j = 0; j < p.Players.Length; ++j)
|
|
{
|
|
DuelPlayer dp = p.Players[j];
|
|
|
|
if (dp == null)
|
|
return "a slot is empty";
|
|
|
|
if (dp.Mobile.Region.IsPartOf<Jail>())
|
|
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)
|
|
{
|
|
IMount 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)
|
|
{
|
|
string error = ValidateStart();
|
|
|
|
if (error != null)
|
|
{
|
|
for (int i = 0; i < Participants.Count; ++i)
|
|
{
|
|
Participant p = Participants[i];
|
|
|
|
for (int j = 0; j < p.Players.Length; ++j)
|
|
{
|
|
DuelPlayer dp = p.Players[j];
|
|
|
|
dp?.Mobile.SendMessage("The duel could not be started because {0}.", error);
|
|
}
|
|
}
|
|
|
|
StartCountdown(10, SendReadyGump);
|
|
|
|
return;
|
|
}
|
|
|
|
ReadyWait = false;
|
|
|
|
List<Mobile> players = new List<Mobile>();
|
|
|
|
for (int i = 0; i < Participants.Count; ++i)
|
|
{
|
|
Participant p = Participants[i];
|
|
|
|
for (int j = 0; j < p.Players.Length; ++j)
|
|
{
|
|
DuelPlayer dp = p.Players[j];
|
|
|
|
if (dp != null)
|
|
players.Add(dp.Mobile);
|
|
}
|
|
}
|
|
|
|
Arena arena = m_OverrideArena ?? Arena.FindArena(players);
|
|
|
|
if (arena == null)
|
|
{
|
|
for (int i = 0; i < Participants.Count; ++i)
|
|
{
|
|
Participant p = Participants[i];
|
|
|
|
for (int j = 0; j < p.Players.Length; ++j)
|
|
{
|
|
DuelPlayer 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 ExitTeleporter tp))
|
|
{
|
|
arena.Teleporter = tp = new ExitTeleporter();
|
|
tp.MoveToWorld(arena.GateOut == Point3D.Zero ? arena.Outside : arena.GateOut, arena.Facet);
|
|
}
|
|
|
|
ArenaMoongate mg = new ArenaMoongate(arena.GateIn == Point3D.Zero ? arena.Outside : arena.GateIn,
|
|
arena.Facet, tp);
|
|
|
|
StartedBeginCountdown = true;
|
|
|
|
for (int i = 0; i < Participants.Count; ++i)
|
|
{
|
|
Participant p = Participants[i];
|
|
|
|
for (int j = 0; j < p.Players.Length; ++j)
|
|
{
|
|
DuelPlayer 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 (int i = 0; i < Participants.Count; ++i)
|
|
{
|
|
Participant p = Participants[i];
|
|
|
|
for (int j = 0; j < p.Players.Length; ++j)
|
|
{
|
|
DuelPlayer 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);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
ReadyWait = true;
|
|
|
|
bool isAllReady = true;
|
|
|
|
for (int i = 0; i < Participants.Count; ++i)
|
|
{
|
|
Participant p = Participants[i];
|
|
|
|
for (int j = 0; j < p.Players.Length; ++j)
|
|
{
|
|
DuelPlayer pl = p.Players[j];
|
|
|
|
if (pl == null)
|
|
continue;
|
|
|
|
Mobile mob = pl.Mobile;
|
|
|
|
if (pl.Ready)
|
|
{
|
|
if (m_Tournament == null)
|
|
{
|
|
mob.CloseGump<ReadyGump>();
|
|
mob.SendGump(new ReadyGump(mob, this, count));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
isAllReady = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (count == -1 && isAllReady)
|
|
StartCountdown(3, SendReadyGump);
|
|
}
|
|
|
|
private class InternalWall : Item
|
|
{
|
|
public InternalWall() : base(0x80) => Movable = false;
|
|
|
|
public InternalWall(Serial serial) : base(serial)
|
|
{
|
|
}
|
|
|
|
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);
|
|
|
|
int 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 = DateTime.UtcNow + TimeSpan.FromMinutes(30.0);
|
|
}
|
|
|
|
public Mobile Mobile { get; }
|
|
|
|
public Point3D Location { get; private set; }
|
|
|
|
public Map Facet { get; private set; }
|
|
|
|
public bool Expired => DateTime.UtcNow >= m_Expire;
|
|
|
|
public void Return()
|
|
{
|
|
if (Facet == Map.Internal || Facet == null)
|
|
return;
|
|
|
|
if (Mobile.Map == Map.Internal)
|
|
{
|
|
Mobile.LogoutLocation = Location;
|
|
Mobile.LogoutMap = Facet;
|
|
}
|
|
else
|
|
{
|
|
Mobile.Location = Location;
|
|
Mobile.Map = Facet;
|
|
}
|
|
}
|
|
|
|
public void Update()
|
|
{
|
|
m_Expire = DateTime.UtcNow + TimeSpan.FromMinutes(30.0);
|
|
|
|
if (Mobile.Map == Map.Internal)
|
|
{
|
|
Facet = Mobile.LogoutMap;
|
|
Location = Mobile.LogoutLocation;
|
|
}
|
|
else
|
|
{
|
|
Facet = Mobile.Map;
|
|
Location = Mobile.Location;
|
|
}
|
|
}
|
|
}
|
|
|
|
private class ExitTeleporter : Item
|
|
{
|
|
private List<ReturnEntry> m_Entries;
|
|
|
|
public ExitTeleporter() : base(0x1822)
|
|
{
|
|
m_Entries = new List<ReturnEntry>();
|
|
|
|
Hue = 0x482;
|
|
Movable = false;
|
|
}
|
|
|
|
public ExitTeleporter(Serial serial) : base(serial)
|
|
{
|
|
}
|
|
|
|
public override string DefaultName => "return teleporter";
|
|
|
|
public void Register(Mobile mob)
|
|
{
|
|
ReturnEntry entry = Find(mob);
|
|
|
|
if (entry != null)
|
|
{
|
|
entry.Update();
|
|
return;
|
|
}
|
|
|
|
m_Entries.Add(new ReturnEntry(mob));
|
|
}
|
|
|
|
private ReturnEntry Find(Mobile mob)
|
|
{
|
|
for (int i = 0; i < m_Entries.Count; ++i)
|
|
{
|
|
ReturnEntry 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;
|
|
|
|
ReturnEntry 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 (int i = 0; i < m_Entries.Count; ++i)
|
|
{
|
|
ReturnEntry 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);
|
|
|
|
int version = reader.ReadInt();
|
|
|
|
switch (version)
|
|
{
|
|
case 0:
|
|
{
|
|
int count = reader.ReadEncodedInt();
|
|
|
|
m_Entries = new List<ReturnEntry>(count);
|
|
|
|
for (int i = 0; i < count; ++i)
|
|
{
|
|
Mobile mob = reader.ReadMobile();
|
|
Point3D loc = reader.ReadPoint3D();
|
|
Map map = reader.ReadMap();
|
|
|
|
m_Entries.Add(new ReturnEntry(mob, loc, map));
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private 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;
|
|
MessageString = "Are you sure you wish to spectate this duel?";
|
|
TitleColor = 0x7800;
|
|
TitleNumber = 1062051; // Gate Warning
|
|
|
|
Timer.DelayCall(TimeSpan.FromSeconds(10.0), Delete);
|
|
}
|
|
|
|
public ArenaMoongate(Serial serial) : base(serial)
|
|
{
|
|
}
|
|
|
|
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);
|
|
|
|
int version = reader.ReadInt();
|
|
|
|
Delete();
|
|
}
|
|
}
|
|
}
|
|
}
|