* Fixes a bug with the main loop. Removes unnecessary optimizations for RDRand. * WIP - Rewriting packets. * Cleanup and updates README * Converts more packets * Fixes header * Converts Effects packets. * Forgot the send command * Adds the start of containers packets. * Adds message packets with caching * Extends the send methods * Starts to add Acquire methods * Converted the packets * Cleanup * Cleanup * Adds more packets * Adds more packets * Fixes clearing arrays from pool. Adds Mobile Incoming * Converts more packets. * Moves more packets * Moves more packets * Test compression * Merges * Migrating to an idiomatic syntax that also supports compression, and proxying. * Optimize the stack alloc. Changes attributes * Converted container packets * Visual Studio doesn't auto save files. I am still getting used to subpar IDEs. * Adds static packet caching. Will profile later. Converts more packets * Fixes packets and changes how compression is configured * WIP - Adds basics for Gumps. Not finished though. * Fix file * Fixes formatting * Changed SpanWriter to be more idiomatic. * Fixes Span vs RawSpan and missing stackallocs * Converts over some more gumps * WIP - Deletes 32bit support. * Drops RDRand32 support. * Fixes gump compilation * Converting gump components * Removes old huffman compression function * Revert signature for backwards compatibility * WIP - Converting more gump components * Creates ArraySet for the strings. Updates AppendTo to reference that. * Converts the maining gump components * Cleans up gump components * Cleans up directives * Cleans up ArraySet and moves it. Adds null-coalescing-assignment * Cleanup, Target Packets, and C# 8 changes. * Removed OPL Packet * World packets * Fixing packet uses * Cleans up code. Fixes packet uses in various places. * More cleanup for packets * Code cleanup * Converts more packet uses and cleans up more code * More code cleanup * Finishes fixing the packets in Item * Updates secure trade packets * Updates core and gets it to compile. * Moved packets to scripts. Fixed account handler use of packets * Code cleanup * Updates chat packets * Code cleanuo * Adds party packets, but need to implement them. * Party packets WIP * Finishes party packets * Rearrange movement namespaces and classes * Finishes plant packets * Code Formatting * Fixes moving effects * Code cleanup, eliminates equipinfo * Adds more packets. Fixes bugs with various packets. * Finishes mahjon packets * Fixes mahjong packet * Code cleanup * Finishes mahjong packets * Adds Map packets * Add multifacet maps and charts * Cleans up some packets with UTF8 * Optimizes packets * Cleans up more packets. Moves the MessageHelper * Removes assistant support. Removes extended protocol. Incorporates MapUO packets as normal packets. * Updates protocol extensions packet receiver * Fixes a few bugs. Fixes a few more packets. * Code cleanup and fixing more packets * Fixes packet effects * Cleaned up more code * Buff Icon cleanup * Removed unused constructors * Code Cleanup. Adds BoatHS Packets * Moves house files. Updates house foundation packets. * Deployment cleanup * Fixes: * Code cleanup * More code cleanup * More code cleanup * Cleaned up BaseHouse * Enforces styling * Converts foreach to linq where possible. * Dont need that * Goals/Readme updates * Removes 32bit support at the highest level. Turns on HRT by default. * Code cleanup. Fixes extended features packet. * Fixes various bugs * Code cleanup * Code cleanup * Code cleanup. Fixes gump X/Y assignment. * Code cleanup using |= operator * More code cleanup * Cleanup * Fixes spacing issues. Thanks Visual Studio. You suck. * Compiler error * Fixes NPE from RunUO 2.7 * Renames ScriptCompiler to AssemblyHandler. Fixes packets. Updates README * Fixes more packets. Stupid trailing nulls. * Fixes various bugs. * Fixes for gumps * Fixes more gump stuff. Going to split it out later since it is getting insane * Recoded the gump writing * WIP * *Added output path of scripts project to dev branch *Activated debugging in code
698 lines
21 KiB
C#
698 lines
21 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using Server.Commands;
|
|
using Server.Commands.Generic;
|
|
using Server.Engines.MLQuests.Gumps;
|
|
using Server.Engines.MLQuests.Objectives;
|
|
using Server.Gumps;
|
|
using Server.Items;
|
|
using Server.Mobiles;
|
|
using Server.Network;
|
|
|
|
namespace Server.Engines.MLQuests
|
|
{
|
|
public static class MLQuestSystem
|
|
{
|
|
public const int MaxConcurrentQuests = 10;
|
|
public const int SpeechColor = 0x3B2;
|
|
|
|
public static readonly bool AutoGenerateNew = true;
|
|
public static readonly bool Debug = false;
|
|
|
|
public static readonly List<MLQuest> EmptyList = new List<MLQuest>();
|
|
|
|
private static List<MLQuest> m_EligiblePool = new List<MLQuest>();
|
|
|
|
static MLQuestSystem()
|
|
{
|
|
Quests = new Dictionary<Type, MLQuest>();
|
|
QuestGivers = new Dictionary<Type, List<MLQuest>>();
|
|
Contexts = new Dictionary<PlayerMobile, MLQuestContext>();
|
|
|
|
string cfgPath = Path.Combine(Core.BaseDirectory, Path.Combine("Data", "MLQuests.cfg"));
|
|
|
|
Type baseQuestType = typeof(MLQuest);
|
|
Type baseQuesterType = typeof(IQuestGiver);
|
|
|
|
if (File.Exists(cfgPath))
|
|
{
|
|
using StreamReader sr = new StreamReader(cfgPath);
|
|
string line;
|
|
|
|
while ((line = sr.ReadLine()) != null)
|
|
{
|
|
if (line.Length == 0 || line.StartsWith("#"))
|
|
continue;
|
|
|
|
string[] split = line.Split('\t');
|
|
|
|
Type type = AssemblyHandler.FindTypeByName(split[0]);
|
|
|
|
if (type == null || !baseQuestType.IsAssignableFrom(type))
|
|
{
|
|
if (Debug)
|
|
Console.WriteLine("Warning: {1} quest type '{0}'", split[0],
|
|
type == null ? "Unknown" : "Invalid");
|
|
|
|
continue;
|
|
}
|
|
|
|
MLQuest quest = null;
|
|
|
|
try
|
|
{
|
|
quest = Activator.CreateInstance(type) as MLQuest;
|
|
}
|
|
catch
|
|
{
|
|
// ignored
|
|
}
|
|
|
|
if (quest == null)
|
|
continue;
|
|
|
|
Register(type, quest);
|
|
|
|
for (int i = 1; i < split.Length; ++i)
|
|
{
|
|
Type questerType = AssemblyHandler.FindTypeByName(split[i]);
|
|
|
|
if (questerType == null || !baseQuesterType.IsAssignableFrom(questerType))
|
|
{
|
|
if (Debug)
|
|
Console.WriteLine("Warning: {1} quester type '{0}'", split[i],
|
|
questerType == null ? "Unknown" : "Invalid");
|
|
|
|
continue;
|
|
}
|
|
|
|
RegisterQuestGiver(quest, questerType);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public static bool Enabled => Core.ML;
|
|
|
|
public static Dictionary<Type, MLQuest> Quests{ get; }
|
|
|
|
public static Dictionary<Type, List<MLQuest>> QuestGivers{ get; }
|
|
|
|
public static Dictionary<PlayerMobile, MLQuestContext> Contexts{ get; }
|
|
|
|
private static void Register(Type type, MLQuest quest)
|
|
{
|
|
Quests[type] = quest;
|
|
}
|
|
|
|
private static void RegisterQuestGiver(MLQuest quest, Type questerType)
|
|
{
|
|
if (!QuestGivers.TryGetValue(questerType, out List<MLQuest> questList))
|
|
QuestGivers[questerType] = questList = new List<MLQuest>();
|
|
|
|
questList.Add(quest);
|
|
}
|
|
|
|
public static void Register(MLQuest quest, params Type[] questerTypes)
|
|
{
|
|
Register(quest.GetType(), quest);
|
|
|
|
foreach (Type questerType in questerTypes)
|
|
RegisterQuestGiver(quest, questerType);
|
|
}
|
|
|
|
public static void Initialize()
|
|
{
|
|
if (!Enabled)
|
|
return;
|
|
|
|
if (AutoGenerateNew)
|
|
foreach (MLQuest quest in Quests.Values)
|
|
if (quest?.Deserialized == false)
|
|
quest.Generate();
|
|
|
|
MLQuestPersistence.EnsureExistence();
|
|
|
|
CommandSystem.Register("MLQuestsInfo", AccessLevel.Administrator, MLQuestsInfo_OnCommand);
|
|
CommandSystem.Register("SaveQuest", AccessLevel.Administrator, SaveQuest_OnCommand);
|
|
CommandSystem.Register("SaveAllQuests", AccessLevel.Administrator, SaveAllQuests_OnCommand);
|
|
CommandSystem.Register("InvalidQuestItems", AccessLevel.Administrator, InvalidQuestItems_OnCommand);
|
|
|
|
TargetCommands.Register(new ViewQuestsCommand());
|
|
TargetCommands.Register(new ViewContextCommand());
|
|
|
|
EventSink.QuestGumpRequest += EventSink_QuestGumpRequest;
|
|
}
|
|
|
|
[Usage("MLQuestsInfo")]
|
|
[Description("Displays general information about the ML quest system, or a quest by type name.")]
|
|
public static void MLQuestsInfo_OnCommand(CommandEventArgs e)
|
|
{
|
|
Mobile m = e.Mobile;
|
|
|
|
if (e.Length == 0)
|
|
{
|
|
m.SendMessage("Quest table length: {0}", Quests.Count);
|
|
return;
|
|
}
|
|
|
|
Type index = AssemblyHandler.FindTypeByName(e.GetString(0));
|
|
|
|
if (index == null || !Quests.TryGetValue(index, out MLQuest quest))
|
|
{
|
|
m.SendMessage("Invalid quest type name.");
|
|
return;
|
|
}
|
|
|
|
m.SendMessage("Activated: {0}", quest.Activated);
|
|
m.SendMessage("Number of objectives: {0}", quest.Objectives.Count);
|
|
m.SendMessage("Objective type: {0}", quest.ObjectiveType);
|
|
m.SendMessage("Number of active instances: {0}", quest.Instances.Count);
|
|
}
|
|
|
|
[Usage("SaveQuest <type> [saveEnabled=true]")]
|
|
[Description("Allows serialization for a specific quest to be turned on or off.")]
|
|
public static void SaveQuest_OnCommand(CommandEventArgs e)
|
|
{
|
|
Mobile m = e.Mobile;
|
|
|
|
if (e.Length == 0 || e.Length > 2)
|
|
{
|
|
m.SendMessage("Syntax: SaveQuest <id> [saveEnabled=true]");
|
|
return;
|
|
}
|
|
|
|
Type index = AssemblyHandler.FindTypeByName(e.GetString(0));
|
|
|
|
if (index == null || !Quests.TryGetValue(index, out MLQuest quest))
|
|
{
|
|
m.SendMessage("Invalid quest type name.");
|
|
return;
|
|
}
|
|
|
|
bool enable = e.Length == 2 ? e.GetBoolean(1) : true;
|
|
|
|
quest.SaveEnabled = enable;
|
|
m.SendMessage("Serialization for quest {0} is now {1}.", quest.GetType().Name, enable ? "enabled" : "disabled");
|
|
|
|
if (AutoGenerateNew && !enable)
|
|
m.SendMessage(
|
|
"Please note that automatic generation of new quests is ON. This quest will be regenerated on the next server start.");
|
|
}
|
|
|
|
[Usage("SaveAllQuests [saveEnabled=true]")]
|
|
[Description("Allows serialization for all quests to be turned on or off.")]
|
|
public static void SaveAllQuests_OnCommand(CommandEventArgs e)
|
|
{
|
|
Mobile m = e.Mobile;
|
|
|
|
if (e.Length > 1)
|
|
{
|
|
m.SendMessage("Syntax: SaveAllQuests [saveEnabled=true]");
|
|
return;
|
|
}
|
|
|
|
bool enable = e.Length != 1 || e.GetBoolean(0);
|
|
|
|
foreach (MLQuest quest in Quests.Values)
|
|
quest.SaveEnabled = enable;
|
|
|
|
m.SendMessage("Serialization for all quests is now {0}.", enable ? "enabled" : "disabled");
|
|
|
|
if (AutoGenerateNew && !enable)
|
|
m.SendMessage(
|
|
"Please note that automatic generation of new quests is ON. All quests will be regenerated on the next server start.");
|
|
}
|
|
|
|
[Usage("InvalidQuestItems")]
|
|
[Description("Provides an overview of all quest items not located in the top-level of a player's backpack.")]
|
|
public static void InvalidQuestItems_OnCommand(CommandEventArgs e)
|
|
{
|
|
Mobile m = e.Mobile;
|
|
|
|
List<object> found = new List<object>();
|
|
|
|
foreach (Item item in World.Items.Values)
|
|
if (item.QuestItem)
|
|
{
|
|
if (item.Parent is Backpack pack)
|
|
if (pack.Parent is PlayerMobile player && player.Backpack == pack)
|
|
continue;
|
|
|
|
found.Add(item);
|
|
}
|
|
|
|
if (found.Count == 0)
|
|
m.SendMessage("No matching objects found.");
|
|
else
|
|
m.SendGump(new InterfaceGump(m, new[] { "Object" }, found, 0, null));
|
|
}
|
|
|
|
private static bool FindQuest(IQuestGiver quester, PlayerMobile pm, MLQuestContext context, out MLQuest quest,
|
|
out MLQuestInstance entry)
|
|
{
|
|
quest = null;
|
|
entry = null;
|
|
|
|
List<MLQuest> quests = quester.MLQuests;
|
|
Type questerType = quester.GetType();
|
|
|
|
// 1. Check quests in progress with this NPC (overriding deliveries is intended)
|
|
if (context != null)
|
|
foreach (MLQuest questEntry in quests)
|
|
{
|
|
MLQuestInstance instance = context.FindInstance(questEntry);
|
|
|
|
if (instance != null && (instance.Quester == quester ||
|
|
!questEntry.IsEscort && instance.QuesterType == questerType))
|
|
{
|
|
entry = instance;
|
|
quest = questEntry;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// 2. Check deliveries (overriding chain offers is intended)
|
|
if ((entry = HandleDelivery(pm, quester, questerType)) != null)
|
|
{
|
|
quest = entry.Quest;
|
|
return true;
|
|
}
|
|
|
|
// 3. Check chain quest offers
|
|
if (context != null)
|
|
foreach (MLQuest questEntry in quests)
|
|
if (questEntry.IsChainTriggered && context.ChainOffers.Contains(questEntry))
|
|
{
|
|
quest = questEntry;
|
|
return true;
|
|
}
|
|
|
|
// 4. Random quest
|
|
quest = RandomStarterQuest(quester, pm, context);
|
|
|
|
return quest != null;
|
|
}
|
|
|
|
public static void OnDoubleClick(IQuestGiver quester, PlayerMobile pm)
|
|
{
|
|
if (quester.Deleted || !pm.Alive)
|
|
return;
|
|
|
|
MLQuestContext context = GetContext(pm);
|
|
|
|
if (!FindQuest(quester, pm, context, out MLQuest quest, out MLQuestInstance entry))
|
|
{
|
|
Tell(quester, pm, 1080107); // I'm sorry, I have nothing for you at this time.
|
|
return;
|
|
}
|
|
|
|
if (entry != null)
|
|
{
|
|
TurnToFace(quester, pm);
|
|
|
|
if (entry.Failed)
|
|
return; // Note: OSI sends no gump at all for failed quests, they have to be cancelled in the quest overview
|
|
if (entry.ClaimReward)
|
|
entry.SendRewardOffer();
|
|
else if (entry.IsCompleted())
|
|
entry.SendReportBackGump();
|
|
else
|
|
entry.SendProgressGump();
|
|
}
|
|
else if (quest.CanOffer(quester, pm, context, true))
|
|
{
|
|
TurnToFace(quester, pm);
|
|
|
|
quest.SendOffer(quester, pm);
|
|
}
|
|
}
|
|
|
|
public static bool CanMarkQuestItem(PlayerMobile pm, Item item, Type type) =>
|
|
GetContext(pm)?.QuestInstances.Any(quest => !quest.ClaimReward && quest.AllowsQuestItem(item, type)) == true;
|
|
|
|
private static void OnMarkQuestItem(PlayerMobile pm, Item item, Type type)
|
|
{
|
|
MLQuestContext context = GetContext(pm);
|
|
|
|
if (context == null)
|
|
return;
|
|
|
|
List<MLQuestInstance> instances = context.QuestInstances;
|
|
|
|
// We don't foreach because CheckComplete() can potentially modify the MLQuests list
|
|
for (int i = instances.Count - 1; i >= 0; --i)
|
|
{
|
|
MLQuestInstance instance = instances[i];
|
|
|
|
if (instance.ClaimReward)
|
|
continue;
|
|
|
|
foreach (BaseObjectiveInstance objective in instance.Objectives)
|
|
if (!objective.Expired && objective.AllowsQuestItem(item, type))
|
|
{
|
|
objective.CheckComplete(); // yes, this can happen multiple times (for multiple quests)
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
public static bool MarkQuestItem(PlayerMobile pm, Item item)
|
|
{
|
|
Type type = item.GetType();
|
|
|
|
if (CanMarkQuestItem(pm, item, type))
|
|
{
|
|
item.QuestItem = true;
|
|
OnMarkQuestItem(pm, item, type);
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public static void HandleSkillGain(PlayerMobile pm, SkillName skill)
|
|
{
|
|
MLQuestContext context = GetContext(pm);
|
|
|
|
if (context == null)
|
|
return;
|
|
|
|
List<MLQuestInstance> instances = context.QuestInstances;
|
|
|
|
for (int i = instances.Count - 1; i >= 0; --i)
|
|
{
|
|
MLQuestInstance instance = instances[i];
|
|
|
|
if (instance.ClaimReward)
|
|
continue;
|
|
|
|
foreach (BaseObjectiveInstance objective in instance.Objectives)
|
|
if (!objective.Expired && objective is GainSkillObjectiveInstance objectiveInstance &&
|
|
objectiveInstance.Handles(skill))
|
|
{
|
|
objectiveInstance.CheckComplete();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
public static void HandleKill(PlayerMobile pm, Mobile mob)
|
|
{
|
|
MLQuestContext context = GetContext(pm);
|
|
|
|
if (context == null)
|
|
return;
|
|
|
|
List<MLQuestInstance> instances = context.QuestInstances;
|
|
|
|
Type type = null;
|
|
|
|
for (int i = instances.Count - 1; i >= 0; --i)
|
|
{
|
|
MLQuestInstance instance = instances[i];
|
|
|
|
if (instance.ClaimReward)
|
|
continue;
|
|
|
|
/* A kill only counts for a single objective within a quest,
|
|
* but it can count for multiple quests. This is something not
|
|
* currently observable on OSI, so it is assumed behavior.
|
|
*/
|
|
foreach (BaseObjectiveInstance objective in instance.Objectives)
|
|
if (!objective.Expired && objective is KillObjectiveInstance kill)
|
|
{
|
|
if (type == null)
|
|
type = mob.GetType();
|
|
|
|
if (kill.AddKill(mob, type))
|
|
{
|
|
kill.CheckComplete();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public static MLQuestInstance HandleDelivery(PlayerMobile pm, IQuestGiver quester, Type questerType)
|
|
{
|
|
MLQuestContext context = GetContext(pm);
|
|
|
|
if (context == null)
|
|
return null;
|
|
|
|
List<MLQuestInstance> instances = context.QuestInstances;
|
|
MLQuestInstance deliverInstance = null;
|
|
|
|
for (int i = instances.Count - 1; i >= 0; --i)
|
|
{
|
|
MLQuestInstance instance = instances[i];
|
|
|
|
// Do NOT skip quests on ClaimReward, because the quester still needs the quest ref!
|
|
//if ( instance.ClaimReward )
|
|
// continue;
|
|
|
|
foreach (BaseObjectiveInstance objective in instance.Objectives)
|
|
// Note: On OSI, expired deliveries can still be completed. Bug?
|
|
if (!objective.Expired && objective is DeliverObjectiveInstance deliver &&
|
|
deliver.IsDestination(quester, questerType))
|
|
{
|
|
if (!deliver.HasCompleted) // objective completes only once
|
|
{
|
|
deliver.HasCompleted = true;
|
|
deliver.CheckComplete();
|
|
|
|
// The quest is continued with this NPC (important for chains)
|
|
instance.Quester = quester;
|
|
}
|
|
|
|
if (deliverInstance == null)
|
|
deliverInstance = instance;
|
|
|
|
break; // don't return, we may have to complete more deliveries
|
|
}
|
|
}
|
|
|
|
return deliverInstance;
|
|
}
|
|
|
|
public static MLQuestContext GetContext(PlayerMobile pm)
|
|
{
|
|
Contexts.TryGetValue(pm, out MLQuestContext context);
|
|
|
|
return context;
|
|
}
|
|
|
|
public static MLQuestContext GetOrCreateContext(PlayerMobile pm)
|
|
{
|
|
if (!Contexts.TryGetValue(pm, out MLQuestContext context))
|
|
Contexts[pm] = context = new MLQuestContext(pm);
|
|
|
|
return context;
|
|
}
|
|
|
|
public static void HandleDeath(PlayerMobile pm)
|
|
{
|
|
MLQuestContext context = GetContext(pm);
|
|
|
|
context?.HandleDeath();
|
|
}
|
|
|
|
public static void HandleDeletion(PlayerMobile pm)
|
|
{
|
|
MLQuestContext context = GetContext(pm);
|
|
|
|
if (context != null)
|
|
{
|
|
context.HandleDeletion();
|
|
Contexts.Remove(pm);
|
|
}
|
|
}
|
|
|
|
public static void HandleDeletion(IQuestGiver quester)
|
|
{
|
|
foreach (MLQuest quest in quester.MLQuests)
|
|
{
|
|
List<MLQuestInstance> instances = quest.Instances;
|
|
|
|
for (int i = instances.Count - 1; i >= 0; --i)
|
|
{
|
|
MLQuestInstance instance = instances[i];
|
|
|
|
if (instance.Quester == quester)
|
|
instance.OnQuesterDeleted();
|
|
}
|
|
}
|
|
}
|
|
|
|
public static void EventSink_QuestGumpRequest(QuestGumpRequestArgs args)
|
|
{
|
|
if (!Enabled || !(args.Mobile is PlayerMobile pm))
|
|
return;
|
|
|
|
pm.SendGump(new QuestLogGump(pm));
|
|
}
|
|
|
|
public static MLQuest RandomStarterQuest(IQuestGiver quester, PlayerMobile pm, MLQuestContext context)
|
|
{
|
|
List<MLQuest> quests = quester.MLQuests;
|
|
|
|
if (quests.Count == 0)
|
|
return null;
|
|
|
|
m_EligiblePool.Clear();
|
|
MLQuest fallback = null;
|
|
|
|
foreach (MLQuest quest in quests)
|
|
{
|
|
if (quest.IsChainTriggered || context?.IsDoingQuest(quest) == true)
|
|
continue;
|
|
|
|
/*
|
|
* Save first quest that reaches the CanOffer call.
|
|
* If no quests are valid at all, return this quest for displaying the CanOffer error message.
|
|
*/
|
|
fallback ??= quest;
|
|
|
|
if (quest.CanOffer(quester, pm, context, false))
|
|
m_EligiblePool.Add(quest);
|
|
}
|
|
|
|
return m_EligiblePool.Count == 0 ? fallback : m_EligiblePool[Utility.Random(m_EligiblePool.Count)];
|
|
}
|
|
|
|
public static void TurnToFace(IQuestGiver quester, Mobile mob)
|
|
{
|
|
if (quester is Mobile m)
|
|
m.Direction = m.GetDirectionTo(mob);
|
|
}
|
|
|
|
public static void Tell(IQuestGiver quester, PlayerMobile pm, int cliloc)
|
|
{
|
|
TurnToFace(quester, pm);
|
|
|
|
if (quester is Mobile mobile)
|
|
mobile.PrivateOverheadMessage(MessageType.Regular, SpeechColor, cliloc, pm.NetState);
|
|
else if (quester is Item item)
|
|
MessageHelper.SendLocalizedMessageTo(item, pm, cliloc, SpeechColor);
|
|
else
|
|
pm.SendLocalizedMessage(cliloc);
|
|
}
|
|
|
|
public static void Tell(IQuestGiver quester, PlayerMobile pm, int cliloc, string args)
|
|
{
|
|
TurnToFace(quester, pm);
|
|
|
|
if (quester is Mobile mobile)
|
|
mobile.PrivateOverheadMessage(MessageType.Regular, SpeechColor, cliloc, args, pm.NetState);
|
|
else if (quester is Item item)
|
|
MessageHelper.SendLocalizedMessageTo(item, pm, cliloc, args, SpeechColor);
|
|
else
|
|
pm.SendLocalizedMessage(cliloc, args);
|
|
}
|
|
|
|
public static void Tell(IQuestGiver quester, PlayerMobile pm, string message)
|
|
{
|
|
TurnToFace(quester, pm);
|
|
|
|
if (quester is Mobile mobile)
|
|
mobile.PrivateOverheadMessage(MessageType.Regular, SpeechColor, false, message, pm.NetState);
|
|
else if (quester is Item item)
|
|
MessageHelper.SendMessageTo(item, pm, message, SpeechColor);
|
|
else
|
|
pm.SendMessage(SpeechColor, message);
|
|
}
|
|
|
|
public static void TellDef(IQuestGiver quester, PlayerMobile pm, TextDefinition def)
|
|
{
|
|
if (def == null)
|
|
return;
|
|
|
|
if (def.Number > 0)
|
|
Tell(quester, pm, def.Number);
|
|
else if (def.String != null)
|
|
Tell(quester, pm, def.String);
|
|
}
|
|
|
|
public static void WriteQuestRef(GenericWriter writer, MLQuest quest)
|
|
{
|
|
writer.Write(quest?.SaveEnabled == true ? quest.GetType().FullName : null);
|
|
}
|
|
|
|
public static MLQuest ReadQuestRef(GenericReader reader)
|
|
{
|
|
string typeName = reader.ReadString();
|
|
|
|
if (typeName == null)
|
|
return null; // not serialized
|
|
|
|
Type questType = AssemblyHandler.FindTypeByFullName(typeName);
|
|
|
|
if (questType == null)
|
|
return null; // no longer a type
|
|
|
|
return questType == null ? null : FindQuest(questType);
|
|
}
|
|
|
|
public static MLQuest FindQuest(Type questType)
|
|
{
|
|
Quests.TryGetValue(questType, out MLQuest result);
|
|
|
|
return result;
|
|
}
|
|
|
|
public static List<MLQuest> FindQuestList(Type questerType) => QuestGivers.TryGetValue(questerType, out List<MLQuest> result) ? result : EmptyList;
|
|
|
|
public class ViewQuestsCommand : BaseCommand
|
|
{
|
|
public ViewQuestsCommand()
|
|
{
|
|
AccessLevel = AccessLevel.GameMaster;
|
|
Supports = CommandSupport.Simple;
|
|
Commands = new[] { "ViewQuests" };
|
|
ObjectTypes = ObjectTypes.Mobiles;
|
|
Usage = "ViewQuests";
|
|
Description = "Displays a targeted mobile's quest overview.";
|
|
}
|
|
|
|
public override void Execute(CommandEventArgs e, object obj)
|
|
{
|
|
Mobile from = e.Mobile;
|
|
|
|
if (!(obj is PlayerMobile pm))
|
|
{
|
|
LogFailure("That is not a player.");
|
|
return;
|
|
}
|
|
|
|
CommandLogging.WriteLine(from, "{0} {1} viewing quest overview of {2}", from.AccessLevel,
|
|
CommandLogging.Format(from), CommandLogging.Format(pm));
|
|
from.SendGump(new QuestLogGump(pm, false));
|
|
}
|
|
}
|
|
|
|
private class ViewContextCommand : BaseCommand
|
|
{
|
|
public ViewContextCommand()
|
|
{
|
|
AccessLevel = AccessLevel.GameMaster;
|
|
Supports = CommandSupport.Simple;
|
|
Commands = new[] { "ViewMLContext" };
|
|
ObjectTypes = ObjectTypes.Mobiles;
|
|
Usage = "ViewMLContext";
|
|
Description = "Opens the ML quest context for a targeted mobile.";
|
|
}
|
|
|
|
public override void Execute(CommandEventArgs e, object obj)
|
|
{
|
|
if (!(obj is PlayerMobile pm))
|
|
LogFailure("They have no ML quest context.");
|
|
else
|
|
e.Mobile.SendGump(new PropertiesGump(e.Mobile, GetOrCreateContext(pm)));
|
|
}
|
|
}
|
|
}
|
|
}
|