171 lines
5.1 KiB
C#
171 lines
5.1 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Runtime.InteropServices;
|
|
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 sealed class NpcData
|
|
{
|
|
public required uint IssuerDataId { get; init; }
|
|
|
|
public required List<QuestInfo> AllQuests { get; init; } = new List<QuestInfo>();
|
|
}
|
|
|
|
private record struct Rng(uint S0, uint S1 = 0u, uint S2 = 0u, uint S3 = 0u)
|
|
{
|
|
public int Next(int range)
|
|
{
|
|
uint s = S3;
|
|
uint s2 = Transform(S0, S1);
|
|
uint s3 = S1;
|
|
uint s4 = S2;
|
|
S0 = s;
|
|
S1 = s2;
|
|
S2 = s3;
|
|
S3 = s4;
|
|
return (int)(S1 % range);
|
|
}
|
|
|
|
private static uint Transform(uint s0, uint s1)
|
|
{
|
|
uint num = s0 ^ (s0 << 11);
|
|
return s1 ^ num ^ ((num ^ (s1 >> 11)) >> 8);
|
|
}
|
|
}
|
|
|
|
private readonly ILogger<AlliedSocietyQuestFunctions> _logger;
|
|
|
|
private readonly Dictionary<EAlliedSociety, List<NpcData>> _questsByAlliedSociety = new Dictionary<EAlliedSociety, List<NpcData>>();
|
|
|
|
private readonly Dictionary<(uint NpcDataId, byte Seed, bool OutranksAll, bool RankedUp), List<QuestId>> _dailyQuests = new Dictionary<(uint, byte, bool, bool), List<QuestId>>();
|
|
|
|
public AlliedSocietyQuestFunctions(QuestData questData, ILogger<AlliedSocietyQuestFunctions> logger)
|
|
{
|
|
_logger = logger;
|
|
foreach (EAlliedSociety item in from x in Enum.GetValues<EAlliedSociety>()
|
|
where x != EAlliedSociety.None
|
|
select x)
|
|
{
|
|
foreach (KeyValuePair<uint, List<QuestInfo>> item2 in (from x in questData.GetAllByAlliedSociety(item)
|
|
where x.IsRepeatable
|
|
group x by x.IssuerDataId).ToDictionary((IGrouping<uint, QuestInfo> x) => x.Key, (IGrouping<uint, QuestInfo> x) => (from y in x
|
|
orderby y.AlliedSocietyQuestGroup == 3, y.QuestId
|
|
select y).ToList()))
|
|
{
|
|
item2.Deconstruct(out var key, out var value);
|
|
uint issuerDataId = key;
|
|
List<QuestInfo> allQuests = value;
|
|
NpcData npcData = new NpcData
|
|
{
|
|
IssuerDataId = issuerDataId,
|
|
AllQuests = allQuests
|
|
};
|
|
if (_questsByAlliedSociety.TryGetValue(item, out List<NpcData> value2))
|
|
{
|
|
value2.Add(npcData);
|
|
continue;
|
|
}
|
|
Dictionary<EAlliedSociety, List<NpcData>> questsByAlliedSociety = _questsByAlliedSociety;
|
|
int num = 1;
|
|
List<NpcData> list = new List<NpcData>(num);
|
|
CollectionsMarshal.SetCount(list, num);
|
|
Span<NpcData> span = CollectionsMarshal.AsSpan(list);
|
|
int index = 0;
|
|
span[index] = npcData;
|
|
questsByAlliedSociety[item] = list;
|
|
}
|
|
}
|
|
}
|
|
|
|
public unsafe List<QuestId> GetAvailableAlliedSocietyQuests(EAlliedSociety alliedSociety)
|
|
{
|
|
byte rank = QuestManager.Instance()->BeastReputation[(int)(alliedSociety - 1)].Rank;
|
|
byte currentRank = (byte)(rank & 0x7F);
|
|
if (currentRank == 0)
|
|
{
|
|
return new List<QuestId>();
|
|
}
|
|
bool flag = (rank & 0x80) != 0;
|
|
byte dailyQuestSeed = QuestManager.Instance()->DailyQuestSeed;
|
|
List<QuestId> list = new List<QuestId>();
|
|
foreach (NpcData item in _questsByAlliedSociety[alliedSociety])
|
|
{
|
|
bool flag2 = item.AllQuests.All((QuestInfo x) => currentRank > x.AlliedSocietyRank);
|
|
(uint, byte, bool, bool) key = (item.IssuerDataId, dailyQuestSeed, flag2, flag);
|
|
if (_dailyQuests.TryGetValue(key, out List<QuestId> value))
|
|
{
|
|
list.AddRange(value);
|
|
continue;
|
|
}
|
|
List<QuestId> list2 = CalculateAvailableQuests(item.AllQuests, dailyQuestSeed, flag2, currentRank, flag);
|
|
_logger.LogInformation("Available for {Tribe} (Seed: {Seed}, Issuer: {IssuerId}): {Quests}", alliedSociety, dailyQuestSeed, item.IssuerDataId, string.Join(", ", list2));
|
|
_dailyQuests[key] = list2;
|
|
list.AddRange(list2);
|
|
}
|
|
return list;
|
|
}
|
|
|
|
private static List<QuestId> CalculateAvailableQuests(List<QuestInfo> allQuests, byte seed, bool outranksAll, byte currentRank, bool rankedUp)
|
|
{
|
|
List<QuestInfo> list = allQuests.Where((QuestInfo q) => IsEligible(q, currentRank, rankedUp)).ToList();
|
|
List<QuestInfo> list2 = new List<QuestInfo>();
|
|
if (list.Count == 0)
|
|
{
|
|
return new List<QuestId>();
|
|
}
|
|
Rng rng = new Rng(seed);
|
|
if (outranksAll)
|
|
{
|
|
int num = 0;
|
|
for (int num2 = Math.Min(list.Count, 3); num < num2; num++)
|
|
{
|
|
int num3 = rng.Next(list.Count);
|
|
while (list2.Contains(list[num3]))
|
|
{
|
|
num3 = (num3 + 1) % list.Count;
|
|
}
|
|
list2.Add(list[num3]);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int num4 = list.FindIndex((QuestInfo q) => q.AlliedSocietyQuestGroup == 3);
|
|
if (num4 >= 0)
|
|
{
|
|
list2.Add(list[num4 + rng.Next(list.Count - num4)]);
|
|
}
|
|
else
|
|
{
|
|
num4 = list.Count;
|
|
}
|
|
int num5 = list2.Count;
|
|
for (int num6 = Math.Min(num4, 3); num5 < num6; num5++)
|
|
{
|
|
int num7 = rng.Next(num4);
|
|
while (list2.Contains(list[num7]))
|
|
{
|
|
num7 = (num7 + 1) % num4;
|
|
}
|
|
list2.Add(list[num7]);
|
|
}
|
|
}
|
|
return list2.Select((QuestInfo x) => (QuestId)x.QuestId).ToList();
|
|
}
|
|
|
|
private static bool IsEligible(QuestInfo questInfo, byte currentRank, bool rankedUp)
|
|
{
|
|
if (!rankedUp)
|
|
{
|
|
return questInfo.AlliedSocietyRank <= currentRank;
|
|
}
|
|
return questInfo.AlliedSocietyRank == currentRank;
|
|
}
|
|
}
|