using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using Microsoft.CodeAnalysis; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("ScanValue.Core")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+dc2e2945334d0124669ecf1cd91a828da6fa9e8b")] [assembly: AssemblyProduct("ScanValue.Core")] [assembly: AssemblyTitle("ScanValue.Core")] [assembly: AssemblyVersion("1.0.0.0")] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } } namespace Auuueser.ScanValue.Core.Localization { public static class ScrapNameDictionaryFiles { public static bool IsActiveDictionaryFile(string? path) { if (string.IsNullOrWhiteSpace(path)) { return false; } string fileName = Path.GetFileName(path); if (fileName.EndsWith(".old", StringComparison.OrdinalIgnoreCase)) { return false; } if (!string.Equals(fileName, "zh-CN.runtime.json", StringComparison.OrdinalIgnoreCase)) { return string.Equals(Path.GetExtension(fileName), ".cfg", StringComparison.OrdinalIgnoreCase); } return true; } } public static class ScrapNameDictionaryParser { public static ScrapNameTranslationMap ParseCfg(string text) { ScrapNameTranslationMap scrapNameTranslationMap = new ScrapNameTranslationMap(); AddCfg(scrapNameTranslationMap, text); return scrapNameTranslationMap; } public static void AddCfg(ScrapNameTranslationMap map, string? text) { if (map == null) { throw new ArgumentNullException("map"); } if (!string.IsNullOrEmpty(text)) { string[] array = text.Split(new string[2] { "\r\n", "\n" }, StringSplitOptions.None); for (int i = 0; i < array.Length; i++) { AddCfgLine(map, array[i]); } } } public static ScrapNameTranslationMap ParseRuntimeJson(string text) { ScrapNameTranslationMap scrapNameTranslationMap = new ScrapNameTranslationMap(); AddRuntimeJson(scrapNameTranslationMap, text); return scrapNameTranslationMap; } public static void AddRuntimeJson(ScrapNameTranslationMap map, string? text) { if (map == null) { throw new ArgumentNullException("map"); } if (string.IsNullOrEmpty(text)) { return; } foreach (string item in EnumerateJsonObjects(text)) { if (!TryGetJsonStringProperty(item, "source", out string value)) { continue; } TryGetJsonStringProperty(item, "target", out string value2); TryGetJsonStringProperty(item, "mode", out string value3); if (!string.Equals(value3, "skip", StringComparison.OrdinalIgnoreCase) && !string.Equals(value3, "regex-preserve", StringComparison.OrdinalIgnoreCase)) { string text2 = (string.Equals(value3, "preserve", StringComparison.OrdinalIgnoreCase) ? value : value2); if (!string.IsNullOrWhiteSpace(text2)) { map.Add(value, text2); } } } } private static void AddCfgLine(ScrapNameTranslationMap map, string rawLine) { string text = rawLine.Trim(); if (text.Length != 0 && !text.StartsWith("#", StringComparison.Ordinal) && !text.StartsWith("//", StringComparison.Ordinal) && !text.StartsWith("/*", StringComparison.Ordinal) && !IsRegexCfgLine(text)) { int num = FindCfgEntrySeparator(text); if (num > 0) { string source = UnescapeCfgValue(text.Substring(0, num).Trim()); string target = UnescapeCfgValue(text.Substring(num + 1).Trim()); map.Add(source, target); } } } private static bool IsRegexCfgLine(string line) { if (!line.StartsWith("r:\"", StringComparison.Ordinal) && !line.StartsWith("sr:\"", StringComparison.Ordinal)) { return line.StartsWith("rex:", StringComparison.Ordinal); } return true; } private static int FindCfgEntrySeparator(string line) { bool flag = false; bool flag2 = false; for (int i = 0; i < line.Length; i++) { char c = line[i]; if (flag) { flag = false; continue; } switch (c) { case '\\': flag = true; continue; case '<': flag2 = true; continue; } if (flag2) { if (c == '>') { flag2 = false; } } else if (c == '=') { return i; } } return -1; } private static string UnescapeCfgValue(string value) { return value.Replace("\\n", "\n", StringComparison.Ordinal).Replace("\\r", "\r", StringComparison.Ordinal).Replace("\\t", "\t", StringComparison.Ordinal) .Replace("\\=", "=", StringComparison.Ordinal) .Replace("\\\"", "\"", StringComparison.Ordinal); } private static IEnumerable EnumerateJsonObjects(string text) { int depth = 0; List starts = new List(); bool inString = false; bool escaped = false; for (int index = 0; index < text.Length; index++) { char c = text[index]; if (inString) { if (escaped) { escaped = false; continue; } switch (c) { case '\\': escaped = true; break; case '"': inString = false; break; } continue; } switch (c) { case '"': inString = true; break; case '{': starts.Add(index); depth++; break; case '}': if (depth != 0) { int num = starts[starts.Count - 1]; starts.RemoveAt(starts.Count - 1); depth--; if (depth >= 1) { yield return text.Substring(num, index - num + 1); } } break; } } } private static bool TryGetJsonStringProperty(string jsonObject, string propertyName, out string value) { value = string.Empty; string text = "\"" + propertyName + "\""; int num = jsonObject.IndexOf(text, StringComparison.Ordinal); if (num < 0) { return false; } for (num += text.Length; num < jsonObject.Length && char.IsWhiteSpace(jsonObject[num]); num++) { } if (num >= jsonObject.Length || jsonObject[num] != ':') { return false; } for (num++; num < jsonObject.Length && char.IsWhiteSpace(jsonObject[num]); num++) { } if (num >= jsonObject.Length || jsonObject[num] != '"') { return false; } num++; List list = new List(); bool flag = false; for (; num < jsonObject.Length; num++) { char c = jsonObject[num]; if (flag) { if (c == 'u' && num + 4 < jsonObject.Length && ushort.TryParse(jsonObject.Substring(num + 1, 4), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var result)) { list.Add((char)result); num += 4; } else { List list2 = list; list2.Add(c switch { 'n' => '\n', 'r' => '\r', 't' => '\t', '"' => '"', '\\' => '\\', '/' => '/', _ => c, }); } flag = false; } else { switch (c) { case '\\': flag = true; break; case '"': value = new string(list.ToArray()); return true; default: list.Add(c); break; } } } return false; } } public sealed class ScrapNameTranslationMap { private readonly Dictionary englishToChinese = new Dictionary(256, StringComparer.Ordinal); private readonly Dictionary chineseToEnglish = new Dictionary(256, StringComparer.Ordinal); public int Count => englishToChinese.Count; public bool HasEntries => englishToChinese.Count > 0; public void Add(string? source, string? target) { if (string.IsNullOrWhiteSpace(source) || string.IsNullOrWhiteSpace(target)) { return; } string text = source.Trim(); string text2 = target.Trim(); if (text.Length != 0 && text2.Length != 0) { if (!englishToChinese.ContainsKey(text)) { englishToChinese.Add(text, text2); } if (!chineseToEnglish.ContainsKey(text2)) { chineseToEnglish.Add(text2, text); } } } public string ToChinese(string currentName) { if (!englishToChinese.TryGetValue(currentName, out string value)) { return currentName; } return value; } public string ToEnglish(string currentName) { if (!chineseToEnglish.TryGetValue(currentName, out string value)) { return currentName; } return value; } } } namespace Auuueser.ScanValue.Core.Domain { public sealed class ScrapDebugOptions { private const float MinLogIntervalSeconds = 0.25f; private const float MaxLogIntervalSeconds = 30f; public static ScrapDebugOptions Disabled { get; } = Create(enabled: false, diagnosticsEnabled: false, showHeldItems: false, showZeroValueItems: false, logRegistrations: false, showCameraTestLabel: false, 3f); public bool Enabled { get; } public bool DiagnosticsEnabled { get; } public bool ShowHeldItems { get; } public bool ShowZeroValueItems { get; } public bool LogRegistrations { get; } public bool ShowCameraTestLabel { get; } public float LogIntervalSeconds { get; } public bool ShouldLogDiagnostics { get { if (Enabled) { return DiagnosticsEnabled; } return false; } } public bool ShouldLogRegistrations { get { if (Enabled) { return LogRegistrations; } return false; } } public bool ShouldShowCameraTestLabel { get { if (Enabled) { return ShowCameraTestLabel; } return false; } } public bool LogVisibilitySummary => DiagnosticsEnabled; private ScrapDebugOptions(bool enabled, bool diagnosticsEnabled, bool showHeldItems, bool showZeroValueItems, bool logRegistrations, bool showCameraTestLabel, float logIntervalSeconds) { Enabled = enabled; DiagnosticsEnabled = diagnosticsEnabled; ShowHeldItems = showHeldItems; ShowZeroValueItems = showZeroValueItems; LogRegistrations = logRegistrations; ShowCameraTestLabel = showCameraTestLabel; LogIntervalSeconds = logIntervalSeconds; } public static ScrapDebugOptions Create(bool enabled, bool diagnosticsEnabled, bool showHeldItems, bool showZeroValueItems, bool logRegistrations, bool showCameraTestLabel, float logIntervalSeconds) { return new ScrapDebugOptions(enabled, diagnosticsEnabled, showHeldItems, showZeroValueItems, logRegistrations, showCameraTestLabel, ClampFinite(logIntervalSeconds, 0.25f, 30f)); } private static float ClampFinite(float value, float min, float max) { if (float.IsNaN(value) || float.IsInfinity(value)) { return min; } if (value < min) { return min; } if (value > max) { return max; } return value; } } public readonly record struct ScrapHighlightOptions(bool Enabled, float Alpha, float Width, int MaxHighlightedItems) { private const float MinAlpha = 0f; private const float MaxAlpha = 1f; private const float MinWidth = 0f; private const float MaxWidth = 0.15f; private const int MinHighlightedItems = 1; private const int MaxHighlightedItemsLimit = 256; public static ScrapHighlightOptions Create(bool enabled, float alpha, float width, int maxHighlightedItems) { return new ScrapHighlightOptions(enabled, ClampFinite(alpha, 0f, 1f), ClampFinite(width, 0f, 0.15f), Clamp(maxHighlightedItems, 1, 256)); } private static float ClampFinite(float value, float min, float max) { if (float.IsNaN(value) || float.IsInfinity(value)) { return min; } if (value < min) { return min; } if (value > max) { return max; } return value; } private static int Clamp(int value, int min, int max) { if (value < min) { return min; } if (value > max) { return max; } return value; } } public enum ScrapItemNameLanguage { Auto, Chinese, English } public readonly record struct ScrapItemNameOptions(bool ShowItemNames, ScrapItemNameLanguage Language) { public static ScrapItemNameOptions Default { get; } = new ScrapItemNameOptions(ShowItemNames: true, ScrapItemNameLanguage.Auto); public static ScrapItemNameOptions Create(bool showItemNames, string? language) { return new ScrapItemNameOptions(showItemNames, ParseLanguage(language)); } public static ScrapItemNameLanguage ParseLanguage(string? value) { if (!Enum.TryParse(value, ignoreCase: true, out var result)) { return ScrapItemNameLanguage.Auto; } return result; } } public readonly record struct ScrapItemSnapshot(int ScrapValue, float DistanceSquaredToPlayer, bool IsHeld, bool IsHeldByEnemy, bool IsDeactivated, bool IsPocketed, bool IsUsedUp); public readonly record struct ScrapLabelCandidate(int Id, float ScreenX, float ScreenY, float DistanceToCamera, int ScrapValue, bool WasVisible); public readonly record struct ScrapLabelLayoutOptions(bool Enabled, float LabelWidth, float LabelHeight, float MinGap, int MaxOffsetSlots) { public static ScrapLabelLayoutOptions Default { get; } = Create(enabled: true, 72f, 26f, 6f, 6); public static ScrapLabelLayoutOptions ForItemNames(bool showItemNames) { if (!showItemNames) { return Default; } return Create(enabled: true, 220f, 96f, 10f, 6); } public static ScrapLabelLayoutOptions Create(bool enabled, float labelWidth, float labelHeight, float minGap, int maxOffsetSlots) { return new ScrapLabelLayoutOptions(enabled, ClampFinite(labelWidth, 24f, 240f), ClampFinite(labelHeight, 12f, 120f), ClampFinite(minGap, 0f, 64f), (maxOffsetSlots >= 0) ? maxOffsetSlots : 0); } private static float ClampFinite(float value, float min, float max) { if (float.IsNaN(value) || float.IsInfinity(value)) { return min; } if (value < min) { return min; } if (!(value > max)) { return value; } return max; } } public sealed class ScrapLabelLayoutResolver { private sealed class IndexPriorityComparer : IComparer { private IReadOnlyList? candidates; private int[]? distanceBuckets; public void Reset(IReadOnlyList candidates, int[] distanceBuckets) { this.candidates = candidates; this.distanceBuckets = distanceBuckets; } public void Clear() { candidates = null; distanceBuckets = null; } public int Compare(int leftIndex, int rightIndex) { IReadOnlyList readOnlyList = candidates; int[] array = distanceBuckets; return ComparePriority(readOnlyList[leftIndex], readOnlyList[rightIndex], array[leftIndex], array[rightIndex]); } } private const float DistancePriorityBucketSize = 0.25f; private static readonly (float X, float Y)[] OffsetSlots = new(float, float)[7] { (0f, 0f), (-0.65f, 0.65f), (0.65f, 0.65f), (0f, 1.25f), (-1.05f, 0f), (1.05f, 0f), (0f, 1.9f) }; private ScrapLabelLayoutOptions options; private readonly List accepted = new List(); private readonly IndexPriorityComparer indexPriorityComparer = new IndexPriorityComparer(); private int[] sortedIndexes = Array.Empty(); private int[] distanceBuckets = Array.Empty(); public ScrapLabelLayoutResolver(ScrapLabelLayoutOptions options) { this.options = options; } public void UpdateOptions(ScrapLabelLayoutOptions options) { this.options = options; } public ScrapLabelPlacement[] Resolve(IReadOnlyList candidates, int maxVisibleLabels) { if (candidates == null) { throw new ArgumentNullException("candidates"); } List list = new List(candidates.Count); ResolveInto(candidates, maxVisibleLabels, list); return list.ToArray(); } public void ResolveInto(IReadOnlyList candidates, int maxVisibleLabels, List results) { if (candidates == null) { throw new ArgumentNullException("candidates"); } if (results == null) { throw new ArgumentNullException("results"); } accepted.Clear(); results.Clear(); EnsureSortBufferCapacity(candidates.Count); if (results.Capacity < candidates.Count) { results.Capacity = candidates.Count; } for (int i = 0; i < candidates.Count; i++) { results.Add(default(ScrapLabelPlacement)); sortedIndexes[i] = i; distanceBuckets[i] = GetDistancePriorityBucket(candidates[i].DistanceToCamera); } indexPriorityComparer.Reset(candidates, distanceBuckets); try { Array.Sort(sortedIndexes, 0, candidates.Count, indexPriorityComparer); int num = 0; for (int j = 0; j < candidates.Count; j++) { int index = sortedIndexes[j]; ScrapLabelCandidate candidate = candidates[index]; if (num >= maxVisibleLabels) { results[index] = Hidden(candidate); continue; } if (!options.Enabled) { ScrapLabelPlacement item = (results[index] = Visible(candidate, candidate.ScreenX, candidate.ScreenY, 0)); accepted.Add(item); num++; continue; } bool flag = false; int num2 = Math.Min(options.MaxOffsetSlots, OffsetSlots.Length - 1); for (int k = 0; k <= num2; k++) { (float, float) tuple = OffsetSlots[k]; float screenX = candidate.ScreenX + tuple.Item1 * (options.LabelWidth + options.MinGap); float screenY = candidate.ScreenY + tuple.Item2 * (options.LabelHeight + options.MinGap); ScrapLabelPlacement scrapLabelPlacement2 = Visible(candidate, screenX, screenY, k); if (!OverlapsAccepted(scrapLabelPlacement2)) { results[index] = scrapLabelPlacement2; accepted.Add(scrapLabelPlacement2); num++; flag = true; break; } } if (!flag) { results[index] = Hidden(candidate); } } } finally { accepted.Clear(); indexPriorityComparer.Clear(); } } private void EnsureSortBufferCapacity(int candidateCount) { if (sortedIndexes.Length < candidateCount) { Array.Resize(ref sortedIndexes, candidateCount); Array.Resize(ref distanceBuckets, candidateCount); } } private bool OverlapsAccepted(ScrapLabelPlacement placement) { for (int i = 0; i < accepted.Count; i++) { if (placement.Overlaps(accepted[i], options.MinGap)) { return true; } } return false; } private ScrapLabelPlacement Visible(ScrapLabelCandidate candidate, float screenX, float screenY, int slotIndex) { return new ScrapLabelPlacement(candidate.Id, IsVisible: true, screenX, screenY, options.LabelWidth, options.LabelHeight, slotIndex); } private static ScrapLabelPlacement Hidden(ScrapLabelCandidate candidate) { return new ScrapLabelPlacement(candidate.Id, IsVisible: false, candidate.ScreenX, candidate.ScreenY, 0f, 0f, -1); } private static int ComparePriority(ScrapLabelCandidate left, ScrapLabelCandidate right, int leftDistanceBucket, int rightDistanceBucket) { int num = leftDistanceBucket.CompareTo(rightDistanceBucket); if (num != 0) { return num; } int num2 = right.ScrapValue.CompareTo(left.ScrapValue); if (num2 != 0) { return num2; } int num3 = right.WasVisible.CompareTo(left.WasVisible); if (num3 != 0) { return num3; } return left.Id.CompareTo(right.Id); } private static int GetDistancePriorityBucket(float distance) { if (float.IsNaN(distance)) { return int.MaxValue; } return (int)MathF.Floor(distance / 0.25f); } } public readonly record struct ScrapLabelPlacement(int Id, bool IsVisible, float ScreenX, float ScreenY, float Width, float Height, int SlotIndex) { public bool Overlaps(ScrapLabelPlacement other, float minGap) { if (!IsVisible || !other.IsVisible) { return false; } float num = Width * 0.5f; float num2 = Height * 0.5f; float num3 = other.Width * 0.5f; float num4 = other.Height * 0.5f; if (MathF.Abs(ScreenX - other.ScreenX) < num + num3 + minGap) { return MathF.Abs(ScreenY - other.ScreenY) < num2 + num4 + minGap; } return false; } } public readonly record struct ScrapLabelPreCandidate(int Id, float DistanceSquaredToPlayer, int ScrapValue, bool WasVisible); public static class ScrapLabelPreCandidatePriority { private const float DistanceSquaredPriorityBucketSize = 0.25f; public static bool IsHigherPriority(ScrapLabelPreCandidate left, ScrapLabelPreCandidate right) { return Compare(left, right) < 0; } public static int Compare(ScrapLabelPreCandidate left, ScrapLabelPreCandidate right) { int num = GetDistancePriorityBucket(left.DistanceSquaredToPlayer).CompareTo(GetDistancePriorityBucket(right.DistanceSquaredToPlayer)); if (num != 0) { return num; } int num2 = right.ScrapValue.CompareTo(left.ScrapValue); if (num2 != 0) { return num2; } int num3 = right.WasVisible.CompareTo(left.WasVisible); if (num3 != 0) { return num3; } return left.Id.CompareTo(right.Id); } private static int GetDistancePriorityBucket(float distanceSquared) { if (float.IsNaN(distanceSquared) || float.IsInfinity(distanceSquared)) { return int.MaxValue; } return (int)MathF.Floor(distanceSquared / 0.25f); } } public static class ScrapPriceText { public static string Format(int value) { return "$" + value.ToString(CultureInfo.InvariantCulture); } } public enum ScrapRevealActivationMode { VanillaScan, Always } public static class ScrapRevealActivationModeParser { public static ScrapRevealActivationMode Parse(string? value) { if (string.Equals(value, "Always", StringComparison.OrdinalIgnoreCase)) { return ScrapRevealActivationMode.Always; } return ScrapRevealActivationMode.VanillaScan; } } public sealed class ScrapScanPerformanceOptions { public bool OptimizeVanillaScan { get; } private ScrapScanPerformanceOptions(bool optimizeVanillaScan) { OptimizeVanillaScan = optimizeVanillaScan; } public static ScrapScanPerformanceOptions Create(bool optimizeVanillaScan) { return new ScrapScanPerformanceOptions(optimizeVanillaScan); } } public sealed class ScrapScanRevealState { public void Trigger(ScrapVisibilityOptions options, float currentTime) { } public void Reset() { } public bool IsActive(ScrapVisibilityOptions options, float currentTime) { return options.ActivationMode == ScrapRevealActivationMode.Always; } } public static class ScrapScanValueText { public const string UnknownValue = "???"; public static bool HasUnknownValue(string? text) { if (string.IsNullOrEmpty(text)) { return false; } return text.Contains("???"); } public static bool TryParseKnownValue(string? text, out int value) { value = 0; if (string.IsNullOrEmpty(text) || HasUnknownValue(text)) { return false; } bool flag = false; bool flag2 = false; foreach (char c in text) { if (!flag) { flag = c == '$'; } else if (c < '0' || c > '9') { if ((!flag2 || (c != ',' && c != ' ' && c != '_')) && flag2) { break; } } else { flag2 = true; value = value * 10 + c - 48; } } return flag2; } } public readonly record struct ScrapValueColorOptions(bool Enabled, int LowValueMax, int MediumValueMax, int HighValueMax, int VeryHighValueMax) { private const int MinThreshold = 0; private const int MaxThreshold = 9999; public static ScrapValueColorOptions Create(bool enabled, int lowValueMax, int mediumValueMax, int highValueMax, int veryHighValueMax) { int num = Clamp(lowValueMax, 0, 9999); int num2 = Clamp(mediumValueMax, num, 9999); int num3 = Clamp(highValueMax, num2, 9999); int veryHighValueMax2 = Clamp(veryHighValueMax, num3, 9999); return new ScrapValueColorOptions(enabled, num, num2, num3, veryHighValueMax2); } public ScrapValueColorTier ResolveTier(bool hasUnknownValue, int value) { if (hasUnknownValue) { return ScrapValueColorTier.Unknown; } if (value <= LowValueMax) { return ScrapValueColorTier.Low; } if (value <= MediumValueMax) { return ScrapValueColorTier.Medium; } if (value <= HighValueMax) { return ScrapValueColorTier.High; } if (value > VeryHighValueMax) { return ScrapValueColorTier.Jackpot; } return ScrapValueColorTier.VeryHigh; } private static int Clamp(int value, int min, int max) { if (value < min) { return min; } if (value > max) { return max; } return value; } } public enum ScrapValueColorTier { Unknown, Low, Medium, High, VeryHigh, Jackpot } public sealed class ScrapVisibilityOptions { private const float MinRevealRadius = 1f; private const float MaxRevealRadius = 100f; private const float DefaultScanRevealDurationSeconds = 10f; private const float MinScanRevealDurationSeconds = 0.5f; private const float MaxScanRevealDurationSeconds = 60f; private const float MinUpdateIntervalSeconds = 0.05f; private const float MaxUpdateIntervalSeconds = 1f; private const int MinVisibleLabels = 1; private const int MaxVisibleLabelsLimit = 256; public bool Enabled { get; } public float RevealRadius { get; } public float RevealRadiusSquared { get; } public ScrapRevealActivationMode ActivationMode { get; } public float ScanRevealDurationSeconds { get; } public float UpdateIntervalSeconds { get; } public int MaxVisibleLabels { get; } private ScrapVisibilityOptions(bool enabled, float revealRadius, ScrapRevealActivationMode activationMode, float scanRevealDurationSeconds, float updateIntervalSeconds, int maxVisibleLabels) { Enabled = enabled; RevealRadius = revealRadius; RevealRadiusSquared = revealRadius * revealRadius; ActivationMode = activationMode; ScanRevealDurationSeconds = scanRevealDurationSeconds; UpdateIntervalSeconds = updateIntervalSeconds; MaxVisibleLabels = maxVisibleLabels; } public static ScrapVisibilityOptions Create(bool enabled, float revealRadius, float updateIntervalSeconds, int maxVisibleLabels, string activationMode = "VanillaScan", float scanRevealDurationSeconds = 10f) { return new ScrapVisibilityOptions(enabled, ClampFinite(revealRadius, 1f, 100f), ScrapRevealActivationModeParser.Parse(activationMode), ClampFiniteOrDefault(scanRevealDurationSeconds, 0.5f, 60f, 10f), ClampFinite(updateIntervalSeconds, 0.05f, 1f), Clamp(maxVisibleLabels, 1, 256)); } private static float ClampFiniteOrDefault(float value, float min, float max, float fallback) { if (float.IsNaN(value) || float.IsInfinity(value)) { return fallback; } if (value < min) { return min; } if (value > max) { return max; } return value; } private static float ClampFinite(float value, float min, float max) { if (float.IsNaN(value) || float.IsInfinity(value)) { return min; } if (value < min) { return min; } if (value > max) { return max; } return value; } private static int Clamp(int value, int min, int max) { if (value < min) { return min; } if (value > max) { return max; } return value; } } public sealed class ScrapVisibilityRules { private readonly ScrapVisibilityOptions options; public ScrapDebugOptions Debug { get; } public ScrapVisibilityRules(ScrapVisibilityOptions options) : this(options, ScrapDebugOptions.Disabled) { } public ScrapVisibilityRules(ScrapVisibilityOptions options, ScrapDebugOptions debug) { this.options = options; Debug = debug; } public bool ShouldShow(ScrapItemSnapshot item) { if (!ShouldShowVanillaScanned(item)) { return false; } return item.DistanceSquaredToPlayer <= options.RevealRadiusSquared; } public bool ShouldShowVanillaScanned(ScrapItemSnapshot item) { if (!options.Enabled) { return false; } if (item.ScrapValue <= 0 && (!Debug.Enabled || !Debug.ShowZeroValueItems)) { return false; } if (item.IsDeactivated || item.IsUsedUp) { return false; } if ((item.IsHeld || item.IsHeldByEnemy || item.IsPocketed) && (!Debug.Enabled || !Debug.ShowHeldItems)) { return false; } return true; } } } namespace Auuueser.ScanValue.Core.Configuration { public static class ChineseProjectDetection { private const string CurrentKnownGuid = "cn.codex.v81testchn"; private const string PackageName = "LC_Chinese_Project"; private const string RepositoryUrl = "github.com/Auuueser/LC-Chinese-Project"; public static bool IsChineseProjectPlugin(string? pluginGuid, string? pluginName, string? pluginLocation) { if (EqualsIgnoreCase(pluginGuid, "cn.codex.v81testchn")) { return true; } if (ContainsIgnoreCase(pluginName, "LC Chinese Project") || ContainsIgnoreCase(pluginName, "Chinese Project") || ContainsIgnoreCase(pluginName, "V81 TEST CHN")) { return true; } if (!ContainsIgnoreCase(pluginLocation, "LC_Chinese_Project") && !ContainsIgnoreCase(pluginLocation, "LC-Chinese-Project")) { return ContainsIgnoreCase(pluginLocation, "LC Chinese Project"); } return true; } public static bool ContainsChineseProjectManifestText(string? manifestText) { if (!ContainsIgnoreCase(manifestText, "LC_Chinese_Project") && !ContainsIgnoreCase(manifestText, "LC Chinese Project") && !ContainsIgnoreCase(manifestText, "LC-Chinese-Project")) { return ContainsIgnoreCase(manifestText, "github.com/Auuueser/LC-Chinese-Project"); } return true; } public static bool ContainsChineseConfigSections(string? configText) { if (!ContainsIgnoreCase(configText, "[通用]") && !ContainsIgnoreCase(configText, "[可见性]") && !ContainsIgnoreCase(configText, "[性能]") && !ContainsIgnoreCase(configText, "[标签]")) { return ContainsIgnoreCase(configText, "[调试]"); } return true; } private static bool EqualsIgnoreCase(string? value, string expected) { return string.Equals(value, expected, StringComparison.OrdinalIgnoreCase); } private static bool ContainsIgnoreCase(string? value, string expected) { if (value != null) { return value.IndexOf(expected, StringComparison.OrdinalIgnoreCase) >= 0; } return false; } } public enum ConfigLanguage { English, Chinese } public static class ConfigTextCatalog { private static readonly ConfigTexts English = new ConfigTexts("General", "Visibility", "Performance", "Label", "Highlight", "Debug", "Enable automatic scrap value labels. Applied immediately.", "Player-centered reveal radius in meters. Applied immediately.", "When labels appear: VanillaScan mirrors the vanilla right-click scan UI; Always keeps the old automatic behavior. Old ScanOnly values are treated as VanillaScan. Applied immediately.", "Legacy setting kept for old configs. VanillaScan follows the vanilla scan UI and does not use a timed reveal window. Applied immediately.", "Seconds between visibility refreshes. Higher values reduce CPU work. Applied immediately.", "Maximum labels shown at once. Applied immediately.", "Vertical world-space offset above each scrap object. Applied immediately.", "World-space scale for each label. Applied immediately.", "TextMeshPro font size. Applied immediately.", "HTML color for the price text, for example #FFD447. Applied immediately.", "HTML color for the text outline. Applied immediately.", "Text outline width from 0 to 0.5. Applied immediately.", "Highlight scanned scrap object outlines. Applied immediately.", "HTML color for scanned scrap outlines, for example #FFD447. Applied immediately.", "Opacity for scanned scrap outlines from 0 to 1. Applied immediately.", "World-space outline proxy expansion from 0 to 0.15. Applied immediately.", "Maximum scanned scrap objects highlighted at once. Applied immediately.", "Enable diagnostic and test-only behavior. Keep disabled for normal play.", "Debug only: log low-frequency camera, registry, filtering, layout, and pool counters. Requires debug mode. Applied immediately.", "Show labels for held or pocketed items while debug mode is enabled. Applied immediately.", "Show $0 labels for registered items while debug mode is enabled. Applied immediately.", "Log item registration events while debug mode is enabled. Applied immediately.", "Debug only: render a fixed $TEST price label in front of the active camera. Requires debug mode. Applied immediately.", "Seconds between debug visibility summaries. Applied immediately."); private static readonly ConfigTexts Chinese = new ConfigTexts("通用", "可见性", "性能", "标签", "高光", "调试", "启用自动废料价格标签。保存后立即生效。", "以本地玩家为中心的显示半径,单位为米。保存后立即生效。", "标签显示方式:VanillaScan 跟随原版右键 scan UI;Always 保留旧的自动显示行为。旧的 ScanOnly 会按 VanillaScan 处理。保存后立即生效。", "兼容旧配置保留的设置。VanillaScan 跟随原版 scan UI,不再使用计时显示窗口。保存后立即生效。", "刷新可见性检测的间隔秒数;数值越高,CPU 开销越低。保存后立即生效。", "同一时间最多显示的价格标签数量。保存后立即生效。", "价格标签相对废料物体向上的世界空间偏移。保存后立即生效。", "每个价格标签的世界空间缩放。保存后立即生效。", "TextMeshPro 字体大小。保存后立即生效。", "价格文字的 HTML 颜色,例如 #FFD447。保存后立即生效。", "文字描边的 HTML 颜色。保存后立即生效。", "文字描边宽度,范围 0 到 0.5。保存后立即生效。", "启用 scan 时废料物体轮廓高光。保存后立即生效。", "scan 时废料轮廓高光的 HTML 颜色,例如 #FFD447。保存后立即生效。", "scan 时废料轮廓高光透明度,范围 0 到 1。保存后立即生效。", "scan 时废料轮廓高光代理的外扩宽度,范围 0 到 0.15。保存后立即生效。", "同一时间最多高光显示的废料数量。保存后立即生效。", "启用诊断和仅测试用行为。正常游玩时保持关闭。", "仅调试:低频输出相机、注册、过滤、布局和标签池计数日志。需要启用调试模式。保存后立即生效。", "调试模式开启时,显示被持有或放入口袋的物品价格。保存后立即生效。", "调试模式开启时,为已注册物品显示 $0 标签。保存后立即生效。", "调试模式开启时,输出物品注册日志。保存后立即生效。", "仅调试:在活动相机前方渲染固定 $TEST 价格标签。需要启用调试模式。保存后立即生效。", "两次调试可见性统计之间的秒数。保存后立即生效。", "在废料价值标签上方显示物品名称。保存后立即生效。", "物品名称语言:Auto、Chinese 或 English。保存后立即生效。", "根据扫描到的废料价值为标签文字和轮廓高光变色。保存后立即生效。", "低价值颜色档的最高价格。保存后立即生效。", "中价值颜色档的最高价格。保存后立即生效。", "高价值颜色档的最高价格。保存后立即生效。", "很高价值颜色档的最高价格。超过该值时使用极品颜色。保存后立即生效。", "未知价值(例如未拔下 Apparatus 的 ???)使用的 HTML 颜色。保存后立即生效。", "低价值废料标签文字和轮廓高光的 HTML 颜色。保存后立即生效。", "中价值废料标签文字和轮廓高光的 HTML 颜色。保存后立即生效。", "高价值废料标签文字和轮廓高光的 HTML 颜色。保存后立即生效。", "很高价值废料标签文字和轮廓高光的 HTML 颜色。保存后立即生效。", "极品价值废料标签文字和轮廓高光的 HTML 颜色。保存后立即生效。", "优化原版右键 scan:缓存 scan UI 组件并避免重复文本重建。保存后立即生效。"); public static ConfigTexts Get(ConfigLanguage language) { if (language != ConfigLanguage.Chinese) { return English; } return Chinese; } } public sealed class ConfigTexts { public string GeneralSection { get; } public string VisibilitySection { get; } public string PerformanceSection { get; } public string LabelSection { get; } public string HighlightSection { get; } public string DebugSection { get; } public string EnabledDescription { get; } public string RevealRadiusDescription { get; } public string ActivationModeDescription { get; } public string ScanRevealDurationSecondsDescription { get; } public string UpdateIntervalSecondsDescription { get; } public string MaxVisibleLabelsDescription { get; } public string HeightOffsetDescription { get; } public string WorldScaleDescription { get; } public string FontSizeDescription { get; } public string LabelColorDescription { get; } public string OutlineColorDescription { get; } public string OutlineWidthDescription { get; } public string HighlightEnabledDescription { get; } public string HighlightColorDescription { get; } public string HighlightAlphaDescription { get; } public string HighlightWidthDescription { get; } public string MaxHighlightedItemsDescription { get; } public string ShowItemNamesDescription { get; } public string ItemNameLanguageDescription { get; } public string ValueBasedColorsEnabledDescription { get; } public string LowValueMaxDescription { get; } public string MediumValueMaxDescription { get; } public string HighValueMaxDescription { get; } public string VeryHighValueMaxDescription { get; } public string UnknownValueColorDescription { get; } public string LowValueColorDescription { get; } public string MediumValueColorDescription { get; } public string HighValueColorDescription { get; } public string VeryHighValueColorDescription { get; } public string JackpotValueColorDescription { get; } public string OptimizeVanillaScanDescription { get; } public string DebugEnabledDescription { get; } public string DebugDiagnosticsEnabledDescription { get; } public string DebugShowHeldItemsDescription { get; } public string DebugShowZeroValueItemsDescription { get; } public string DebugLogRegistrationsDescription { get; } public string DebugShowCameraTestLabelDescription { get; } public string DebugLogIntervalSecondsDescription { get; } public ConfigTexts(string generalSection, string visibilitySection, string performanceSection, string labelSection, string highlightSection, string debugSection, string enabledDescription, string revealRadiusDescription, string activationModeDescription, string scanRevealDurationSecondsDescription, string updateIntervalSecondsDescription, string maxVisibleLabelsDescription, string heightOffsetDescription, string worldScaleDescription, string fontSizeDescription, string labelColorDescription, string outlineColorDescription, string outlineWidthDescription, string highlightEnabledDescription, string highlightColorDescription, string highlightAlphaDescription, string highlightWidthDescription, string maxHighlightedItemsDescription, string debugEnabledDescription, string debugDiagnosticsEnabledDescription, string debugShowHeldItemsDescription, string debugShowZeroValueItemsDescription, string debugLogRegistrationsDescription, string debugShowCameraTestLabelDescription, string debugLogIntervalSecondsDescription, string showItemNamesDescription = "Show the item name above the scrap value label. Applied immediately.", string itemNameLanguageDescription = "Item-name language: Auto, Chinese, or English. Applied immediately.", string valueBasedColorsEnabledDescription = "Color scrap labels and scan outlines by the scanned scrap value. Applied immediately.", string lowValueMaxDescription = "Maximum value for the low-value color tier. Applied immediately.", string mediumValueMaxDescription = "Maximum value for the medium-value color tier. Applied immediately.", string highValueMaxDescription = "Maximum value for the high-value color tier. Applied immediately.", string veryHighValueMaxDescription = "Maximum value for the very-high-value color tier. Values above this use the jackpot color. Applied immediately.", string unknownValueColorDescription = "HTML color for unknown scan values such as docked Apparatus ???. Applied immediately.", string lowValueColorDescription = "HTML color for low-value scrap labels and scan outlines. Applied immediately.", string mediumValueColorDescription = "HTML color for medium-value scrap labels and scan outlines. Applied immediately.", string highValueColorDescription = "HTML color for high-value scrap labels and scan outlines. Applied immediately.", string veryHighValueColorDescription = "HTML color for very-high-value scrap labels and scan outlines. Applied immediately.", string jackpotValueColorDescription = "HTML color for jackpot-value scrap labels and scan outlines. Applied immediately.", string optimizeVanillaScanDescription = "Optimize the vanilla right-click scan by caching scan UI components and avoiding repeated text rebuilds. Applied immediately.") { GeneralSection = generalSection; VisibilitySection = visibilitySection; PerformanceSection = performanceSection; LabelSection = labelSection; HighlightSection = highlightSection; DebugSection = debugSection; EnabledDescription = enabledDescription; RevealRadiusDescription = revealRadiusDescription; ActivationModeDescription = activationModeDescription; ScanRevealDurationSecondsDescription = scanRevealDurationSecondsDescription; UpdateIntervalSecondsDescription = updateIntervalSecondsDescription; MaxVisibleLabelsDescription = maxVisibleLabelsDescription; HeightOffsetDescription = heightOffsetDescription; WorldScaleDescription = worldScaleDescription; FontSizeDescription = fontSizeDescription; LabelColorDescription = labelColorDescription; OutlineColorDescription = outlineColorDescription; OutlineWidthDescription = outlineWidthDescription; HighlightEnabledDescription = highlightEnabledDescription; HighlightColorDescription = highlightColorDescription; HighlightAlphaDescription = highlightAlphaDescription; HighlightWidthDescription = highlightWidthDescription; MaxHighlightedItemsDescription = maxHighlightedItemsDescription; ShowItemNamesDescription = showItemNamesDescription; ItemNameLanguageDescription = itemNameLanguageDescription; ValueBasedColorsEnabledDescription = valueBasedColorsEnabledDescription; LowValueMaxDescription = lowValueMaxDescription; MediumValueMaxDescription = mediumValueMaxDescription; HighValueMaxDescription = highValueMaxDescription; VeryHighValueMaxDescription = veryHighValueMaxDescription; UnknownValueColorDescription = unknownValueColorDescription; LowValueColorDescription = lowValueColorDescription; MediumValueColorDescription = mediumValueColorDescription; HighValueColorDescription = highValueColorDescription; VeryHighValueColorDescription = veryHighValueColorDescription; JackpotValueColorDescription = jackpotValueColorDescription; OptimizeVanillaScanDescription = optimizeVanillaScanDescription; DebugEnabledDescription = debugEnabledDescription; DebugDiagnosticsEnabledDescription = debugDiagnosticsEnabledDescription; DebugShowHeldItemsDescription = debugShowHeldItemsDescription; DebugShowZeroValueItemsDescription = debugShowZeroValueItemsDescription; DebugLogRegistrationsDescription = debugLogRegistrationsDescription; DebugShowCameraTestLabelDescription = debugShowCameraTestLabelDescription; DebugLogIntervalSecondsDescription = debugLogIntervalSecondsDescription; } } } namespace System.Runtime.CompilerServices { internal static class IsExternalInit { } }