using System; using Server; using Server.Engines.MLQuests; using Server.Mobiles; using Server.Gumps; using System.Collections.Generic; using Server.Misc; using Server.Items; namespace Server.Engines.MLQuests.Objectives { public class EscortObjective : BaseObjective { private QuestArea m_Destination; public QuestArea Destination { get { return m_Destination; } set { m_Destination = value; } } public EscortObjective() : this( null ) { } public EscortObjective( QuestArea destination ) { m_Destination = destination; } public override bool CanOffer( BaseCreature quester, PlayerMobile pm, bool message ) { BaseEscortable escortable = quester as BaseEscortable; if ( quester.Controlled || ( escortable != null && escortable.IsBeingDeleted ) ) return false; MLQuestContext context = MLQuestSystem.GetContext( pm ); if ( context != null ) { foreach ( MLQuestInstance instance in context.QuestInstances ) { if ( instance.Quest.IsEscort ) { if ( message ) MLQuestSystem.Tell( quester, pm, 500896 ); // I see you already have an escort. return false; } } } DateTime nextEscort = pm.LastEscortTime + BaseEscortable.EscortDelay; // Note: On OSI Bravehorn doesn't check the time limit, but it does SET the last escort time... bug! if ( nextEscort > DateTime.Now ) { if ( message ) { int minutes = (int)Math.Ceiling( ( nextEscort - DateTime.Now ).TotalMinutes ); if ( minutes == 1 ) MLQuestSystem.Tell( quester, pm, "You must rest 1 minute before we set out on this journey." ); else MLQuestSystem.Tell( quester, pm, 1071195, minutes.ToString() ); // You must rest ~1_minsleft~ minutes before we set out on this journey. } return false; } return true; } public override void WriteToGump( Gump g, ref int y ) { g.AddHtmlLocalized( 98, y, 312, 16, 1072206, 0x15F90, false, false ); // Escort to if ( m_Destination.Name.Number > 0 ) g.AddHtmlLocalized( 173, y, 312, 20, m_Destination.Name.Number, 0xFFFFFF, false, false ); else if ( m_Destination.Name.String != null ) g.AddLabel( 173, y, 0x481, m_Destination.Name.String ); y += 16; } public override BaseObjectiveInstance CreateInstance( MLQuestInstance instance ) { if ( instance == null || m_Destination == null ) return null; return new EscortObjectiveInstance( this, instance ); } } public class EscortObjectiveInstance : BaseObjectiveInstance { private EscortObjective m_Objective; private bool m_HasCompleted; private Timer m_Timer; private DateTime m_LastSeenEscorter; public bool HasCompleted { get { return m_HasCompleted; } set { m_HasCompleted = value; } } public EscortObjectiveInstance( EscortObjective objective, MLQuestInstance instance ) : base( instance, objective ) { m_Objective = objective; m_HasCompleted = false; m_Timer = Timer.DelayCall( TimeSpan.FromSeconds( 5 ), TimeSpan.FromSeconds( 5 ), new TimerCallback( CheckDestination ) ); m_LastSeenEscorter = DateTime.Now; } public override bool IsCompleted() { // Once complete, it stays complete return m_HasCompleted; } private void CheckDestination() { if ( m_HasCompleted ) // Completed by deserialization { StopTimer(); return; } MLQuestInstance instance = Instance; BaseCreature escort = instance.Quester; PlayerMobile pm = instance.Player; if ( instance.Removed ) // Player cancelled or player died { Abandon(); } else if ( m_Objective.Destination.Contains( escort ) ) // We've arrived! { m_HasCompleted = true; CheckComplete(); InternalOnQuestCompleted(); StopTimer(); } else if ( pm.Map != escort.Map || !pm.InRange( escort, 30 ) ) // Player abandoned us (range not verified) { if ( m_LastSeenEscorter + BaseEscortable.AbandonDelay <= DateTime.Now ) Abandon(); } else // Player is still with us { m_LastSeenEscorter = DateTime.Now; } } private void StopTimer() { if ( m_Timer != null ) { m_Timer.Stop(); m_Timer = null; } } public static void BeginFollow( BaseCreature quester, PlayerMobile pm ) { quester.ControlSlots = 0; quester.SetControlMaster( pm ); quester.ActiveSpeed = 0.1; quester.PassiveSpeed = 0.2; quester.ControlOrder = OrderType.Follow; quester.ControlTarget = pm; quester.CantWalk = false; quester.CurrentSpeed = 0.1; } public static void EndFollow( BaseCreature quester ) { quester.ActiveSpeed = 0.2; quester.PassiveSpeed = 1.0; quester.ControlOrder = OrderType.None; quester.ControlTarget = null; quester.CurrentSpeed = 1.0; quester.SetControlMaster( null ); if ( quester is BaseEscortable ) ( (BaseEscortable)quester ).BeginDelete(); } public override void OnQuestAccepted() { MLQuestInstance instance = Instance; PlayerMobile pm = instance.Player; pm.LastEscortTime = DateTime.Now; BeginFollow( instance.Quester, pm ); } public void Abandon() { StopTimer(); MLQuestInstance instance = Instance; BaseCreature quester = instance.Quester; PlayerMobile pm = instance.Player; if ( !quester.Deleted ) { if ( !pm.Alive ) quester.Say( 500901 ); // Ack! My escort has come to haunt me! else quester.Say( 500902 ); // My escort seems to have abandoned me! EndFollow( quester ); } // Note: this sound is sent twice on OSI (once here and once in Cancel()) //m_Player.SendSound( 0x5B3 ); // private sound pm.SendLocalizedMessage( 1071194 ); // You have failed your escort quest... if ( !instance.Removed ) instance.Cancel(); } private void InternalOnQuestCompleted() // To make sure it's never executed twice (when combined with a CollectObjective for example) { MLQuestInstance instance = Instance; PlayerMobile pm = instance.Player; BaseCreature quester = instance.Quester; quester.Say( 1042809, pm.Name ); // We have arrived! I thank thee, ~1_PLAYER_NAME~! I have no further need of thy services. Here is thy pay. // Verified: auto double click goes here MLQuestSystem.OnDoubleClick( quester, pm ); // Auto claim reward if ( pm.Young || quester.Region.IsPartOf( "Haven Island" ) ) Titles.AwardFame( pm, 10, true ); else VirtueHelper.AwardVirtue( pm, VirtueName.Compassion, ( quester is BaseEscortable && ( (BaseEscortable)quester ).IsPrisoner ) ? 400 : 200 ); EndFollow( quester ); } public override void OnQuesterDeleted() { if ( IsCompleted() || Instance.Removed ) return; Abandon(); } public override void OnPlayerDeath() { // Note: OSI also cancels it when the quest is already complete if ( /*IsCompleted() ||*/ Instance.Removed ) return; Instance.Cancel(); } public override void OnExpire() { Abandon(); } public override void WriteToGump( Gump g, ref int y ) { m_Objective.WriteToGump( g, ref y ); base.WriteToGump( g, ref y ); // No extra instance stuff printed for this objective } public override DataType ExtraDataType { get { return DataType.EscortObjective; } } public override void Serialize( GenericWriter writer ) { base.Serialize( writer ); writer.Write( m_HasCompleted ); } } }