namespace Aeshnidae.QuestBonus;
public static class QuestBonusExtensions
{
///
/// Where a character's accumulated quest points are stored.
///
/// This is a "fake" PropertyFloat: ACE's own PropertyFloat enum currently stops
/// at 171, so 20002 cannot collide, and the shard DB persists unknown property
/// ids just fine. 20002 is deliberately the same id Aquafir's ACE.Shared uses
/// for FakeFloat.QuestBonus, so the data stays interchangeable with his mods.
///
/// Changing this orphans every character's stored total (they resync on next
/// login, but the old rows linger), so treat it as fixed.
///
public const PropertyFloat QuestBonusProperty = (PropertyFloat)20002;
/// Replaces ACE.Shared's QuestExtensions.HasSolves.
public static bool HasSolves(this CharacterPropertiesQuestRegistry quest) => quest.NumTimesCompleted != 0;
///
/// Points a quest is worth: an exact entry in QuestWeights, else the first matching
/// QuestWeightPatterns entry, else Settings.DefaultPoints. The same answer serves
/// the live increments and the login resync, so the two cannot disagree.
///
public static double WeightOf(string questFormat)
{
var name = QuestManager.GetQuestName(questFormat);
if (Mod.Settings.QuestWeights.TryGetValue(name, out var weight))
return weight;
return Mod.Settings.PatternWeightOf(name) ?? Mod.Settings.DefaultPoints;
}
public static double GetQuestPoints(this Player player) =>
player.GetProperty(QuestBonusProperty) ?? 0;
public static void SetQuestPoints(this Player player, double points) =>
player.SetProperty(QuestBonusProperty, Math.Max(0, points));
public static void AddQuestPoints(this Player player, double delta) =>
player.SetQuestPoints(player.GetQuestPoints() + delta);
/// Sums the weights of every quest this character has actually solved.
public static double CalculateQuestPoints(this Player player)
{
double total = 0;
foreach (var quest in player.QuestManager.GetQuests())
{
if (quest.HasSolves())
total += WeightOf(quest.QuestName);
}
return total;
}
///
/// Recomputes from scratch and stores the result. This is the authoritative
/// path; the incremental Add/Subtract patches are just to keep it live.
///
public static void ResyncQuestPoints(this Player player) =>
player.SetQuestPoints(player.CalculateQuestPoints());
///
/// The XP multiplier: 1 + points * BonusConversion, capped by MaxMultiplier.
/// Always >= 1.
///
public static double QuestBonusMultiplier(this Player player)
{
var multiplier = 1 + player.GetQuestPoints() * Mod.Settings.BonusConversion;
if (Mod.Settings.MaxMultiplier > 0)
multiplier = Math.Min(multiplier, Mod.Settings.MaxMultiplier);
return Math.Max(1, multiplier);
}
/// "+4.20%" - the bonus on its own, which reads better than a raw multiplier.
public static string QuestBonusText(this Player player) =>
$"+{(player.QuestBonusMultiplier() - 1) * 100:0.##}%";
}