Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
321 lines
18 KiB
C#
321 lines
18 KiB
C#
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using Decentralized.Domain;
|
|
using TMPro;
|
|
using UnityEngine;
|
|
using UnityEngine.UI;
|
|
|
|
namespace Decentralized
|
|
{
|
|
/// <summary>
|
|
/// 放大镜检视显卡:和香烟一样把物品放大摆在检视台上,正反面各一张,下面是标签栏。
|
|
/// 标签栏每行一个标签(型号、风扇、金手指、挡板、丝印、贴纸):左边是卖家的说法,右边的按钮"指出"——
|
|
/// 说法属实就没事,隐瞒了问题价格回到真实情况,指错了掉信誉。归自己的卡,有标签机时按钮改成"改",随便改说法。
|
|
/// 游戏没有给 mod 开放检视界面,这里不打补丁,只在检视台(InspectionUIManager.panel)下面挂一个自己的界面,
|
|
/// 用一个常驻的 MonoBehaviour 每帧看一眼是否正在检视显卡。
|
|
/// </summary>
|
|
public sealed class GpuInspectView : MonoBehaviour
|
|
{
|
|
private const float RowH = 26f, CardScale = 3f, PanelTop = -112f, DetailH = 26f, GroupGap = 7f;
|
|
private static readonly int RowCount = CardLabels.Rows.Length;
|
|
private TextMeshProUGUI priceText;
|
|
private static GpuInspectView instance;
|
|
private RectTransform root, cardRoot;
|
|
private Button flip;
|
|
private TextMeshProUGUI flipText;
|
|
private GameItem shown;
|
|
private string shownSig;
|
|
private bool hidGlass;
|
|
private Transform buttonTemplate;
|
|
private TMP_FontAsset font;
|
|
private readonly TextMeshProUGUI[] rowName = new TextMeshProUGUI[16], rowText = new TextMeshProUGUI[16];
|
|
private readonly Button[] rowButton = new Button[16];
|
|
private readonly TextMeshProUGUI[] rowButtonText = new TextMeshProUGUI[16];
|
|
private TextMeshProUGUI serialText;
|
|
|
|
public static void Ensure()
|
|
{
|
|
if (instance != null) return;
|
|
var go = new GameObject("pd_gpu_inspect_view");
|
|
DontDestroyOnLoad(go);
|
|
instance = go.AddComponent<GpuInspectView>();
|
|
}
|
|
|
|
public static void Remove()
|
|
{
|
|
if (instance != null) Destroy(instance.gameObject);
|
|
instance = null;
|
|
}
|
|
|
|
private void Update()
|
|
{
|
|
var m = InspectionUIManager.Instance;
|
|
var item = m != null && m.panel != null && m.panel.activeSelf ? m.currentInspectingItem : null;
|
|
if (item == null || !Items.IsGpu(item))
|
|
{
|
|
if (root != null && root.gameObject.activeSelf) root.gameObject.SetActive(false);
|
|
shown = null;
|
|
if (hidGlass && m != null && m.magnifyingGlass != null) { m.magnifyingGlass.SetActive(true); hidGlass = false; }
|
|
return;
|
|
}
|
|
Log.Guard("显卡检视大图", () =>
|
|
{
|
|
if (root == null) Build(m);
|
|
if (!root.gameObject.activeSelf) root.gameObject.SetActive(true);
|
|
// 检视台自带的放大镜图标会压在大图下面,显卡用不着
|
|
if (m.magnifyingGlass != null && m.magnifyingGlass.activeSelf) { m.magnifyingGlass.SetActive(false); hidGlass = true; }
|
|
var keys = new List<string>();
|
|
string sig = Items.LookKeys(item, keys) + "|" + string.Join(",", keys) + "|" + item.unitValue + "|" + Items.Exposed(item) + "|" + (Items.GetClaim(item)?.Encode() ?? "") + "|" + GeneralHelper.IsItemOwned(item) + "|" + HasLabeler();
|
|
if (item != shown || sig != shownSig)
|
|
{
|
|
shown = item;
|
|
shownSig = sig;
|
|
Redraw(item);
|
|
UpdateRows(item);
|
|
}
|
|
});
|
|
}
|
|
|
|
/// <summary>检视台上的叠加层:每个标签的每个取值对应一种细节,一一对应。</summary>
|
|
public static List<string> BigOverlays(GameItem item)
|
|
{
|
|
var o = new List<string>();
|
|
var u = Items.ToUnit(item);
|
|
if (u.Dead) return o;
|
|
var l = u.Appearance;
|
|
string P = "gpu_big_ov_";
|
|
if (!Items.IsBack(item))
|
|
{
|
|
int n = Items.FansOf(u);
|
|
if (l.Fan == 1) o.Add(P + "fan_dust2_" + n);
|
|
if (l.Fan == 2) { o.Add(P + "fan_dust1_" + n); o.Add(P + "fan_chip_" + n); }
|
|
if (l.Fingers == 1) o.Add(P + "fin_dim");
|
|
if (l.Fingers == 2) o.Add(P + "fin_oxide");
|
|
if (l.Bracket == 1) o.Add(P + "brk_scratch");
|
|
}
|
|
else
|
|
{
|
|
string model = (l.Print == 1 && u.FakeOf != null ? u.FakeOf : u.Model).ToLowerInvariant();
|
|
o.Add(P + "print_" + (l.Print == 2 ? "skew_" : l.Print == 1 ? "stamp_" : "normal_") + model);
|
|
if (l.Sticker == 2) o.Add(P + "seal_missing");
|
|
}
|
|
return o;
|
|
}
|
|
|
|
private static bool HasLabeler() => EmporiumEntry.Instance != null && EmporiumEntry.Instance.GetAllOwnedItemsByTag("LABELER_TAG").Count > 0;
|
|
|
|
private static void MakeRect(Transform parent, string name, Vector2 pos, Vector2 size, Color color)
|
|
{
|
|
var go = new GameObject(name, typeof(RectTransform), typeof(Image));
|
|
var rt = go.GetComponent<RectTransform>();
|
|
rt.SetParent(parent, false);
|
|
rt.sizeDelta = size;
|
|
rt.localPosition = pos;
|
|
var img = go.GetComponent<Image>();
|
|
img.color = color;
|
|
img.raycastTarget = false;
|
|
}
|
|
|
|
private TextMeshProUGUI MakeText(Transform parent, string name, Vector2 pos, Vector2 size, TextAlignmentOptions align, float fontSize)
|
|
{
|
|
var go = new GameObject(name, typeof(RectTransform), typeof(TextMeshProUGUI));
|
|
var rt = go.GetComponent<RectTransform>();
|
|
rt.SetParent(parent, false);
|
|
rt.sizeDelta = size;
|
|
rt.localPosition = pos;
|
|
var t = go.GetComponent<TextMeshProUGUI>();
|
|
if (font != null) t.font = font;
|
|
t.fontSize = fontSize;
|
|
t.alignment = align;
|
|
t.enableWordWrapping = false;
|
|
t.overflowMode = TextOverflowModes.Ellipsis;
|
|
t.raycastTarget = false;
|
|
return t;
|
|
}
|
|
|
|
private Button MakeButton(Transform parent, string name, Vector2 pos, Vector2 size, out TextMeshProUGUI label)
|
|
{
|
|
var go = Instantiate(buttonTemplate.gameObject, parent, false);
|
|
go.name = name;
|
|
var rt = (RectTransform)go.transform;
|
|
rt.sizeDelta = size;
|
|
rt.localPosition = pos;
|
|
var b = go.GetComponent<Button>();
|
|
b.onClick = new Button.ButtonClickedEvent();
|
|
label = go.GetComponentInChildren<TextMeshProUGUI>();
|
|
label.enableAutoSizing = true; label.fontSizeMin = 12; label.fontSizeMax = 24;
|
|
// 去掉复制来的按钮上驱动文字的本地化组件,文字由这里设置
|
|
foreach (var mb in go.GetComponentsInChildren<MonoBehaviour>(true))
|
|
{
|
|
string n = mb.GetType().Name;
|
|
if (mb is Button || mb is Image || mb is TextMeshProUGUI) continue;
|
|
if (n.Contains("Local") || n.Contains("Appraisal") || n.Contains("Send")) Destroy(mb);
|
|
}
|
|
return b;
|
|
}
|
|
|
|
private void Build(InspectionUIManager m)
|
|
{
|
|
root = new GameObject("_PdGpuInspection", typeof(RectTransform)).GetComponent<RectTransform>();
|
|
root.SetParent(m.panel.transform, false);
|
|
root.sizeDelta = new Vector2(1920, 1080);
|
|
root.localPosition = Vector3.zero;
|
|
cardRoot = new GameObject("Card", typeof(RectTransform)).GetComponent<RectTransform>();
|
|
cardRoot.SetParent(root, false);
|
|
cardRoot.localPosition = new Vector3(0, 85, 0);
|
|
buttonTemplate = m.appraisalPanel.transform.Find("buttonSend");
|
|
// 贴纸上的序列号:随卡变化,运行时用文字写在贴纸的空白处
|
|
serialText = MakeText(root, "Serial", Vector2.zero, new Vector2(48 * CardScale, 8 * CardScale), TextAlignmentOptions.Center, 20);
|
|
serialText.color = new Color(0.1f, 0.1f, 0.13f);
|
|
serialText.fontStyle = FontStyles.Bold;
|
|
serialText.overflowMode = TextOverflowModes.Overflow;
|
|
serialText.rectTransform.sizeDelta = new Vector2(48 * CardScale, 14 * CardScale);
|
|
if (buttonTemplate == null) return;
|
|
font = buttonTemplate.GetComponentInChildren<TextMeshProUGUI>()?.font;
|
|
if (font != null) serialText.font = font;
|
|
// 标签区:深色底加浅色边框,和上面的大图分开。三组:四项真伪依据、型号、四项成色,最下面一行是标签价
|
|
float panelH = RowCount * RowH + 10f + DetailH + GroupGap * 2, panelY = PanelTop - panelH / 2;
|
|
MakeRect(root, "LabelFrame", new Vector2(0, panelY), new Vector2(648, panelH + 8), new Color(0.78f, 0.68f, 0.45f, 1f));
|
|
MakeRect(root, "LabelBack", new Vector2(0, panelY), new Vector2(640, panelH), new Color(0.10f, 0.11f, 0.14f, 0.96f));
|
|
MakeRect(root, "LabelDivider1", new Vector2(0, GroupTop(CardLabels.AuthRows) + GroupGap / 2 + 1), new Vector2(620, 2), new Color(0.78f, 0.68f, 0.45f, 0.8f));
|
|
MakeRect(root, "LabelDivider2", new Vector2(0, GroupTop(CardLabels.AuthRows + 1) + GroupGap / 2 + 1), new Vector2(620, 2), new Color(0.78f, 0.68f, 0.45f, 0.5f));
|
|
MakeRect(root, "LabelDivider3", new Vector2(0, PanelTop - 5 - RowH * RowCount - GroupGap * 2 - 1), new Vector2(620, 2), new Color(0.78f, 0.68f, 0.45f, 0.8f));
|
|
priceText = MakeText(root, "LabelPrice", new Vector2(165, PanelTop - 5 - RowH * RowCount - GroupGap * 2 - DetailH / 2 - 2), new Vector2(300, DetailH), TextAlignmentOptions.Right, 21);
|
|
priceText.color = new Color(1f, 0.85f, 0.45f);
|
|
PledgerSkin.Fit(priceText);
|
|
flip = MakeButton(root, "Flip", new Vector2(0, PanelTop - panelH - 24), new Vector2(200, 30), out flipText);
|
|
flip.onClick.AddListener(() => { if (shown != null) Items.Flip(shown); });
|
|
for (int i = 0; i < RowCount; i++)
|
|
{
|
|
int row = CardLabels.Rows[i];
|
|
float y = GroupTop(i) - RowH / 2;
|
|
rowName[i] = MakeText(root, "RowName" + i, new Vector2(-232, y), new Vector2(156, RowH), TextAlignmentOptions.Left, 20);
|
|
rowText[i] = MakeText(root, "RowText" + i, new Vector2(24, y), new Vector2(300, RowH), TextAlignmentOptions.Left, 20);
|
|
rowButton[i] = MakeButton(root, "RowButton" + i, new Vector2(245, y), new Vector2(110, RowH - 3), out rowButtonText[i]);
|
|
rowButton[i].onClick.AddListener(() => Log.Guard("标签栏", () => OnRowButton(row)));
|
|
}
|
|
}
|
|
|
|
/// <summary>第 i 行的上沿(三组之间各留一道间隔)。</summary>
|
|
private static float GroupTop(int i) => PanelTop - 5 - RowH * i - (i >= CardLabels.AuthRows ? GroupGap : 0f) - (i >= CardLabels.AuthRows + 1 ? GroupGap : 0f);
|
|
|
|
private static string RowKey(int row) => row == CardLabels.RowSpec ? "spec" : row == CardLabels.RowCount ? "count" : row == CardLabels.RowPin ? "pin" : row == CardLabels.RowFan ? "fan" : row == CardLabels.RowFingers ? "fingers"
|
|
: row == CardLabels.RowBracket ? "bracket" : row == CardLabels.RowPrint ? "print" : row == CardLabels.RowSerial ? "serial" : "seal";
|
|
|
|
private static string ValueText(int row, int value) => row == CardLabels.RowSpec ? "" : L.T("lbl." + RowKey(row) + "." + (row == CardLabels.RowSeal && value == 1 ? 0 : value));
|
|
|
|
private static string RowName(int row) => L.T("lbl.row." + RowKey(row));
|
|
|
|
/// <summary>现场有没有卖家 / 鉴定客户在:决定"指出"按钮是否出现。</summary>
|
|
private static bool CanPoint(GameItem item)
|
|
{
|
|
var client = PlayerStore.Instance.currentClientInstance?.GetClientBlueprint();
|
|
if (client == null || GeneralHelper.IsItemOwned(item)) return false;
|
|
if (client.clientIntent == StoreClient.ClientIntent.APPRAISAL_SERVICE) return true;
|
|
return client.clientIntent == StoreClient.ClientIntent.SELL && !InspectionUIManager.IsConfrontLocked();
|
|
}
|
|
|
|
private void UpdateRows(GameItem item)
|
|
{
|
|
if (rowName[0] == null) return;
|
|
var u = Items.ToUnit(item);
|
|
var real = Claim.Truth(u);
|
|
var claim = Items.GetClaim(item);
|
|
int exposed = Items.Exposed(item);
|
|
bool owned = GeneralHelper.IsItemOwned(item);
|
|
bool canPoint = CanPoint(item), canEdit = owned && HasLabeler();
|
|
for (int i = 0; i < RowCount; i++)
|
|
{
|
|
int row = CardLabels.Rows[i];
|
|
rowName[i].text = RowName(row);
|
|
rowName[i].color = i < CardLabels.AuthRows ? new Color(1f, 0.85f, 0.45f) : new Color(0.6f, 0.85f, 1f); // 真伪行金黄,成色行浅蓝
|
|
rowName[i].fontSize = 20; PledgerSkin.Fit(rowName[i]); PledgerSkin.Fit(rowText[i]);
|
|
string text;
|
|
Color color = new Color(0.96f, 0.98f, 1f);
|
|
string Show(Claim c) => row == CardLabels.RowSpec ? DisplayModel(c.Model) : ValueText(row, c.Get(row));
|
|
if (claim == null) text = Show(real);
|
|
else if ((exposed & row) != 0)
|
|
{
|
|
text = Show(real);
|
|
bool lied = row == CardLabels.RowSpec ? claim.Value.Model != real.Model : claim.Value.Get(row) != real.Get(row);
|
|
if (lied) color = new Color(1f, 0.45f, 0.4f); // 卖家的说法只在机制里起作用,面板上不写
|
|
else color = new Color(0.5f, 1f, 0.55f);
|
|
}
|
|
else text = Show(claim.Value);
|
|
rowText[i].text = text;
|
|
rowText[i].color = color;
|
|
bool editable = row == CardLabels.RowSpec || !CardLabels.IsAuth(row);
|
|
bool hasButton = row == CardLabels.RowSpec || !CardLabels.IsAuth(row);
|
|
rowButton[i].gameObject.SetActive(hasButton && (canEdit ? editable : canPoint && (exposed & row) == 0));
|
|
rowButtonText[i].text = L.T(canEdit ? "lbl.edit" : row == CardLabels.RowSpec ? "lbl.point.model" : "lbl.point");
|
|
}
|
|
if (priceText != null) priceText.text = L.T("lbl.price", item.unitValue);
|
|
if (flipText != null) flipText.text = L.T(Items.IsBack(item) ? "inspect.flip.front" : "inspect.flip.back");
|
|
// 贴纸上的序列号:只在背面显示
|
|
if (serialText != null)
|
|
{
|
|
serialText.gameObject.SetActive(Items.IsBack(item) && !u.Dead);
|
|
serialText.text = CardLabels.SerialText(u);
|
|
// 贴纸空白处中心在大图 (142, 91):卡片坐标以图中心为原点,y 向上
|
|
serialText.rectTransform.localPosition = new Vector3((142 - 96) * CardScale, 85 + (64 - 91) * CardScale, 0);
|
|
}
|
|
}
|
|
|
|
private static string DisplayModel(string model) => Items.DisplayModel(model);
|
|
|
|
private void OnRowButton(int row)
|
|
{
|
|
if (shown == null) return;
|
|
var item = shown;
|
|
if (GeneralHelper.IsItemOwned(item))
|
|
{
|
|
// 标签机:改说法,每按一次换下一个取值(型号是在八个型号里轮换)
|
|
var u = Items.ToUnit(item);
|
|
var cur = Items.GetClaim(item) ?? Claim.Truth(u);
|
|
if (row == CardLabels.RowSpec)
|
|
{
|
|
var models = Hardware.Specs.Values.Where(s => !s.Asic && !s.Scrap).Select(s => s.Model).ToList();
|
|
Items.EditClaim(item, row, 0, models[(models.IndexOf(cur.Model) + 1) % models.Count]);
|
|
}
|
|
else Items.EditClaim(item, row, row == CardLabels.RowSeal ? (cur.Get(row) == 0 ? 2 : 0) : row == CardLabels.RowBracket ? (cur.Get(row) + 1) % 2 : (cur.Get(row) + 1) % 3);
|
|
EmporiumAudioPlayer.Instance?.audioLabeler?.Play();
|
|
return;
|
|
}
|
|
int kind = Items.Point(item, row);
|
|
Log.Info($"指出 {row},结果 {kind},报价 {item.unitValue}");
|
|
if (kind == 0)
|
|
{
|
|
InspectionUIManager.Instance.OnConfrontFailed();
|
|
DialogUIManager.Instance.StartDialogue(new Dialogue().SetText("CLIENT", L.T("point.none." + Random.Range(1, 3))));
|
|
}
|
|
else
|
|
{
|
|
InspectionUIManager.Instance.OnConfrontSuccess();
|
|
DialogUIManager.Instance.StartDialogue(new Dialogue().SetText("CLIENT", L.T(kind == 2 ? "point.fake." + Random.Range(1, 3) : "point.worn." + Random.Range(1, 3))));
|
|
}
|
|
}
|
|
|
|
private void Redraw(GameItem item)
|
|
{
|
|
foreach (Transform c in cardRoot) Destroy(c.gameObject);
|
|
var keys = new List<string>();
|
|
string baseKey = Items.LookKeys(item, keys); // 小图键名只用来取底图(品牌与正反面)
|
|
var layers = new List<string> { "gpu_big_" + baseKey.Substring("gpu_".Length) };
|
|
layers.AddRange(BigOverlays(item));
|
|
foreach (var k in layers)
|
|
{
|
|
var sprite = Items.ModSprite(k);
|
|
if (sprite == null) continue;
|
|
var go = new GameObject(k, typeof(RectTransform), typeof(Image));
|
|
var rt = go.GetComponent<RectTransform>();
|
|
rt.SetParent(cardRoot, false);
|
|
rt.sizeDelta = new Vector2(sprite.rect.width, sprite.rect.height);
|
|
rt.localScale = Vector3.one * CardScale;
|
|
var img = go.GetComponent<Image>();
|
|
img.sprite = sprite;
|
|
img.raycastTarget = false;
|
|
}
|
|
}
|
|
}
|
|
}
|