2626 lines
64 KiB
C#
2626 lines
64 KiB
C#
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using Server.Commands;
|
|
using Server.Engines.PartySystem;
|
|
using Server.Factions;
|
|
using Server.Gumps;
|
|
using Server.Items;
|
|
using Server.Mobiles;
|
|
using Server.Network;
|
|
using Server.Spells;
|
|
using Server.Spells.Bushido;
|
|
using Server.Spells.Chivalry;
|
|
using Server.Spells.Necromancy;
|
|
using Server.Spells.Ninjitsu;
|
|
using Server.Spells.Seventh;
|
|
using Server.Spells.Spellweaving;
|
|
|
|
namespace Server.Engines.ConPVP
|
|
{
|
|
public delegate void CountdownCallback( int count );
|
|
|
|
public class DuelContext
|
|
{
|
|
private Mobile m_Initiator;
|
|
private ArrayList m_Participants;
|
|
private Ruleset m_Ruleset;
|
|
private Arena m_Arena;
|
|
private bool m_Registered = true;
|
|
private bool m_Finished, m_Started;
|
|
|
|
private bool m_ReadyWait;
|
|
private int m_ReadyCount;
|
|
|
|
private bool m_Rematch;
|
|
|
|
public bool Rematch => m_Rematch;
|
|
|
|
public bool ReadyWait => m_ReadyWait;
|
|
public int ReadyCount => m_ReadyCount;
|
|
|
|
public bool Registered => m_Registered;
|
|
public bool Finished => m_Finished;
|
|
public bool Started => m_Started;
|
|
|
|
public Mobile Initiator => m_Initiator;
|
|
public ArrayList Participants => m_Participants;
|
|
public Ruleset Ruleset => m_Ruleset;
|
|
public Arena Arena => m_Arena;
|
|
|
|
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, new TimerStateCallback( DelayBounce_Callback ), new object[]{ mob, corpse } );
|
|
}
|
|
|
|
public static bool AllowSpecialMove( Mobile from, string name, SpecialMove move )
|
|
{
|
|
if ( !(from is PlayerMobile pm) )
|
|
return true;
|
|
|
|
DuelContext dc = pm.DuelContext;
|
|
|
|
// No DuelContext, or InstaAllowSpecialMove
|
|
return dc?.InstAllowSpecialMove( from, name, move ) != false;
|
|
}
|
|
|
|
public bool InstAllowSpecialMove( Mobile from, string name, SpecialMove move )
|
|
{
|
|
|
|
if ( !m_StartedBeginCountdown )
|
|
return true;
|
|
|
|
DuelPlayer pl = Find( from );
|
|
|
|
if ( pl == null || pl.Eliminated )
|
|
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 || m_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 ( !m_StartedBeginCountdown )
|
|
return true;
|
|
|
|
DuelPlayer pl = Find( from );
|
|
|
|
if ( pl == null || pl.Eliminated )
|
|
return true;
|
|
|
|
if ( CantDoAnything( from ) )
|
|
return false;
|
|
|
|
if ( spell is Server.Spells.Fourth.RecallSpell )
|
|
from.SendMessage( "You may not cast this spell." );
|
|
|
|
string title = null, option = null;
|
|
|
|
if ( spell is ArcanistSpell )
|
|
{
|
|
title = "Spellweaving";
|
|
option = spell.Name;
|
|
}
|
|
else if ( spell is PaladinSpell )
|
|
{
|
|
title = "Chivalry";
|
|
option = spell.Name;
|
|
}
|
|
else if ( spell is NecromancerSpell )
|
|
{
|
|
title = "Necromancy";
|
|
option = spell.Name;
|
|
}
|
|
else if ( spell is NinjaSpell )
|
|
{
|
|
title = "Ninjitsu";
|
|
option = spell.Name;
|
|
}
|
|
else if ( spell is SamuraiSpell )
|
|
{
|
|
title = "Bushido";
|
|
option = spell.Name;
|
|
}
|
|
else if ( spell is MagerySpell magerySpell )
|
|
{
|
|
switch( magerySpell.Circle )
|
|
{
|
|
case SpellCircle.First: title = "1st Circle"; break;
|
|
case SpellCircle.Second: title = "2nd Circle"; break;
|
|
case SpellCircle.Third: title = "3rd Circle"; break;
|
|
case SpellCircle.Fourth: title = "4th Circle"; break;
|
|
case SpellCircle.Fifth: title = "5th Circle"; break;
|
|
case SpellCircle.Sixth: title = "6th Circle"; break;
|
|
case SpellCircle.Seventh: title = "7th Circle"; break;
|
|
case SpellCircle.Eighth: title = "8th Circle"; break;
|
|
}
|
|
|
|
option = magerySpell.Name;
|
|
}
|
|
else
|
|
{
|
|
title = "Other Spell";
|
|
option = spell.Name;
|
|
}
|
|
|
|
if ( title == null || option == null || m_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 ( !m_StartedBeginCountdown )
|
|
return true;
|
|
|
|
DuelPlayer pl = Find( from );
|
|
|
|
if ( pl == null || pl.Eliminated )
|
|
return true;
|
|
|
|
if ( item is Dagger || CheckItemEquip( from, item ) )
|
|
return true;
|
|
|
|
from.SendMessage( "The dueling ruleset prevents you from equiping 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 ( !m_StartedBeginCountdown )
|
|
return true;
|
|
|
|
DuelPlayer pl = Find( from );
|
|
|
|
if ( pl?.Eliminated != false )
|
|
return true;
|
|
|
|
if ( CantDoAnything( from ) )
|
|
return false;
|
|
|
|
if ( m_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 ( !m_Ruleset.GetOption( "Weapons", "Wrestling" ) )
|
|
return false;
|
|
}
|
|
else if ( item is BaseArmor armor )
|
|
{
|
|
if ( armor.ProtectionLevel > ArmorProtectionLevel.Regular && !m_Ruleset.GetOption( "Armor", "Magical" ) )
|
|
return false;
|
|
|
|
if ( !Core.AOS && armor.Resource != armor.DefaultResource && !m_Ruleset.GetOption( "Armor", "Colored" ) )
|
|
return false;
|
|
|
|
if ( armor is BaseShield && !m_Ruleset.GetOption( "Armor", "Shields" ) )
|
|
return false;
|
|
}
|
|
else if ( item is BaseWeapon weapon )
|
|
{
|
|
if ( (weapon.DamageLevel > WeaponDamageLevel.Regular || weapon.AccuracyLevel > WeaponAccuracyLevel.Regular) && !m_Ruleset.GetOption( "Weapons", "Magical" ) )
|
|
return false;
|
|
|
|
if ( !Core.AOS && weapon.Resource != CraftResource.Iron && weapon.Resource != CraftResource.None && !m_Ruleset.GetOption( "Weapons", "Runics" ) )
|
|
return false;
|
|
|
|
if ( weapon is BaseRanged && !m_Ruleset.GetOption( "Weapons", "Ranged" ) )
|
|
return false;
|
|
|
|
if ( !(weapon is BaseRanged) && !m_Ruleset.GetOption( "Weapons", "Melee" ) )
|
|
return false;
|
|
|
|
if ( weapon.PoisonCharges > 0 && weapon.Poison != null && !m_Ruleset.GetOption( "Weapons", "Poisoned" ) )
|
|
return false;
|
|
|
|
if ( weapon is BaseWand && !m_Ruleset.GetOption( "Items", "Wands" ) )
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public bool AllowSkillUse( Mobile from, SkillName skill )
|
|
{
|
|
if ( !m_StartedBeginCountdown )
|
|
return true;
|
|
|
|
DuelPlayer pl = Find( from );
|
|
|
|
if ( pl == null || pl.Eliminated )
|
|
return true;
|
|
|
|
if ( CantDoAnything( from ) )
|
|
return false;
|
|
|
|
int id = (int)skill;
|
|
|
|
if ( id >= 0 && id < SkillInfo.Table.Length )
|
|
{
|
|
if ( m_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 ( !m_StartedBeginCountdown )
|
|
return true;
|
|
|
|
DuelPlayer pl = Find( from );
|
|
|
|
if ( pl == null || pl.Eliminated )
|
|
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 && m_StartedBeginCountdown && !m_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 || m_Ruleset.GetOption( title, option ) )
|
|
return true;
|
|
|
|
from.SendMessage( "The dueling ruleset prevents you from using this item." );
|
|
return false;
|
|
}
|
|
|
|
private void DelayBounce_Callback( object state )
|
|
{
|
|
object[] states = (object[])state;
|
|
Mobile mob = (Mobile) states[0];
|
|
Container corpse = (Container) states[1];
|
|
|
|
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 ( !m_Registered || !m_StartedBeginCountdown || m_Finished )
|
|
return;
|
|
|
|
Arena arena = m_Arena;
|
|
|
|
if ( arena == null )
|
|
return;
|
|
|
|
if ( mob.Map == arena.Facet && arena.Bounds.Contains( mob.Location ) )
|
|
return;
|
|
|
|
DuelPlayer pl = Find( mob );
|
|
|
|
if ( pl == null || pl.Eliminated )
|
|
return;
|
|
|
|
if ( mob.Map == Map.Internal ) {
|
|
// they've logged out
|
|
|
|
if ( mob.LogoutMap == arena.Facet && arena.Bounds.Contains( mob.LogoutLocation ) ) {
|
|
// they logged out inside the arena.. set them to eject on login
|
|
|
|
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, String.Format( "{0} has forfeited by leaving the dueling arena.", mob.Name ) );
|
|
|
|
Participant winner = CheckCompletion();
|
|
|
|
if ( winner != null )
|
|
Finish( winner );
|
|
}
|
|
|
|
private bool m_Yielding;
|
|
|
|
public void OnDeath( Mobile mob, Container corpse )
|
|
{
|
|
if ( !m_Registered || !m_Started )
|
|
return;
|
|
|
|
DuelPlayer pl = Find( mob );
|
|
|
|
if ( pl != null && !pl.Eliminated )
|
|
{
|
|
if ( m_EventGame != null && !m_EventGame.OnDeath( mob, corpse ) )
|
|
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, String.Format( "{0} has been defeated.", mob.Name ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
public bool CheckFull()
|
|
{
|
|
for ( int i = 0; i < m_Participants.Count; ++i )
|
|
{
|
|
Participant p = (Participant)m_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 != null && killer.Player )
|
|
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 ( m_Arena == null )
|
|
return;
|
|
|
|
mob.Combatant = null;
|
|
mob.MoveToWorld( m_Arena.Outside, m_Arena.Facet );
|
|
}
|
|
|
|
private Point3D m_GatePoint;
|
|
private Map m_GateFacet;
|
|
|
|
public void Finish( Participant winner )
|
|
{
|
|
if ( m_Finished )
|
|
return;
|
|
|
|
EndAutoTie();
|
|
StopSDTimers();
|
|
|
|
m_Finished = true;
|
|
|
|
for ( int i = 0; i < winner.Players.Length; ++i )
|
|
{
|
|
DuelPlayer pl = winner.Players[i];
|
|
|
|
if ( pl != null && !pl.Eliminated )
|
|
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.TournyPart != null )
|
|
{
|
|
m_Match.Winner = winner.TournyPart;
|
|
winner.TournyPart.WonMatch( m_Match );
|
|
m_Tournament.HandleWon( m_Arena, m_Match, winner.TournyPart );
|
|
}
|
|
|
|
for ( int i = 0; i < m_Participants.Count; ++i )
|
|
{
|
|
Participant loser = (Participant)m_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.TournyPart != null )
|
|
loser.TournyPart.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( typeof( BeginGump ) );
|
|
|
|
if ( m_Tournament != null )
|
|
loser.Players[j].Mobile.SendEverything();
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( IsOneVsOne )
|
|
{
|
|
DuelPlayer dp1 = ((Participant)m_Participants[0]).Players[0];
|
|
DuelPlayer dp2 = ((Participant)m_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 );
|
|
}
|
|
}
|
|
|
|
if ( m_EventGame != null )
|
|
m_EventGame.OnStop();
|
|
|
|
Timer.DelayCall( TimeSpan.FromSeconds( 9.0 ), new TimerCallback( UnregisterRematch ) );
|
|
}
|
|
|
|
public void Award( Mobile us, Mobile them, bool won )
|
|
{
|
|
Ladder ladder = ( m_Arena == null ? Ladder.Instance : m_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 ( !m_Registered )
|
|
return;
|
|
|
|
m_Registered = false;
|
|
|
|
if ( m_Arena != null )
|
|
m_Arena.Evict();
|
|
|
|
StopSDTimers();
|
|
|
|
Type[] types = new Type[]{ typeof( BeginGump ), typeof( DuelContextGump ), typeof( ParticipantGump ), typeof( PickRulesetGump ), typeof( ReadyGump ), typeof( ReadyUpGump ), typeof( RulesetGump ) };
|
|
|
|
for ( int i = 0; i < m_Participants.Count; ++i )
|
|
{
|
|
Participant p = (Participant)m_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;
|
|
|
|
for ( int k = 0; k < types.Length; ++k )
|
|
pl.Mobile.CloseGump( types[k] );
|
|
}
|
|
}
|
|
|
|
if ( queryRematch && m_Tournament == null )
|
|
QueryRematch();
|
|
}
|
|
|
|
public void QueryRematch()
|
|
{
|
|
DuelContext dc = new DuelContext( m_Initiator, m_Ruleset.Layout, false );
|
|
|
|
dc.m_Ruleset = m_Ruleset;
|
|
dc.m_Rematch = true;
|
|
|
|
dc.m_Participants.Clear();
|
|
|
|
for ( int i = 0; i < m_Participants.Count; ++i )
|
|
{
|
|
Participant oldPart = (Participant)m_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.m_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 < m_Participants.Count; ++i )
|
|
{
|
|
Participant p = (Participant)m_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 && pl2 != null && pl1.Participant == pl2.Participant );
|
|
}
|
|
|
|
public Participant CheckCompletion()
|
|
{
|
|
Participant winner = null;
|
|
|
|
bool hasWinner = false;
|
|
int eliminated = 0;
|
|
|
|
for ( int i = 0; i < m_Participants.Count; ++i )
|
|
{
|
|
Participant p = (Participant)m_Participants[i];
|
|
|
|
if ( p.Eliminated )
|
|
{
|
|
++eliminated;
|
|
|
|
if ( eliminated == (m_Participants.Count - 1) )
|
|
hasWinner = true;
|
|
}
|
|
else
|
|
{
|
|
winner = p;
|
|
}
|
|
}
|
|
|
|
if ( hasWinner )
|
|
return winner == null ? (Participant) m_Participants[0] : winner;
|
|
|
|
return null;
|
|
}
|
|
|
|
private Timer m_Countdown;
|
|
|
|
public void StartCountdown( int count, CountdownCallback cb )
|
|
{
|
|
cb(count);
|
|
m_Countdown=Timer.DelayCall( TimeSpan.FromSeconds( 1.0 ), TimeSpan.FromSeconds( 1.0 ), count, new TimerStateCallback( Countdown_Callback ), new object[]{ count-1, cb } );
|
|
}
|
|
|
|
public void StopCountdown()
|
|
{
|
|
if ( m_Countdown != null )
|
|
m_Countdown.Stop();
|
|
|
|
m_Countdown = null;
|
|
}
|
|
|
|
private void Countdown_Callback( object state )
|
|
{
|
|
object[] states = (object[])state;
|
|
|
|
int count = (int)states[0];
|
|
CountdownCallback cb = (CountdownCallback)states[1];
|
|
|
|
if ( count==0 )
|
|
{
|
|
if ( m_Countdown != null )
|
|
m_Countdown.Stop();
|
|
|
|
m_Countdown=null;
|
|
}
|
|
|
|
cb( count );
|
|
|
|
states[0] = count - 1;
|
|
}
|
|
|
|
private Timer m_AutoTieTimer;
|
|
private bool m_Tied;
|
|
|
|
public bool Tied => m_Tied;
|
|
|
|
private bool m_IsSuddenDeath;
|
|
|
|
public bool IsSuddenDeath{ get => m_IsSuddenDeath;
|
|
set => m_IsSuddenDeath = value;
|
|
}
|
|
|
|
private Timer m_SDWarnTimer, m_SDActivateTimer;
|
|
|
|
public void StopSDTimers()
|
|
{
|
|
if ( m_SDWarnTimer != null )
|
|
m_SDWarnTimer.Stop();
|
|
|
|
m_SDWarnTimer = null;
|
|
|
|
if ( m_SDActivateTimer != null )
|
|
m_SDActivateTimer.Stop();
|
|
|
|
m_SDActivateTimer = null;
|
|
}
|
|
|
|
public void StartSuddenDeath( TimeSpan timeUntilActive )
|
|
{
|
|
if ( m_SDWarnTimer != null )
|
|
m_SDWarnTimer.Stop();
|
|
|
|
m_SDWarnTimer = Timer.DelayCall( TimeSpan.FromMinutes( timeUntilActive.TotalMinutes * 0.9 ), new TimerCallback( WarnSuddenDeath ) );
|
|
|
|
if ( m_SDActivateTimer != null )
|
|
m_SDActivateTimer.Stop();
|
|
|
|
m_SDActivateTimer = Timer.DelayCall( timeUntilActive, new TimerCallback( ActivateSuddenDeath ) );
|
|
}
|
|
|
|
public void WarnSuddenDeath()
|
|
{
|
|
for ( int i = 0; i < m_Participants.Count; ++i )
|
|
{
|
|
Participant p = (Participant)m_Participants[i];
|
|
|
|
for ( int j = 0; j < p.Players.Length; ++j )
|
|
{
|
|
DuelPlayer pl = p.Players[j];
|
|
|
|
if ( pl == null || pl.Eliminated )
|
|
continue;
|
|
|
|
pl.Mobile.SendSound( 0x1E1 );
|
|
pl.Mobile.SendMessage( 0x22, "Warning! Warning! Warning!" );
|
|
pl.Mobile.SendMessage( 0x22, "Sudden death will be active soon!" );
|
|
}
|
|
}
|
|
|
|
if ( m_Tournament != null )
|
|
m_Tournament.Alert( m_Arena, "Sudden death will be active soon!" );
|
|
|
|
if ( m_SDWarnTimer != null )
|
|
m_SDWarnTimer.Stop();
|
|
|
|
m_SDWarnTimer = null;
|
|
}
|
|
|
|
public static bool CheckSuddenDeath( Mobile mob )
|
|
{
|
|
return mob is PlayerMobile pm && pm.DuelPlayer?.Eliminated == false && pm.DuelContext?.IsSuddenDeath == true;
|
|
}
|
|
|
|
public void ActivateSuddenDeath()
|
|
{
|
|
for ( int i = 0; i < m_Participants.Count; ++i )
|
|
{
|
|
Participant p = (Participant)m_Participants[i];
|
|
|
|
for ( int j = 0; j < p.Players.Length; ++j )
|
|
{
|
|
DuelPlayer pl = p.Players[j];
|
|
|
|
if ( pl == null || pl.Eliminated )
|
|
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." );
|
|
}
|
|
}
|
|
|
|
if ( m_Tournament != null )
|
|
m_Tournament.Alert( m_Arena, "Sudden death has been activated!" );
|
|
|
|
m_IsSuddenDeath = true;
|
|
|
|
if ( m_SDActivateTimer != null )
|
|
m_SDActivateTimer.Stop();
|
|
|
|
m_SDActivateTimer = null;
|
|
}
|
|
|
|
public void BeginAutoTie()
|
|
{
|
|
if ( m_AutoTieTimer != null )
|
|
m_AutoTieTimer.Stop();
|
|
|
|
TimeSpan ts = ( m_Tournament == null || m_Tournament.TournyType == TournyType.Standard )
|
|
? AutoTieDelay
|
|
: TimeSpan.FromMinutes( 90.0 );
|
|
|
|
m_AutoTieTimer = Timer.DelayCall( ts, new TimerCallback( InvokeAutoTie ) );
|
|
}
|
|
|
|
public void EndAutoTie()
|
|
{
|
|
if ( m_AutoTieTimer != null )
|
|
m_AutoTieTimer.Stop();
|
|
|
|
m_AutoTieTimer = null;
|
|
}
|
|
|
|
public void InvokeAutoTie()
|
|
{
|
|
m_AutoTieTimer = null;
|
|
|
|
if ( !m_Started || m_Finished )
|
|
return;
|
|
|
|
m_Tied = true;
|
|
m_Finished = true;
|
|
|
|
StopSDTimers();
|
|
|
|
ArrayList remaining = new ArrayList();
|
|
|
|
for ( int i = 0; i < m_Participants.Count; ++i )
|
|
{
|
|
Participant p = (Participant)m_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 != null && !pl.Eliminated )
|
|
DelayBounce( TimeSpan.FromSeconds( 8.0 ), pl.Mobile, null );
|
|
}
|
|
|
|
if ( p.TournyPart != null )
|
|
remaining.Add( p.TournyPart );
|
|
}
|
|
|
|
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();
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( m_Tournament != null )
|
|
m_Tournament.HandleTie( m_Arena, m_Match, remaining );
|
|
|
|
Timer.DelayCall( TimeSpan.FromSeconds( 10.0 ), new TimerCallback( Unregister ) );
|
|
}
|
|
|
|
public bool IsOneVsOne
|
|
{
|
|
get
|
|
{
|
|
if ( m_Participants.Count != 2 )
|
|
return false;
|
|
|
|
if ( ((Participant)m_Participants[0]).Players.Length != 1 )
|
|
return false;
|
|
|
|
if ( ((Participant)m_Participants[1]).Players.Length != 1 )
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
public static void Initialize()
|
|
{
|
|
EventSink.Speech += new SpeechEventHandler( EventSink_Speech );
|
|
EventSink.Login += new LoginEventHandler( EventSink_Login );
|
|
|
|
CommandSystem.Register( "vli", AccessLevel.GameMaster, new CommandEventHandler( vli_oc ) );
|
|
}
|
|
|
|
private static void vli_oc( CommandEventArgs e )
|
|
{
|
|
e.Mobile.BeginTarget( -1, false, Targeting.TargetFlags.None, new TargetCallback( 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 ) );
|
|
}
|
|
}
|
|
|
|
private static TimeSpan CombatDelay = TimeSpan.FromSeconds( 30.0 );
|
|
private static TimeSpan AutoTieDelay = TimeSpan.FromMinutes( 15.0 );
|
|
|
|
public static bool CheckCombat( Mobile m )
|
|
{
|
|
for ( int i = 0; i < m.Aggressed.Count; ++i )
|
|
{
|
|
AggressorInfo info = m.Aggressed[i];
|
|
|
|
if ( info.Defender.Player && (DateTime.UtcNow - info.LastCombatTime) < CombatDelay )
|
|
return true;
|
|
}
|
|
|
|
for ( int i = 0; i < m.Aggressors.Count; ++i )
|
|
{
|
|
AggressorInfo info = m.Aggressors[i];
|
|
|
|
if ( info.Attacker.Player && (DateTime.UtcNow - info.LastCombatTime) < CombatDelay )
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private static void EventSink_Login( LoginEventArgs e )
|
|
{
|
|
if ( !(e.Mobile 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.m_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, object state )
|
|
{
|
|
if ( obj is PlayerMobile pm )
|
|
{
|
|
Ladder ladder = (Ladder)state;
|
|
|
|
LadderEntry entry = ladder.Find( pm );
|
|
|
|
if ( entry == null )
|
|
return; // sanity
|
|
|
|
string text = String.Format( "{{0}} are ranked {0} at level {1}.", LadderGump.Rank( entry.Index + 1 ), 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( typeof( Regions.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( typeof( 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 = String.Format( "{{0}} {{1}} ranked {0} at level {1}.", LadderGump.Rank( entry.Index + 1 ), 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, Targeting.TargetFlags.None, new TargetStateCallback( 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.m_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.m_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.m_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, String.Format( "{0} has yielded.", pm.Name ) );
|
|
|
|
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 DuelContext( Mobile initiator, RulesetLayout layout ) : this( initiator, layout, true )
|
|
{
|
|
}
|
|
|
|
public DuelContext( Mobile initiator, RulesetLayout layout, bool addNew )
|
|
{
|
|
m_Initiator = initiator;
|
|
m_Participants = new ArrayList();
|
|
m_Ruleset = new Ruleset( layout );
|
|
m_Ruleset.ApplyDefault( layout.Defaults[0] );
|
|
|
|
if ( addNew )
|
|
{
|
|
m_Participants.Add( new Participant( this, 1 ) );
|
|
m_Participants.Add( new Participant( this, 1 ) );
|
|
|
|
((Participant)m_Participants[0]).Add( initiator );
|
|
}
|
|
}
|
|
|
|
public void CloseAllGumps()
|
|
{
|
|
Type[] types = new Type[]{ typeof( DuelContextGump ), typeof( ParticipantGump ), typeof( RulesetGump ) };
|
|
int[] defs = new int[]{ -1, -1, -1 };
|
|
|
|
for ( int i = 0; i < m_Participants.Count; ++i )
|
|
{
|
|
Participant p = (Participant)m_Participants[i];
|
|
|
|
for ( int j = 0; j < p.Players.Length; ++j )
|
|
{
|
|
DuelPlayer pl = p.Players[j];
|
|
|
|
if ( pl == null )
|
|
continue;
|
|
|
|
Mobile mob = pl.Mobile;
|
|
|
|
for ( int k = 0; k < types.Length; ++k )
|
|
mob.CloseGump( types[k] );
|
|
//mob.CloseGump( types[k], defs[k] );
|
|
}
|
|
}
|
|
}
|
|
|
|
public void RejectReady( Mobile rejector, string page )
|
|
{
|
|
if ( m_StartedReadyCountdown )
|
|
return; // sanity
|
|
|
|
Type[] types = new Type[]{ typeof( DuelContextGump ), typeof( ReadyUpGump ), typeof( ReadyGump ) };
|
|
int[] defs = new int[]{ -1, -1, -1 };
|
|
|
|
for ( int i = 0; i < m_Participants.Count; ++i )
|
|
{
|
|
Participant p = (Participant)m_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}.", m_Rematch ? "rematch" : page );
|
|
else
|
|
mob.SendMessage( 0x22, "{0} has rejected the {1}.", rejector.Name, m_Rematch ? "rematch" : page );
|
|
}
|
|
|
|
for ( int k = 0; k < types.Length; ++k )
|
|
mob.CloseGump( types[k] );
|
|
//mob.CloseGump( types[k], defs[k] );
|
|
}
|
|
}
|
|
|
|
if ( m_Rematch )
|
|
Unregister();
|
|
else if ( !m_Yielding )
|
|
m_Initiator.SendGump( new DuelContextGump( m_Initiator, this ) );
|
|
|
|
m_ReadyWait = false;
|
|
m_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;
|
|
Spells.Second.ProtectionSpell.Registry.Remove( mob );
|
|
|
|
Spells.Fourth.ArchProtectionSpell.RemoveEntry( mob );
|
|
|
|
mob.EndAction( typeof( DefensiveSpell ) );
|
|
}
|
|
|
|
TransformationSpellHelper.RemoveContext( mob, true );
|
|
AnimalForm.RemoveContext( mob, true );
|
|
|
|
if ( DisguiseTimers.IsDisguised( mob ) )
|
|
DisguiseTimers.StopTimer( mob );
|
|
|
|
if ( !mob.CanBeginAction( typeof( PolymorphSpell ) ) )
|
|
{
|
|
mob.BodyMod = 0;
|
|
mob.HueMod = -1;
|
|
mob.EndAction( typeof( 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 );
|
|
|
|
Targeting.Target.Cancel( mob );
|
|
}
|
|
|
|
private bool m_StartedBeginCountdown;
|
|
private bool m_StartedReadyCountdown;
|
|
|
|
public bool StartedBeginCountdown => m_StartedBeginCountdown;
|
|
public bool StartedReadyCountdown => m_StartedReadyCountdown;
|
|
|
|
private 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 InternalWall( Serial serial ) : base( serial )
|
|
{
|
|
}
|
|
|
|
public override void Serialize( GenericWriter writer )
|
|
{
|
|
base.Serialize( writer );
|
|
|
|
writer.Write( (int) 0 );
|
|
}
|
|
|
|
public override void Deserialize( GenericReader reader )
|
|
{
|
|
base.Deserialize( reader );
|
|
|
|
int version = reader.ReadInt();
|
|
|
|
Delete();
|
|
}
|
|
}
|
|
|
|
private ArrayList m_Walls = new ArrayList();
|
|
|
|
public void DestroyWall()
|
|
{
|
|
for ( int i = 0; i < m_Walls.Count; ++i )
|
|
((Item)m_Walls[i]).Delete();
|
|
|
|
m_Walls.Clear();
|
|
}
|
|
|
|
public void CreateWall()
|
|
{
|
|
if ( m_Arena == null )
|
|
return;
|
|
|
|
Point3D start = m_Arena.Points.EdgeWest;
|
|
Point3D wall = m_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, m_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, m_Arena.Facet );
|
|
|
|
m_Walls.Add( created );
|
|
}
|
|
}
|
|
|
|
public void BuildParties()
|
|
{
|
|
for ( int i = 0; i < m_Participants.Count; ++i )
|
|
{
|
|
Participant p = (Participant)m_Participants[i];
|
|
|
|
if ( p.Players.Length > 1 )
|
|
{
|
|
ArrayList players = new ArrayList();
|
|
|
|
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 = (Mobile) 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 = (Mobile)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 < m_Participants.Count; ++i )
|
|
{
|
|
Participant p = (Participant)m_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 == null ? "#" + item.LabelNumber.ToString() : item.Name ); // You can no longer wield your ~1_WEAPON~
|
|
else if ( item is BaseArmor && !(item is BaseShield) )
|
|
mob.SendLocalizedMessage( 1062002, item.Name == null ? "#" + item.LabelNumber.ToString() : item.Name ); // You can no longer wear your ~1_ARMOR~
|
|
else
|
|
mob.SendLocalizedMessage( 1062003, item.Name == null ? "#" + item.LabelNumber.ToString() : item.Name ); // 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.m_Parent == mob )
|
|
pack.DropItem( inHand );
|
|
else
|
|
inHand.Bounce( mob );
|
|
|
|
inHand.ClearBounce();
|
|
}
|
|
}
|
|
|
|
private void MessageRuleset( Mobile mob ) {
|
|
if ( m_Ruleset == null ) {
|
|
return;
|
|
}
|
|
|
|
Ruleset ruleset = m_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)ruleset.Flavors[i]).Options );
|
|
|
|
mob.SendMessage( " + {0}", ((Ruleset)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 ( !m_Registered || m_Finished )
|
|
return;
|
|
|
|
if ( count == 10 )
|
|
{
|
|
CreateWall();
|
|
BuildParties();
|
|
ClearIllegalItems();
|
|
}
|
|
else if ( count == 0 )
|
|
{
|
|
DestroyWall();
|
|
}
|
|
|
|
m_StartedBeginCountdown = true;
|
|
|
|
if ( count == 0 )
|
|
{
|
|
m_Started = true;
|
|
BeginAutoTie();
|
|
}
|
|
|
|
Type[] types = new Type[]{ typeof( ReadyGump ), typeof( ReadyUpGump ), typeof( BeginGump ) };
|
|
|
|
for ( int i = 0; i < m_Participants.Count; ++i )
|
|
{
|
|
Participant p = (Participant)m_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 )
|
|
CloseAndSendGump( mob, new BeginGump( count ), types );
|
|
|
|
mob.Frozen = true;
|
|
}
|
|
else
|
|
{
|
|
mob.CloseGump( typeof( BeginGump ) );
|
|
mob.Frozen = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private ArrayList m_Entered = new ArrayList();
|
|
|
|
private class ReturnEntry
|
|
{
|
|
private Mobile m_Mobile;
|
|
private Point3D m_Location;
|
|
private Map m_Facet;
|
|
private DateTime m_Expire;
|
|
|
|
public Mobile Mobile => m_Mobile;
|
|
public Point3D Location => m_Location;
|
|
public Map Facet => m_Facet;
|
|
|
|
public void Return()
|
|
{
|
|
if ( m_Facet == Map.Internal || m_Facet == null )
|
|
return;
|
|
|
|
if ( m_Mobile.Map == Map.Internal )
|
|
{
|
|
m_Mobile.LogoutLocation = m_Location;
|
|
m_Mobile.LogoutMap = m_Facet;
|
|
}
|
|
else
|
|
{
|
|
m_Mobile.Location = m_Location;
|
|
m_Mobile.Map = m_Facet;
|
|
}
|
|
}
|
|
|
|
public ReturnEntry( Mobile mob )
|
|
{
|
|
m_Mobile = mob;
|
|
|
|
Update();
|
|
}
|
|
|
|
public ReturnEntry( Mobile mob, Point3D loc, Map facet )
|
|
{
|
|
m_Mobile = mob;
|
|
m_Location = loc;
|
|
m_Facet = facet;
|
|
m_Expire = DateTime.UtcNow + TimeSpan.FromMinutes( 30.0 );
|
|
}
|
|
|
|
public bool Expired => ( DateTime.UtcNow >= m_Expire );
|
|
|
|
public void Update()
|
|
{
|
|
m_Expire = DateTime.UtcNow + TimeSpan.FromMinutes( 30.0 );
|
|
|
|
if ( m_Mobile.Map == Map.Internal )
|
|
{
|
|
m_Facet = m_Mobile.LogoutMap;
|
|
m_Location = m_Mobile.LogoutLocation;
|
|
}
|
|
else
|
|
{
|
|
m_Facet = m_Mobile.Map;
|
|
m_Location = m_Mobile.Location;
|
|
}
|
|
}
|
|
}
|
|
|
|
private class ExitTeleporter : Item
|
|
{
|
|
private ArrayList m_Entries;
|
|
|
|
public override string DefaultName => "return teleporter";
|
|
|
|
public ExitTeleporter() : base( 0x1822 )
|
|
{
|
|
m_Entries = new ArrayList();
|
|
|
|
Hue = 0x482;
|
|
Movable = false;
|
|
}
|
|
|
|
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 = (ReturnEntry)m_Entries[i];
|
|
|
|
if ( entry.Mobile == mob )
|
|
return entry;
|
|
else 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;
|
|
}
|
|
else
|
|
{
|
|
m.SendLocalizedMessage( 1049383 ); // The teleporter doesn't seem to work for you.
|
|
return true;
|
|
}
|
|
}
|
|
|
|
public ExitTeleporter( Serial serial ) : base( serial )
|
|
{
|
|
}
|
|
|
|
public override void Serialize( GenericWriter writer )
|
|
{
|
|
base.Serialize( writer );
|
|
|
|
writer.Write( (int) 0 );
|
|
|
|
writer.WriteEncodedInt( (int) m_Entries.Count );
|
|
|
|
for ( int i = 0; i < m_Entries.Count; ++i )
|
|
{
|
|
ReturnEntry entry = (ReturnEntry)m_Entries[i];
|
|
|
|
writer.Write( (Mobile) entry.Mobile );
|
|
writer.Write( (Point3D) entry.Location );
|
|
writer.Write( (Map) entry.Facet );
|
|
|
|
if ( entry.Expired )
|
|
m_Entries.RemoveAt( i-- );
|
|
}
|
|
}
|
|
|
|
public override void Deserialize( GenericReader reader )
|
|
{
|
|
base.Deserialize( reader );
|
|
|
|
int version = reader.ReadInt();
|
|
|
|
switch ( version )
|
|
{
|
|
case 0:
|
|
{
|
|
int count = reader.ReadEncodedInt();
|
|
|
|
m_Entries = new ArrayList( 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 ExitTeleporter m_Teleporter;
|
|
|
|
public override string DefaultName => "spectator moongate";
|
|
|
|
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 ), new TimerCallback( Delete ) );
|
|
}
|
|
|
|
public override void CheckGate(Mobile m, int range)
|
|
{
|
|
if ( DuelContext.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 ( DuelContext.CheckCombat( m ) )
|
|
{
|
|
m.SendMessage( 0x22, "You have recently been in combat with another player and cannot use this moongate." );
|
|
}
|
|
else
|
|
{
|
|
if ( m_Teleporter != null && !m_Teleporter.Deleted )
|
|
m_Teleporter.Register( m );
|
|
|
|
base.UseGate( m );
|
|
}
|
|
}
|
|
|
|
public void Appear( Point3D loc, Map map )
|
|
{
|
|
Effects.PlaySound( loc, map, 0x20E );
|
|
MoveToWorld( loc, map );
|
|
}
|
|
|
|
public ArenaMoongate( Serial serial ) : base( serial )
|
|
{
|
|
}
|
|
|
|
public override void Serialize( GenericWriter writer )
|
|
{
|
|
base.Serialize( writer );
|
|
|
|
writer.Write( (int) 0 );
|
|
}
|
|
|
|
public override void Deserialize( GenericReader reader )
|
|
{
|
|
base.Deserialize( reader );
|
|
|
|
int version = reader.ReadInt();
|
|
|
|
Delete();
|
|
}
|
|
}
|
|
|
|
public void RemoveAggressions( Mobile mob )
|
|
{
|
|
for ( int i = 0; i < m_Participants.Count; ++i )
|
|
{
|
|
Participant p = (Participant)m_Participants[i];
|
|
|
|
for ( int j = 0; j < p.Players.Length; ++j )
|
|
{
|
|
DuelPlayer dp = (DuelPlayer)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 ( !m_Registered )
|
|
return;
|
|
|
|
m_ReadyWait = true;
|
|
m_ReadyCount = -1;
|
|
|
|
Type[] types = new Type[]{ typeof( ReadyUpGump ) };
|
|
|
|
for ( int i = 0; i < m_Participants.Count; ++i )
|
|
{
|
|
Participant p = (Participant)m_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 ( mob != null )
|
|
{
|
|
if ( m_Tournament == null )
|
|
CloseAndSendGump( mob, new ReadyUpGump( mob, this ), types );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public string ValidateStart()
|
|
{
|
|
if ( m_Tournament == null && TournamentController.IsActive )
|
|
return "a tournament is active";
|
|
|
|
for ( int i = 0; i < m_Participants.Count; ++i )
|
|
{
|
|
Participant p = (Participant)m_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( typeof( Regions.Jail ) ) )
|
|
return String.Format( "{0} is in jail", dp.Mobile.Name );
|
|
|
|
if ( Sigil.ExistsOn( dp.Mobile ) )
|
|
return String.Format( "{0} is holding a sigil", dp.Mobile.Name );
|
|
|
|
if ( !dp.Mobile.Alive )
|
|
{
|
|
if ( m_Tournament == null )
|
|
return String.Format( "{0} is dead", dp.Mobile.Name );
|
|
else
|
|
dp.Mobile.Resurrect();
|
|
}
|
|
|
|
if ( m_Tournament == null && CheckCombat( dp.Mobile ) )
|
|
return String.Format( "{0} is in combat", dp.Mobile.Name );
|
|
|
|
if ( dp.Mobile.Mounted )
|
|
{
|
|
IMount mount = dp.Mobile.Mount;
|
|
|
|
if ( m_Tournament != null && mount != null )
|
|
mount.Rider = null;
|
|
else
|
|
return String.Format( "{0} is mounted", dp.Mobile.Name );
|
|
}
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public Arena m_OverrideArena;
|
|
public Tournament m_Tournament;
|
|
public TournyMatch m_Match;
|
|
public EventGame m_EventGame;
|
|
|
|
public Tournament Tournament => m_Tournament;
|
|
|
|
public void SendReadyGump( int count )
|
|
{
|
|
if ( !m_Registered )
|
|
return;
|
|
|
|
if ( count != -1 )
|
|
m_StartedReadyCountdown = true;
|
|
|
|
m_ReadyCount = count;
|
|
|
|
if ( count == 0 )
|
|
{
|
|
string error = ValidateStart();
|
|
|
|
if ( error != null )
|
|
{
|
|
for ( int i = 0; i < m_Participants.Count; ++i )
|
|
{
|
|
Participant p = (Participant)m_Participants[i];
|
|
|
|
for ( int j = 0; j < p.Players.Length; ++j )
|
|
{
|
|
DuelPlayer dp = p.Players[j];
|
|
|
|
if ( dp != null )
|
|
dp.Mobile.SendMessage( "The duel could not be started because {0}.", error );
|
|
}
|
|
}
|
|
|
|
StartCountdown( 10, new CountdownCallback( SendReadyGump ) );
|
|
|
|
return;
|
|
}
|
|
|
|
m_ReadyWait = false;
|
|
|
|
List<Mobile> players = new List<Mobile>();
|
|
|
|
for ( int i = 0; i < m_Participants.Count; ++i )
|
|
{
|
|
Participant p = (Participant)m_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;
|
|
|
|
if ( arena == null )
|
|
arena = Arena.FindArena( players );
|
|
|
|
if ( arena == null )
|
|
{
|
|
for ( int i = 0; i < m_Participants.Count; ++i )
|
|
{
|
|
Participant p = (Participant)m_Participants[i];
|
|
|
|
for ( int j = 0; j < p.Players.Length; ++j )
|
|
{
|
|
DuelPlayer dp = p.Players[j];
|
|
|
|
if ( dp != null )
|
|
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, new CountdownCallback( SendReadyGump ) );
|
|
return;
|
|
}
|
|
|
|
if ( !arena.IsOccupied )
|
|
{
|
|
m_Arena = arena;
|
|
|
|
if ( m_Initiator.Map == Map.Internal )
|
|
{
|
|
m_GatePoint = m_Initiator.LogoutLocation;
|
|
m_GateFacet = m_Initiator.LogoutMap;
|
|
}
|
|
else
|
|
{
|
|
m_GatePoint = m_Initiator.Location;
|
|
m_GateFacet = m_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 );
|
|
|
|
m_StartedBeginCountdown = true;
|
|
|
|
for ( int i = 0; i < m_Participants.Count; ++i )
|
|
{
|
|
Participant p = (Participant)m_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 );
|
|
}
|
|
|
|
if ( m_EventGame != null )
|
|
m_EventGame.OnStart();
|
|
|
|
StartCountdown( 10, new CountdownCallback( SendBeginGump ) );
|
|
|
|
mg.Appear( m_GatePoint, m_GateFacet );
|
|
}
|
|
else
|
|
{
|
|
for ( int i = 0; i < m_Participants.Count; ++i )
|
|
{
|
|
Participant p = (Participant)m_Participants[i];
|
|
|
|
for ( int j = 0; j < p.Players.Length; ++j )
|
|
{
|
|
DuelPlayer dp = p.Players[j];
|
|
|
|
if ( dp != null )
|
|
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, new CountdownCallback( SendReadyGump ) );
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
m_ReadyWait = true;
|
|
|
|
bool isAllReady = true;
|
|
|
|
Type[] types = new Type[]{ typeof( ReadyGump ) };
|
|
|
|
for ( int i = 0; i < m_Participants.Count; ++i )
|
|
{
|
|
Participant p = (Participant)m_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 )
|
|
CloseAndSendGump( mob, new ReadyGump( mob, this, count ), types );
|
|
}
|
|
else
|
|
{
|
|
isAllReady = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( count == -1 && isAllReady )
|
|
StartCountdown( 3, new CountdownCallback( SendReadyGump ) );
|
|
}
|
|
|
|
public static void CloseAndSendGump( Mobile mob, Gump g, params Type[] types )
|
|
{
|
|
CloseAndSendGump( mob.NetState, g, types );
|
|
}
|
|
|
|
public static void CloseAndSendGump( NetState ns, Gump g, params Type[] types )
|
|
{
|
|
if ( ns != null ) {
|
|
Mobile mob = ns.Mobile;
|
|
|
|
if ( mob != null ) {
|
|
foreach ( Type type in types ) {
|
|
mob.CloseGump( type );
|
|
}
|
|
|
|
mob.SendGump( g );
|
|
}
|
|
}
|
|
|
|
/*if ( ns == null )
|
|
return;
|
|
|
|
for ( int i = 0; i < types.Length; ++i )
|
|
ns.Send( new CloseGump( Gump.GetTypeID( types[i] ), 0 ) );
|
|
|
|
g.SendTo( ns );
|
|
|
|
ns.AddGump( g );
|
|
|
|
Packet[] packets = new Packet[types.Length + 1];
|
|
|
|
for ( int i = 0; i < types.Length; ++i )
|
|
packets[i] = new CloseGump( Gump.GetTypeID( types[i] ), 0 );
|
|
|
|
packets[types.Length] = (Packet) typeof( Gump ).InvokeMember( "Compile", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.InvokeMethod, null, g, null, null );
|
|
|
|
bool compress = ns.CompressionEnabled;
|
|
ns.CompressionEnabled = false;
|
|
ns.Send( BindPackets( compress, packets ) );
|
|
ns.CompressionEnabled = compress;*/
|
|
}
|
|
|
|
/*public static Packet BindPackets( bool compress, params Packet[] packets )
|
|
{
|
|
if ( packets.Length == 0 )
|
|
throw new ArgumentException( "No packets to bind", "packets" );
|
|
|
|
byte[][] compiled = new byte[packets.Length][];
|
|
int[] lengths = new int[packets.Length];
|
|
|
|
int length = 0;
|
|
|
|
for ( int i = 0; i < packets.Length; ++i )
|
|
{
|
|
compiled[i] = packets[i].Compile( compress, out lengths[i] );
|
|
length += lengths[i];
|
|
}
|
|
|
|
return new BoundPackets( length, compiled, lengths );
|
|
}
|
|
|
|
private class BoundPackets : Packet
|
|
{
|
|
public BoundPackets( int length, byte[][] compiled, int[] lengths ) : base( 0, length )
|
|
{
|
|
m_Stream.Seek( 0, System.IO.SeekOrigin.Begin );
|
|
|
|
for ( int i = 0; i < compiled.Length; ++i )
|
|
m_Stream.Write( compiled[i], 0, lengths[i] );
|
|
}
|
|
}*/
|
|
}
|
|
}
|