Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
292 lines
15 KiB
C#
292 lines
15 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 = 30f, CardScale = 3f;
|
|
private static GpuInspectView instance;
|
|
private RectTransform root, cardRoot;
|
|
private Button flip;
|
|
private TextMeshProUGUI flipText;
|
|
private GameItem shown;
|
|
private string shownSig;
|
|
private Transform buttonTemplate;
|
|
private TMP_FontAsset font;
|
|
private readonly TextMeshProUGUI[] rowName = new TextMeshProUGUI[6], rowText = new TextMeshProUGUI[6];
|
|
private readonly Button[] rowButton = new Button[6];
|
|
private readonly TextMeshProUGUI[] rowButtonText = new TextMeshProUGUI[6];
|
|
|
|
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;
|
|
return;
|
|
}
|
|
Log.Guard("显卡检视大图", () =>
|
|
{
|
|
if (root == null) Build(m);
|
|
if (!root.gameObject.activeSelf) root.gameObject.SetActive(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))
|
|
{
|
|
if (l.Fan == 1) o.Add(P + "fan_dust2");
|
|
if (l.Fan == 2) { o.Add(P + "fan_dust1"); o.Add(P + "fan_chip"); }
|
|
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");
|
|
if (l.Bracket == 2) o.Add(P + "brk_port");
|
|
}
|
|
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 == 1) o.Add(P + "sticker_hand");
|
|
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 || mb is CanvasRenderer) 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");
|
|
if (buttonTemplate == null) return;
|
|
font = buttonTemplate.GetComponentInChildren<TextMeshProUGUI>()?.font;
|
|
// 标签区:深色底加浅色边框,和上面的大图分开
|
|
MakeRect(root, "LabelFrame", new Vector2(0, -201), new Vector2(648, 194), new Color(0.78f, 0.68f, 0.45f, 1f));
|
|
MakeRect(root, "LabelBack", new Vector2(0, -201), new Vector2(640, 186), new Color(0.10f, 0.11f, 0.14f, 0.96f));
|
|
flip = MakeButton(root, "Flip", new Vector2(0, -326), new Vector2(200, 34), out flipText);
|
|
flip.onClick.AddListener(() => { if (shown != null) Items.Flip(shown); });
|
|
for (int i = 0; i < 6; i++)
|
|
{
|
|
int row = CardLabels.Rows[i];
|
|
float y = -126 - i * RowH;
|
|
rowName[i] = MakeText(root, "RowName" + i, new Vector2(-255, y), new Vector2(110, RowH), TextAlignmentOptions.Left, 24);
|
|
rowText[i] = MakeText(root, "RowText" + i, new Vector2(-30, y), new Vector2(330, RowH), TextAlignmentOptions.Left, 24);
|
|
rowButton[i] = MakeButton(root, "RowButton" + i, new Vector2(245, y), new Vector2(110, RowH - 4), out rowButtonText[i]);
|
|
rowButton[i].onClick.AddListener(() => Log.Guard("标签栏", () => OnRowButton(row)));
|
|
}
|
|
}
|
|
|
|
private static string ValueText(int row, int value) =>
|
|
row == CardLabels.RowSpec ? "" : L.T("lbl." + (row == CardLabels.RowFan ? "fan" : row == CardLabels.RowFingers ? "fingers" : row == CardLabels.RowBracket ? "bracket" : row == CardLabels.RowPrint ? "print" : "sticker") + "." + value);
|
|
|
|
private static string RowName(int row) => L.T("lbl.row." + (row == CardLabels.RowSpec ? "spec" : row == CardLabels.RowFan ? "fan" : row == CardLabels.RowFingers ? "fingers" : row == CardLabels.RowBracket ? "bracket" : row == CardLabels.RowPrint ? "print" : "sticker"));
|
|
|
|
/// <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 < 6; i++)
|
|
{
|
|
int row = CardLabels.Rows[i];
|
|
rowName[i].text = RowName(row);
|
|
rowName[i].color = new Color(1f, 0.85f, 0.45f);
|
|
string text;
|
|
Color color = new Color(0.96f, 0.98f, 1f);
|
|
if (claim == null)
|
|
{
|
|
text = row == CardLabels.RowSpec ? DisplayModel(real.Model) : ValueText(row, real.Get(row));
|
|
}
|
|
else if ((exposed & row) != 0)
|
|
{
|
|
int r = row == CardLabels.RowSpec ? 0 : real.Get(row);
|
|
text = row == CardLabels.RowSpec ? DisplayModel(real.Model) : ValueText(row, r);
|
|
bool lied = row == CardLabels.RowSpec ? claim.Value.Model != real.Model : claim.Value.Get(row) != r;
|
|
if (lied) { color = new Color(1f, 0.45f, 0.4f); text += L.T("lbl.said", row == CardLabels.RowSpec ? DisplayModel(claim.Value.Model) : ValueText(row, claim.Value.Get(row))); }
|
|
else color = new Color(0.5f, 1f, 0.55f);
|
|
}
|
|
else
|
|
{
|
|
text = row == CardLabels.RowSpec ? DisplayModel(claim.Value.Model) : ValueText(row, claim.Value.Get(row));
|
|
}
|
|
rowText[i].text = text;
|
|
rowText[i].color = color;
|
|
bool show = row != CardLabels.RowSpec && ((canPoint && claim != null && (exposed & row) == 0) || canEdit || (canPoint && claim == null));
|
|
bool point = canPoint && !canEdit;
|
|
rowButton[i].gameObject.SetActive(row != CardLabels.RowSpec ? ((canPoint && (exposed & row) == 0) || canEdit) : canEdit);
|
|
rowButtonText[i].text = L.T(canEdit ? "lbl.edit" : "lbl.point");
|
|
}
|
|
if (flipText != null) flipText.text = L.T(Items.IsBack(item) ? "inspect.flip.front" : "inspect.flip.back");
|
|
}
|
|
|
|
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).Select(s => s.Model).ToList();
|
|
Items.EditClaim(item, row, 0, models[(models.IndexOf(cur.Model) + 1) % models.Count]);
|
|
}
|
|
else Items.EditClaim(item, row, (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;
|
|
}
|
|
}
|
|
}
|
|
}
|