forked from aly/qstbak
270 lines
8.8 KiB
C#
270 lines
8.8 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Collections.Immutable;
|
|
using System.Linq;
|
|
using System.Runtime.InteropServices;
|
|
using Dalamud.Plugin.Services;
|
|
using FFXIVClientStructs.FFXIV.Application.Network.WorkDefinitions;
|
|
using FFXIVClientStructs.FFXIV.Client.Game;
|
|
using Lumina.Excel.Sheets;
|
|
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 QuestData _questData;
|
|
|
|
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>>();
|
|
|
|
private readonly ImmutableDictionary<uint, ushort> _reputationRequiredByRank;
|
|
|
|
private readonly ImmutableDictionary<uint, ushort> _questReputationRewards;
|
|
|
|
private readonly ImmutableDictionary<EAlliedSociety, byte> _maxRankByAlliedSociety;
|
|
|
|
private readonly ImmutableDictionary<(EAlliedSociety, byte), QuestId> _rankUpQuests;
|
|
|
|
public AlliedSocietyQuestFunctions(QuestData questData, IDataManager dataManager, ILogger<AlliedSocietyQuestFunctions> logger)
|
|
{
|
|
_logger = logger;
|
|
_questData = questData;
|
|
_reputationRequiredByRank = (from x in dataManager.GetExcelSheet<BeastReputationRank>()
|
|
where x.RowId != 0
|
|
select x).ToImmutableDictionary((BeastReputationRank x) => x.RowId, (BeastReputationRank x) => x.RequiredReputation);
|
|
_questReputationRewards = (from x in dataManager.GetExcelSheet<Lumina.Excel.Sheets.Quest>()
|
|
where x.RowId != 0 && x.BeastTribe.RowId != 0 && x.BeastReputationValue > 0
|
|
select x).ToImmutableDictionary((Lumina.Excel.Sheets.Quest x) => x.RowId, (Lumina.Excel.Sheets.Quest x) => x.BeastReputationValue);
|
|
_maxRankByAlliedSociety = (from x in dataManager.GetExcelSheet<BeastTribe>()
|
|
where x.RowId != 0 && !x.Name.IsEmpty && Enum.IsDefined(typeof(EAlliedSociety), (byte)x.RowId)
|
|
select x).ToImmutableDictionary((BeastTribe x) => (EAlliedSociety)x.RowId, (BeastTribe x) => x.MaxRank);
|
|
Dictionary<(EAlliedSociety, byte), QuestId> dictionary = new Dictionary<(EAlliedSociety, byte), QuestId>();
|
|
foreach (EAlliedSociety item in from x in Enum.GetValues<EAlliedSociety>()
|
|
where x != EAlliedSociety.None
|
|
select x)
|
|
{
|
|
foreach (QuestInfo item2 in from x in questData.GetAllByAlliedSociety(item)
|
|
where !x.IsRepeatable
|
|
select x)
|
|
{
|
|
byte b = (byte)item2.AlliedSocietyRank;
|
|
if (b > 0)
|
|
{
|
|
dictionary[(item, b)] = (QuestId)item2.QuestId;
|
|
}
|
|
}
|
|
}
|
|
_rankUpQuests = dictionary.ToImmutableDictionary();
|
|
foreach (EAlliedSociety item3 in from x in Enum.GetValues<EAlliedSociety>()
|
|
where x != EAlliedSociety.None
|
|
select x)
|
|
{
|
|
foreach (KeyValuePair<uint, List<QuestInfo>> item4 in (from x in questData.GetAllByAlliedSociety(item3)
|
|
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()))
|
|
{
|
|
item4.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(item3, 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);
|
|
CollectionsMarshal.AsSpan(list)[0] = npcData;
|
|
questsByAlliedSociety[item3] = list;
|
|
}
|
|
}
|
|
}
|
|
|
|
public unsafe List<QuestId> GetAvailableAlliedSocietyQuests(EAlliedSociety alliedSociety)
|
|
{
|
|
if (alliedSociety == EAlliedSociety.None)
|
|
{
|
|
return new List<QuestId>();
|
|
}
|
|
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>();
|
|
if (!_questsByAlliedSociety.TryGetValue(alliedSociety, out List<NpcData> value))
|
|
{
|
|
return list;
|
|
}
|
|
foreach (NpcData item in value)
|
|
{
|
|
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> value2))
|
|
{
|
|
list.AddRange(value2);
|
|
continue;
|
|
}
|
|
List<QuestId> list2 = CalculateAvailableQuests(item.AllQuests, dailyQuestSeed, flag2, currentRank, flag);
|
|
_logger.LogDebug("Available for {Tribe} (Seed: {Seed}, Issuer: {IssuerId}): {Quests}", alliedSociety, dailyQuestSeed, item.IssuerDataId, string.Join(", ", list2));
|
|
_dailyQuests[key] = list2;
|
|
list.AddRange(list2);
|
|
}
|
|
return list;
|
|
}
|
|
|
|
public unsafe (byte CurrentRank, ushort CurrentValue, ushort MaxValue, bool IsMaxRank, bool IsAtCapacity)? GetReputationStatus(EAlliedSociety alliedSociety)
|
|
{
|
|
BeastReputationWork beastReputationWork = QuestManager.Instance()->BeastReputation[(int)(alliedSociety - 1)];
|
|
byte b = (byte)(beastReputationWork.Rank & 0x7F);
|
|
if (b == 0)
|
|
{
|
|
return null;
|
|
}
|
|
ushort value = beastReputationWork.Value;
|
|
ushort valueOrDefault = _reputationRequiredByRank.GetValueOrDefault(b, ushort.MaxValue);
|
|
byte valueOrDefault2 = _maxRankByAlliedSociety.GetValueOrDefault<EAlliedSociety, byte>(alliedSociety, 8);
|
|
bool item = b >= valueOrDefault2;
|
|
bool item2 = value >= valueOrDefault;
|
|
return (b, value, valueOrDefault, item, item2);
|
|
}
|
|
|
|
public QuestId? GetRankUpQuest(EAlliedSociety alliedSociety, byte currentRank)
|
|
{
|
|
return _rankUpQuests.GetValueOrDefault((alliedSociety, currentRank));
|
|
}
|
|
|
|
public ushort GetQuestReputationReward(QuestId questId)
|
|
{
|
|
return _questReputationRewards.GetValueOrDefault<uint, ushort>(questId.Value, 0);
|
|
}
|
|
|
|
public bool WouldOvercapReputation(QuestId questId)
|
|
{
|
|
if (!_questData.TryGetQuestInfo(questId, out IQuestInfo questInfo) || !(questInfo is QuestInfo questInfo2))
|
|
{
|
|
return false;
|
|
}
|
|
if (questInfo2.AlliedSociety == EAlliedSociety.None)
|
|
{
|
|
return false;
|
|
}
|
|
(byte, ushort, ushort, bool, bool)? reputationStatus = GetReputationStatus(questInfo2.AlliedSociety);
|
|
if (!reputationStatus.HasValue)
|
|
{
|
|
return false;
|
|
}
|
|
(byte, ushort, ushort, bool, bool) value = reputationStatus.Value;
|
|
ushort item = value.Item2;
|
|
ushort item2 = value.Item3;
|
|
if (value.Item4)
|
|
{
|
|
return false;
|
|
}
|
|
ushort questReputationReward = GetQuestReputationReward(questId);
|
|
return item + questReputationReward > item2;
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|