using System.Collections.Generic;
using HarmonyLib;
using UnityEngine;
namespace SCore.Features.Quality.Harmony
{
///
/// Replaces vanilla's flat -1 quality-per-repair with a configurable loss.
///
/// Flat levels (takes priority over percentage). Whole-number levels, stored as integer:
/// Player cvar RepairQualityLossLevels (highest priority — set via buffs/progression)
///
///
/// Percentage-based. Whole-number percent, cvar stored as fraction (0.10 = 10%):
/// Player cvar RepairQualityLoss (highest priority — set via buffs/progression)
///
///
///
/// Both support per-item (items.xml) and global (AdvancedItemFeatures in blocks.xml) values.
/// Per-item takes priority over global. If nothing is configured, vanilla -1 flat reduction is used.
///
[HarmonyPatch(typeof(ItemActionEntryRepair))]
[HarmonyPatch(nameof(ItemActionEntryRepair.OnActivated))]
public class ItemActionEntryRepairQualityLoss
{
private static readonly string AdvFeatureClass = "AdvancedItemFeatures";
private static readonly string PercentProperty = "RepairQualityLoss";
private static readonly string FlatLevelsProperty = "RepairQualityLossLevels";
private const string MetaKey = "SCoreRepairOrigQuality";
// Queue tracks one pre-repair quality per queued repair, in FIFO order.
// Used as fallback when ItemValue.Clone() does not copy metadata.
private static readonly Queue _pendingOriginalQualities = new Queue();
///
/// Returns flat quality levels to lose per repair, or -1 if not configured.
/// Priority: player cvar → per-item XML → global XML.
///
private static int GetLossLevels(ItemValue itemValue)
{
// Player cvar (highest priority). Stored as a whole integer: 2 = lose 2 levels. 0 = unset.
var player = GameManager.Instance?.World?.GetPrimaryPlayer();
if (player != null && player.Buffs.HasCustomVar(FlatLevelsProperty))
{
int cvarVal = (int)player.Buffs.GetCustomVar(FlatLevelsProperty);
if (cvarVal != 0)
return Mathf.Max(0, cvarVal);
}
if (itemValue.ItemClass.Properties.Contains(FlatLevelsProperty))
{
var str = itemValue.ItemClass.Properties.GetString(FlatLevelsProperty);
if (int.TryParse(str, out int itemLevels))
return itemLevels;
}
var globalStr = Configuration.GetPropertyValue(AdvFeatureClass, FlatLevelsProperty);
if (!string.IsNullOrEmpty(globalStr) && int.TryParse(globalStr, out int globalLevels))
return globalLevels;
return -1;
}
///
/// Returns the loss percent (0–100) to apply, or -1 if no source is configured (vanilla fallback).
/// Priority: player cvar → per-item XML → global XML.
///
private static float GetLossPercent(ItemValue itemValue)
{
// Player cvar (highest priority). Stored as a fraction: 0.05 = 5%. 0 = unset.
var player = GameManager.Instance?.World?.GetPrimaryPlayer();
if (player != null && player.Buffs.HasCustomVar(PercentProperty))
{
float cvarVal = player.Buffs.GetCustomVar(PercentProperty);
if (cvarVal != 0f)
return Mathf.Max(0f, cvarVal * 100f);
}
if (itemValue.ItemClass.Properties.Contains(PercentProperty))
{
var str = itemValue.ItemClass.Properties.GetString(PercentProperty);
if (float.TryParse(str, out float itemLoss))
return itemLoss;
}
var globalStr = Configuration.GetPropertyValue(AdvFeatureClass, PercentProperty);
if (!string.IsNullOrEmpty(globalStr) && float.TryParse(globalStr, out float globalLoss))
return globalLoss;
return -1f;
}
private static bool HasAnyLossConfigured(ItemValue itemValue)
{
return GetLossLevels(itemValue) >= 0 || GetLossPercent(itemValue) >= 0f;
}
public static void Prefix(ItemActionEntryRepair __instance)
{
var itemValue = ItemClassUtils.GetItemValue(__instance.ItemController);
if (itemValue == null) return;
if (!HasAnyLossConfigured(itemValue)) return;
// Snapshot quality BEFORE vanilla's repair queue applies the -1 reduction.
int originalQuality = (int)itemValue.Quality;
_pendingOriginalQualities.Enqueue(originalQuality);
// Also write to metadata so the value travels with the cloned ItemValue that
// AddRepairItemToQueue stores internally (works if Clone copies metadata).
itemValue.SetMetadata(MetaKey, originalQuality, TypedMetadataValue.TypeTag.Integer);
// Allow multiple subscriptions — one per queued repair — so simultaneous repairs
// each get their own OnRepairItem call. The old "-= then +=" pattern was a bug:
// it ensured only one handler, so the second of two queued repairs never fired.
QuestEventManager.Current.RepairItem += OnRepairItem;
}
private static void OnRepairItem(ItemValue itemValue)
{
QuestEventManager.Current.RepairItem -= OnRepairItem;
// Resolve original quality: metadata is preferred (item-specific, survives Clone),
// queue is the fallback in FIFO order matching the repair queue.
int originalQuality = -1;
var raw = itemValue.GetMetadata(MetaKey);
if (raw is int metaQuality)
{
originalQuality = metaQuality;
if (_pendingOriginalQualities.Count > 0)
_pendingOriginalQualities.Dequeue();
}
else if (_pendingOriginalQualities.Count > 0)
{
originalQuality = _pendingOriginalQualities.Dequeue();
}
if (originalQuality < 0) return;
// Flat levels take priority over percentage.
int lossLevels = GetLossLevels(itemValue);
if (lossLevels >= 0)
{
int newQualityFlat = Mathf.Max(QualityUtils.GetMinQuality(), originalQuality - lossLevels);
itemValue.Quality = (ushort)newQualityFlat;
return;
}
float lossPercent = GetLossPercent(itemValue);
if (lossPercent < 0f) return;
float lossFraction = lossPercent / 100f;
int qualityLoss = Mathf.CeilToInt(originalQuality * lossFraction);
int newQuality = Mathf.Max(QualityUtils.GetMinQuality(), originalQuality - qualityLoss);
itemValue.Quality = (ushort)newQuality;
}
}
}