Cards: appearance follows wear, hash depends on fan and contacts, seller confrontation
Fan, contact and scratch levels are recomputed from health and a per-card seed, so a card looks older as it wears. Fan and contacts scale hashrate. Each inspection-tray detail now maps to exactly one dimension value. Quoting dealers price the card as if its condition were fine; the player points out fan, contacts, bracket, print or sticker in the tray to take the matching discount or expose a fake (a wrong accusation costs reputation like the vanilla confrontation). Unowned cards show a coarse grade and rated specs. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
68265095a8
commit
f93dbc6eec
9 files changed
+279
-76
No files matched your search
@@ -15,6 +15,10 @@ namespace Decentralized
|
||||
private static GpuInspectView instance;
|
||||
private RectTransform root, cardRoot;
|
||||
private Button flip;
|
||||
private readonly Button[] point = new Button[5];
|
||||
private RectTransform pointRow;
|
||||
private static readonly int[] PointBits = { Items.PointFan, Items.PointFingers, Items.PointBracket, Items.PointPrint, Items.PointSticker };
|
||||
private static readonly string[] PointKeys = { "point.fan", "point.fingers", "point.bracket", "point.print", "point.sticker" };
|
||||
private TextMeshProUGUI flipText;
|
||||
private GameItem shown;
|
||||
private string shownSig;
|
||||
@@ -47,6 +51,7 @@ namespace Decentralized
|
||||
{
|
||||
if (root == null) Build(m);
|
||||
if (!root.gameObject.activeSelf) root.gameObject.SetActive(true);
|
||||
UpdatePointButtons(item);
|
||||
var keys = new List<string>();
|
||||
string sig = Items.LookKeys(item, keys) + "|" + string.Join(",", keys);
|
||||
if (item != shown || sig != shownSig)
|
||||
@@ -64,24 +69,24 @@ namespace Decentralized
|
||||
var o = new List<string>();
|
||||
var u = Items.ToUnit(item);
|
||||
if (u.Dead) return o;
|
||||
var l = u.Look;
|
||||
var l = u.Appearance;
|
||||
string P = "gpu_big_ov_";
|
||||
// 每个维度的取值对应一种细节,一一对应,不靠组合触发
|
||||
if (!Items.IsBack(item))
|
||||
{
|
||||
if (l.Fan == 1) o.Add(P + (l.Fingers > 0 ? "fan_dust2" : "fan_dust1"));
|
||||
if (l.Fan == 2) { o.Add(P + "fan_chip"); if (l.Fingers == 2) o.Add(P + "fan_oil"); }
|
||||
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.Fan > 0) o.Add(P + "shroud_chip"); }
|
||||
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.Print == 2) o.Add(P + "screw_opened"); // 重印过,说明拆开过
|
||||
if (l.Sticker == 1) o.Add(P + "sticker_hand");
|
||||
if (l.Sticker == 2) o.Add(P + (l.Print != 0 ? "seal_reglued" : "seal_missing"));
|
||||
if (l.Sticker == 2) o.Add(P + "seal_missing");
|
||||
}
|
||||
return o;
|
||||
}
|
||||
@@ -94,14 +99,37 @@ namespace Decentralized
|
||||
root.localPosition = Vector3.zero;
|
||||
cardRoot = new GameObject("Card", typeof(RectTransform)).GetComponent<RectTransform>();
|
||||
cardRoot.SetParent(root, false);
|
||||
cardRoot.localPosition = new Vector3(0, 40, 0);
|
||||
cardRoot.localPosition = new Vector3(0, 0, 0);
|
||||
// 翻面按钮:复制鉴定面板里"发送"按钮的外观
|
||||
var template = m.appraisalPanel.transform.Find("buttonSend");
|
||||
if (template != null)
|
||||
{
|
||||
pointRow = new GameObject("PointRow", typeof(RectTransform)).GetComponent<RectTransform>();
|
||||
pointRow.SetParent(root, false);
|
||||
pointRow.localPosition = new Vector3(0, 312, 0);
|
||||
for (int i = 0; i < point.Length; i++)
|
||||
{
|
||||
int bit = PointBits[i];
|
||||
var pb = Instantiate(template.gameObject, pointRow, false);
|
||||
pb.name = "Point" + i;
|
||||
var prt = (RectTransform)pb.transform;
|
||||
prt.sizeDelta = new Vector2(140, 40);
|
||||
prt.localPosition = new Vector3((i - 2) * 148, 0, 0);
|
||||
point[i] = pb.GetComponent<Button>();
|
||||
point[i].onClick = new Button.ButtonClickedEvent();
|
||||
point[i].onClick.AddListener(() => Log.Guard("指出问题", () => OnPoint(bit)));
|
||||
var t = pb.GetComponentInChildren<TextMeshProUGUI>();
|
||||
t.enableAutoSizing = true; t.fontSizeMin = 12; t.fontSizeMax = 26;
|
||||
foreach (var mb in pb.GetComponentsInChildren<MonoBehaviour>(true))
|
||||
{
|
||||
string n = mb.GetType().Name;
|
||||
if (n.Contains("Local") || n.Contains("TMPLocalizer")) Destroy(mb);
|
||||
}
|
||||
t.text = L.T(PointKeys[i]);
|
||||
}
|
||||
var go = Instantiate(template.gameObject, root, false);
|
||||
go.name = "Flip";
|
||||
go.transform.localPosition = new Vector3(0, -300, 0);
|
||||
go.transform.localPosition = new Vector3(0, -290, 0);
|
||||
flip = go.GetComponent<Button>();
|
||||
flip.onClick = new Button.ButtonClickedEvent();
|
||||
flip.onClick.AddListener(() => { if (shown != null) Items.Flip(shown); });
|
||||
@@ -116,6 +144,40 @@ namespace Decentralized
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>对峙:卖家的报价装作成色没问题,玩家逐项指出问题;只在还没买下、卖家在场、卖家报价的卡上出现。</summary>
|
||||
private void UpdatePointButtons(GameItem item)
|
||||
{
|
||||
if (pointRow == null) return;
|
||||
var client = PlayerStore.Instance.currentClientInstance?.GetClientBlueprint();
|
||||
bool show = client != null && client.clientIntent == StoreClient.ClientIntent.SELL && item.IsTag(Items.QuotedTag)
|
||||
&& !GeneralHelper.IsItemOwned(item) && !InspectionUIManager.IsConfrontLocked();
|
||||
pointRow.gameObject.SetActive(show);
|
||||
if (!show) return;
|
||||
for (int i = 0; i < point.Length; i++)
|
||||
if (point[i] != null) point[i].gameObject.SetActive((Items.Pointed(item) & PointBits[i]) == 0);
|
||||
}
|
||||
|
||||
private void OnPoint(int bit)
|
||||
{
|
||||
if (shown == null) return;
|
||||
var item = shown;
|
||||
int kind = Items.Point(item, bit);
|
||||
var d = new Dialogue();
|
||||
if (kind == 0)
|
||||
{
|
||||
d.SetText("CLIENT", L.T("point.none." + Random.Range(1, 3)));
|
||||
InspectionUIManager.Instance.OnConfrontFailed();
|
||||
}
|
||||
else
|
||||
{
|
||||
d.SetText("CLIENT", L.T(kind == 2 ? "point.fake." + Random.Range(1, 3) : "point.worn." + Random.Range(1, 3)));
|
||||
InspectionUIManager.Instance.OnConfrontSuccess();
|
||||
}
|
||||
Log.Info($"对峙:指出 {bit},结果 {kind},报价 {item.unitValue}");
|
||||
DialogUIManager.Instance.StartDialogue(d);
|
||||
InspectionUIManager.Instance.CloseUI();
|
||||
}
|
||||
|
||||
private void Redraw(GameItem item)
|
||||
{
|
||||
foreach (Transform c in cardRoot) Destroy(c.gameObject);
|
||||
|
||||
+56
-11
@@ -21,7 +21,7 @@ namespace Decentralized
|
||||
public const string FakeTag = "PD_FAKE_OF";
|
||||
public const string RefurbTag = "PD_REFURBISHED";
|
||||
public const string DeadTag = "PD_DEAD";
|
||||
public const string LookTag = "PD_LOOK", BackTag = "PD_BACK", QuoteTag = "PD_QUOTE";
|
||||
public const string LookTag = "PD_LOOK", BackTag = "PD_BACK", SeedTag = "PD_SEED", QuotedTag = "PD_QUOTED", MarkupTag = "PD_MARKUP", PointedTag = "PD_POINTED", ExposedTag = "PD_EXPOSED";
|
||||
|
||||
public static Action OpenTerminal;
|
||||
|
||||
@@ -95,13 +95,51 @@ namespace Decentralized
|
||||
return item;
|
||||
}
|
||||
|
||||
/// <summary>贩子的柜台报价:只在物品还不属于玩家时按报价算,归玩家之后立刻只值真实价值。</summary>
|
||||
public static void SetQuote(GameItem item, long quote)
|
||||
/// <summary>贩子的柜台报价:在物品还不属于玩家时,按 <see cref="CardValue.Quote"/> 算(装作成色没问题,玩家指出一项才扣一项);
|
||||
/// 归玩家之后立刻只值真实价值。老实人的卡不报价。</summary>
|
||||
public static void SetQuote(GameItem item, int markupPct)
|
||||
{
|
||||
item.ModifyTag(QuoteTag, t => t.Enable().SetLong(quote));
|
||||
item.EnableTag(QuotedTag);
|
||||
item.ModifyTag(MarkupTag, t => t.Enable().SetInt(markupPct));
|
||||
Refresh(item);
|
||||
}
|
||||
|
||||
public static int Pointed(GameItem item) => item.IsTag(PointedTag) ? item.GetTagReadonly(PointedTag).GetInt() : 0;
|
||||
|
||||
/// <summary>玩家对峙时能指出的五项:风扇、金手指、挡板、丝印、贴纸。</summary>
|
||||
public const int PointFan = 1, PointFingers = 2, PointBracket = 4, PointPrint = 8, PointSticker = 16;
|
||||
|
||||
/// <summary>指出过的项换算成报价里扣价的维度位(丝印、接口数、手写序列号不扣价,只拆穿假货)。</summary>
|
||||
private static int PricedDims(int pointed, CardLook l) =>
|
||||
((pointed & PointFan) != 0 ? CardLook.DimFan : 0) | ((pointed & PointFingers) != 0 ? CardLook.DimFingers : 0)
|
||||
| ((pointed & PointBracket) != 0 ? CardLook.DimScratch : 0) | ((pointed & PointSticker) != 0 ? CardLook.DimSeal : 0);
|
||||
|
||||
/// <summary>这一项有没有问题;返回 0 没问题,1 老化类问题(扣价),2 假货迹象(拆穿假货)。</summary>
|
||||
public static int Defect(GameItem item, int point)
|
||||
{
|
||||
var u = ToUnit(item);
|
||||
var l = u.Appearance;
|
||||
switch (point)
|
||||
{
|
||||
case PointFan: return l.Fan > 0 ? 1 : 0;
|
||||
case PointFingers: return l.Fingers > 0 ? 1 : 0;
|
||||
case PointBracket: return l.Bracket == 2 ? 2 : l.Bracket == 1 ? 1 : 0;
|
||||
case PointPrint: return l.Print != 0 ? 2 : 0;
|
||||
case PointSticker: return l.Sticker == 1 ? 2 : l.Sticker == 2 ? 1 : 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>玩家指出一项:记下来,有问题就按维度扣价或拆穿假货;返回这一项的情况(0 没问题,1 扣价,2 拆穿)。</summary>
|
||||
public static int Point(GameItem item, int point)
|
||||
{
|
||||
int kind = Defect(item, point);
|
||||
item.ModifyTag(PointedTag, t => t.Enable().SetInt(Pointed(item) | point));
|
||||
if (kind == 2) item.EnableTag(ExposedTag);
|
||||
Refresh(item);
|
||||
return kind;
|
||||
}
|
||||
|
||||
/// <summary>说明:算力和耗电按标称写;成色在归玩家之前只给粗略等级(要看外观自己判断),归玩家之后给精确健康度。</summary>
|
||||
private static string Desc(GameItem item, Unit u)
|
||||
{
|
||||
@@ -117,7 +155,10 @@ namespace Decentralized
|
||||
if (item.IsTag(DeadTag)) return;
|
||||
string desc = Desc(item, ToUnit(item));
|
||||
if (item.shortDescription != desc) item.shortDescription = desc;
|
||||
long value = item.IsTag(QuoteTag) && !GeneralHelper.IsItemOwned(item) ? item.GetTagReadonly(QuoteTag).GetLong() : CardValue.True(ToUnit(item));
|
||||
var u = ToUnit(item);
|
||||
long value = item.IsTag(QuotedTag) && !GeneralHelper.IsItemOwned(item)
|
||||
? CardValue.Quote(u, item.IsTag(MarkupTag) ? item.GetTagReadonly(MarkupTag).GetInt() : 0, PricedDims(Pointed(item), u.Appearance), item.IsTag(ExposedTag))
|
||||
: CardValue.True(u);
|
||||
if (item.unitValue != value) item.unitValue = value;
|
||||
}
|
||||
|
||||
@@ -131,6 +172,8 @@ namespace Decentralized
|
||||
u.Refurbished = item.IsTag(RefurbTag);
|
||||
u.Dead = item.IsTag(DeadTag);
|
||||
if (item.IsTag(LookTag)) u.Look = CardLook.Decode(item.GetTagReadonly(LookTag).GetString());
|
||||
// 外观种子:没有记录的卡(正规渠道的新卡)用物品编号当种子,之后同样随损耗变旧
|
||||
u.Look.Seed = item.IsTag(SeedTag) ? (uint)item.GetTagReadonly(SeedTag).GetInt() : (uint)(item.uniqueId * 2654435761u) | 1u;
|
||||
return u;
|
||||
}
|
||||
|
||||
@@ -141,6 +184,7 @@ namespace Decentralized
|
||||
if (u.Refurbished) item.EnableTag(RefurbTag);
|
||||
if (u.Dead) item.EnableTag(DeadTag);
|
||||
item.ModifyTag(LookTag, s => s.Enable().SetString(u.Look.Encode()));
|
||||
item.ModifyTag(SeedTag, s => s.Enable().SetInt((int)u.Look.Seed));
|
||||
Refresh(item);
|
||||
}
|
||||
|
||||
@@ -176,21 +220,22 @@ namespace Decentralized
|
||||
public static string LookKeys(GameItem item, List<string> overlays)
|
||||
{
|
||||
var u = ToUnit(item);
|
||||
var l = u.Appearance;
|
||||
bool back = IsBack(item);
|
||||
string brand = u.Label.Brand == Hardware.Probidia ? "gpu_probidia" : "gpu_pmd";
|
||||
if (u.Dead) return back ? "gpu_burnt_b" : "gpu_burnt_f";
|
||||
if (!back)
|
||||
{
|
||||
if (u.Look.Fan > 0) overlays.Add("gpu_ov_fan" + u.Look.Fan);
|
||||
if (u.Look.Fingers > 0) overlays.Add("gpu_ov_fin" + u.Look.Fingers);
|
||||
if (u.Look.Bracket > 0) overlays.Add("gpu_ov_brk" + u.Look.Bracket);
|
||||
if (l.Fan > 0) overlays.Add("gpu_ov_fan" + l.Fan);
|
||||
if (l.Fingers > 0) overlays.Add("gpu_ov_fin" + l.Fingers);
|
||||
if (l.Bracket > 0) overlays.Add("gpu_ov_brk" + l.Bracket);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 丝印:0 标称型号;1 印的是真实芯片型号;2 标称型号但字歪(重印过)
|
||||
string model = u.Look.Print == 1 && u.FakeOf != null ? u.FakeOf : u.Model;
|
||||
overlays.Add((u.Look.Print == 2 ? "gpu_ov_txs_" : "gpu_ov_txt_") + model.ToLowerInvariant());
|
||||
if (u.Look.Sticker > 0) overlays.Add("gpu_ov_stk" + u.Look.Sticker);
|
||||
string model = l.Print == 1 && u.FakeOf != null ? u.FakeOf : u.Model;
|
||||
overlays.Add((l.Print == 2 ? "gpu_ov_txs_" : "gpu_ov_txt_") + model.ToLowerInvariant());
|
||||
if (l.Sticker > 0) overlays.Add("gpu_ov_stk" + l.Sticker);
|
||||
}
|
||||
return brand + (back ? "_b" : "_f");
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ mat.common_electronic,Common electronics
|
||||
mat.rare_electronic,Rare electronics
|
||||
rig.label.cards,Graphics cards
|
||||
item.gpu.name,{0} {1}
|
||||
item.gpu.desc,"Hashrate {0} MH/s. Draws {1} kJ per day. Condition: {2}."
|
||||
item.gpu.desc,"Rated hashrate {0} MH/s. Rated draw {1} kJ per day. Condition: {2}."
|
||||
item.gpu.burnt.name,Burnt {0} {1}
|
||||
item.gpu.burnt.desc,This card no longer works.
|
||||
term.title,Trading Terminal
|
||||
@@ -72,3 +72,14 @@ item.gpu.grade.1,"like new"
|
||||
item.gpu.grade.2,"decent"
|
||||
item.gpu.grade.3,"clearly worn"
|
||||
item.gpu.grade.4,"badly worn"
|
||||
point.fan,"Point out fan"
|
||||
point.fingers,"Point out contacts"
|
||||
point.bracket,"Point out bracket"
|
||||
point.print,"Point out print"
|
||||
point.sticker,"Point out sticker"
|
||||
point.none.1,"There's nothing wrong with it. You misread it."
|
||||
point.none.2,"Where? Don't make things up if you're not buying."
|
||||
point.worn.1,"...Fine, it has that. I'll knock something off."
|
||||
point.worn.2,"Sharp eyes. All right, this price."
|
||||
point.fake.1,"...You caught it. It's a reflashed one. I'll price it for what it is."
|
||||
point.fake.2,"Tch, not the original chip. Fine, the real chip's price."
|
||||
|
@@ -13,7 +13,7 @@ mat.common_electronic,普通电子元件
|
||||
mat.rare_electronic,稀有电子元件
|
||||
rig.label.cards,显卡
|
||||
item.gpu.name,{0} {1}
|
||||
item.gpu.desc,算力 {0} MH/s。耗电 {1} kJ/天。成色 {2}。
|
||||
item.gpu.desc,标称算力 {0} MH/s。标称耗电 {1} kJ/天。成色 {2}。
|
||||
item.gpu.burnt.name,烧毁的 {0} {1}
|
||||
item.gpu.burnt.desc,此显卡已无法工作。
|
||||
term.title,交易终端
|
||||
@@ -71,3 +71,14 @@ item.gpu.grade.1,"接近全新"
|
||||
item.gpu.grade.2,"成色尚可"
|
||||
item.gpu.grade.3,"明显旧了"
|
||||
item.gpu.grade.4,"磨损严重"
|
||||
point.fan,"指出风扇"
|
||||
point.fingers,"指出金手指"
|
||||
point.bracket,"指出挡板"
|
||||
point.print,"指出丝印"
|
||||
point.sticker,"指出贴纸"
|
||||
point.none.1,"没毛病,你看错了吧。"
|
||||
point.none.2,"这儿哪有问题?不买别瞎说。"
|
||||
point.worn.1,"……行,这点毛病是有,算你便宜点。"
|
||||
point.worn.2,"眼睛挺毒。好吧,这个价。"
|
||||
point.fake.1,"……被你看出来了。是翻新的货,按真的价给你。"
|
||||
point.fake.2,"啧,不是原装的。那就按芯片本来的价吧。"
|
||||
|
@@ -36,7 +36,9 @@ namespace Decentralized
|
||||
ModHelper.AddDebugFunction(ModInfo.Id, ModInfo.Name, "Used dealer visit (test)", () => Log.Guard("二手贩子(测试)", () =>
|
||||
{
|
||||
var m = PlayerStore.Instance.storeClientManager;
|
||||
m.AddNextClient(Create(DealerKind.Scammer, 40));
|
||||
var c = Create(DealerKind.Scammer, 40);
|
||||
PlayerStore.Instance.currentClientInstance = StoreClientInstance.CreateClientInstance(c); // 测试:直接换成这位贩子
|
||||
Place(DealerKind.Scammer, UsedDealers.Cards(DealerKind.Scammer, 40, Session.Data.Rng), Session.Data.Rng);
|
||||
Log.Info($"二手贩子(测试)已排入,队列 {m.clientStack.Count} 人");
|
||||
}));
|
||||
}
|
||||
@@ -85,22 +87,24 @@ namespace Decentralized
|
||||
};
|
||||
StoreClientFactionSetup.InitScav(client);
|
||||
client.mainDialogue.SetText(client.displayName, L.T("dealer.line1." + rng.Range(1, 3))).NextDialogue().SetText(client.displayName, L.T("dealer.line2." + rng.Range(1, 3)))
|
||||
.SetEndAction(() => Log.Guard("二手贩子上货", () =>
|
||||
.SetEndAction(() => Place(kind, cards, rng));
|
||||
client.AddBasicDialog();
|
||||
client.CompleteClientCreation();
|
||||
return client;
|
||||
}
|
||||
|
||||
private static void Place(DealerKind kind, List<UsedCard> cards, Rng rng) => Log.Guard("二手贩子上货", () =>
|
||||
{
|
||||
NegociationUIManager.Instance.OpenUI();
|
||||
var placed = new List<string>();
|
||||
foreach (var c in cards)
|
||||
{
|
||||
var item = Items.NewCard(c.Model, c.Health, c.FakeOf, rng);
|
||||
if (c.MarkupPct != 0) Items.SetQuote(item, (long)Math.Round(CardValue.True(Items.ToUnit(item)) * (1 + c.MarkupPct / 100.0)));
|
||||
if (c.Quoted) Items.SetQuote(item, c.MarkupPct);
|
||||
PlayerStore.Instance.AddDirectSellingItemToTable(item);
|
||||
placed.Add($"{c.Model}{(c.FakeOf != null ? "←" + c.FakeOf : "")} {c.Health:0}% {c.MarkupPct:+0;-0}% 柜台价 {item.unitValue}(真实 {CardValue.True(Items.ToUnit(item))})");
|
||||
placed.Add($"{c.Model}{(c.FakeOf != null ? "←" + c.FakeOf : "")} {c.Health:0}% {(c.Quoted ? c.MarkupPct + "%" : "不报价")} 柜台价 {item.unitValue}(真实 {CardValue.True(Items.ToUnit(item))})");
|
||||
}
|
||||
Log.Info($"二手贩子({kind}):{string.Join(",", placed)}");
|
||||
}));
|
||||
client.AddBasicDialog();
|
||||
client.CompleteClientCreation();
|
||||
return client;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user