using System; using System.Collections.Generic; using System.Linq; using Decentralized.Domain; using UnityEngine; namespace Decentralized { /// /// 显卡鉴定指南:游戏的纸质指南(PaperUI),版式和原版鉴定指南一致——笔记本背景、委员会页眉、居中标题、 /// 插图加图注、“价格不变 / -n%”的折价标注、末页的鉴定示例。第一次打开时按当前语言生成并登记,语言变了重新生成。 /// 坐标是设计单位(314×402,屏幕上放大 2 倍)。 /// public static class GpuGuide { public const string BookId = "gpu_guide"; private const float X = 35f, W = 253f, Top = 80f, Bottom = 368f; private const string Ink = "#221C1A", Good = "#3F7D4B", Bad = "#9E1F1A"; private static string _locale; private static LoadedMod _mod; public static void OnAssetsLoaded(LoadedMod mod) { if (mod?.Manifest?.ID == ModInfo.Id) _mod = mod; } /// 原版背景、按钮和页眉图按名字找不到时(纸张素材目录没打包),把内存里同名的原版精灵登记给本 mod。 private static void ProvideMissingPaperSprites() { if (_mod == null) return; foreach (var sprite in Resources.FindObjectsOfTypeAll()) { string n = sprite.name; if (n != "notebook_manual" && n != "guide_logo" && !n.StartsWith("paper_buttons")) continue; if (_mod.PaperSprites.ContainsKey(n) || PaperAssets.FindInCatalog(n) != null) continue; _mod.PaperSprites[n] = sprite; } } public static void Open(int page = 0) { try { if (_locale != L.Code || !ModHelper.GetPaperBookIds(ModInfo.Id).Contains(BookId)) Register(); var ui = ModHelper.OpenPaper(ModInfo.Id, BookId); if (ui == null) { Log.Warn("[GpuGuide] 打不开鉴定指南"); return; } ui.FlipTo(page); RaiseAboveModWindows(ui); } catch (Exception ex) { Log.Error("[GpuGuide] " + ex); } } /// 纸质画布在 mod 窗口所在的层下面:从终端里打开时会被盖住,打开期间调到上面,关上时恢复。 private static void RaiseAboveModWindows(PaperUI ui) { var canvas = ui.Root != null ? ui.Root.GetComponentInParent()?.rootCanvas : null; if (canvas == null || canvas.sortingOrder >= 950) return; int old = canvas.sortingOrder; canvas.sortingOrder = 950; void Restore(PaperUI u) { u.Closed -= Restore; if (canvas != null) canvas.sortingOrder = old; } ui.Closed += Restore; } // ================= 生成 ================= private static void Register() { ProvideMissingPaperSprites(); ModHelper.RemovePaperBooks(ModInfo.Id); var b = ModHelper.CreatePaper(ModInfo.Id, BookId, "notebook_manual"); b.SetSize(628f, 804f); b.SetPosition(-450f, 0f); b.SetChrome(); Page1(b); Page2(b); Page3(b); Page4(b); Page5(b); Page6(b); Page7(b); Page8(b); Page9(b); ModHelper.RegisterPaperBook(ModInfo.Id, b.Data); _locale = L.Code; Log.Info($"[GpuGuide] 已登记显卡鉴定指南({b.Data.pages.Count} 页,{_locale})"); } private static string T(string key, params object[] a) => TuningText.Render(L.T(key, a)); /// 新的一页:原版的页眉(委员会印章字)和标题。 private static float NewPage(PaperUI b) { b.AddPage(null, "guide_page"); b.SetSlot("header", L.T("gg.header")); b.SetSlot("title", L.T("item.guide.name")); b.SetSlot("body", " "); return Top; } private static string Img(string name) => ModInfo.Id + ":" + name; private static TMPro.TextMeshProUGUI _measurer; private static float Measure(string text, string style, float size, float width) { if (_measurer == null) { var go = new GameObject("pd_gpu_guide_measure", typeof(RectTransform)); UnityEngine.Object.DontDestroyOnLoad(go); _measurer = go.AddComponent(); _measurer.enableWordWrapping = true; _measurer.richText = true; } var st = PaperTextStyle.Get(style); var font = st.Font(); if (font != null) _measurer.font = font; _measurer.fontSize = size > 0 ? size : st.size; float h = _measurer.GetPreferredValues(text, width, 0).y; return h > 1 ? h + 1.5f : (size > 0 ? size : st.size) * 1.5f * (1 + text.Split('\n').Length); } /// 一段文字,返回下一块的 y。 private static float Text(PaperUI b, float y, string text, string style = "body", string color = Ink, string align = "left", float gap = 5f, float x = X, float w = W) { float h = Measure(text, style, 0, w); b.AddText(x, y, w, h, text, style, align: align); b.Last.color = color; if (y + h > Bottom) Log.Warn($"[GpuGuide] 内容超出纸面 overflow:{y + h:0} > {Bottom}({text.Substring(0, Math.Min(18, text.Length))})"); return y + h + gap; } /// 小节标题(原版的蓝灰色粗体小标题)。 private static float Section(PaperUI b, float y, string text) { b.AddText(X, y, W, 13, text, "label", fontSize: 8f); b.Last.color = "#2E5A78"; return y + 15f; } /// 一张图(底图加叠加层,同一个位置),居中,宽高是显示尺寸;返回图的底边。 private static float Art(PaperUI b, float x, float y, float scale, params string[] layers) { float w = 192f * scale, h = 128f * scale; foreach (var l in layers) { b.AddImage(x, y, w, h, Img(l), "stretch"); } return y + h; } private static string Front(string brand, int tier) => $"gpu_big_{brand}_f_t{tier}"; private static int Fans(int tier) => Hardware.FanCount[tier]; private static void Caption(PaperUI b, float x, float y, float w, string text, string color = null) { b.AddText(x, y, w, 10, text, "caption", align: "center"); if (color != null) b.Last.color = color; } // ---- 1 概述 ---- private static void Page1(PaperUI b) { float y = NewPage(b); y = Text(b, y, L.T("gg.p1.body"), gap: 6f); float x = X + (W - 192f * 0.9f) / 2; float bottom = Art(b, x, y, 0.9f, Front("probidia", 1)); Caption(b, X, bottom + 1, W, L.T("gg.p1.cap")); y = bottom + 16f; y = Text(b, y, L.T("gg.p1.makers"), gap: 8f); Text(b, y, T("gg.p1.note"), "body", Good); b.Last.fontSize = 7f; } // ---- 2 档次 ---- private static void Page2(PaperUI b) { float y = NewPage(b); y = Section(b, y, L.T("gg.p2.h")); y = Text(b, y, L.T("gg.p2.body"), gap: 6f); float cw = W / 2; for (int row = 0; row < 2; row++) { float rowBottom = y; for (int col = 0; col < 2; col++) { int tier = row * 2 + col; float ax = X + col * cw + (cw - 120f) / 2; float bottom = Art(b, ax, y, 0.625f, Front("probidia", tier)); Caption(b, X + col * cw, bottom, cw, L.T("gg.tier." + tier)); rowBottom = bottom + 12f; } y = rowBottom + 4f; } Text(b, y + 2f, L.T("gg.p2.models"), "body"); } // ---- 3 叶片 ---- private static void Page3(PaperUI b) { float y = NewPage(b); y = Section(b, y, L.T("gg.fan.h")); for (int s = 0; s < 3; s++) { var layers = new List { Front("probidia", 1) }; if (s == 1) layers.Add("gpu_big_ov_fan_dust2_" + Fans(1)); if (s == 2) { layers.Add("gpu_big_ov_fan_dust1_" + Fans(1)); layers.Add("gpu_big_ov_fan_chip_" + Fans(1)); } float bottom = Art(b, X + (W - 96f) / 2, y, 0.5f, layers.ToArray()); Caption(b, X, bottom, W, T("gg.fan." + s), s == 0 ? null : Bad); y = bottom + 15f; } } // ---- 4 金手指 ---- private static void Page4(PaperUI b) { float y = NewPage(b); y = Section(b, y, L.T("gg.fin.h")); for (int s = 0; s < 3; s++) { var layers = new List { Front("probidia", 1) }; if (s == 1) layers.Add("gpu_big_ov_fin_dim"); if (s == 2) layers.Add("gpu_big_ov_fin_oxide"); float bottom = Art(b, X + (W - 96f) / 2, y, 0.5f, layers.ToArray()); Caption(b, X, bottom, W, T("gg.fin." + s), s == 0 ? null : Bad); y = bottom + 15f; } } // ---- 5 挡板与封条 ---- private static void Page5(PaperUI b) { float y = NewPage(b); y = Section(b, y, L.T("gg.brk.h")); float cw = W / 2; float bottom = y; for (int s = 0; s < 2; s++) { var layers = new List { Front("probidia", 1) }; if (s == 1) layers.Add("gpu_big_ov_brk_scratch"); float bx = Art(b, X + s * cw + (cw - 120f) / 2, y, 0.625f, layers.ToArray()); Caption(b, X + s * cw, bx, cw, T("gg.brk." + s), s == 0 ? null : Bad); bottom = bx + 12f; } y = bottom + 10f; y = Section(b, y, L.T("gg.seal.h")); bottom = y; for (int s = 0; s < 2; s++) { var layers = new List { "gpu_big_probidia_b", "gpu_big_ov_print_normal_rtx70" }; if (s == 1) layers.Add("gpu_big_ov_seal_missing"); float bx = Art(b, X + s * cw + (cw - 120f) / 2, y, 0.625f, layers.ToArray()); Caption(b, X + s * cw, bx, cw, T(s == 0 ? "gg.seal.0" : "gg.seal.2"), s == 0 ? null : Bad); bottom = bx + 12f; } Text(b, bottom + 8f, L.T("gg.wear.note"), "body"); } // ---- 6 序列号 ---- private static void Page6(PaperUI b) { float y = NewPage(b); y = Section(b, y, L.T("gg.serial.h")); y = Text(b, y, L.T("gg.serial.body"), gap: 6f); float bottom = Art(b, X + (W - 150f) / 2, y, 0.78f, "gpu_big_probidia_b", "gpu_big_ov_print_normal_rtx80"); Caption(b, X, bottom + 1, W, L.T("gg.serial.cap")); y = bottom + 16f; y = Text(b, y, L.T("gg.serial.pb"), gap: 4f); y = Text(b, y, L.T("gg.serial.md"), gap: 8f); Text(b, y, L.T("gg.serial.rule"), "body"); } // ---- 7 背面丝印 ---- private static void Page7(PaperUI b) { float y = NewPage(b); y = Section(b, y, L.T("gg.print.h")); string[] kinds = { "normal", "stamp", "skew" }; for (int s = 0; s < 3; s++) { string model = s == 1 ? "rtx60" : "rtx80"; float bottom = Art(b, X + (W - 96f) / 2, y, 0.5f, "gpu_big_probidia_b", $"gpu_big_ov_print_{kinds[s]}_{model}"); Caption(b, X, bottom, W, L.T("gg.print." + s), s == 0 ? null : Bad); y = bottom + 14f; } Text(b, y, L.T("gg.print.note"), "body"); } // ---- 8 卖家与指出 ---- private static void Page8(PaperUI b) { float y = NewPage(b); y = Section(b, y, L.T("gg.seller.h")); y = Text(b, y, T("gg.seller.body"), gap: 8f); y = Section(b, y, L.T("gg.point.h")); Text(b, y, T("gg.point.body"), "body"); } // ---- 9 鉴定示例 ---- private static void Page9(PaperUI b) { float y = NewPage(b); y = Section(b, y, L.T("gg.ex.h")); const int health = 60; var u = new Unit { Model = "RTX80", Health = health, Look = new CardLook { Seed = 0, Fan = 2, Fingers = 1, Sticker = 2 }, }; long baseV = CardValue.Of("RTX80", health); long fan = (long)Math.Round(baseV * Tuning.CardFanChipped), fin = (long)Math.Round(baseV * Tuning.CardFingersDark), seal = (long)Math.Round(baseV * Tuning.CardSealMissing); long total = CardLabels.Value(u, null, 0); var layers = new[] { Front("probidia", 2), "gpu_big_ov_fan_dust1_" + Fans(2), "gpu_big_ov_fan_chip_" + Fans(2), "gpu_big_ov_fin_dim" }; float bottom = Art(b, X + (W - 150f) / 2, y, 0.78f, layers); y = bottom + 6f; y = Text(b, y, L.T("gg.ex.card", "Probidia RTX 80", health), "body", align: "center", gap: 10f); const float lx = X + 38f, lw = W - 76f; y = Text(b, y, L.T("gg.ex.base", baseV), "caption", align: "right", gap: 3f, x: lx, w: lw); y = Text(b, y, L.T("gg.ex.fan", fan), "caption", Bad, "right", 3f, lx, lw); y = Text(b, y, L.T("gg.ex.fin", fin), "caption", Bad, "right", 3f, lx, lw); y = Text(b, y, L.T("gg.ex.seal", seal), "caption", Bad, "right", 6f, lx, lw); b.AddLine(lx, y, lx + lw, y, 1f, "#6A6458"); y += 6f; y = Text(b, y, L.T("gg.ex.total", total), "label", Ink, "right", 10f, lx, lw); Text(b, y, L.T("gg.ex.note"), "caption", Ink, "center"); } } }