Phase 2: customers paying in coin (CRC/CRT/CRA/PSC), taint from stolen goods

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ZHU MINCHAOandClaude Sonnet 5.5 committed 2026-11-03 16:01:20 +08:00
1 parent 1ba0652705
commit 090a973d23
10 files changed
+149

No files matched your search

+4
View File
@@ -2,6 +2,10 @@
## 未发布 ## 未发布
### 第二阶段(进行中)
- 用币付款的顾客:排队的买家里,上层付 CRC(12%)、黑市付 CRT(25%)、革命派付 CRA(25%)、下层付 PSC(10%)。成交时扣回信用点,按当日币价换成币进热钱包;赃物款项不记销赃,改成币带污点(货值 × 赃物热度%);夜间日志汇总当天用币结算的额度。开发者模式 F9 有测试入口。游戏内成交流程尚未实测(测试存档里顾客不上柜台),机制本身在技术验证里通过。
### 第一阶段完成(入门、挖矿、显卡鉴定、终端) ### 第一阶段完成(入门、挖矿、显卡鉴定、终端)
以下条目取代下面较早条目里已过时的部分(型号、入口价、终端页面): 以下条目取代下面较早条目里已过时的部分(型号、入口价、终端页面):
+47
View File
@@ -0,0 +1,47 @@
using System;
namespace Decentralized.Domain
{
/// <summary>
/// 顾客用币付款:成交的信用点在店里扣回,按当前币价换成币进热钱包。
/// 收赃物的款项不再记销赃,改为币带污点:货值 × 赃物热度%。
/// </summary>
public static class CoinPayment
{
/// <summary>各派系用什么币付款(其余派系不用币)。</summary>
public static string AssetFor(string faction)
{
switch (faction)
{
case "FACTION_UPPER_LEVEL": return Assets.CRC;
case "FACTION_BLACK_MARKET": return Assets.CRT;
case "FACTION_REVOLUTION": return Assets.CRA;
case "FACTION_LOWER_LEVEL": return Assets.PSC;
default: return null;
}
}
/// <summary>一位顾客用币付款的概率。</summary>
public static double Chance(string asset)
{
switch (asset)
{
case Assets.CRC: return Tuning.CoinPayCrc;
case Assets.CRT: return Tuning.CoinPayCrt;
case Assets.CRA: return Tuning.CoinPayCra;
default: return Tuning.CoinPayPsc;
}
}
/// <summary>收款:返回收到的币数。heatPct 是物品的赃物热度(0–100)。</summary>
public static double Receive(Wallet w, string asset, double credits, double price, double heatPct)
{
if (credits <= 0 || price <= 0) return 0;
double qty = credits / price;
w.Add(asset, qty);
w.Taint += credits * Math.Max(0, Math.Min(100, heatPct)) / 100.0;
w.Basis += credits;
return qty;
}
}
}
+2
View File
@@ -57,6 +57,8 @@ namespace Decentralized.Domain
public const int AmlWindowDays = 7; public const int AmlWindowDays = 7;
public const double AmlTaintRatio = 0.10, AmlHeavyRatio = 0.30, AmlMixerBonus = 0.30, AmlMaxChance = 0.9; public const double AmlTaintRatio = 0.10, AmlHeavyRatio = 0.30, AmlMixerBonus = 0.30, AmlMaxChance = 0.9;
public const double TaxRate = 0.08; public const double TaxRate = 0.08;
/// <summary>各派系顾客用币付款的概率。</summary>
public const double CoinPayCrc = 0.12, CoinPayCrt = 0.25, CoinPayCra = 0.25, CoinPayPsc = 0.10;
/// <summary>单个金融违规事件记入的罪证上限。</summary> /// <summary>单个金融违规事件记入的罪证上限。</summary>
public const double CrimeEventCap = 400; public const double CrimeEventCap = 400;
+78
View File
@@ -0,0 +1,78 @@
using System.Collections.Generic;
using Decentralized.Domain;
namespace Decentralized
{
/// <summary>
/// 用币付款的顾客:每天排队的买家里,按派系(上层 CRC、黑市 CRT、革命派 CRA、下层 PSC)有一小部分改用币结算。
/// 成交时在店里扣回刚入账的信用点,换成币进热钱包;赃物的款项不记销赃,改成币带污点(货值 × 赃物热度%)。
/// </summary>
public static class CoinCustomers
{
private static readonly HashSet<StoreClient> Marked = new HashSet<StoreClient>();
private static int markDay = -1;
public static void Subscribe()
{
ModHook.OnGenerateCustomerLate += OnGenerate;
ModHelper.AddDebugFunction(ModInfo.Id, ModInfo.Name, "Coin buyer (test)", () => Log.Guard("用币付款顾客(测试)", () =>
{
var c = new StoreClient
{
identifier = "pd_coin_test", displayName = "Coin buyer", spriteName = SpriteDict.GetRandomScavSpriteName(),
clientFaction = "FACTION_BLACK_MARKET", useClientBudget = true, clientIntent = StoreClient.ClientIntent.BUY,
canClientExposeFeature = ClientCanExposeFunc.BasicClientExposeCapacity,
};
c.SetBudget(5000);
c.clientBuyingTagList.Add("WATER"); c.clientBuyingTagList.Add("BREVAGE");
c.mainDialogue.SetText(c.displayName, "CRT.").SetEndAction(() => { });
c.AddBasicDialog();
c.CompleteClientCreation();
Mark(c, Assets.CRT);
PlayerStore.Instance.currentClientInstance = StoreClientInstance.CreateClientInstance(c); // 测试:直接换成这位顾客
}));
}
public static void Unsubscribe() => ModHook.OnGenerateCustomerLate -= OnGenerate;
private static void OnGenerate(StoreClientManager manager)
{
if (Session.Data == null || !Unlock.IsUnlocked) return;
Log.Guard("用币付款", () =>
{
int day = StoreStation.GetDayCounter();
if (day != markDay) { Marked.Clear(); markDay = day; }
foreach (var c in manager.clientStack)
{
if (c == null || c.isSecurity || Marked.Contains(c) || c.clientIntent != StoreClient.ClientIntent.BUY || c.isNoContributeToEvidence) continue;
Marked.Add(c);
string asset = CoinPayment.AssetFor(c.clientFaction);
if (asset == null || !Session.Data.Rng.Chance(CoinPayment.Chance(asset))) continue;
Mark(c, asset);
}
});
}
private static void Mark(StoreClient c, string asset)
{
var before = c.onItemBought;
c.isNoContributeToEvidence = true;
c.onItemBought = (item, cost) =>
{
before?.Invoke(item, cost);
Log.Guard("用币付款成交", () => Pay(asset, item, cost));
};
}
private static void Pay(string asset, GameItem item, int cost)
{
var d = Session.Data;
double price = Session.Price(asset);
if (cost <= 0 || price <= 0) return;
int heat = item.IsTag("STOLEN_VALUE_INT") ? item.GetTagReadonly("STOLEN_VALUE_INT").GetInt() : 0;
ModHelper.AddCredit(-cost, false);
double qty = CoinPayment.Receive(d.Hot, asset, cost, price, heat);
d.CoinSalesToday += cost;
Log.Info($"用币付款:{item.name} {cost} 信用点 → {qty:0.####} {asset},污点 +{cost * heat / 100.0:0}");
}
}
}
+2
View File
@@ -16,6 +16,7 @@ namespace Decentralized
Night.Subscribe(); Night.Subscribe();
DealerStock.Subscribe(); DealerStock.Subscribe();
UsedDealerClient.Subscribe(); UsedDealerClient.Subscribe();
CoinCustomers.Subscribe();
GpuInspectView.Ensure(); GpuInspectView.Ensure();
ModHook.OnGameLoadedLate += Session.Load; ModHook.OnGameLoadedLate += Session.Load;
ModHook.OnNewGameStarted += OnNewGame; ModHook.OnNewGameStarted += OnNewGame;
@@ -28,6 +29,7 @@ namespace Decentralized
Night.Unsubscribe(); Night.Unsubscribe();
DealerStock.Unsubscribe(); DealerStock.Unsubscribe();
UsedDealerClient.Unsubscribe(); UsedDealerClient.Unsubscribe();
CoinCustomers.Unsubscribe();
GpuInspectView.Remove(); GpuInspectView.Remove();
ModHook.OnGameLoadedLate -= Session.Load; ModHook.OnGameLoadedLate -= Session.Load;
ModHook.OnNewGameStarted -= OnNewGame; ModHook.OnNewGameStarted -= OnNewGame;
@@ -50,6 +50,7 @@ report.burnt,Cards burnt out: {0}
report.price,PSC price: {0} credits report.price,PSC price: {0} credits
nightlog.mined,Mining rigs produced {0} PSC. nightlog.mined,Mining rigs produced {0} PSC.
nightlog.burnt,A {0} burnt out. nightlog.burnt,A {0} burnt out.
nightlog.coinpay,Customers paying in coin settled {0} credits; the coins went to the hot wallet.
nightlog.unpowered,{0} mining rig(s) had no power. nightlog.unpowered,{0} mining rig(s) had no power.
news.open.title,Poinbase Lists PSC news.open.title,Poinbase Lists PSC
news.open.desc,The Upper Level exchange Poinbase now lists PSC against CRC. Graphics cards are in demand across the station. news.open.desc,The Upper Level exchange Poinbase now lists PSC against CRC. Graphics cards are in demand across the station.
1 # Probably Decentralized localization table: English (en)
50 report.price
51 nightlog.mined
52 nightlog.burnt
53 nightlog.coinpay
54 nightlog.unpowered
55 news.open.title
56 news.open.desc
@@ -49,6 +49,7 @@ report.burnt,烧毁的显卡:{0}
report.price,PSC 价格:{0} 信用点 report.price,PSC 价格:{0} 信用点
nightlog.mined,矿架挖出 {0} PSC。 nightlog.mined,矿架挖出 {0} PSC。
nightlog.burnt,一张 {0} 烧毁了。 nightlog.burnt,一张 {0} 烧毁了。
nightlog.coinpay,用币付款的顾客共结算 {0} 信用点,已换成币进了热钱包。
nightlog.unpowered,{0} 个矿架没有电。 nightlog.unpowered,{0} 个矿架没有电。
news.open.title,Poinbase 上架 PSC news.open.title,Poinbase 上架 PSC
news.open.desc,上层交易所 Poinbase 上架 PSC/CRC 交易对。全站显卡需求上升。 news.open.desc,上层交易所 Poinbase 上架 PSC/CRC 交易对。全站显卡需求上升。
1 # Probably Decentralized 本地化表:简体中文(zh)
49 report.price
50 nightlog.mined
51 nightlog.burnt
52 nightlog.coinpay
53 nightlog.unpowered
54 news.open.title
55 news.open.desc
+2
View File
@@ -120,6 +120,8 @@ namespace Decentralized
foreach (var name in report.Burnt) ps.AddNightLog(L.T("nightlog.burnt", name), "cc7755"); foreach (var name in report.Burnt) ps.AddNightLog(L.T("nightlog.burnt", name), "cc7755");
int unpowered = report.Rigs - report.PoweredRigs; int unpowered = report.Rigs - report.PoweredRigs;
if (unpowered > 0 && units.Count + unpowered > 0) ps.AddNightLog(L.T("nightlog.unpowered", unpowered), "cc7755"); if (unpowered > 0 && units.Count + unpowered > 0) ps.AddNightLog(L.T("nightlog.unpowered", unpowered), "cc7755");
if (d.CoinSalesToday > 0) ps.AddNightLog(L.T("nightlog.coinpay", Fmt.Credits(d.CoinSalesToday)));
d.CoinSalesToday = 0;
Session.Save(); Session.Save();
} }
} }
+2
View File
@@ -28,6 +28,8 @@ namespace Decentralized
public List<MarketEvent> ActiveEvents = new List<MarketEvent>(); public List<MarketEvent> ActiveEvents = new List<MarketEvent>();
public NightReport LastNight; public NightReport LastNight;
public bool OpeningNewsShown; public bool OpeningNewsShown;
/// <summary>当天用币付款的成交额(信用点),夜间清零。</summary>
public double CoinSalesToday;
} }
/// <summary>当前这一局的状态:读档 / 新游戏时载入,睡觉结算后写回。</summary> /// <summary>当前这一局的状态:读档 / 新游戏时载入,睡觉结算后写回。</summary>
@@ -488,6 +488,16 @@ internal static class Program
Check(Near(back.Market.Price, m.Price, 1e-9), "读回后继续推进,行情与原来完全相同"); Check(Near(back.Market.Price, m.Price, 1e-9), "读回后继续推进,行情与原来完全相同");
}); });
Test("用币付款:派系对应的币、按币价换算、污点与成本", () =>
{
var w = new Wallet { Address = "t" };
Check(CoinPayment.AssetFor("FACTION_BLACK_MARKET") == Assets.CRT && CoinPayment.AssetFor("FACTION_SECURITY") == null, "派系对应的币");
double q = CoinPayment.Receive(w, Assets.CRT, 200, 0.5, 40);
Check(Near(q, 400, 1e-9) && Near(w.Get(Assets.CRT), 400, 1e-9), "按币价换算数量");
Check(Near(w.Taint, 80, 1e-9) && Near(w.Basis, 200, 1e-9), "污点 = 货值 × 热度%,成本 = 货值");
Check(CoinPayment.Receive(w, Assets.CRC, 0, 1, 10) == 0, "零金额不收");
});
Console.WriteLine($"\n{_passed} 通过,{_failed} 失败"); Console.WriteLine($"\n{_passed} 通过,{_failed} 失败");
return _failed == 0 ? 0 : 1; return _failed == 0 ? 0 : 1;
} }