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 AllQuests { get; init; } = new List(); } 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 _logger; private readonly Dictionary> _questsByAlliedSociety = new Dictionary>(); private readonly Dictionary<(uint NpcDataId, byte Seed, bool OutranksAll, bool RankedUp), List> _dailyQuests = new Dictionary<(uint, byte, bool, bool), List>(); public AlliedSocietyQuestFunctions(QuestData questData, ILogger logger) { _logger = logger; foreach (EAlliedSociety item in from x in Enum.GetValues() where x != EAlliedSociety.None select x) { foreach (KeyValuePair> item2 in (from x in questData.GetAllByAlliedSociety(item) where x.IsRepeatable group x by x.IssuerDataId).ToDictionary((IGrouping x) => x.Key, (IGrouping 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 allQuests = value; NpcData npcData = new NpcData { IssuerDataId = issuerDataId, AllQuests = allQuests }; if (_questsByAlliedSociety.TryGetValue(item, out List value2)) { value2.Add(npcData); continue; } Dictionary> questsByAlliedSociety = _questsByAlliedSociety; int num = 1; List list = new List(num); CollectionsMarshal.SetCount(list, num); Span span = CollectionsMarshal.AsSpan(list); int index = 0; span[index] = npcData; questsByAlliedSociety[item] = list; } } } public unsafe List GetAvailableAlliedSocietyQuests(EAlliedSociety alliedSociety) { byte rank = QuestManager.Instance()->BeastReputation[(int)(alliedSociety - 1)].Rank; byte currentRank = (byte)(rank & 0x7F); if (currentRank == 0) { return new List(); } bool flag = (rank & 0x80) != 0; byte dailyQuestSeed = QuestManager.Instance()->DailyQuestSeed; List list = new List(); 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 value)) { list.AddRange(value); continue; } List 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 CalculateAvailableQuests(List allQuests, byte seed, bool outranksAll, byte currentRank, bool rankedUp) { List list = allQuests.Where((QuestInfo q) => IsEligible(q, currentRank, rankedUp)).ToList(); List list2 = new List(); if (list.Count == 0) { return new List(); } 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; } }