forked from liza/Questionable
Show available daily quests in journal
This commit is contained in:
parent
ba0cd60dae
commit
70c47e20fb
@ -235,6 +235,16 @@ internal sealed class QuestData
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public List<QuestInfo> GetAllByAlliedSociety(EAlliedSociety alliedSociety)
|
||||
{
|
||||
return _quests.Values
|
||||
.Where(x => x is QuestInfo)
|
||||
.Cast<QuestInfo>()
|
||||
.Where(x => x.AlliedSociety == alliedSociety)
|
||||
.OrderBy(x => x.QuestId)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public List<QuestInfo> GetClassJobQuests(EClassJob classJob)
|
||||
{
|
||||
List<uint> chapterIds = classJob switch
|
||||
|
133
Questionable/Functions/AlliedSocietyQuestFunctions.cs
Normal file
133
Questionable/Functions/AlliedSocietyQuestFunctions.cs
Normal file
@ -0,0 +1,133 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Questionable.Data;
|
||||
using Questionable.Model;
|
||||
using Questionable.Model.Questing;
|
||||
|
||||
namespace Questionable.Functions;
|
||||
|
||||
internal sealed class AlliedSocietyQuestFunctions
|
||||
{
|
||||
private readonly ILogger<AlliedSocietyQuestFunctions> _logger;
|
||||
private readonly Dictionary<EAlliedSociety, List<NpcData>> _questsByAlliedSociety = [];
|
||||
private readonly Dictionary<(uint NpcDataId, byte Seed, bool OutranksAll), List<QuestId>> _dailyQuests = [];
|
||||
|
||||
public AlliedSocietyQuestFunctions(QuestData questData, ILogger<AlliedSocietyQuestFunctions> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
foreach (var alliedSociety in Enum.GetValues<EAlliedSociety>().Where(x => x != EAlliedSociety.None))
|
||||
{
|
||||
var allQuests = questData.GetAllByAlliedSociety(alliedSociety);
|
||||
var questsByIssuer = allQuests
|
||||
.Where(x => x.IsRepeatable)
|
||||
.GroupBy(x => x.IssuerDataId)
|
||||
.ToDictionary(x => x.Key,
|
||||
x => x.OrderBy(y => y.AlliedSocietyQuestGroup == 3).ThenBy(y => y.QuestId).ToList());
|
||||
foreach ((uint issuerDataId, List<QuestInfo> quests) in questsByIssuer)
|
||||
{
|
||||
var npcData = new NpcData { IssuerDataId = issuerDataId, AllQuests = quests };
|
||||
if (_questsByAlliedSociety.TryGetValue(alliedSociety, out List<NpcData>? existingNpcs))
|
||||
existingNpcs.Add(npcData);
|
||||
else
|
||||
_questsByAlliedSociety[alliedSociety] = [npcData];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe List<QuestId> GetAvailableAlliedSocietyQuests(EAlliedSociety alliedSociety)
|
||||
{
|
||||
byte rankData = QuestManager.Instance()->BeastReputation[(int)alliedSociety - 1].Rank;
|
||||
byte currentRank = (byte)(rankData & 0x7F);
|
||||
if (currentRank == 0)
|
||||
return [];
|
||||
|
||||
bool rankedUp = (rankData & 0x80) != 0;
|
||||
byte seed = 183;
|
||||
List<QuestId> result = [];
|
||||
foreach (NpcData npcData in _questsByAlliedSociety[alliedSociety])
|
||||
{
|
||||
bool outranksAll = npcData.AllQuests.All(x => currentRank > x.AlliedSocietyRank);
|
||||
var key = (NpcDataId: npcData.IssuerDataId, seed, outranksAll);
|
||||
if (_dailyQuests.TryGetValue(key, out List<QuestId>? questIds))
|
||||
result.AddRange(questIds);
|
||||
else
|
||||
{
|
||||
var quests = CalculateAvailableQuests(npcData.AllQuests, seed, outranksAll, currentRank, rankedUp);
|
||||
_logger.LogInformation("Available for {Tribe} (Issuer: {IssuerId}: {Quests}", alliedSociety, npcData.IssuerDataId, string.Join(", ", quests));
|
||||
|
||||
_dailyQuests[key] = quests;
|
||||
result.AddRange(quests);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static List<QuestId> CalculateAvailableQuests(List<QuestInfo> allQuests, byte seed, bool outranksAll,
|
||||
byte currentRank, bool rankedUp)
|
||||
{
|
||||
List<QuestInfo> eligible = [.. allQuests.Where(q => IsEligible(q, currentRank, rankedUp))];
|
||||
List<QuestInfo> available = [];
|
||||
if (eligible.Count == 0)
|
||||
return [];
|
||||
|
||||
var rng = new Rng(seed);
|
||||
if (outranksAll)
|
||||
{
|
||||
for (int i = 0, cnt = Math.Min(eligible.Count, 3); i < cnt; ++i)
|
||||
{
|
||||
var index = rng.Next(eligible.Count);
|
||||
while (available.Contains(eligible[index]))
|
||||
index = (index + 1) % eligible.Count;
|
||||
available.Add(eligible[index]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var firstExclusive = eligible.FindIndex(q => q.AlliedSocietyQuestGroup == 3);
|
||||
if (firstExclusive >= 0)
|
||||
available.Add(eligible[firstExclusive + rng.Next(eligible.Count - firstExclusive)]);
|
||||
else
|
||||
firstExclusive = eligible.Count;
|
||||
for (int i = available.Count, cnt = Math.Min(firstExclusive, 3); i < cnt; ++i)
|
||||
{
|
||||
var index = rng.Next(firstExclusive);
|
||||
while (available.Contains(eligible[index]))
|
||||
index = (index + 1) % firstExclusive;
|
||||
available.Add(eligible[index]);
|
||||
}
|
||||
}
|
||||
|
||||
return available.Select(x => (QuestId)x.QuestId).ToList();
|
||||
}
|
||||
|
||||
private static bool IsEligible(QuestInfo questInfo, byte currentRank, bool rankedUp)
|
||||
{
|
||||
return rankedUp ? questInfo.AlliedSocietyRank == currentRank : questInfo.AlliedSocietyRank <= currentRank;
|
||||
}
|
||||
|
||||
private sealed class NpcData
|
||||
{
|
||||
public required uint IssuerDataId { get; init; }
|
||||
public required List<QuestInfo> AllQuests { get; init; } = [];
|
||||
}
|
||||
|
||||
private record struct Rng(uint S0, uint S1 = 0, uint S2 = 0, uint S3 = 0)
|
||||
{
|
||||
public int Next(int range)
|
||||
{
|
||||
(S0, S1, S2, S3) = (S3, Transform(S0, S1), S1, S2);
|
||||
return (int)(S1 % range);
|
||||
}
|
||||
|
||||
// returns new value for s1
|
||||
private static uint Transform(uint s0, uint s1)
|
||||
{
|
||||
var temp = s0 ^ (s0 << 11);
|
||||
return s1 ^ temp ^ ((temp ^ (s1 >> 11)) >> 8);
|
||||
}
|
||||
}
|
||||
}
|
@ -28,6 +28,7 @@ internal sealed unsafe class QuestFunctions
|
||||
private readonly QuestRegistry _questRegistry;
|
||||
private readonly QuestData _questData;
|
||||
private readonly AetheryteFunctions _aetheryteFunctions;
|
||||
private readonly AlliedSocietyQuestFunctions _alliedSocietyQuestFunctions;
|
||||
private readonly AlliedSocietyData _alliedSocietyData;
|
||||
private readonly Configuration _configuration;
|
||||
private readonly IDataManager _dataManager;
|
||||
@ -38,6 +39,7 @@ internal sealed unsafe class QuestFunctions
|
||||
QuestRegistry questRegistry,
|
||||
QuestData questData,
|
||||
AetheryteFunctions aetheryteFunctions,
|
||||
AlliedSocietyQuestFunctions alliedSocietyQuestFunctions,
|
||||
AlliedSocietyData alliedSocietyData,
|
||||
Configuration configuration,
|
||||
IDataManager dataManager,
|
||||
@ -47,6 +49,7 @@ internal sealed unsafe class QuestFunctions
|
||||
_questRegistry = questRegistry;
|
||||
_questData = questData;
|
||||
_aetheryteFunctions = aetheryteFunctions;
|
||||
_alliedSocietyQuestFunctions = alliedSocietyQuestFunctions;
|
||||
_alliedSocietyData = alliedSocietyData;
|
||||
_configuration = configuration;
|
||||
_dataManager = dataManager;
|
||||
@ -447,8 +450,11 @@ internal sealed unsafe class QuestFunctions
|
||||
if (IsQuestAccepted(questId))
|
||||
return false;
|
||||
|
||||
if (QuestManager.Instance()->IsDailyQuestCompleted(questId.Value))
|
||||
return false;
|
||||
if (quest.Info.AlliedSociety != EAlliedSociety.None)
|
||||
{
|
||||
if (QuestManager.Instance()->IsDailyQuestCompleted(questId.Value))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -546,6 +552,9 @@ internal sealed unsafe class QuestFunctions
|
||||
if (questInfo.GrandCompany != GrandCompany.None && questInfo.GrandCompany != GetGrandCompany())
|
||||
return true;
|
||||
|
||||
if (questInfo.AlliedSociety != EAlliedSociety.None)
|
||||
return !IsDailyAlliedSocietyQuestAndAvailableToday(questId);
|
||||
|
||||
return !HasCompletedPreviousQuests(questInfo, extraCompletedQuest) || !HasCompletedPreviousInstances(questInfo);
|
||||
}
|
||||
|
||||
@ -569,6 +578,21 @@ internal sealed unsafe class QuestFunctions
|
||||
return !HasCompletedPreviousQuests(questInfo, null);
|
||||
}
|
||||
|
||||
public bool IsDailyAlliedSocietyQuest(QuestId questId)
|
||||
{
|
||||
var questInfo = (QuestInfo)_questData.GetQuestInfo(questId);
|
||||
return questInfo.AlliedSociety != EAlliedSociety.None && questInfo.IsRepeatable;
|
||||
}
|
||||
|
||||
public bool IsDailyAlliedSocietyQuestAndAvailableToday(QuestId questId)
|
||||
{
|
||||
if (!IsDailyAlliedSocietyQuest(questId))
|
||||
return false;
|
||||
|
||||
var questInfo = (QuestInfo)_questData.GetQuestInfo(questId);
|
||||
return _alliedSocietyQuestFunctions.GetAvailableAlliedSocietyQuests(questInfo.AlliedSociety).Contains(questId);
|
||||
}
|
||||
|
||||
public bool IsQuestUnobtainable(ElementId elementId, ElementId? extraCompletedQuest = null)
|
||||
{
|
||||
if (elementId is QuestId questId)
|
||||
|
@ -60,6 +60,8 @@ internal sealed class QuestInfo : IQuestInfo
|
||||
PreviousInstanceContentJoin = (EQuestJoin)quest.InstanceContentJoin;
|
||||
GrandCompany = (GrandCompany)quest.GrandCompany.RowId;
|
||||
AlliedSociety = (EAlliedSociety)quest.BeastTribe.RowId;
|
||||
AlliedSocietyQuestGroup = quest.Unknown11;
|
||||
AlliedSocietyRank = (int)quest.BeastReputationRank.RowId;
|
||||
ClassJobs = QuestInfoUtils.AsList(quest.ClassJobCategory0.ValueNullable!);
|
||||
IsSeasonalEvent = quest.Festival.RowId != 0;
|
||||
NewGamePlusChapter = newGamePlusChapter;
|
||||
@ -85,6 +87,8 @@ internal sealed class QuestInfo : IQuestInfo
|
||||
public bool CompletesInstantly { get; }
|
||||
public GrandCompany GrandCompany { get; }
|
||||
public EAlliedSociety AlliedSociety { get; }
|
||||
public byte AlliedSocietyQuestGroup { get; }
|
||||
public int AlliedSocietyRank { get; }
|
||||
public IReadOnlyList<EClassJob> ClassJobs { get; }
|
||||
public bool IsSeasonalEvent { get; }
|
||||
public uint NewGamePlusChapter { get; }
|
||||
|
@ -111,6 +111,7 @@ public sealed class QuestionablePlugin : IDalamudPlugin
|
||||
serviceCollection.AddSingleton<GameFunctions>();
|
||||
serviceCollection.AddSingleton<ChatFunctions>();
|
||||
serviceCollection.AddSingleton<QuestFunctions>();
|
||||
serviceCollection.AddSingleton<AlliedSocietyQuestFunctions>();
|
||||
serviceCollection.AddSingleton<DalamudReflector>();
|
||||
|
||||
serviceCollection.AddSingleton<AetherCurrentData>();
|
||||
|
@ -24,6 +24,15 @@ internal sealed class UiUtils
|
||||
{
|
||||
if (_questFunctions.IsQuestAccepted(elementId))
|
||||
return (ImGuiColors.DalamudYellow, FontAwesomeIcon.PersonWalkingArrowRight, "Active");
|
||||
else if (elementId is QuestId questId && _questFunctions.IsDailyAlliedSocietyQuestAndAvailableToday(questId))
|
||||
{
|
||||
if (!_questFunctions.IsReadyToAcceptQuest(questId))
|
||||
return (ImGuiColors.ParsedGreen, FontAwesomeIcon.Check, "Complete");
|
||||
else if (_questFunctions.IsQuestComplete(questId))
|
||||
return (ImGuiColors.ParsedBlue, FontAwesomeIcon.Running, "Available (Complete)");
|
||||
else
|
||||
return (ImGuiColors.DalamudYellow, FontAwesomeIcon.Running, "Available");
|
||||
}
|
||||
else if (_questFunctions.IsQuestAcceptedOrComplete(elementId))
|
||||
return (ImGuiColors.ParsedGreen, FontAwesomeIcon.Check, "Complete");
|
||||
else if (_questFunctions.IsQuestUnobtainable(elementId))
|
||||
|
Loading…
Reference in New Issue
Block a user