### Summary Modifying a ValueLinkList using one of the methods will bump the "version". This field is used by iterators (foreach loops) to determine if the link list was modified while iterating. The sector.Items (and in the future other lists), will no longer be safe to modify while iterating. The server will _CRASH_ if the ValueLinkList is modified. Thanks to @stefanomerotta for help! ### Screenshots <img width="588" alt="image" src="https://github.com/modernuo/ModernUO/assets/3953314/83ee0b6e-ff4f-4768-9e29-84456e04b1ec">
307 lines
8.8 KiB
C#
307 lines
8.8 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using Server.Collections;
|
|
using Server.Engines.MLQuests.Gumps;
|
|
using Server.Engines.MLQuests.Objectives;
|
|
using Server.Engines.MLQuests.Rewards;
|
|
using Server.Engines.Spawners;
|
|
using Server.Mobiles;
|
|
|
|
namespace Server.Engines.MLQuests
|
|
{
|
|
public enum ObjectiveType
|
|
{
|
|
All,
|
|
Any
|
|
}
|
|
|
|
public class MLQuest
|
|
{
|
|
// You've completed a quest! Don't forget to collect your reward.
|
|
public static TextDefinition CompletionNoticeDefault = 1072273;
|
|
|
|
public static TextDefinition CompletionNoticeShort = 1046258; // Your quest is complete.
|
|
|
|
// Your quest is complete. Return for your reward.
|
|
public static TextDefinition CompletionNoticeShortReturn = 1073775;
|
|
|
|
// You obtained what you seek, now receive your reward.
|
|
public static TextDefinition CompletionNoticeCraft = 1073967;
|
|
|
|
public MLQuest()
|
|
{
|
|
Activated = false;
|
|
Objectives = new List<BaseObjective>();
|
|
ObjectiveType = ObjectiveType.All;
|
|
Rewards = new List<BaseReward>();
|
|
CompletionNotice = CompletionNoticeDefault;
|
|
|
|
Instances = new List<MLQuestInstance>();
|
|
|
|
SaveEnabled = true;
|
|
}
|
|
|
|
public bool Deserialized { get; set; }
|
|
|
|
public bool SaveEnabled { get; set; }
|
|
|
|
// TODO: Flags? (Deserialized, SaveEnabled, Activated)
|
|
|
|
public bool Activated { get; set; }
|
|
|
|
public List<BaseObjective> Objectives { get; set; }
|
|
|
|
public ObjectiveType ObjectiveType { get; set; }
|
|
|
|
public List<BaseReward> Rewards { get; set; }
|
|
|
|
public List<MLQuestInstance> Instances { get; set; }
|
|
|
|
public bool OneTimeOnly { get; set; }
|
|
|
|
public bool HasRestartDelay { get; set; }
|
|
|
|
public bool IsEscort => HasObjective<EscortObjective>();
|
|
|
|
public bool IsSkillTrainer => HasObjective<GainSkillObjective>();
|
|
|
|
public bool RequiresCollection => HasObjective<CollectObjective>() || HasObjective<DeliverObjective>();
|
|
|
|
public virtual bool RecordCompletion => OneTimeOnly || HasRestartDelay;
|
|
|
|
public virtual bool IsChainTriggered => false;
|
|
public virtual Type NextQuest => null;
|
|
|
|
public TextDefinition Title { get; set; }
|
|
|
|
public TextDefinition Description { get; set; }
|
|
|
|
public TextDefinition RefusalMessage { get; set; }
|
|
|
|
public TextDefinition InProgressMessage { get; set; }
|
|
|
|
public TextDefinition CompletionMessage { get; set; }
|
|
|
|
public TextDefinition CompletionNotice { get; set; }
|
|
|
|
public virtual int Version => 0;
|
|
|
|
public bool HasObjective<T>() where T : BaseObjective
|
|
{
|
|
foreach (var obj in Objectives)
|
|
{
|
|
if (obj is T)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public virtual void Generate()
|
|
{
|
|
if (MLQuestSystem.Debug)
|
|
{
|
|
Console.WriteLine("INFO: Generating quest: {0}", GetType());
|
|
}
|
|
}
|
|
|
|
public MLQuestInstance CreateInstance(IQuestGiver quester, PlayerMobile pm) =>
|
|
new(this, quester, pm);
|
|
|
|
public bool CanOffer(IQuestGiver quester, PlayerMobile pm, bool message) =>
|
|
CanOffer(quester, pm, MLQuestSystem.GetContext(pm), message);
|
|
|
|
public virtual bool CanOffer(IQuestGiver quester, PlayerMobile pm, MLQuestContext context, bool message)
|
|
{
|
|
if (!Activated || quester.Deleted)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (context != null)
|
|
{
|
|
if (context.IsFull)
|
|
{
|
|
if (message)
|
|
{
|
|
MLQuestSystem.Tell(quester, pm, 1080107); // I'm sorry, I have nothing for you at this time.
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
var checkQuest = this;
|
|
|
|
while (checkQuest != null)
|
|
{
|
|
if (context.HasDoneQuest(checkQuest, out var nextAvailable))
|
|
{
|
|
if (checkQuest.OneTimeOnly)
|
|
{
|
|
if (message)
|
|
{
|
|
MLQuestSystem.Tell(quester, pm, 1075454); // I cannot offer you the quest again.
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
if (nextAvailable > Core.Now)
|
|
{
|
|
if (message)
|
|
{
|
|
// I'm sorry, but I don't have anything else for you right now.
|
|
// Could you check back with me in a few minutes?
|
|
MLQuestSystem.Tell(quester, pm, 1075575);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (checkQuest.NextQuest == null)
|
|
{
|
|
break;
|
|
}
|
|
|
|
checkQuest = MLQuestSystem.FindQuest(checkQuest.NextQuest);
|
|
}
|
|
}
|
|
|
|
foreach (var obj in Objectives)
|
|
{
|
|
if (!obj.CanOffer(quester, pm, message))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public virtual void SendOffer(IQuestGiver quester, PlayerMobile pm)
|
|
{
|
|
pm.SendGump(new QuestOfferGump(this, quester, pm));
|
|
}
|
|
|
|
public virtual void OnAccept(IQuestGiver quester, PlayerMobile pm)
|
|
{
|
|
if (!CanOffer(quester, pm, true))
|
|
{
|
|
return;
|
|
}
|
|
|
|
var instance = CreateInstance(quester, pm);
|
|
|
|
pm.SendLocalizedMessage(1049019); // You have accepted the Quest.
|
|
pm.SendSound(0x2E7); // private sound
|
|
|
|
OnAccepted(instance);
|
|
|
|
foreach (var obj in instance.Objectives)
|
|
{
|
|
obj.OnQuestAccepted();
|
|
}
|
|
}
|
|
|
|
public virtual void OnAccepted(MLQuestInstance instance)
|
|
{
|
|
}
|
|
|
|
public virtual void OnRefuse(IQuestGiver quester, PlayerMobile pm)
|
|
{
|
|
pm.SendGump(new QuestConversationGump(this, pm, RefusalMessage));
|
|
}
|
|
|
|
public virtual void GetRewards(MLQuestInstance instance)
|
|
{
|
|
instance.SendRewardGump();
|
|
}
|
|
|
|
public virtual void OnRewardClaimed(MLQuestInstance instance)
|
|
{
|
|
}
|
|
|
|
public virtual void OnCancel(MLQuestInstance instance)
|
|
{
|
|
}
|
|
|
|
public virtual void OnQuesterDeleted(MLQuestInstance instance)
|
|
{
|
|
}
|
|
|
|
public virtual void OnPlayerDeath(MLQuestInstance instance)
|
|
{
|
|
}
|
|
|
|
public virtual TimeSpan GetRestartDelay() => TimeSpan.FromSeconds(Utility.Random(1, 5) * 30);
|
|
|
|
public static void Serialize(IGenericWriter writer, MLQuest quest)
|
|
{
|
|
MLQuestSystem.WriteQuestRef(writer, quest);
|
|
writer.Write(quest.Version);
|
|
}
|
|
|
|
public static void Deserialize(IGenericReader reader, int version)
|
|
{
|
|
var quest = MLQuestSystem.ReadQuestRef(reader);
|
|
var oldVersion = reader.ReadInt();
|
|
|
|
if (quest == null)
|
|
{
|
|
return; // not saved or no longer exists
|
|
}
|
|
|
|
quest.Refresh(oldVersion);
|
|
quest.Deserialized = true;
|
|
}
|
|
|
|
public virtual void Refresh(int oldVersion)
|
|
{
|
|
}
|
|
|
|
public void PutSpawner(Spawner s, Point3D loc, Map map)
|
|
{
|
|
var name = $"MLQS-{GetType().Name}";
|
|
|
|
using var queue = PooledRefQueue<Item>.Create();
|
|
foreach (var item in map.GetItemsAt(loc))
|
|
{
|
|
// This predates GUIDs. Let's build these spawners and export them so we can delete this code!
|
|
if (item is BaseSpawner spawner && spawner.Name == name)
|
|
{
|
|
queue.Enqueue(item);
|
|
}
|
|
}
|
|
|
|
while (queue.Count > 0)
|
|
{
|
|
queue.Dequeue().Delete();
|
|
}
|
|
|
|
s.Name = name;
|
|
s.MoveToWorld(loc, map);
|
|
}
|
|
|
|
public static void PutDeco(Item deco, Point3D loc, Map map)
|
|
{
|
|
using var queue = PooledRefQueue<Item>.Create();
|
|
foreach (var item in map.GetItemsAt(loc))
|
|
{
|
|
if (item.ItemID == deco.ItemID && item.Z == loc.Z)
|
|
{
|
|
queue.Enqueue(item);
|
|
}
|
|
}
|
|
|
|
while (queue.Count > 0)
|
|
{
|
|
queue.Dequeue().Delete();
|
|
}
|
|
|
|
deco.MoveToWorld(loc, map);
|
|
}
|
|
}
|
|
}
|