Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
45f73580a5 | ||
|
|
e95588a76f | ||
|
|
00f7811e7c | ||
|
|
a2dc1dd2f6 | ||
|
|
9441d4d1bd | ||
|
|
ce95ec923d | ||
|
|
0560c34231 | ||
|
|
967077a2db | ||
|
|
4b10c6ad7f | ||
|
|
9165c9edf4 | ||
|
|
393dd8057a | ||
|
|
f564a895d0 | ||
|
|
d6a456c986 | ||
|
|
559a702ee3 |
No files matched your search
@@ -2,6 +2,19 @@
|
||||
|
||||
## 未发布
|
||||
|
||||
### 第一阶段完成(入门、挖矿、显卡鉴定、终端)
|
||||
|
||||
以下条目取代下面较早条目里已过时的部分(型号、入口价、终端页面):
|
||||
|
||||
- 显卡改成每个牌子四档(低端、中端、高端、旗舰):Probidia RZX 60 / 70 / 80 / 90,PMD RZ 70 / 80 / 800 / 900;型号只是显示名,性能由牌子和档次定;档次对应供电口(6 / 8 / 8 / 8+8 针)和风扇数(1 / 2 / 3 / 3)。显卡 3×2 格,正反面各一张图;价格约为最初的 1.8 倍,出块奖励 90。
|
||||
- 显卡外观:叶片、金手指、挡板、封条,随每晚损耗变差,影响价值、算力、损耗、暴毙。
|
||||
- 二手卡贩子(老实人、中间商、骗子)、假卡(低档芯片套高档壳)、序列号(牌子前缀、档次码、工厂码、流水)、背面丝印、鉴定公会收显卡、显卡鉴定手册。
|
||||
- 放大镜检视显卡:专门的大图和标签栏(型号真伪加叶片、金手指、挡板、封条四项成色),卖家说法与逐项指出、标签机改说法、买家验货与按价差扣信誉。
|
||||
- 减半抬高价格锚(传导 0.6,减半前一周有预期行情,减半前 3 天上报纸)。
|
||||
- 交易终端重做:首页三个入口(银行、Poinbase、Web3)加报告;左右分栏表格;买入均价;Web3 页的稳定币价格与状态;银行提取前的审查提示;金额输入框加宽。
|
||||
- 入口升级价 400,开局送交易终端、矿架、RZX 60 和鉴定手册。
|
||||
- 经济模拟 v7(运维库 research/sim/out_v7.md):电网电价下 RZX 70 挖矿第 30 天占总收入 18%、第 60 天 20%。
|
||||
|
||||
- 纯逻辑层:带种子的随机数、资产登记、地址账本(污点与成本随币走)、恒定乘积交易对、挖矿(显卡、矿机、全网算力、减半、损耗与暴毙)。
|
||||
- 纯逻辑层:市场(价格锚、情绪、游戏事件冲击、NPC 大户、多交易对套利回归、当日均价),三种稳定币(CRC 脱锚与减记、CRT 脱锚与冻结概率、CRA 补贴池与死亡螺旋),合规出金与 AML(大额与累计阈值、污点比例、轻度与重度命中、只对收益计税)。
|
||||
- mod 本体(第一阶段的第一部分):人脉网络"Web3 治理社区"(300,整个 mod 的入口)交付交易终端和一张 RZX 50;矿架向杰克逊博士购买;矿架是带电池槽和 4 个显卡槽的机器,每晚从电池扣电挖 PSC;显卡按型号显示算力、耗电与健康度,每晚损耗并可能烧毁;交易终端有钱包、交易(四个交易对、存入信用点、提取 CRC 过合规审查)、报告三页;夜间结算写晨间日志;PSC 交易所开业与减半上报纸;状态存进游戏的 mod 存档数据。中英双语。
|
||||
|
||||
@@ -173,7 +173,7 @@ def gpu_front(accent, accent_dark, brand_text, tier=1, burnt=False):
|
||||
# 前沿斜切装饰条:品牌色 + 品牌字
|
||||
for x in range(x0 + 4, x1 - 20):
|
||||
c.rect(x, y1 - 5, x, y1 - 3, accent if x % 11 else accent_dark)
|
||||
text57(c, 136 if count == 1 else 112, 84, brand_text, 'h')
|
||||
text57(c, 30 if count == 1 else 112, 84, brand_text, 'h') # 单风扇的大风扇占了右边,品牌字改放左下
|
||||
# 顶沿:四根铜热管端头与 8 针供电口
|
||||
for i in range(4):
|
||||
x = 38 + i * 9
|
||||
|
||||
@@ -30,6 +30,11 @@ C = {
|
||||
'Y': (0xC8, 0xA4, 0x4A), # 金手指
|
||||
'c': (0x2E, 0x2A, 0x2A), # 焦黑
|
||||
'e': (0x6B, 0x4A, 0x3A), # 焦痕
|
||||
'u': (0x4F, 0x86, 0xD0), # 蓝
|
||||
'U': (0x2B, 0x4A, 0x82), # 深蓝
|
||||
'v': (0x9B, 0x76, 0xD6), # 紫
|
||||
'V': (0x5E, 0x45, 0x8F), # 深紫
|
||||
'y': (0xF0, 0xCF, 0x64), # 亮金
|
||||
'p': (0xC0, 0x7A, 0x4A), # 铜
|
||||
'q': (0x8A, 0x4F, 0x34), # 暗铜
|
||||
'w': (0xE6, 0xE2, 0xD0), # 白
|
||||
@@ -514,6 +519,76 @@ def key(model):
|
||||
return model.replace(' ', '').lower()
|
||||
|
||||
|
||||
def ui_bank():
|
||||
"""银行图标(32×32):带山花的石柱建筑,门前一枚金币。"""
|
||||
c = Canvas(32, 32)
|
||||
for k in range(0, 14): # 山花(三角)
|
||||
c.rect(16 - k, 4 + k // 2, 16 + k, 4 + k // 2, 'h' if k < 3 else 'l')
|
||||
c.rect(2, 11, 29, 12, 'b'); c.rect(2, 13, 29, 13, 'd') # 檐口
|
||||
for x0 in (4, 10, 16, 22): # 四根柱子
|
||||
c.rect(x0, 14, x0 + 3, 24, 'l')
|
||||
c.rect(x0, 14, x0, 24, 'h'); c.rect(x0 + 3, 14, x0 + 3, 24, 'd')
|
||||
c.rect(x0 - 1, 14, x0 + 4, 14, 'b'); c.rect(x0 - 1, 24, x0 + 4, 24, 'b')
|
||||
c.rect(1, 25, 30, 26, 'b'); c.rect(0, 27, 31, 28, 'm'); c.rect(0, 29, 31, 29, 'd')
|
||||
c.rect(14, 8, 17, 9, 'y'); c.put(15, 8, 'w') # 山花里的小徽标
|
||||
for yy in range(19, 27): # 金币
|
||||
for xx in range(23, 31):
|
||||
d = (xx - 26.5) ** 2 + (yy - 22.5) ** 2
|
||||
if d <= 14:
|
||||
c.put(xx, yy, 'e' if d > 9 else 'y')
|
||||
c.put(25, 21, 'w'); c.put(26, 21, 'w'); c.put(26, 22, 'y')
|
||||
outline(c)
|
||||
return c
|
||||
|
||||
|
||||
def ui_poinbase():
|
||||
"""Poinbase 图标(32×32):蓝色盾牌,中间一枚金色 P 币,两侧一上一下的箭头。"""
|
||||
c = Canvas(32, 32)
|
||||
for y in range(3, 29): # 盾牌
|
||||
half = 12 if y < 16 else max(0, 12 - (y - 16) * 12 // 12)
|
||||
for x in range(16 - half, 16 + half + 1):
|
||||
c.put(x, y, 'u' if x < 16 else 'U')
|
||||
c.put(16 - half, y, 'h') if half else None
|
||||
for x in range(4, 29):
|
||||
c.put(x, 3, 'h'); c.put(x, 4, 'l')
|
||||
for y in range(7, 21): # 币
|
||||
for x in range(9, 24):
|
||||
d = (x - 16) ** 2 + (y - 13.5) ** 2
|
||||
if d <= 36:
|
||||
c.put(x, y, 'e' if d > 28 else 'y')
|
||||
c.put(12, 9, 'w'); c.put(13, 8, 'w'); c.put(12, 10, 'w')
|
||||
c.rect(13, 9, 14, 18, 'U'); c.rect(15, 9, 18, 10, 'U'); c.rect(18, 11, 19, 13, 'U'); c.rect(15, 14, 18, 15, 'U') # P
|
||||
for k in range(4): # 下方两个箭头:买入、卖出
|
||||
c.put(9 + k, 24 - k, 'w') if k < 3 else None
|
||||
for x in range(8, 13): c.put(x, 22, 'w')
|
||||
for x in range(19, 24): c.put(x, 22, 'w')
|
||||
c.put(19, 23, 'w'); c.put(20, 24, 'w'); c.put(23, 23, 'w'); c.put(22, 24, 'w')
|
||||
outline(c)
|
||||
return c
|
||||
|
||||
|
||||
def ui_web3():
|
||||
"""Web3 图标(32×32):六边形节点网络,紫色底。"""
|
||||
c = Canvas(32, 32)
|
||||
nodes = [(16, 7), (7, 14), (25, 14), (11, 24), (21, 24), (16, 16)]
|
||||
edges = [(0, 1), (0, 2), (1, 3), (2, 4), (3, 4), (5, 0), (5, 1), (5, 2), (5, 3), (5, 4)]
|
||||
for (a, b) in edges:
|
||||
(x0, y0), (x1, y1) = nodes[a], nodes[b]
|
||||
n = max(abs(x1 - x0), abs(y1 - y0))
|
||||
for i in range(n + 1):
|
||||
px, py = x0 + (x1 - x0) * i // n, y0 + (y1 - y0) * i // n
|
||||
c.put(px, py, 'v'); c.put(px + 1, py, 'v'); c.put(px, py + 1, 'v')
|
||||
for i, (x, y) in enumerate(nodes):
|
||||
r = 3 if i == 5 else 2
|
||||
for dy in range(-r, r + 1):
|
||||
for dx in range(-r, r + 1):
|
||||
if dx * dx + dy * dy <= r * r + 1:
|
||||
c.put(x + dx, y + dy, 'V' if (dx + dy) > 0 else 'h' if i != 5 else 'y')
|
||||
c.put(x, y, 'w')
|
||||
outline(c)
|
||||
return c
|
||||
|
||||
|
||||
def guide():
|
||||
"""鉴定手册(16×16,一格):竖放的小册子,封面带品牌色条,书脊在左。"""
|
||||
c = Canvas(16, 16)
|
||||
@@ -542,6 +617,8 @@ for _tier in range(4):
|
||||
SPRITES['gpu_probidia_f_t%d' % _tier] = (lambda t=_tier: gpu_front('G', 'g2', tier=t))
|
||||
SPRITES['gpu_pmd_f_t%d' % _tier] = (lambda t=_tier: gpu_front('R', 'r2', tier=t))
|
||||
|
||||
UI_ICONS = {'ui_bank': ui_bank, 'ui_poinbase': ui_poinbase, 'ui_web3': ui_web3}
|
||||
|
||||
# 叠加图:和底图同尺寸,位置 (0,0) 对齐,只画变化的像素,不参与判格
|
||||
OVERLAYS = {
|
||||
'gpu_ov_fin1': lambda: ov_fingers(1), 'gpu_ov_fin2': lambda: ov_fingers(2),
|
||||
@@ -581,6 +658,8 @@ if __name__ == '__main__':
|
||||
if oc.px[y][x] is not None:
|
||||
b.px[y][x] = oc.px[y][x]
|
||||
return b
|
||||
for name, make in UI_ICONS.items():
|
||||
make().png(os.path.join(sys.argv[2], name + '.png'), scale=8, bg=(0x2a, 0x2c, 0x34))
|
||||
stack('gpu_probidia_f_t1', 'gpu_ov_fan2_2', 'gpu_ov_fin1', 'gpu_ov_brk1').png(os.path.join(sys.argv[2], 'prev_front_worn.png'), scale=6, bg=(0xA0, 0xA8, 0x9C))
|
||||
print(sys.argv[2])
|
||||
else:
|
||||
@@ -594,6 +673,8 @@ if __name__ == '__main__':
|
||||
canvas.png(os.path.join(out, name + '.png'))
|
||||
for name, make in OVERLAYS.items():
|
||||
make().png(os.path.join(out, name + '.png'))
|
||||
for name, make in UI_ICONS.items():
|
||||
make().png(os.path.join(out, name + '.png'))
|
||||
import big, big_cases
|
||||
for name, make in big.BIG.items():
|
||||
make().png(os.path.join(out, name + '.png'))
|
||||
|
||||
@@ -42,14 +42,17 @@ namespace Decentralized.Domain
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 标签栏的数据:八个标签(型号、供电与风扇、风扇叶片、金手指、挡板、背面丝印、序列号、封条),每个标签有卖家的说法和真实情况。
|
||||
/// 标签栏的数据:五个标签(型号真伪、风扇叶片、金手指、挡板、封条),每个标签有卖家的说法和真实情况;
|
||||
/// 供电与风扇、背面丝印、序列号三项是型号真伪的依据,只存状态,不单独成行。
|
||||
/// 一个标签被指出(拆穿)之后,说法改回真实情况;价格按「已指出的标签取真实情况,其余取说法」算,
|
||||
/// 所以卖家隐瞒的问题要玩家一项项指出来才能把价格拉回真实价值。指出假货的证据(供电与风扇、丝印、序列号)会连带把型号改回真实型号。
|
||||
/// 所以卖家隐瞒的问题要玩家一项项指出来才能把价格拉回真实价值。指出型号有问题,型号自动改回真实型号。
|
||||
/// </summary>
|
||||
public static class CardLabels
|
||||
{
|
||||
public const int RowSpec = 1, RowBuild = 2, RowFan = 4, RowFingers = 8, RowBracket = 16, RowPrint = 32, RowSerial = 64, RowSeal = 128, AllRows = 255;
|
||||
public static readonly int[] Rows = { RowSpec, RowBuild, RowFan, RowFingers, RowBracket, RowPrint, RowSerial, RowSeal };
|
||||
/// <summary>标签栏的顺序:第一行鉴定真伪(型号;依据是供电口与风扇数、序列号、背面丝印,玩家对着大图自己看),后四行鉴定成色。</summary>
|
||||
public static readonly int[] Rows = { RowSpec, RowFan, RowFingers, RowBracket, RowSeal };
|
||||
public const int AuthRows = 1;
|
||||
|
||||
/// <summary>各标签各取值对价格的影响(百分比,相加):风扇叶片积灰 −4%、缺角 −12%;金手指发暗 −4%、氧化 −12%;挡板划痕 −2%;封条缺失 −6%。
|
||||
/// 型号、供电与风扇、丝印、序列号本身不扣价,它们决定的是这块卡是什么芯片。</summary>
|
||||
@@ -121,7 +124,6 @@ namespace Decentralized.Domain
|
||||
{
|
||||
if ((exposed & r) != 0) continue;
|
||||
if (r == RowSpec) { if (claim.Model != real.Model) lies |= r; }
|
||||
else if (IsEvidence(r, claim, real)) lies |= r;
|
||||
else if (claim.Get(r) != real.Get(r) && Pct(r, claim.Get(r)) > Pct(r, real.Get(r))) lies |= r;
|
||||
}
|
||||
return lies;
|
||||
|
||||
@@ -16,6 +16,8 @@ namespace Decentralized.Domain
|
||||
public sealed class Market
|
||||
{
|
||||
public double Sentiment = 1.0;
|
||||
/// <summary>供给因子:出块奖励减半抬高挖矿成本,价格锚随之上移;减半前一周有预期行情。每晚向目标靠拢。</summary>
|
||||
public double Supply = 1.0;
|
||||
public List<Pool> Pools = new List<Pool>();
|
||||
/// <summary>开门时的参考价,夜间用它和收盘价算当日均价(合约的标记价)。</summary>
|
||||
public double OpenPrice = Tuning.PscStartPrice;
|
||||
@@ -42,9 +44,20 @@ namespace Decentralized.Domain
|
||||
/// <summary>PSC 的参考价(信用点)。</summary>
|
||||
public double Price => Main.Price;
|
||||
|
||||
/// <summary>供给因子的目标:奖励比起始奖励每少一半,锚上移 2^传导指数;减半前 7 天内再逐日加预期。</summary>
|
||||
public static double SupplyTarget(int day, double reward)
|
||||
{
|
||||
double t = Math.Pow(Tuning.BlockReward / Math.Max(1e-9, reward), Tuning.HalvingPassThrough);
|
||||
int toHalving = Tuning.HalvingDays - day % Tuning.HalvingDays; // 减半当晚 day % HalvingDays == 0,那天之后又是一个完整周期
|
||||
if (toHalving <= Tuning.HalvingRampDays) t *= 1 + Tuning.HalvingRamp * (Tuning.HalvingRampDays - toHalving + 1) / Tuning.HalvingRampDays;
|
||||
return t;
|
||||
}
|
||||
|
||||
public void UpdateSupply(int day, double reward) => Supply += (SupplyTarget(day, reward) - Supply) * Tuning.SupplyAdjust;
|
||||
|
||||
public double Anchor(ICollection<MarketEvent> active)
|
||||
{
|
||||
double a = Tuning.PscStartPrice * Sentiment;
|
||||
double a = Tuning.PscStartPrice * Sentiment * Supply;
|
||||
if (active.Contains(MarketEvent.Blackout)) a *= 1.10;
|
||||
if (active.Contains(MarketEvent.Crackdown)) a *= 0.92;
|
||||
return a;
|
||||
|
||||
@@ -9,6 +9,13 @@ namespace Decentralized.Domain
|
||||
public const double PscStartPrice = 100;
|
||||
public const double BlockReward = 90;
|
||||
public const int HalvingDays = 90;
|
||||
/// <summary>减半对币价的传导:奖励减半时,价格锚上移 2^传导指数 倍(0.6 即约 +52%),市场已部分预期到,所以不是翻倍。</summary>
|
||||
public const double HalvingPassThrough = 0.6;
|
||||
/// <summary>减半前一周的预期行情:锚逐日抬高,到减半当天为 +8%。</summary>
|
||||
public const int HalvingRampDays = 7;
|
||||
public const double HalvingRamp = 0.08;
|
||||
/// <summary>价格锚向目标的逐晚靠拢比例(约 7 天完成大半)。</summary>
|
||||
public const double SupplyAdjust = 0.15;
|
||||
public const double NpcHash0 = 360;
|
||||
public const double NpcKjPerH = 2.4;
|
||||
public const double ElecPrice = 1.5;
|
||||
|
||||
@@ -19,11 +19,30 @@ namespace Decentralized.Domain
|
||||
/// <summary>被执法标记。</summary>
|
||||
public bool Flagged;
|
||||
|
||||
/// <summary>买入记录:每种币买入的数量与花费(信用点等值),用来显示买入均价。卖出或转出时按比例减少。</summary>
|
||||
public Dictionary<string, double> BuyQty = new Dictionary<string, double>(), BuyCost = new Dictionary<string, double>();
|
||||
|
||||
public void RecordBuy(string asset, double qty, double costCredits)
|
||||
{
|
||||
if (qty <= 0) return;
|
||||
BuyQty[asset] = (BuyQty.TryGetValue(asset, out var q) ? q : 0) + qty;
|
||||
BuyCost[asset] = (BuyCost.TryGetValue(asset, out var c) ? c : 0) + Math.Max(0, costCredits);
|
||||
}
|
||||
|
||||
/// <summary>买入均价(信用点);没有买入记录(例如全是挖出来的)返回 null。</summary>
|
||||
public double? AvgBuy(string asset) => BuyQty.TryGetValue(asset, out var q) && q > 1e-9 ? BuyCost[asset] / q : (double?)null;
|
||||
|
||||
public double Get(string asset) => Balances.TryGetValue(asset, out var v) ? v : 0;
|
||||
|
||||
public void Add(string asset, double amount)
|
||||
{
|
||||
if (amount == 0) return;
|
||||
if (amount < 0 && BuyQty.TryGetValue(asset, out var bq))
|
||||
{
|
||||
double before = Get(asset), keep = before > 1e-12 ? Math.Max(0, 1 + amount / before) : 0;
|
||||
BuyQty[asset] = bq * keep; BuyCost[asset] *= keep;
|
||||
if (BuyQty[asset] < 1e-9) { BuyQty.Remove(asset); BuyCost.Remove(asset); }
|
||||
}
|
||||
double v = Get(asset) + amount;
|
||||
if (v < -1e-9) throw new InvalidOperationException($"{Address}: {asset} 余额不足");
|
||||
if (Math.Abs(v) < 1e-12) Balances.Remove(asset);
|
||||
|
||||
@@ -41,6 +41,33 @@ namespace Decentralized
|
||||
Walk(m.panel.transform, 0);
|
||||
Log.Info(sb.ToString());
|
||||
}));
|
||||
ModHelper.AddDebugFunction(ModInfo.Id, ModInfo.Name, "Edit claim + offer to buyer (test)", () => Log.Guard("卖出侧测试", () =>
|
||||
{
|
||||
var rng = Session.Data.Rng;
|
||||
// 自己的一张真卡:标签机改成旗舰型号并把成色说成没问题,再摆给一位买家
|
||||
var item = Items.NewCard("RZX70", 55, null, rng);
|
||||
Items.Give(item);
|
||||
var u = Items.ToUnit(item);
|
||||
var c = Claim.Truth(u); c.Model = "RZX90"; c.Fan = 0; c.Fingers = 0; c.Bracket = 0; c.Sticker = 0;
|
||||
item.EnableTag(Items.EditedTag);
|
||||
Items.SetClaim(item, c, 0);
|
||||
var client = new StoreClient { identifier = "pd_test_buyer", displayName = "买家", spriteName = SpriteDict.GetRandomScavSpriteName(), clientIntent = StoreClient.ClientIntent.BUY };
|
||||
client.CompleteClientCreation();
|
||||
PlayerStore.Instance.currentClientInstance = StoreClientInstance.CreateClientInstance(client);
|
||||
Log.Info($"测试:卡 #{item.uniqueId} 说法 {c.Encode()},真实 {Claim.Truth(u).Encode()},价格 {item.unitValue},信誉前");
|
||||
Items.OfferForTest(item);
|
||||
Log.Info($"测试:验货后 Exposed={Items.Exposed(item)} 价格 {item.unitValue}");
|
||||
}));
|
||||
ModHelper.AddDebugFunction(ModInfo.Id, ModInfo.Name, "Buy scam card (test)", () => Log.Guard("买入侧测试", () =>
|
||||
{
|
||||
var rng = Session.Data.Rng;
|
||||
var item = Items.NewCard("RZX90", 60, "RZX60", rng, DealerKind.Scammer, 0);
|
||||
long before = item.unitValue;
|
||||
GeneralHelper.SetItemOwned(item, true);
|
||||
Items.Refresh(item);
|
||||
Items.Give(item);
|
||||
Log.Info($"买入测试:卡 #{item.uniqueId} 买前报价 {before},买下后价值 {item.unitValue},真实 {CardValue.True(Items.ToUnit(item))},说法 {(Items.GetClaim(item)?.Encode() ?? "无")}");
|
||||
}));
|
||||
ModHelper.AddDebugFunction(ModInfo.Id, ModInfo.Name, "Dr. Jackson stock (test)", () =>
|
||||
{
|
||||
EmporiumEntry.Instance.ClearFrontInv();
|
||||
|
||||
@@ -16,8 +16,9 @@ namespace Decentralized
|
||||
/// </summary>
|
||||
public sealed class GpuInspectView : MonoBehaviour
|
||||
{
|
||||
private const float RowH = 27f, CardScale = 2.7f, PanelTop = -86f;
|
||||
private const int RowCount = 8;
|
||||
private const float RowH = 30f, CardScale = 3f, PanelTop = -112f, DetailH = 26f, GroupGap = 8f;
|
||||
private const int RowCount = 5;
|
||||
private TextMeshProUGUI detailText;
|
||||
private static GpuInspectView instance;
|
||||
private RectTransform root, cardRoot;
|
||||
private Button flip;
|
||||
@@ -161,7 +162,7 @@ namespace Decentralized
|
||||
root.localPosition = Vector3.zero;
|
||||
cardRoot = new GameObject("Card", typeof(RectTransform)).GetComponent<RectTransform>();
|
||||
cardRoot.SetParent(root, false);
|
||||
cardRoot.localPosition = new Vector3(0, 95, 0);
|
||||
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);
|
||||
@@ -173,15 +174,17 @@ namespace Decentralized
|
||||
font = buttonTemplate.GetComponentInChildren<TextMeshProUGUI>()?.font;
|
||||
if (font != null) serialText.font = font;
|
||||
// 标签区:深色底加浅色边框,和上面的大图分开
|
||||
float panelH = RowCount * RowH + 10f, panelY = PanelTop - panelH / 2;
|
||||
float panelH = RowCount * RowH + 10f + DetailH + GroupGap, 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, "LabelDivider", new Vector2(0, PanelTop - 5 - RowH * CardLabels.AuthRows - DetailH - GroupGap / 2), new Vector2(620, 2), new Color(0.78f, 0.68f, 0.45f, 0.8f));
|
||||
detailText = MakeText(root, "Detail", new Vector2(-15, PanelTop - 5 - RowH * CardLabels.AuthRows - DetailH / 2), new Vector2(600, DetailH), TextAlignmentOptions.Left, 17);
|
||||
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 = PanelTop - 5 - RowH * (i + 0.5f);
|
||||
float y = PanelTop - 5 - RowH * (i + 0.5f) - (i >= CardLabels.AuthRows ? DetailH + GroupGap : 0f);
|
||||
rowName[i] = MakeText(root, "RowName" + i, new Vector2(-255, y), new Vector2(110, RowH), TextAlignmentOptions.Left, 22);
|
||||
rowText[i] = MakeText(root, "RowText" + i, new Vector2(-30, y), new Vector2(330, RowH), TextAlignmentOptions.Left, 22);
|
||||
rowButton[i] = MakeButton(root, "RowButton" + i, new Vector2(245, y), new Vector2(110, RowH - 3), out rowButtonText[i]);
|
||||
@@ -218,7 +221,8 @@ namespace Decentralized
|
||||
{
|
||||
int row = CardLabels.Rows[i];
|
||||
rowName[i].text = RowName(row);
|
||||
rowName[i].color = new Color(1f, 0.85f, 0.45f);
|
||||
rowName[i].color = i < CardLabels.AuthRows ? new Color(1f, 0.85f, 0.45f) : new Color(0.6f, 0.85f, 1f); // 真伪行金黄,成色行浅蓝
|
||||
rowName[i].fontSize = 22;
|
||||
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));
|
||||
@@ -237,6 +241,14 @@ namespace Decentralized
|
||||
rowButton[i].gameObject.SetActive(canEdit ? editable : canPoint && (exposed & row) == 0);
|
||||
rowButtonText[i].text = L.T(canEdit ? "lbl.edit" : "lbl.point");
|
||||
}
|
||||
// 型号真伪的依据:没拆穿之前提示去看哪里;拆穿之后列出实际查到的问题
|
||||
if (detailText != null)
|
||||
{
|
||||
bool spec = (exposed & CardLabels.RowSpec) != 0;
|
||||
string detail = claim == null ? "" : !spec ? L.T("lbl.hint") : Evidence(u, claim.Value, real);
|
||||
detailText.text = detail;
|
||||
detailText.color = spec ? new Color(1f, 0.7f, 0.6f) : new Color(0.75f, 0.78f, 0.85f);
|
||||
}
|
||||
if (flipText != null) flipText.text = L.T(Items.IsBack(item) ? "inspect.flip.front" : "inspect.flip.back");
|
||||
// 贴纸上的序列号:只在背面显示
|
||||
if (serialText != null)
|
||||
@@ -244,10 +256,20 @@ namespace Decentralized
|
||||
serialText.gameObject.SetActive(Items.IsBack(item) && !u.Dead);
|
||||
serialText.text = CardLabels.SerialText(u);
|
||||
// 贴纸空白处中心在大图 (142, 91):卡片坐标以图中心为原点,y 向上
|
||||
serialText.rectTransform.localPosition = new Vector3((142 - 96) * CardScale, 95 + (64 - 91) * CardScale, 0);
|
||||
serialText.rectTransform.localPosition = new Vector3((142 - 96) * CardScale, 85 + (64 - 91) * CardScale, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>型号真伪的依据:实物与卖家说法对不上的地方(供电口与风扇数、序列号、背面丝印)。</summary>
|
||||
private static string Evidence(Unit u, Claim claim, Claim real)
|
||||
{
|
||||
var parts = new List<string>();
|
||||
if (claim.Tier != real.Tier) parts.Add(L.T("lbl.ev.build", L.T("lbl.tier." + real.Tier), Hardware.PowerPins[real.Tier], Hardware.FanCount[real.Tier]));
|
||||
if (real.Serial != 0) parts.Add(L.T("lbl.ev.serial." + real.Serial));
|
||||
if (real.Print != 0) parts.Add(L.T("lbl.ev.print." + real.Print));
|
||||
return parts.Count == 0 ? L.T("lbl.ev.none") : L.T("lbl.ev.prefix") + string.Join(";", parts);
|
||||
}
|
||||
|
||||
private static string DisplayModel(string model) => Items.DisplayModel(model);
|
||||
|
||||
private void OnRowButton(int row)
|
||||
|
||||
@@ -22,7 +22,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", SeedTag = "PD_SEED", ClaimTag = "PD_CLAIM", ExposedTag = "PD_EXPOSED", MarkupTag = "PD_MARKUP";
|
||||
public const string LookTag = "PD_LOOK", BackTag = "PD_BACK", SeedTag = "PD_SEED", ClaimTag = "PD_CLAIM", ExposedTag = "PD_EXPOSED", MarkupTag = "PD_MARKUP", EditedTag = "PD_EDITED";
|
||||
|
||||
public static Action OpenTerminal;
|
||||
|
||||
@@ -111,7 +111,11 @@ namespace Decentralized
|
||||
var item = ModHelper.SpawnItem(GpuId(model));
|
||||
var u = new Unit { Model = model, FakeOf = fakeOf, Health = health, Look = CardLook.Generate(rng, health, fakeOf != null) };
|
||||
Store(item, u);
|
||||
if (seller != null) SetClaim(item, CardLabels.DealerClaim(u, seller.Value, rng), 0, markupPct);
|
||||
if (seller != null)
|
||||
{
|
||||
GeneralHelper.SetItemOwned(item, false); // 卖家的货:买下之前不属于玩家,说法才有效
|
||||
SetClaim(item, CardLabels.DealerClaim(u, seller.Value, rng), 0, markupPct);
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
@@ -119,15 +123,21 @@ namespace Decentralized
|
||||
|
||||
private static object checkedClient;
|
||||
private static readonly HashSet<int> checkedItems = new HashSet<int>();
|
||||
public const double CustomerSpotChance = 0.35;
|
||||
public const double CustomerSpotChance = 0.50;
|
||||
public const int RepLossMax = 25;
|
||||
|
||||
/// <summary>被顾客看穿时的信誉损失:骗的价差每满一周房租扣 1 点,最少 1,最多 25。</summary>
|
||||
public static int RepLoss(long gap) => (int)Math.Max(1, Math.Min(RepLossMax, Math.Round(gap / (double)Math.Max(1, PlayerStore.Instance.rentValue))));
|
||||
|
||||
/// <summary>
|
||||
/// 自己的卡摆给想买的顾客:说法里隐瞒的每一项,顾客有 35% 的概率看出来;看出来就拆穿这一项(价格回到真实情况),
|
||||
/// 自己的卡摆给想买的顾客:说法里隐瞒的每一项,顾客有 50% 的概率看出来;看出来就拆穿这一项(价格回到真实情况),
|
||||
/// 并且掉一点信誉。每位顾客对每张卡只看一次。说法比实际更差(把好卡说差了)顾客不会去指。
|
||||
/// </summary>
|
||||
private static void OnOffered(GameItem item)
|
||||
public static void OfferForTest(GameItem item) { checkedItems.Clear(); OnOffered(item, true); }
|
||||
|
||||
private static void OnOffered(GameItem item, bool force = false)
|
||||
{
|
||||
if (item.IsTag(DeadTag) || !GeneralHelper.IsItemOwned(item) || !item.IsRootedInBackInv()) return;
|
||||
if (item.IsTag(DeadTag) || !GeneralHelper.IsItemOwned(item) || (!force && !item.IsRootedInBackInv())) return;
|
||||
var ci = PlayerStore.Instance.currentClientInstance;
|
||||
var client = ci?.GetClientBlueprint();
|
||||
if (client == null || (client.clientIntent != StoreClient.ClientIntent.BUY && client.clientIntent != StoreClient.ClientIntent.SELLNBUY)) return;
|
||||
@@ -148,12 +158,14 @@ namespace Decentralized
|
||||
if (r != CardLabels.RowSpec && CardLabels.IsEvidence(r, claim.Value, Claim.Truth(u))) found |= CardLabels.RowSpec | CardLabels.RowBuild;
|
||||
}
|
||||
if (found == 0) return;
|
||||
long before = item.unitValue;
|
||||
item.ModifyTag(ExposedTag, t => t.Enable().SetInt(exposed | found));
|
||||
Refresh(item);
|
||||
int rep = RepLoss(before - item.unitValue);
|
||||
string faction = string.IsNullOrEmpty(client.clientFaction) || StoreReputation.GetStoreReputation(client.clientFaction) == null ? "FACTION_LOWER_LEVEL" : client.clientFaction;
|
||||
StoreReputation.ModReputation(faction, -2);
|
||||
StoreReputation.ModReputation(faction, -rep);
|
||||
EmporiumAudioPlayer.Instance?.audioFail?.Play();
|
||||
Log.Info($"顾客看出标签不实:{found},价格 {item.unitValue},信誉 −2");
|
||||
Log.Info($"顾客看出标签不实:{found},价格 {item.unitValue},信誉 −{rep}");
|
||||
DialogUIManager.Instance.StartDialogue(new Dialogue().SetText("CLIENT", L.T((found & CardLabels.RowSpec) != 0 ? "cust.fake." + rng.Range(1, 2) : "cust.worn." + rng.Range(1, 2))));
|
||||
}
|
||||
|
||||
@@ -165,7 +177,13 @@ namespace Decentralized
|
||||
return string.IsNullOrEmpty(c.Model) ? (Claim?)null : c;
|
||||
}
|
||||
|
||||
public static int Exposed(GameItem item) => item.IsTag(ExposedTag) ? item.GetTagReadonly(ExposedTag).GetInt() : 0;
|
||||
/// <summary>已指出的标签。型号一旦被拆穿,四个真伪行(型号、供电与风扇、序列号、丝印)一起亮出真实情况。</summary>
|
||||
public static int Exposed(GameItem item)
|
||||
{
|
||||
int e = item.IsTag(ExposedTag) ? item.GetTagReadonly(ExposedTag).GetInt() : 0;
|
||||
if ((e & CardLabels.RowSpec) != 0) e |= CardLabels.RowSpec | CardLabels.RowBuild | CardLabels.RowSerial | CardLabels.RowPrint;
|
||||
return e;
|
||||
}
|
||||
|
||||
public static void SetClaim(GameItem item, Claim claim, int exposed, int markupPct = 0)
|
||||
{
|
||||
@@ -204,6 +222,7 @@ namespace Decentralized
|
||||
var claim = GetClaim(item) ?? Claim.Truth(u);
|
||||
claim = row == CardLabels.RowSpec ? new Claim { Model = model ?? claim.Model, Fan = claim.Fan, Fingers = claim.Fingers, Bracket = claim.Bracket, Print = claim.Print, Sticker = claim.Sticker } : claim.With(row, value);
|
||||
int exposed = Exposed(item) & ~row;
|
||||
item.EnableTag(EditedTag);
|
||||
SetClaim(item, claim, exposed);
|
||||
}
|
||||
|
||||
@@ -220,6 +239,13 @@ namespace Decentralized
|
||||
private static void SyncValue(GameItem item)
|
||||
{
|
||||
if (item.IsTag(DeadTag)) return;
|
||||
// 买下之后卖家的说法作废:卡回到真实情况,除非玩家用标签机自己改过
|
||||
if (GeneralHelper.IsItemOwned(item) && item.IsTag(ClaimTag) && !item.IsTag(EditedTag))
|
||||
{
|
||||
item.ModifyTag(ClaimTag, t => t.Disable());
|
||||
item.ModifyTag(ExposedTag, t => t.Disable());
|
||||
item.ModifyTag(MarkupTag, t => t.Disable());
|
||||
}
|
||||
var u = ToUnit(item);
|
||||
string desc = Desc(item, u);
|
||||
if (item.shortDescription != desc) item.shortDescription = desc;
|
||||
@@ -233,7 +259,7 @@ namespace Decentralized
|
||||
public static Unit ToUnit(GameItem item)
|
||||
{
|
||||
string model = item.identifier.Substring(GpuPrefix.Length);
|
||||
model = Hardware.Specs.Keys.FirstOrDefault(k => k.ToLowerInvariant() == model) ?? "RZX60"; // 旧版本存档里的型号已改名,当作低端卡
|
||||
model = Hardware.Specs.Keys.FirstOrDefault(k => k.ToLowerInvariant() == model) ?? "RZX60";
|
||||
var u = new Unit { Model = model, Health = 100 };
|
||||
if (item.IsTag(HealthTag)) u.Health = item.GetTagReadonly(HealthTag).GetDouble();
|
||||
if (item.IsTag(FakeTag)) u.FakeOf = item.GetTagReadonly(FakeTag).GetString();
|
||||
|
||||
@@ -18,15 +18,12 @@ 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
|
||||
tab.wallet,Wallet
|
||||
tab.trade,Trade
|
||||
tab.report,Report
|
||||
wallet.line,"{0}: {1} (about {2} credits)"
|
||||
wallet.empty,The wallet is empty.
|
||||
wallet.total,Total value: {0} credits
|
||||
wallet.taint,Tainted value: {0} credits
|
||||
trade.amount,Amount
|
||||
trade.hint,Buy spends the amount in the quote coin. Sell sells the amount of the traded coin.
|
||||
trade.deposit,Deposit credits as CRC
|
||||
trade.withdraw,Withdraw CRC as credits
|
||||
trade.pair,"{0}/{1} ({2})"
|
||||
@@ -94,7 +91,6 @@ cust.fake.1,"That's not what the label says. You trying to pass off a fake?"
|
||||
cust.fake.2,"The model doesn't match. That isn't the chip it claims."
|
||||
cust.worn.1,"That's not what you wrote. You left out the flaws."
|
||||
cust.worn.2,"The condition on the label is false. Not at this price."
|
||||
lbl.row.spec,"Model"
|
||||
lbl.row.build,"Power & fans"
|
||||
lbl.row.fan,"Blades"
|
||||
lbl.row.fingers,"Contacts"
|
||||
@@ -119,3 +115,60 @@ lbl.tier.2,"High"
|
||||
lbl.tier.3,"Flagship"
|
||||
item.guide.name,"Graphics card appraisal guide"
|
||||
item.guide.desc,"Tiers: entry has a 6-pin connector and one fan; mid 8-pin and two fans; high 8-pin and three fans; flagship dual 8-pin and three fans. Model names are display only: Probidia RZX 60 / 70 / 80 / 90, PMD RZ 70 / 80 / 800 / 900, lowest to highest tier. Serial: prefix PB (Probidia) or MD (PMD), tier code 1 to 4, a dash, a two-digit factory code, a dash, four digits. Valid factory codes: PB 11, 12, 21, 34, 47; MD 13, 22, 35, 41, 58. A tier code that does not match the model, or a bad factory code or length, means a fake. Appearance discounts: dusty blades -4%, chipped -12%; dull contacts -4%, oxidised -12%; scratched bracket -2%; missing seal -6%."
|
||||
lbl.row.spec,"Model authenticity"
|
||||
lbl.hint,"Check: power connector and fan count, serial, back print"
|
||||
lbl.ev.prefix,"Evidence: "
|
||||
lbl.ev.none,"Everything matches the model"
|
||||
lbl.ev.build,"Hardware is {0} ({1}-pin, {2} fans)"
|
||||
lbl.ev.serial.1,"Serial: bad factory code or length"
|
||||
lbl.ev.serial.2,"Serial: tier code does not match"
|
||||
lbl.ev.print.1,"Print shows another model"
|
||||
lbl.ev.print.2,"Print was redone"
|
||||
news.prehalving.title,"PSC Halving Ahead"
|
||||
news.prehalving.desc,"Miners will receive half as many coins per night in three days. Holders expect the price to rise."
|
||||
tab.home,"Home"
|
||||
tab.bank,"Bank"
|
||||
tab.poinbase,"Poinbase"
|
||||
tab.web3,"Web3"
|
||||
home.enter,"Enter"
|
||||
home.summary,"Credits {0} | On-chain assets about {1} credits"
|
||||
home.lastnight,"Night of day {0}: mined {1} PSC, {2} / {3} rigs powered"
|
||||
entry.bank,"Bank"
|
||||
entry.bank.l1,"Deposits, withdrawals, rent"
|
||||
entry.bank.l2,"Credits {0}"
|
||||
entry.poinbase,"Poinbase"
|
||||
entry.poinbase.l1,"Compliant exchange"
|
||||
entry.poinbase.l2,"PSC {0} credits"
|
||||
entry.web3,"Web3"
|
||||
entry.web3.l1,"Wallet and decentralised trading"
|
||||
entry.web3.l2,"About {0} credits"
|
||||
bank.cash,"Credits: {0}"
|
||||
bank.rent,"Rent: {0} credits, due in {1} days"
|
||||
bank.crc,"Poinbase account (CRC): {0}"
|
||||
poin.account,"Poinbase account: CRC {0}, PSC {1}"
|
||||
avg.line,"{0} average buy price: {1} credits"
|
||||
avg.short,"avg. buy {0}"
|
||||
avg.none,"—"
|
||||
col.asset,"Asset"
|
||||
col.amount,"Amount"
|
||||
col.value,"Value"
|
||||
col.avg,"Avg. buy"
|
||||
bank.cash.k,"Credits"
|
||||
bank.rent.k,"Rent"
|
||||
bank.rent.v,"{0}, due in {1} days"
|
||||
bank.crc.k,"Poinbase account (CRC)"
|
||||
bank.ops,"Deposit / withdraw"
|
||||
poin.account.h,"Poinbase account"
|
||||
trade.h,"Trade"
|
||||
web3.wallet.h,"On-chain wallet"
|
||||
trade.hint,"Buying pays in the quote coin; |selling sells the traded coin."
|
||||
bank.hint,"Deposit turns credits into CRC; |withdrawing turns CRC back into credits |and passes compliance review."
|
||||
col.peg,"Price"
|
||||
col.status,"Status"
|
||||
stable.ok,"Pegged"
|
||||
stable.depeg,"Depegged"
|
||||
stable.warn,"Warning: staking subsidy pool low"
|
||||
stable.spiral,"Collapsing"
|
||||
aml.window,"Withdrawn in the last {0} days: {1} credits (cumulative line {2})"
|
||||
aml.taint,"Wallet taint share: {0}%"
|
||||
aml.chance,"At this amount, estimated review chance: {0}%"
|
||||
|
@@ -17,15 +17,12 @@ item.gpu.desc,标称算力 {0} MH/s。标称耗电 {1} kJ/天。成色 {2}。
|
||||
item.gpu.burnt.name,烧毁的 {0} {1}
|
||||
item.gpu.burnt.desc,此显卡已无法工作。
|
||||
term.title,交易终端
|
||||
tab.wallet,钱包
|
||||
tab.trade,交易
|
||||
tab.report,报告
|
||||
wallet.line,{0}:{1}(约 {2} 信用点)
|
||||
wallet.empty,钱包为空。
|
||||
wallet.total,总值:{0} 信用点
|
||||
wallet.taint,污点:{0} 信用点
|
||||
trade.amount,数量
|
||||
trade.hint,买入按计价币的数量支付;卖出按所交易币的数量卖出。
|
||||
trade.deposit,存入信用点(换成 CRC)
|
||||
trade.withdraw,提取 CRC(换成信用点)
|
||||
trade.pair,{0}/{1}({2})
|
||||
@@ -93,7 +90,6 @@ cust.fake.1,"这不是它标的那块。你拿假货糊弄我?"
|
||||
cust.fake.2,"型号对不上,里面的芯片根本不是这个。"
|
||||
cust.worn.1,"跟你写的不一样,毛病你没说。"
|
||||
cust.worn.2,"标签上的成色是假的吧,这个价我不要。"
|
||||
lbl.row.spec,"型号"
|
||||
lbl.row.build,"供电与风扇"
|
||||
lbl.row.fan,"叶片"
|
||||
lbl.row.fingers,"金手指"
|
||||
@@ -118,3 +114,60 @@ lbl.tier.2,"高端"
|
||||
lbl.tier.3,"旗舰"
|
||||
item.guide.name,"显卡鉴定手册"
|
||||
item.guide.desc,"档次:低端 6 针供电、单风扇;中端 8 针、双风扇;高端 8 针、三风扇;旗舰双 8 针、三风扇。型号只是显示名:Probidia 为 RZX 60 / 70 / 80 / 90,PMD 为 RZ 70 / 80 / 800 / 900,按档次从低到高。序列号:前缀 PB(Probidia)或 MD(PMD),接档次码 1 至 4,一道横杠,两位工厂码,一道横杠,四位流水。合法工厂码:PB 为 11、12、21、34、47;MD 为 13、22、35、41、58。档次码与型号不符,或工厂码、位数不对,都是假的。外观折扣:叶片积灰 -4%,缺角 -12%;金手指发暗 -4%,氧化 -12%;挡板划痕 -2%;封条缺失 -6%。"
|
||||
lbl.row.spec,"型号真伪"
|
||||
lbl.hint,"依据:供电口与风扇数、序列号、背面丝印"
|
||||
lbl.ev.prefix,"依据:"
|
||||
lbl.ev.none,"各项依据都与型号相符"
|
||||
lbl.ev.build,"实物是{0}({1} 针,{2} 风扇)"
|
||||
lbl.ev.serial.1,"序列号工厂码或位数不合规"
|
||||
lbl.ev.serial.2,"序列号档次码与型号不符"
|
||||
lbl.ev.print.1,"丝印印着另一个型号"
|
||||
lbl.ev.print.2,"丝印重印过"
|
||||
news.prehalving.title,"PSC 即将减半"
|
||||
news.prehalving.desc,"三天后矿工每晚获得的币减半。持币者预期币价上行。"
|
||||
tab.home,"首页"
|
||||
tab.bank,"银行"
|
||||
tab.poinbase,"Poinbase"
|
||||
tab.web3,"Web3"
|
||||
home.enter,"进入"
|
||||
home.summary,"信用点 {0} | 链上资产约 {1} 信用点"
|
||||
home.lastnight,"第 {0} 天夜间:挖出 {1} PSC,{2} / {3} 个矿架有电"
|
||||
entry.bank,"银行"
|
||||
entry.bank.l1,"信用点存取、房租"
|
||||
entry.bank.l2,"信用点 {0}"
|
||||
entry.poinbase,"Poinbase"
|
||||
entry.poinbase.l1,"合规交易所"
|
||||
entry.poinbase.l2,"PSC {0} 信用点"
|
||||
entry.web3,"Web3"
|
||||
entry.web3.l1,"钱包与去中心化交易"
|
||||
entry.web3.l2,"约 {0} 信用点"
|
||||
bank.cash,"信用点:{0}"
|
||||
bank.rent,"房租:{0} 信用点,{1} 天后支付"
|
||||
bank.crc,"Poinbase 账户(CRC):{0}"
|
||||
poin.account,"Poinbase 账户:CRC {0},PSC {1}"
|
||||
avg.line,"{0} 买入均价:{1} 信用点"
|
||||
avg.short,"买入均价 {0}"
|
||||
avg.none,"—"
|
||||
col.asset,"币种"
|
||||
col.amount,"数量"
|
||||
col.value,"估值"
|
||||
col.avg,"买入均价"
|
||||
bank.cash.k,"信用点"
|
||||
bank.rent.k,"房租"
|
||||
bank.rent.v,"{0},{1} 天后支付"
|
||||
bank.crc.k,"Poinbase 账户(CRC)"
|
||||
bank.ops,"存取"
|
||||
poin.account.h,"Poinbase 账户"
|
||||
trade.h,"交易"
|
||||
web3.wallet.h,"链上钱包"
|
||||
trade.hint,"买入按计价币的数量支付;|卖出按所交易币的数量卖出。"
|
||||
bank.hint,"存入把信用点换成 CRC;|提取把 CRC 换回信用点,|提取要过合规审查。"
|
||||
col.peg,"价格"
|
||||
col.status,"状态"
|
||||
stable.ok,"锚定正常"
|
||||
stable.depeg,"脱锚"
|
||||
stable.warn,"预兆:质押补贴池告急"
|
||||
stable.spiral,"崩盘中"
|
||||
aml.window,"近 {0} 天已提取 {1} 信用点(累计线 {2})"
|
||||
aml.taint,"钱包污点比例:{0}%"
|
||||
aml.chance,"按这个数量,预计审查概率:{0}%"
|
||||
|
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 247 B |
|
After Width: | Height: | Size: 260 B |
|
After Width: | Height: | Size: 297 B |
@@ -108,6 +108,8 @@ namespace Decentralized
|
||||
if (d.Network.Reward < rewardBefore) News.Show("pd_news_halving_" + day, L.T("news.halving.title"), L.T("news.halving.desc"), 2);
|
||||
double crcFactor = d.Stable.Night(day, d.Rng, 0);
|
||||
if (crcFactor < 1) d.Hot.Balances[Assets.CRC] = d.Hot.Get(Assets.CRC) * crcFactor;
|
||||
d.Market.UpdateSupply(day, d.Network.Reward);
|
||||
if (Tuning.HalvingDays - day % Tuning.HalvingDays == 3) News.Show("pd_news_prehalving_" + day, L.T("news.prehalving.title"), L.T("news.prehalving.desc"), 3);
|
||||
d.Market.Night(events, d.Rng, day, Session.Price);
|
||||
report.Price = d.Market.Price;
|
||||
d.LastNight = report;
|
||||
|
||||
@@ -1,17 +1,21 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using Decentralized.Domain;
|
||||
|
||||
namespace Decentralized
|
||||
{
|
||||
/// <summary>交易终端窗口:钱包、交易、报告三页。每次操作后按当前页重建窗口。</summary>
|
||||
/// <summary>
|
||||
/// 交易终端窗口:首页三个入口(银行、Poinbase、Web3)加报告。银行是真实的信用点(存取、房租);
|
||||
/// Poinbase 是合规交易所;Web3 是去中心化交易和链上钱包。每次操作后按当前页重建窗口。
|
||||
/// </summary>
|
||||
public static class TerminalWindow
|
||||
{
|
||||
public const string Id = "pd_terminal";
|
||||
private enum Page { Wallet, Trade, Report }
|
||||
private enum Page { Home, Bank, Poinbase, Web3, Report }
|
||||
|
||||
private static Page _page = Page.Wallet;
|
||||
private static Page _page = Page.Home;
|
||||
private static string _amount = "";
|
||||
private static string _message;
|
||||
|
||||
@@ -20,7 +24,7 @@ namespace Decentralized
|
||||
if (Session.Data == null) return;
|
||||
var b = CustomUIManager.Window(Id, L.T("term.title"));
|
||||
if (b == null) return;
|
||||
b.SetSize(620, 560).SetDraggable(true).SetCloseOnEscape(true).UseMachineryStyle();
|
||||
b.SetSize(780, 580).SetDraggable(true).SetCloseOnEscape(true).UseMachineryStyle();
|
||||
b.BeginRow();
|
||||
foreach (Page p in Enum.GetValues(typeof(Page)))
|
||||
{
|
||||
@@ -30,11 +34,13 @@ namespace Decentralized
|
||||
}
|
||||
b.End();
|
||||
b.AddSeparator();
|
||||
b.BeginScroll(440);
|
||||
b.BeginScroll(450);
|
||||
switch (_page)
|
||||
{
|
||||
case Page.Wallet: WalletPage(b); break;
|
||||
case Page.Trade: TradePage(b); break;
|
||||
case Page.Home: HomePage(b); break;
|
||||
case Page.Bank: BankPage(b); break;
|
||||
case Page.Poinbase: ExchangePage(b, Venue.Exchange); break;
|
||||
case Page.Web3: Web3Page(b); break;
|
||||
default: ReportPage(b); break;
|
||||
}
|
||||
b.End();
|
||||
@@ -42,30 +48,148 @@ namespace Decentralized
|
||||
b.Show();
|
||||
}
|
||||
|
||||
private static void WalletPage(CustomUIBuilder b)
|
||||
private static void Go(Page p) { _page = p; _message = null; Open(); }
|
||||
|
||||
private static void Entry(CustomUIBuilder b, string icon, string name, string line1, string line2, Page target)
|
||||
{
|
||||
var w = Session.Data.Hot;
|
||||
if (w.Balances.Count == 0) b.AddLabel(L.T("wallet.empty"));
|
||||
foreach (var kv in w.Balances.OrderBy(k => k.Key))
|
||||
b.AddLabel(L.T("wallet.line", kv.Key, Fmt.Num(kv.Value), Fmt.Credits(kv.Value * Session.Price(kv.Key))));
|
||||
b.AddSeparator();
|
||||
b.AddLabel(L.T("wallet.total", Fmt.Credits(w.Value(Session.Price))));
|
||||
if (w.Taint > 0.5) b.AddLabel(L.T("wallet.taint", Fmt.Credits(w.Taint)));
|
||||
b.BeginPanel(220f, 250f, TextAnchor.UpperCenter, 10f, 6f);
|
||||
b.AddImage(ModInfo.Id + ":" + icon, 96f, 96f);
|
||||
b.AddHeader(name);
|
||||
b.AddLabel(line1);
|
||||
if (line2 != null) b.AddLabel(line2);
|
||||
b.AddFlexibleSpace();
|
||||
b.AddButton(L.T("home.enter"), () => Go(target));
|
||||
b.End();
|
||||
}
|
||||
|
||||
private static void TradePage(CustomUIBuilder b)
|
||||
private static void HomePage(CustomUIBuilder b)
|
||||
{
|
||||
var d = Session.Data;
|
||||
double chain = d.Hot.Value(Session.Price);
|
||||
b.AddLabel(L.T("home.summary", PlayerStore.Instance.playerCash, Fmt.Credits(chain)));
|
||||
b.AddSpace(6f);
|
||||
b.BeginRow(8f, TextAnchor.UpperCenter);
|
||||
Entry(b, "ui_bank", L.T("entry.bank"), L.T("entry.bank.l1"), L.T("entry.bank.l2", PlayerStore.Instance.playerCash), Page.Bank);
|
||||
Entry(b, "ui_poinbase", L.T("entry.poinbase"), L.T("entry.poinbase.l1"), null, Page.Poinbase);
|
||||
Entry(b, "ui_web3", L.T("entry.web3"), L.T("entry.web3.l1"), L.T("entry.web3.l2", Fmt.Credits(chain)), Page.Web3);
|
||||
b.End();
|
||||
b.AddSpace(8f);
|
||||
b.AddSeparator();
|
||||
var r = d.LastNight;
|
||||
b.AddLabel(r == null ? L.T("report.none") : L.T("home.lastnight", r.Day, Fmt.Num(r.Mined), r.PoweredRigs, r.Rigs));
|
||||
}
|
||||
|
||||
private const float LeftW = 430f, RightW = 300f;
|
||||
|
||||
private static void Cell(CustomUIBuilder b, float w, string text, bool header = false)
|
||||
{
|
||||
b.BeginPanel(w, 30f, TextAnchor.MiddleLeft, 6f, 0f);
|
||||
b.AddLabel(header ? "<b>" + text + "</b>" : text);
|
||||
b.End();
|
||||
}
|
||||
|
||||
private static void TableRow(CustomUIBuilder b, float[] widths, params string[] cells)
|
||||
{
|
||||
b.BeginRow(2f);
|
||||
for (int i = 0; i < widths.Length; i++) Cell(b, widths[i], i < cells.Length ? cells[i] : "");
|
||||
b.End();
|
||||
}
|
||||
|
||||
private static void TableHeader(CustomUIBuilder b, float[] widths, params string[] cells)
|
||||
{
|
||||
b.BeginRow(2f);
|
||||
for (int i = 0; i < widths.Length; i++) Cell(b, widths[i], cells[i], true);
|
||||
b.End();
|
||||
}
|
||||
|
||||
/// <summary>左右分栏:左边是账户与持仓表格,右边是操作区。</summary>
|
||||
private static void Columns(CustomUIBuilder b, Action<CustomUIBuilder> left, Action<CustomUIBuilder> right)
|
||||
{
|
||||
b.BeginRow(10f);
|
||||
b.BeginPanel(LeftW, -1f, TextAnchor.UpperLeft, 8f, 4f);
|
||||
left(b);
|
||||
b.End();
|
||||
b.BeginPanel(RightW, -1f, TextAnchor.UpperLeft, 8f, 6f);
|
||||
right(b);
|
||||
b.End();
|
||||
b.End();
|
||||
}
|
||||
|
||||
private static void AmountRow(CustomUIBuilder b)
|
||||
{
|
||||
b.AddLabel(L.T("trade.hint"));
|
||||
b.BeginRow();
|
||||
b.AddLabel(L.T("trade.amount"));
|
||||
b.BeginPanel(RightW - 30f, -1f, TextAnchor.MiddleCenter, 0f, 0f); // 给输入框一个固定宽度,容得下大额数字
|
||||
b.AddInput("0", _amount, s => _amount = s, "amount", s => _amount = s);
|
||||
b.End();
|
||||
b.BeginRow();
|
||||
b.AddButton(L.T("trade.deposit"), Deposit);
|
||||
b.AddButton(L.T("trade.withdraw"), Withdraw);
|
||||
b.End();
|
||||
}
|
||||
|
||||
/// <summary>银行:真实的信用点。现金、房租,以及信用点与 Poinbase 账户(CRC)之间的存取。</summary>
|
||||
private static void BankPage(CustomUIBuilder b)
|
||||
{
|
||||
var ps = PlayerStore.Instance;
|
||||
var w = new[] { 200f, 220f };
|
||||
Columns(b, l =>
|
||||
{
|
||||
l.AddHeader(L.T("entry.bank"));
|
||||
TableRow(l, w, L.T("bank.cash.k"), ps.playerCash.ToString());
|
||||
TableRow(l, w, L.T("bank.rent.k"), L.T("bank.rent.v", Session.Rent(), ps.GetRentDayCounter()));
|
||||
TableRow(l, w, L.T("bank.crc.k"), Fmt.Num(Session.Data.Hot.Get(Assets.CRC)));
|
||||
}, r =>
|
||||
{
|
||||
r.AddHeader(L.T("bank.ops"));
|
||||
foreach (var line in L.T("bank.hint").Split('|')) r.AddLabel(line);
|
||||
AmountRow(r);
|
||||
AmlPreview(r);
|
||||
r.AddButton(L.T("trade.deposit"), Deposit);
|
||||
r.AddButton(L.T("trade.withdraw"), Withdraw);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>提取前的合规审查提示:本周已提取的累计、污点比例,以及按当前数量估算的审查概率。</summary>
|
||||
private static void AmlPreview(CustomUIBuilder b)
|
||||
{
|
||||
var d = Session.Data;
|
||||
int day = StoreStation.GetDayCounter();
|
||||
double rent = Session.Rent();
|
||||
double recent = d.Exchange.History.Where(h => h.Key > day - Tuning.AmlWindowDays).Sum(h => h.Value);
|
||||
b.AddSeparator();
|
||||
foreach (var pool in Session.Data.Market.Pools)
|
||||
b.AddLabel(L.T("aml.window", Tuning.AmlWindowDays, Fmt.Credits(recent), Fmt.Credits(Tuning.AmlCumRents * rent)));
|
||||
double total = d.Hot.Value(Session.Price);
|
||||
double taintRatio = total > 0 ? d.Hot.Taint / total : 0;
|
||||
b.AddLabel(L.T("aml.taint", (int)Math.Round(taintRatio * 100)));
|
||||
if (double.TryParse((_amount ?? "").Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out double v) && v > 0)
|
||||
{
|
||||
double value = Math.Min(v, d.Hot.Get(Assets.CRC)) * Session.Price(Assets.CRC);
|
||||
double taint = total > 0 ? d.Hot.Taint * Math.Min(1, value / total) : 0;
|
||||
double chance = Exchange.HitChance(value, taint, recent, rent, d.Hot.Flagged);
|
||||
b.AddLabel(L.T("aml.chance", (int)Math.Round(chance * 100)));
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly float[] StableW = { 80f, 100f, 220f };
|
||||
|
||||
private static string StableStatus(Stablecoins st, string asset, int day)
|
||||
{
|
||||
double p = st.Price(asset);
|
||||
if (asset == Assets.CRA && st.CraWarning(day)) return L.T("stable.warn");
|
||||
if (asset == Assets.CRA && p < 0.97) return L.T("stable.spiral");
|
||||
return p < 0.99 ? L.T("stable.depeg") : L.T("stable.ok");
|
||||
}
|
||||
|
||||
/// <summary>稳定币行情:价格与状态(脱锚、预兆)。</summary>
|
||||
private static void StableTable(CustomUIBuilder b)
|
||||
{
|
||||
var st = Session.Data.Stable;
|
||||
int day = StoreStation.GetDayCounter();
|
||||
b.AddSeparator();
|
||||
TableHeader(b, StableW, L.T("col.asset"), L.T("col.peg"), L.T("col.status"));
|
||||
foreach (var a in new[] { Assets.CRC, Assets.CRT, Assets.CRA })
|
||||
TableRow(b, StableW, a, Fmt.Num(st.Price(a)), StableStatus(st, a, day));
|
||||
}
|
||||
|
||||
private static void TradeRows(CustomUIBuilder b, Venue venue)
|
||||
{
|
||||
foreach (var pool in Session.Data.Market.Pools.Where(x => x.Venue == venue))
|
||||
{
|
||||
var p = pool;
|
||||
b.AddLabel(L.T("trade.pair", p.Base, p.Quote, L.T("venue." + p.Venue)) + " " + L.T("trade.price", Fmt.Num(p.Price)));
|
||||
@@ -76,6 +200,60 @@ namespace Decentralized
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly float[] HoldW = { 80f, 100f, 110f, 110f };
|
||||
|
||||
private static void Holdings(CustomUIBuilder b, Wallet w, Func<string, bool> show)
|
||||
{
|
||||
TableHeader(b, HoldW, L.T("col.asset"), L.T("col.amount"), L.T("col.value"), L.T("col.avg"));
|
||||
bool any = false;
|
||||
foreach (var kv in w.Balances.OrderBy(k => k.Key).Where(k => show(k.Key)))
|
||||
{
|
||||
any = true;
|
||||
TableRow(b, HoldW, kv.Key, Fmt.Num(kv.Value), Fmt.Credits(kv.Value * Session.Price(kv.Key)), AvgText(w, kv.Key));
|
||||
}
|
||||
if (!any) b.AddLabel(L.T("wallet.empty"));
|
||||
}
|
||||
|
||||
/// <summary>Poinbase:合规交易所。左边账户持仓,右边交易;信用点的存取在银行。</summary>
|
||||
private static void ExchangePage(CustomUIBuilder b, Venue venue)
|
||||
{
|
||||
var w = Session.Data.Hot;
|
||||
Columns(b, l =>
|
||||
{
|
||||
l.AddHeader(L.T("poin.account.h"));
|
||||
Holdings(l, w, a => a == Assets.CRC || a == Assets.PSC);
|
||||
}, r =>
|
||||
{
|
||||
r.AddHeader(L.T("trade.h"));
|
||||
foreach (var line in L.T("trade.hint").Split('|')) r.AddLabel(line);
|
||||
AmountRow(r);
|
||||
TradeRows(r, venue);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>Web3:链上钱包与去中心化交易。</summary>
|
||||
private static void Web3Page(CustomUIBuilder b)
|
||||
{
|
||||
var w = Session.Data.Hot;
|
||||
Columns(b, l =>
|
||||
{
|
||||
l.AddHeader(L.T("web3.wallet.h"));
|
||||
Holdings(l, w, a => true);
|
||||
l.AddSeparator();
|
||||
l.AddLabel(L.T("wallet.total", Fmt.Credits(w.Value(Session.Price))));
|
||||
if (w.Taint > 0.5) l.AddLabel(L.T("wallet.taint", Fmt.Credits(w.Taint)));
|
||||
StableTable(l);
|
||||
}, r =>
|
||||
{
|
||||
r.AddHeader(L.T("trade.h"));
|
||||
foreach (var line in L.T("trade.hint").Split('|')) r.AddLabel(line);
|
||||
AmountRow(r);
|
||||
TradeRows(r, Venue.Dex);
|
||||
});
|
||||
}
|
||||
|
||||
private static string AvgText(Wallet w, string asset) => w.AvgBuy(asset) is double a ? Fmt.Num(a) : L.T("avg.none");
|
||||
|
||||
private static void ReportPage(CustomUIBuilder b)
|
||||
{
|
||||
var r = Session.Data.LastNight;
|
||||
@@ -109,6 +287,7 @@ namespace Decentralized
|
||||
ModHelper.AddCredit(-credits, false);
|
||||
var w = Session.Data.Hot;
|
||||
w.Add(Assets.CRC, credits / Session.Data.Stable.Crc);
|
||||
w.RecordBuy(Assets.CRC, credits / Session.Data.Stable.Crc, credits);
|
||||
w.Basis += credits;
|
||||
_message = L.T("msg.deposit", credits);
|
||||
Session.Save();
|
||||
@@ -141,8 +320,10 @@ namespace Decentralized
|
||||
string pay = buy ? p.Quote : p.Base, get = buy ? p.Base : p.Quote;
|
||||
if (w.Get(pay) + 1e-9 < v) { _message = L.T("msg.balance"); Open(); return; }
|
||||
double got = buy ? p.BuyBase(v) : p.SellBase(v);
|
||||
double spent = v * Session.Price(pay);
|
||||
w.Add(pay, -v);
|
||||
w.Add(get, got);
|
||||
w.RecordBuy(get, got, spent);
|
||||
_message = L.T("msg.trade", Fmt.Num(v), pay, Fmt.Num(got), get);
|
||||
Session.Save();
|
||||
Open();
|
||||
|
||||
@@ -248,6 +248,35 @@ internal static class Program
|
||||
Check(hi.Price <= 300 + 1e-9, $"上限 3 倍:{hi.Price:0.0}");
|
||||
});
|
||||
|
||||
Test("钱包:买入均价按买入记录算,卖出和转出按比例减少,挖出的币不计入", () =>
|
||||
{
|
||||
var w = new Wallet { Address = "x" };
|
||||
w.Add("PSC", 10); // 挖出的 10 个,无买入记录
|
||||
Check(w.AvgBuy("PSC") == null, "没有买入记录时没有均价");
|
||||
w.Add("PSC", 5); w.RecordBuy("PSC", 5, 400);
|
||||
w.Add("PSC", 5); w.RecordBuy("PSC", 5, 600);
|
||||
Check(Near(w.AvgBuy("PSC").Value, 100, 1e-9), "买 5 个花 400 + 买 5 个花 600:均价 100");
|
||||
w.Add("PSC", -10); // 卖掉一半(20 个里的 10 个)
|
||||
Check(Near(w.AvgBuy("PSC").Value, 100, 1e-9) && Near(w.BuyQty["PSC"], 5, 1e-9), "卖出后均价不变,买入记录按比例减少");
|
||||
w.Add("PSC", -10);
|
||||
Check(w.AvgBuy("PSC") == null, "清仓后记录清除");
|
||||
});
|
||||
|
||||
Test("市场:减半抬高价格锚(逐日靠拢),减半前一周有预期行情", () =>
|
||||
{
|
||||
Check(Near(Market.SupplyTarget(10, Tuning.BlockReward), 1.0, 1e-9), "减半前很远:目标因子 1");
|
||||
double before = Market.SupplyTarget(89, Tuning.BlockReward), start = Market.SupplyTarget(83, Tuning.BlockReward);
|
||||
Check(start > 1.0 && before > start && before <= 1 + Tuning.HalvingRamp + 1e-9, $"减半前 7 天逐日抬高,最多 +8%:{start:0.000} → {before:0.000}");
|
||||
double after = Market.SupplyTarget(91, Tuning.BlockReward / 2);
|
||||
Check(Near(after, Math.Pow(2, Tuning.HalvingPassThrough), 1e-9), $"减半后目标因子 2^0.6 = {after:0.000}");
|
||||
var m = new Market();
|
||||
for (int i = 0; i < 7; i++) m.UpdateSupply(91 + i, Tuning.BlockReward / 2);
|
||||
Check(m.Supply > 1.0 && m.Supply < after, "逐晚靠拢目标,不一次跳到位");
|
||||
for (int i = 0; i < 40; i++) m.UpdateSupply(100 + i % 5, Tuning.BlockReward / 2);
|
||||
Check(Near(m.Supply, after, 0.02), "几十晚后基本到位");
|
||||
Check(m.Anchor(new MarketEvent[0]) > Tuning.PscStartPrice * 1.4, "锚随之上移");
|
||||
});
|
||||
|
||||
Test("市场:其他交易对每晚被套利拉向参考价,三角价差收敛", () =>
|
||||
{
|
||||
var m = Market.CreateDefault(1);
|
||||
@@ -370,7 +399,7 @@ internal static class Program
|
||||
int ex2 = 0;
|
||||
var honestUnit = new Unit { Model = "RZX70", Health = 60, Look = new CardLook { Fan = 1 } };
|
||||
Check(CardLabels.Point(honestUnit, CardLabels.DealerClaim(honestUnit, DealerKind.Honest, new Rng(1)), CardLabels.RowFan, ref ex2) == 0, "老实人说的是实话,指出去没用");
|
||||
Check(CardLabels.Lies(u, claim, 0) == (CardLabels.RowSpec | CardLabels.RowBuild | CardLabels.RowFan | CardLabels.RowFingers | CardLabels.RowPrint), "骗子隐瞒了型号、供电与风扇、风扇叶片、金手指、丝印");
|
||||
Check(CardLabels.Lies(u, claim, 0) == (CardLabels.RowSpec | CardLabels.RowFan | CardLabels.RowFingers), "骗子隐瞒了型号、叶片、金手指");
|
||||
Check(CardLabels.Lies(u, claim, ex) == 0, "指出之后不再算隐瞒");
|
||||
Check(Claim.Decode(claim.Encode()).Encode() == claim.Encode(), "说法能存取");
|
||||
});
|
||||
|
||||