using System; using Server.Gumps; using Server.Misc; using Server.Mobiles; namespace Server.Engines.MLQuests.Objectives { public class EscortObjective : BaseObjective { public EscortObjective(QuestArea destination = null) => Destination = destination; public QuestArea Destination { get; set; } public override bool CanOffer(IQuestGiver quester, PlayerMobile pm, bool message) { if (quester is BaseCreature creature && creature.Controlled || quester is BaseEscortable escortable && escortable.IsBeingDeleted) { return false; } var context = MLQuestSystem.GetContext(pm); if (context != null) { foreach (var instance in context.QuestInstances) { if (instance.Quest.IsEscort) { if (message) { MLQuestSystem.Tell(quester, pm, 500896); // I see you already have an escort. } return false; } } } var nextEscort = pm.LastEscortTime + BaseEscortable.EscortDelay; if (nextEscort > Core.Now) { if (message) { var minutes = (int)Math.Ceiling((nextEscort - Core.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); // Escort to if (Destination.Name.Number > 0) { g.AddHtmlLocalized(173, y, 312, 20, Destination.Name.Number, 0xFFFFFF); } else if (Destination.Name.String != null) { g.AddLabel(173, y, 0x481, Destination.Name.String); } y += 16; } public override BaseObjectiveInstance CreateInstance(MLQuestInstance instance) { if (instance == null || Destination == null) { return null; } return new EscortObjectiveInstance(this, instance); } } public class EscortObjectiveInstance : BaseObjectiveInstance { private readonly BaseCreature m_Escort; private readonly EscortObjective m_Objective; private DateTime m_LastSeenEscorter; private TimerExecutionToken _timerToken; public EscortObjectiveInstance(EscortObjective objective, MLQuestInstance instance) : base(instance, objective) { m_Objective = objective; HasCompleted = false; Timer.StartTimer(TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(5), CheckDestination, out _timerToken); m_LastSeenEscorter = Core.Now; m_Escort = instance.Quester as BaseCreature; if (MLQuestSystem.Debug && m_Escort == null && instance.Quester != null) { Console.WriteLine( "Warning: EscortObjective is not supported for type '{0}'", instance.Quester.GetType().Name ); } } public bool HasCompleted { get; set; } public override DataType ExtraDataType => DataType.EscortObjective; public override bool IsCompleted() => HasCompleted; private void CheckDestination() { if (m_Escort == null || HasCompleted) // Completed by deserialization { StopTimer(); return; } var instance = Instance; var pm = instance.Player; if (instance.Removed) { Abandon(); } else if (m_Objective.Destination.Contains(m_Escort)) { m_Escort.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. if (pm.Young || m_Escort.Region.IsPartOf("Haven Island")) { Titles.AwardFame(pm, 10, true); } else { VirtueHelper.AwardVirtue( pm, VirtueName.Compassion, m_Escort is BaseEscortable escortable && escortable.IsPrisoner ? 400 : 200 ); } EndFollow(m_Escort); StopTimer(); HasCompleted = true; CheckComplete(); // Auto claim reward MLQuestSystem.OnDoubleClick(m_Escort, pm); } else if (pm.Map != m_Escort.Map || !pm.InRange(m_Escort, 30)) // TODO: verify range { if (m_LastSeenEscorter + BaseEscortable.AbandonDelay <= Core.Now) { Abandon(); } } else { m_LastSeenEscorter = Core.Now; } } private void StopTimer() { _timerToken.Cancel(); } 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); (quester as BaseEscortable)?.BeginDelete(); } public override void OnQuestAccepted() { var instance = Instance; var pm = instance.Player; pm.LastEscortTime = Core.Now; if (m_Escort != null) { BeginFollow(m_Escort, pm); } } public void Abandon() { StopTimer(); var instance = Instance; var pm = instance.Player; if (m_Escort?.Deleted == false) { if (!pm.Alive) { m_Escort.Say(500901); // Ack! My escort has come to haunt me! } else { m_Escort.Say(500902); // My escort seems to have abandoned me! } EndFollow(m_Escort); } // 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(); } } 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 void Serialize(IGenericWriter writer) { base.Serialize(writer); writer.Write(HasCompleted); } } }