515 lines
23 KiB
C#
515 lines
23 KiB
C#
using AssortGenerator.Common.Helpers;
|
|
using QuestValidator.Common;
|
|
using QuestValidator.Common.Helpers;
|
|
using QuestValidator.Common.Models;
|
|
using QuestValidator.Helpers;
|
|
using QuestValidator.Models;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Text.RegularExpressions;
|
|
using Quest = QuestValidator.Models.Quest;
|
|
|
|
namespace GenerateQuestFile
|
|
{
|
|
class Program
|
|
{
|
|
/// <summary>
|
|
/// Generate a quests.json file in /output/
|
|
/// Uses every quest from the live quest dump file
|
|
/// If any quests are missing, it will use the quests.json file to fill in the blanks
|
|
/// </summary>
|
|
/// <param name="args"></param>
|
|
static void Main(string[] args)
|
|
{
|
|
var inputPath = DiskHelpers.CreateWorkingFolders();
|
|
InputFileHelper.SetInputFiles(inputPath);
|
|
|
|
// Read in quest files
|
|
var questBlacklist = QuestHelper.GetQuestBlacklist();
|
|
var existingQuestData = QuestHelper.GetQuestData();
|
|
var liveQuestData = QuestHelper.GetLiveQuestData();
|
|
|
|
var mergedLiveData = QuestHelper.MergeLiveQuestFiles(liveQuestData, questBlacklist);
|
|
|
|
OutputQuestRequirementsToConsole(mergedLiveData.data);
|
|
|
|
JsonWriter.WriteJson(mergedLiveData, "output", Directory.GetCurrentDirectory(), "mergedlivejson");
|
|
|
|
// Find the quests that are missing from the live file from existing quests.json
|
|
var missingQuests = GetMissingQuestsNotInLiveFile(existingQuestData, mergedLiveData, questBlacklist);
|
|
|
|
// Create a list of quests to output
|
|
// Use all quests in live file
|
|
// Use quests from quests.json to fill in missing quests
|
|
// Add live quests to collection to return later
|
|
var questsToOutputToFile = new Dictionary<string, Quest>();
|
|
foreach (var liveQuest in mergedLiveData.data)
|
|
{
|
|
questsToOutputToFile.Add(liveQuest._id, liveQuest);
|
|
}
|
|
|
|
// Add missing quests from existing quest data to fill in blanks from live data
|
|
foreach (var missingQuest in missingQuests)
|
|
{
|
|
// Going from a pre-12.7.x version has problems, it doesnt have the new quest data format
|
|
//CheckAndFixMissingProperties(missingQuest);
|
|
|
|
questsToOutputToFile.Add(missingQuest._id, missingQuest);
|
|
}
|
|
|
|
// Now old + new quests have been merged, check quest list to see if any quests are missing
|
|
foreach (var missingQuest in QuestNames.GetQuests())
|
|
{
|
|
if (!questsToOutputToFile.Any(x => x.Key == missingQuest.Value))
|
|
{
|
|
LoggingHelpers.LogWarning($" quest not found in new or old data: {missingQuest.Key}");
|
|
}
|
|
}
|
|
|
|
if (!questsToOutputToFile.ContainsKey("5e383a6386f77465910ce1f3")) // TextileP1Bear
|
|
{
|
|
// add textileP1Bear
|
|
}
|
|
|
|
if (!questsToOutputToFile.ContainsKey("5e4d515e86f77438b2195244")) // TextileP2Bear
|
|
{
|
|
// add TextileP2Bear
|
|
}
|
|
|
|
foreach (var quest in questsToOutputToFile)
|
|
{
|
|
AddQuestName(quest);
|
|
|
|
var originalQuest = existingQuestData.FirstOrDefault(x => x.Key == quest.Key).Value;
|
|
|
|
if (originalQuest is null)
|
|
{
|
|
LoggingHelpers.LogWarning($"Cant check for original start conditions. Unable to find original quest {quest.Key} {QuestHelper.GetQuestNameById(quest.Key)}, skipping.");
|
|
continue;
|
|
}
|
|
|
|
AddMissingFields(quest);
|
|
|
|
// Quest has start conditions, check to ensure they're carried over
|
|
if (originalQuest.conditions.AvailableForStart.Count > 0)
|
|
{
|
|
AddMissingAvailableForStartConditions(originalQuest, quest);
|
|
}
|
|
|
|
if (originalQuest.rewards.Fail.Count > 0)
|
|
{
|
|
AddMissingFailRewards(originalQuest, quest);
|
|
}
|
|
|
|
// To make diffs more sane, copy the random IDs from the existing quests.json if possible
|
|
CopyExistingRandomIds(originalQuest, quest.Value);
|
|
}
|
|
|
|
// Iterate over quest objects a final time and add hard coded quest requirements if they dont already exist
|
|
foreach (var quest in questsToOutputToFile)
|
|
{
|
|
var questRequirements = QuestRequirements.GetQuestRequirements(quest.Key);
|
|
if (questRequirements is null || questRequirements.Count == 0)
|
|
{
|
|
LoggingHelpers.LogWarning($"Quest requirement not found for : {quest.Value.QuestName}, skipping.");
|
|
|
|
continue;
|
|
}
|
|
|
|
foreach (var requirement in questRequirements)
|
|
{
|
|
if (requirement.PreReqType == PreRequisiteType.Quest)
|
|
{
|
|
// Does quest have requirement
|
|
if (!quest.Value.conditions.AvailableForStart.Any(x => x._parent == "Quest"
|
|
&& x._props.target.ToString() == requirement.Quest.Id))
|
|
{
|
|
LoggingHelpers.LogSuccess($"{quest.Value.QuestName} needs a prereq of quest {requirement.Quest.Name}, adding.");
|
|
|
|
string hashData = quest.Value._id + requirement.Quest.Id;
|
|
quest.Value.conditions.AvailableForStart.Add(new AvailableFor
|
|
{
|
|
_parent = "Quest",
|
|
_props = new AvailableForProps
|
|
{
|
|
id = Sha256(hashData),
|
|
index = GetNextIndex(quest.Value.conditions.AvailableForStart.LastOrDefault()?._props?.index),
|
|
parentId = "",
|
|
status = GetQuestStatus(requirement.QuestStatus),
|
|
target = requirement.Quest.Id,
|
|
visibilityConditions = new List<object>(),
|
|
availableAfter = 0
|
|
}
|
|
}
|
|
);
|
|
}
|
|
else
|
|
{
|
|
if (questRequirements != null)
|
|
{
|
|
LoggingHelpers.LogInfo($"{quest.Value.QuestName} already has prereq of quest {requirement.Quest.Name}, skipping.");
|
|
}
|
|
}
|
|
}
|
|
|
|
if (requirement.PreReqType == PreRequisiteType.RemoveQuest)
|
|
{
|
|
if (quest.Value.conditions.AvailableForStart.RemoveAll(x =>
|
|
x._parent == "Quest" && x._props.target.ToString() == requirement.Quest.Id) > 0)
|
|
{
|
|
LoggingHelpers.LogSuccess($"{quest.Value.QuestName} required {requirement.Quest.Name}, removing.");
|
|
}
|
|
}
|
|
|
|
if (requirement.PreReqType == PreRequisiteType.Level)
|
|
{
|
|
if (!quest.Value.conditions.AvailableForStart.Any(x => x._parent == "Level"
|
|
&& int.Parse(x._props.value.ToString()) == requirement.Level))
|
|
{
|
|
LoggingHelpers.LogSuccess($"{quest.Value.QuestName} needs a prereq of level {requirement.Level}, adding.");
|
|
|
|
string hashData = quest.Value._id + "Level";
|
|
quest.Value.conditions.AvailableForStart.Add(new AvailableFor
|
|
{
|
|
_parent = "Level",
|
|
_props = new AvailableForProps
|
|
{
|
|
id = Sha256(hashData),
|
|
index = GetNextIndex(quest.Value.conditions.AvailableForStart.LastOrDefault()?._props?.index),
|
|
parentId = "",
|
|
dynamicLocale = false,
|
|
value = requirement.Level,
|
|
compareMethod = ">=",
|
|
visibilityConditions = new List<object>()
|
|
}
|
|
}
|
|
);
|
|
}
|
|
}
|
|
|
|
if (requirement.PreReqType == PreRequisiteType.RemoveLevel)
|
|
{
|
|
if (quest.Value.conditions.AvailableForStart.RemoveAll(x => x._parent == "Level") > 0)
|
|
{
|
|
LoggingHelpers.LogSuccess($"{quest.Value.QuestName} required level {requirement.Level}, removing.");
|
|
}
|
|
}
|
|
}
|
|
|
|
// To make diffs more sane, copy the random IDs from the existing quests.json if possible
|
|
var originalQuest = existingQuestData.FirstOrDefault(x => x.Key == quest.Key).Value;
|
|
if (originalQuest != null)
|
|
{
|
|
CopyExistingRandomIds(originalQuest, quest.Value);
|
|
}
|
|
}
|
|
OutputQuestRequirementsToConsole2(questsToOutputToFile);
|
|
JsonWriter.WriteJson(questsToOutputToFile, "output", Directory.GetCurrentDirectory(), "quests");
|
|
}
|
|
|
|
private static void OutputQuestRequirementsToConsole(List<Quest> quests)
|
|
{
|
|
var output = new List<string>();
|
|
foreach (var quest in quests)
|
|
{
|
|
var questConditions = quest.conditions.AvailableForStart.Where(x => x._parent == "Quest");
|
|
if (questConditions != null)
|
|
{
|
|
foreach (var questCondition in questConditions)
|
|
{
|
|
var x = questCondition._props.target.ToString();
|
|
Console.WriteLine($"{QuestHelper.GetQuestNameById(quest._id)} needs {QuestHelper.GetQuestNameById(x)}");
|
|
}
|
|
}
|
|
}
|
|
|
|
// JsonWriter.WriteJson<QuestRoot>(output, "output", Directory.GetCurrentDirectory(), "questRequirements");
|
|
}
|
|
|
|
private static void OutputQuestRequirementsToConsole2(Dictionary<string, Quest> quests)
|
|
{
|
|
var output = new List<string>();
|
|
foreach (var quest in quests)
|
|
{
|
|
var questConditions = quest.Value.conditions.AvailableForStart.Where(x => x._parent == "Quest");
|
|
if (questConditions != null)
|
|
{
|
|
foreach (var questCondition in questConditions)
|
|
{
|
|
var x = questCondition._props.target.ToString();
|
|
Console.WriteLine($"{QuestHelper.GetQuestNameById(quest.Value._id)} needs {QuestHelper.GetQuestNameById(x)}");
|
|
}
|
|
}
|
|
}
|
|
|
|
// JsonWriter.WriteJson<QuestRoot>(output, "output", Directory.GetCurrentDirectory(), "questRequirements");
|
|
}
|
|
|
|
private static int[] GetQuestStatus(QuestStatus status)
|
|
{
|
|
switch (status)
|
|
{
|
|
case QuestStatus.Started:
|
|
case QuestStatus.Success:
|
|
case QuestStatus.Fail:
|
|
return new int[] { (int)status };
|
|
case QuestStatus.StartedSuccess:
|
|
return new int[] { (int)QuestStatus.Started, (int)QuestStatus.Success };
|
|
case QuestStatus.SuccessFail:
|
|
return new int[] { (int)QuestStatus.Success, (int)QuestStatus.Fail };
|
|
}
|
|
|
|
throw new Exception($"Unable to process quest status {status}");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Latest version of eft has changed the quest json structure, this method adds missing fields
|
|
/// Mega hack as we dont have a full dump as of 30/06/2022
|
|
/// </summary>
|
|
/// <param name="quest">quest to add missing fields to</param>
|
|
private static void AddMissingFields(KeyValuePair<string, Quest> quest)
|
|
{
|
|
//side
|
|
if (String.IsNullOrEmpty(quest.Value.side))
|
|
{
|
|
quest.Value.side = "Pmc";
|
|
LoggingHelpers.LogInfo($"Updated quest {quest.Value.QuestName} to have a side of 'pmc'");
|
|
}
|
|
|
|
//changeQuestMessageText
|
|
if (String.IsNullOrEmpty(quest.Value.changeQuestMessageText))
|
|
{
|
|
quest.Value.changeQuestMessageText = $"{quest.Value._id} changeQuestMessageText";
|
|
LoggingHelpers.LogInfo($"Updated quest {quest.Value.QuestName} to have a changeQuestMessageText value");
|
|
}
|
|
|
|
// findInRaid
|
|
foreach (var success in quest.Value.rewards.Success)
|
|
{
|
|
if (string.Equals(success.type, "item", StringComparison.OrdinalIgnoreCase)
|
|
&& success.findInRaid == null)
|
|
{
|
|
success.findInRaid = true;
|
|
LoggingHelpers.LogInfo($"Updated quest {quest.Value.QuestName} to have a success item reward findInRaid value of 'true'");
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void AddMissingFailRewards(Quest originalQuest, KeyValuePair<string, Quest> quest)
|
|
{
|
|
foreach (var originalFailReward in originalQuest.rewards.Fail)
|
|
{
|
|
// already has a fail reward of same type and target, skip
|
|
if (quest.Value.rewards.Fail.Any(x => x.type == originalFailReward.type && x.target == originalFailReward.target))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
quest.Value.rewards.Fail.Add(originalFailReward);
|
|
}
|
|
}
|
|
|
|
private static void CopyExistingRandomIds(Quest originalQuest, Quest quest)
|
|
{
|
|
CopyRewardRandomIds(originalQuest.rewards.Started, quest.rewards.Started);
|
|
CopyRewardRandomIds(originalQuest.rewards.Success, quest.rewards.Success);
|
|
CopyRewardRandomIds(originalQuest.rewards.Fail, quest.rewards.Fail);
|
|
|
|
CopyConditionRandomIds(originalQuest.conditions.AvailableForStart, quest.conditions.AvailableForStart);
|
|
}
|
|
|
|
private static void CopyRewardRandomIds(List<RewardStatus> originalRewards, List<RewardStatus> rewards)
|
|
{
|
|
foreach (var reward in rewards)
|
|
{
|
|
var originalReward = originalRewards.FirstOrDefault(x => x.id == reward.id);
|
|
if (originalReward == null)
|
|
{
|
|
LoggingHelpers.LogWarning($"Unable to find matching original reward for {reward.id}. Skipping.");
|
|
continue;
|
|
}
|
|
|
|
reward.target = originalReward.target;
|
|
|
|
if (reward.items != null)
|
|
{
|
|
foreach (var item in reward.items)
|
|
{
|
|
QuestRewardItem originalItem = originalReward.items.FirstOrDefault(x => x._tpl == item._tpl && x.slotId == item.slotId);
|
|
if (originalItem == null)
|
|
{
|
|
LoggingHelpers.LogWarning($"Unable to find matching original reward item for {reward.id}-{item._tpl}. Skipping");
|
|
continue;
|
|
}
|
|
|
|
item._id = originalItem._id;
|
|
item.parentId = originalItem.parentId;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Allow stripping all whitespace in a string, used for comparing _props.target, which may have differing whitespace but still match
|
|
private static readonly Regex whitespace = new Regex(@"\s+");
|
|
private static string StripAllWhitespace(string input)
|
|
{
|
|
if (input == null)
|
|
{
|
|
return "";
|
|
}
|
|
|
|
return whitespace.Replace(input, "");
|
|
}
|
|
|
|
private static void CopyConditionRandomIds(List<AvailableFor> originalConditions, List<AvailableFor> conditions)
|
|
{
|
|
foreach (var condition in conditions)
|
|
{
|
|
var originalCondition = originalConditions.FirstOrDefault(
|
|
x => x._parent == condition._parent &&
|
|
x._props.index == condition._props.index &&
|
|
StripAllWhitespace(x._props.target?.ToString()) == StripAllWhitespace(condition._props.target?.ToString()) &&
|
|
x._props.counter?.id == condition._props.counter?.id
|
|
);
|
|
|
|
if (originalCondition == null)
|
|
{
|
|
LoggingHelpers.LogWarning($"Unable to find matching original condition for {condition._parent}-{StripAllWhitespace(condition._props.target?.ToString())}. Skipping.");
|
|
continue;
|
|
}
|
|
|
|
condition._props.id = originalCondition._props.id;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Check original quest for start conditions and if missing from new quest, add them
|
|
/// </summary>
|
|
/// <param name="originalQuest"></param>
|
|
/// <param name="questToUpdate">quest to add to output json</param>
|
|
private static void AddMissingAvailableForStartConditions(Quest originalQuest, KeyValuePair<string, Quest> questToUpdate)
|
|
{
|
|
// Iterate over quest requirements in existing quest file
|
|
foreach (var questRequirementToAdd in originalQuest.conditions.AvailableForStart.ToList())
|
|
{
|
|
//Exists already, skip
|
|
//if (questToUpdate.Value.conditions.AvailableForStart.Any(
|
|
// x => x._parent == questRequirementToAdd._parent
|
|
// && x._props.target?.ToString() == questRequirementToAdd._props.target?.ToString())
|
|
// )
|
|
//{
|
|
// continue;
|
|
//}
|
|
|
|
if (questToUpdate.Value.conditions.AvailableForStart.Any(x => string.Equals(x._parent, "quest", StringComparison.CurrentCultureIgnoreCase)))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (questRequirementToAdd._parent == "Quest")
|
|
{
|
|
LoggingHelpers.LogInfo($"Quest {questToUpdate.Value.QuestName} missing AvailableForStart quest requirement, adding prereq of {questRequirementToAdd._props.target} {QuestHelper.GetQuestNameById(questRequirementToAdd._props.target?.ToString())}");
|
|
|
|
if (!questRequirementToAdd._props.availableAfter.HasValue)
|
|
{
|
|
questRequirementToAdd._props.availableAfter = 0;
|
|
}
|
|
|
|
if (questRequirementToAdd._props.visibilityConditions == null || !questRequirementToAdd._props.visibilityConditions.Any())
|
|
{
|
|
questRequirementToAdd._props.visibilityConditions = new List<object>();
|
|
}
|
|
|
|
questRequirementToAdd._props.index = GetNextIndex(questToUpdate.Value.conditions.AvailableForStart.LastOrDefault()?._props?.index);
|
|
|
|
}
|
|
|
|
// Already exists, skip
|
|
if (questToUpdate.Value.conditions.AvailableForStart
|
|
.Any(x => x._props.target?.ToString() == questRequirementToAdd._props.target?.ToString()
|
|
&& x._parent == questRequirementToAdd._parent))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
questToUpdate.Value.conditions.AvailableForStart.Add(questRequirementToAdd);
|
|
}
|
|
|
|
if (questToUpdate.Value.conditions.AvailableForStart.Count(x => x._parent == "Quest") > 1)
|
|
{
|
|
LoggingHelpers.LogWarning($"Quest {questToUpdate.Value.QuestName} has {questToUpdate.Value.conditions.AvailableForStart.Count(x => x._parent == "Quest")} quest prereqs, is this correct?");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get a bsg happy guid, must be 24 chars long
|
|
/// </summary>
|
|
/// <param name="randomSalt"></param>
|
|
/// <returns></returns>
|
|
static string Sha256(string randomSalt)
|
|
{
|
|
var crypt = new System.Security.Cryptography.SHA256Managed();
|
|
var hash = new System.Text.StringBuilder();
|
|
byte[] crypto = crypt.ComputeHash(Encoding.UTF8.GetBytes(randomSalt));
|
|
foreach (byte theByte in crypto)
|
|
{
|
|
hash.Append(theByte.ToString("x2"));
|
|
}
|
|
return hash.ToString().Substring(0, 24);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Look up the quests name by guid and add human readable string to quest object
|
|
/// </summary>
|
|
/// <param name="quest"></param>
|
|
private static void AddQuestName(KeyValuePair<string, Quest> quest)
|
|
{
|
|
var questName = QuestHelper.GetQuestNameById(quest.Value._id); // special characters like ", brake the client when it parses it, gotta remove
|
|
var rgx = new Regex("[^a-zA-Z0-9 -]");
|
|
quest.Value.QuestName = rgx.Replace(questName, "");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Loop over live quests and use if it exists, otherwise use existing data
|
|
/// </summary>
|
|
private static List<Quest> GetMissingQuestsNotInLiveFile(Dictionary<string, Quest> existingQuests, QuestRoot liveQuestData, List<string> blacklistedQuests)
|
|
{
|
|
var missingQuestsToReturn = new List<Quest>();
|
|
foreach (var quest in existingQuests.Values)
|
|
{
|
|
var liveQuest = liveQuestData.data.Find(x => x._id == quest._id);
|
|
if (liveQuest is null)
|
|
{
|
|
if (blacklistedQuests.Contains(quest._id))
|
|
{
|
|
LoggingHelpers.LogInfo($"Skipping quest: {quest.QuestName}");
|
|
continue;
|
|
}
|
|
|
|
missingQuestsToReturn.Add(quest);
|
|
LoggingHelpers.LogError($"ERROR Quest {quest._id} {QuestHelper.GetQuestNameById(quest._id)} missing in live file. Will use fallback quests.json");
|
|
}
|
|
else
|
|
{
|
|
LoggingHelpers.LogSuccess($"SUCCESS Quest {quest._id} {QuestHelper.GetQuestNameById(quest._id)} found in live file.");
|
|
}
|
|
}
|
|
|
|
return missingQuestsToReturn;
|
|
}
|
|
|
|
private static int GetNextIndex(int? previousIndex)
|
|
{
|
|
if (previousIndex == null)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
return previousIndex.Value + 1;
|
|
}
|
|
}
|
|
}
|