交易
玩家把船开进城市港口范围后,通过港口面板进入“购物车式”交易界面,按“基础价 × 库存系数 × 关系系数(卖出再乘新鲜度系数)“的价格与城市买卖物品;城市库存每个游戏日自动补货,奢侈品价格与库存随距产地距离梯度变化。
交易是金币的主要来源与去向:玩家通过出售生产品、怪物掉落物换取金币,再购入弹药、食物、建材维持航行与建设。累计卖出收入还直接驱动“贸易大亨”胜利路线(见 游戏循环与胜负)。
玩家接触路径:
- 把船开进某城市的触发半径(来自
cities.csv的 radius 列 × 世界缩放,详见下文)。 - 屏幕右侧自动弹出港口面板(含“交易 / 修船 / 招募 / 外交”按钮,
HarborPanel.cs)。敌对城市的交易按钮被禁用。 - 点击“交易”打开全屏交易弹窗(
TradingPanel.cs):左列是城市在售商品(购买),右列是玩家全船 Storage 容器里的物品(出售)。 - 用
+/−/组按钮把买卖条目加入“待执行清单”,底部实时显示金币与买入箱空间预览,点确认一次性结算。
交易对象目前只有两类:城市(CityTrader.cs,每个城市生成时自动挂载)和测试商家(TradingTestSetup.cs,仅当场景预设配了 TestTrader.json 时生成,开发用)。TraderType.NPCShip 枚举已定义但当前没有任何生成路径——不存在与 NPC 船只的交易。
交易对象与触发条件
Section titled “交易对象与触发条件”| 规则 | 数值 | 来源 |
|---|---|---|
| 触发判定 | 玩家船与港口参考点的直线距离 ≤ 城市触发半径,每帧检测 | 代码 CityTradingTrigger.cs |
| 触发参考点 | 优先用 HarborRegistry 注册的港口位置,无港口时回退城市自身位置 |
代码 CityTradingTrigger.cs |
| 城市触发半径 | cities.csv 的 radius(45~60)× 世界 cityRadiusScale |
CSV cities.csv + JSON DataSets/world/*.json |
cityRadiusScale |
real 世界 = 1.0(半径 45~60 米);compact 世界 = 0.2(9~12 米) | JSON DataSets/world/real.json / compact.json |
| 敌对城市 | 进入范围仍弹港口面板,但交易/修船/招募按钮禁用;也不记录“停靠港口”(不破除 Stress、不更新复活点) | 代码 HarborPanel.cs、CityTradingTrigger.cs |
敌对判定来自玩家与城市所属国家的声望关系(FactionRelation.GetRelation,默认阈值:声望 ≥ 50 为 Friendly,≤ −30 为 Hostile,其余 Neutral;自由港所属 country_free 不参与外交,永久 Neutral)。详见 阵营与外交。
进入城市范围还有两个交易相关副作用(见 探索与情报):
- 自动“发现”该城市及其所属国家(
DiscoveryAccess.DiscoverCity)。 - 发布一次港口简报:自上次简报以来的世界新闻 + 最多 3 条流言。流言之一是邻近(直线 3000 米内)已发现产地城市的奢侈品价格,带 ±30% 随机误差(
RumorGenerator.cs,Random.Range(0.7, 1.3))。
交易流程(购物车模式)
Section titled “交易流程(购物车模式)”整个交易在一个会话(TradingSession.cs)中进行,确认前不产生任何实际转移:
- 开始:UI 调用
CityTradingTrigger.StartTrading(),要求玩家仍在范围内。会话记录交易城市与国家 ID。交易开始后港口面板让位给交易弹窗。 - 加入购买(玩家从城市买):请求数量超过“城市当前库存 − 已加入购物车数量”时自动截断到最大可买量(仍算成功);库存已被购物车占满时返回“库存已售罄”。加入时锁定单价(之后城市库存变化不影响已加入条目的价格)。
- 加入出售(玩家卖给城市):同样按“玩家持有量 − 已加入数量”自动截断。出售按 (物品, 新鲜度) 分行展示,但同一物品的鲜/陈两行共享同一个待卖计数。
- 预览验证(
CalculatePreview):- 金币验证:
交易后金币 = 当前金币 + 总卖出收入 − 总购买支出,< 0 则“金币不足”。即同一次结算里卖货收入可以直接抵扣买货支出。 - 空间验证:购买需要的买入箱格子数 = 每种物品
⌈数量 ÷ 最大堆叠⌉之和(最大堆叠取自物品配置,缺省 50),超过买入箱剩余格子则“买入箱空间不足”。
- 金币验证:
- 执行:先执行全部出售(先拿钱),再执行全部购买(再花钱)。执行成功后会话保持打开,可继续交易;执行后清空购物车。
- 结束:关闭弹窗调用
TradingManager.EndTrading(),不结算未确认的购物车。
买卖价格体系
Section titled “买卖价格体系”基础价(运行时全局价格表)
Section titled “基础价(运行时全局价格表)”所有价格都从一张全局静态价格表(ItemPriceConfig.cs)查起,该表按以下顺序被写入:
- 代码默认表:20 种物品的硬编码价格(见下方“公式与数值”),未配置物品一律回退 10 金。
- 城市初始化覆盖:每个城市生成时,把自己在
CityInventory.csv中的buy_price(> 0 时)写入全局表(CityTrader.LoadInventoryFromCityManager)。 - 奢侈品产地价覆盖:随后把 GoodsOrigin 距离梯度算出的本城奢侈品价 × 全局价格修正器写入全局表(
CityTrader.LoadLuxuryGradientInventory)。
注意这是一张全局表:多个城市对同一物品写不同价格时,最后初始化的城市覆盖前面所有城市(详见“已知限制”)。
玩家买入价(城市卖给玩家)
Section titled “玩家买入价(城市卖给玩家)”买入单价 = round( round( 基础价 × 库存价格系数 ) × 关系系数 )- 库存价格系数(
TraderItemConfig.PriceModifier):城市商品恒为 1.0(CityTrader.cs写死);只有测试商家的TestTrader.json里可配非 1 值。 - 关系系数见下表。
玩家卖出价(城市收购)
Section titled “玩家卖出价(城市收购)”收购单价 = round( 基础价' × (1 / 关系系数) ) (关系系数为 1.0 时不变)实际成交单价 = max(1, round( 收购单价 × 新鲜度系数 ))- 基础价’:城市有该商品条目时 =
round(基础价 × 库存价格系数);城市不卖该物品时直接取全局基础价(城市默认收购一切物品,NPCTrader._buyAllItems = true)。 - 关系系数取倒数:关系差时城市压价收购,关系好且同国籍时城市加价收购。
- 新鲜度系数:Fresh / 无保鲜概念 = 1.0;Stale(陈旧)= 0.5(
FreshnessState.cs常量StaleValueMultiplier),折后最低 1 金。腐烂物不存在折扣问题——过期物品已被转换成RottenFood/StaleWater(基础价均为 1 金)。见 物品与库存。
默认没有买卖价差:同一物品在同一城市,买入价和收购价都从同一基础价推出(CityInventory.csv 的 sell_price 列代码从不读取)。价差完全由两个修正产生:
- 关系:Neutral 城市买入贵 20%、收购低约 16.7%(×1/1.2)。自由港永久 Neutral,所以在自由港交易永远吃这组双向价差。
- 新鲜度:陈旧货卖出打 5 折。
全局价格修正器(GlobalPriceModifier)
Section titled “全局价格修正器(GlobalPriceModifier)”- 世界事件调度器(
EventScheduler.cs)每 7 个游戏日投放 1 条新闻,4 类各 25% 概率:宣战 / 停战 / 通货变动 / 怪物潮(NewsTemplates.cs)。 - 通货变动新闻从 {IronPlate, Wood, Coal, Bullet} 均匀抽 1 种物品,价格乘数 0.95 或 1.05(各 50%),对该物品的全局修正系数累乘(
GlobalPriceModifier.Apply)。 - 修正系数随存档保存/读取(
WorldStateInfo.cs,见 存档)。 - 当前唯一应用点:城市初始化时计算奢侈品上架价(
CityTrader.LoadLuxuryGradientInventory中price × modifier.Get(itemId))。注意这两个集合不相交——见“已知限制”。
产地系统(奢侈品距离梯度)
Section titled “产地系统(奢侈品距离梯度)”6 种奢侈品各有唯一产地城市(GoodsOrigin.csv,GoodsOriginRegistry.cs 懒加载)。每个城市初始化时按“距产地的直线距离”计算本城的上架价与库存:
距离km = √( (Δx × positionScale)² + (Δz × positionScale)² ) ÷ 1000 (Δ 取 cities.csv 坐标差)上架价 = round( BasePrice × (1 + PriceRisePerKm × 距离km) ) × 全局价格修正器,最低 1 金上架库存 = round( max( BaseStock × (1 − StockDropPerKm × 距离km), BaseStock × MinStockRatio ) ),最低 0规则:
- 产地城市距离为 0:原价、满库存。
- 自由港(city_005)一律不上架任何奢侈品(
GoodsOriginRegistry.cs硬编码跳过,返回“不上架”)。 - 库存梯度有保底:跌到
BaseStock × MinStockRatio后不再下降。 - 奢侈品条目追加在
CityInventory.csv条目之后;同物品 ID 时 GoodsOrigin 覆盖 CityInventory(当前两表物品不重叠,规则备而未用)。 - 库存派生:
MaxStock = 上架库存 × 2,DailyRefresh = max(1, 上架库存 ÷ 5)(整除)。
示例(“正常游玩”预设,real 世界 positionScale = 1.0):
| 物品 | 在产地 | 在异地 | 计算过程 |
|---|---|---|---|
| LuxuryWine(产地马德里 city_001) | 5 金 / 库存 400 | 塞维利亚 city_002:11 金 / 库存 248 | 距离 ≈ 1.90 km;价 = round(5×(1+0.6×1.90));库存 = round(400×(1−0.2×1.90)) |
| LuxurySilk(产地马赛 city_007) | 80 金 / 库存 150 | 马德里 city_001:622 金 / 库存 8 | 距离 ≈ 5.64 km;价 = round(80×(1+1.2×5.64));库存触底 = round(150×0.05)=8 |
compact 世界(positionScale = 0.1)所有距离缩小 10 倍,梯度相应变缓。
城市库存:初始化与每日补货
Section titled “城市库存:初始化与每日补货”城市商品来自 CityInventory.csv(每行一个 城市×物品 条目),运行时派生为商家库存(CityTrader.cs → TraderInventory.cs):
| 运行时字段 | 派生公式 | 说明 |
|---|---|---|
| InitialStock(初始库存) | = CSV stock |
开局即有 |
| MaxStock(库存上限) | = CSV stock × 2 |
补货与收购都不会超过它 |
| DailyRefresh(每日补货量) | = CSV stock ÷ 5(整除) |
例:stock 80 → 每日 +16 |
| PriceModifier | = 1.0(写死) | 城市无单品价格系数 |
补货规则(TraderInventory.cs):
- 监听游戏日变化事件(
DateTimeManager.OnDayChanged),每个游戏日刷新一次(同一天去重)。 新库存 = min(当前库存 + DailyRefresh, MaxStock)。- 1 游戏日 = 1440 游戏分钟;默认速度下 1 现实秒 = 1 游戏小时,即 1 游戏日 = 24 现实秒(随游戏速度档位缩放,见 时间与调度)。
- 库存为 0 的商品不出现在在售列表,补货后重新上架。
玩家卖给城市的物品去向(NPCTrader.Buy):城市有该商品条目时入库(仍受 MaxStock 封顶,溢出部分消失);城市没有该条目时物品直接消失。两种情况金币都照付——城市金币无限(NPCTrader.Gold = long.MaxValue)。
交易限制与验证
Section titled “交易限制与验证”| 限制 | 规则 | 来源 |
|---|---|---|
| 必须有买入箱 | 购买的物品只进“买入箱”建筑(PurchaseBox);一个都没有时无法购买 |
代码 PlayerTrader.cs |
| 买入箱容量 | 每个买入箱 100 格 Storage 容器;1×1 建筑,需玩家自己建造(见 修建) | CSV BuildingContainerConfig.csv、BuildingConfig.csv |
| 空间验证单位 | 按格子数验证:⌈数量 ÷ 物品最大堆叠⌉,最大堆叠见 物品数据表,未配置回退 50 |
代码 TradingSession.cs |
| 出售货源 | 全船 Storage 类容器(含仓库、买入箱),不含地面物品、生产建筑的输入/输出格 | 代码 PlayerTrader.cs |
| 出售扣货顺序 | 按容器/槽位遍历顺序扣减,不保证陈货先出 | 代码 PlayerTrader.cs 注释 |
| 金币 | 存在玩家船数据上(ShipData.gold),见 船只 |
代码 TradingManager.cs |
交易完成的连锁反应
Section titled “交易完成的连锁反应”每次成功执行且会话带国家 ID 时发布交易完成事件(TradeCompletedMessage),两个系统消费:
声望(FactionEventHandler.cs,数值硬编码在 FactionManager.cs):
| 事件 | 条件 | 对交易国声望 |
|---|---|---|
| FirstVisit | 本局首次在该城市完成交易(按城市去重;只靠岸不交易不算) | +10 |
| TradeSmall | 单次结算卖出收入 1~100 金 | +3 |
| TradeMedium | 单次结算卖出收入 101~500 金 | +5 |
| TradeLarge | 单次结算卖出收入 > 500 金 | +8 |
只有卖出计声望,纯购买不加;声望只给交易城市所属国家。
胜利(VictoryManager.cs):累计卖出收入 ≥ 50000 金(代码常量 TradeTycoonThreshold)达成“贸易大亨”胜利。
standard / rich 数据集差异
Section titled “standard / rich 数据集差异”CityInventory.csv 有两个变体,由场景预设 JSON 的 trading 轴选择(ScenarioLoader.cs):
| 变体 | 定位 | 与 standard 的差异 |
|---|---|---|
DataSets/trading/standard/ |
正常游玩数值 | 基准 |
DataSets/trading/rich/ |
测试用富裕环境 | 同样的城市×物品组合,库存约 ×4~×10(多数 ×5),价格约 ÷2;额外带 TestTrader.json(在 (10, 0, 10) 生成一个蓝色圆柱测试商家,9 种商品) |
预设归属:正常游玩.json 未配 trading 轴 → 走默认路径 standard;战斗测试.json 显式 standard;其余测试预设(交易测试、日常测试、集成测试、外循环测试等共 11 个)全部用 rich。standard 变体目录下没有 TestTrader.json,所以正常游玩不会出现测试商家。
价格公式汇总
Section titled “价格公式汇总”| 公式 | 内容 | 来源 |
|---|---|---|
| 玩家买入单价 | round( round(基础价 × 库存系数) × 关系系数 ) |
代码 TraderInventory.cs + CityTrader.cs |
| 城市收购单价 | round( 基础价' × (1/关系系数) ) |
代码 CityTrader.cs |
| 实际卖出成交价 | max(1, round(收购单价 × 新鲜度系数)) |
代码 TradingSession.cs |
| 奢侈品上架价 | max(1, round( round(BasePrice × (1 + PriceRisePerKm × 距离km)) × 全局修正 )) |
代码 GoodsOriginRegistry.cs + CityTrader.cs |
| 奢侈品上架库存 | round( max(BaseStock × (1 − StockDropPerKm × 距离km), BaseStock × MinStockRatio) ) |
代码 GoodsOriginRegistry.cs |
| 交易后金币 | 当前金币 + Σ卖出 − Σ购买(≥ 0 才放行) |
代码 TradingSession.cs |
| 购买所需格子 | Σ ⌈数量 ÷ 最大堆叠⌉(按物品种类分别向上取整) |
代码 TradingSession.cs |
| 每日补货 | min(当前库存 + DailyRefresh, MaxStock),每游戏日一次 |
代码 TraderInventory.cs |
关系系数表(CityTrader.cs 代码常量)
Section titled “关系系数表(CityTrader.cs 代码常量)”| 玩家与城市所属国关系 | 买入系数(玩家付) | 收购系数(玩家收) |
|---|---|---|
| Friendly 且同国籍 | ×0.9 | ×1/0.9 ≈ ×1.111 |
| Friendly | ×1.0 | ×1.0 |
| Neutral(含自由港,永久中立) | ×1.2 | ×1/1.2 ≈ ×0.833 |
| Hostile | ×1.5(理论值,UI 已拦截交易,正常到不了) | ×1/1.5 ≈ ×0.667 |
代码默认基础价表(ItemPriceConfig.cs 代码常量,单位:金/个)
Section titled “代码默认基础价表(ItemPriceConfig.cs 代码常量,单位:金/个)”| 物品 | 价 | 物品 | 价 | 物品 | 价 |
|---|---|---|---|---|---|
| SaltPork | 10 | Coal | 2 | MonsterCorpse | 30 |
| DrinkableWater | 3 | Bullet | 1 | MonsterMeat | 8 |
| Grog | 8 | CannonShell | 3 | CookedMeal | 20 |
| Water | 2 | AA_Gun_Bullet | 1 | MonsterBrew | 40 |
| Steam | 3 | Wood | 3 | RottenFood | 1 |
| BeastOil | 12 | Stone | 4 | StaleWater | 1 |
| BlackPowder | 6 | IronPlate | 8 | (未配置物品) | 10 |
这张表是兜底:城市初始化后,凡出现在 CityInventory.csv / GoodsOrigin.csv 的物品会被运行时覆盖(见“已知限制”第 1 条)。Fuel 不在默认表里,价格完全由 CSV 提供。
CityInventory.csv 全量(standard / rich 对照)
Section titled “CityInventory.csv 全量(standard / rich 对照)”列含义:city_id 城市、item_id 物品、stock 初始库存(同时派生上限 ×2、日补 ÷5)、buy_price 写入全局基础价、sell_price 代码未读取。两变体各 41 行数据。
| 城市 | 物品 | standard 库存/价 | rich 库存/价 |
|---|---|---|---|
| city_001 马德里 | SaltPork | 80 / 8 | 320 / 4 |
| city_001 马德里 | Coal | 200 / 2 | 800 / 1 |
| city_001 马德里 | MonsterBrew | 30 / 40 | 120 / 20 |
| city_001 马德里 | DrinkableWater | 400 / 3 | 2000 / 2 |
| city_001 马德里 | Bullet | 300 / 2 | 2000 / 1 |
| city_001 马德里 | CannonShell | 100 / 4 | 1000 / 2 |
| city_001 马德里 | Wood | 150 / 4 | 1000 / 2 |
| city_001 马德里 | IronPlate | 100 / 8 | 500 / 4 |
| city_001 马德里 | Stone | 100 / 5 | 500 / 3 |
| city_002 塞维利亚 | SaltPork | 80 / 8 | 320 / 4 |
| city_002 塞维利亚 | Coal | 200 / 2 | 800 / 1 |
| city_002 塞维利亚 | MonsterBrew | 30 / 40 | 120 / 20 |
| city_002 塞维利亚 | DrinkableWater | 600 / 2 | 3000 / 1 |
| city_002 塞维利亚 | Bullet | 100 / 2 | 1000 / 1 |
| city_002 塞维利亚 | Wood | 200 / 3 | 1000 / 2 |
| city_002 塞维利亚 | IronPlate | 250 / 6 | 1500 / 3 |
| city_003 普利茅斯 | Bullet | 800 / 1 | 5000 / 1 |
| city_003 普利茅斯 | CannonShell | 500 / 3 | 3000 / 2 |
| city_003 普利茅斯 | AA_Gun_Bullet | 300 / 1 | 2000 / 1 |
| city_003 普利茅斯 | Fuel | 300 / 6 | 2000 / 3 |
| city_003 普利茅斯 | DrinkableWater | 200 / 4 | 1000 / 2 |
| city_003 普利茅斯 | IronPlate | 100 / 9 | 500 / 5 |
| city_003 普利茅斯 | Wood | 100 / 5 | 500 / 3 |
| city_004 朴次茅斯 | Wood | 400 / 2 | 2000 / 1 |
| city_004 朴次茅斯 | Stone | 300 / 3 | 1500 / 2 |
| city_004 朴次茅斯 | IronPlate | 200 / 7 | 1000 / 4 |
| city_004 朴次茅斯 | Coal | 300 / 2 | 1500 / 1 |
| city_004 朴次茅斯 | Fuel | 200 / 5 | 1000 / 3 |
| city_004 朴次茅斯 | DrinkableWater | 200 / 3 | 1000 / 2 |
| city_005 自由港 | DrinkableWater | 300 / 5 | 1500 / 3 |
| city_005 自由港 | Bullet | 200 / 3 | 1000 / 2 |
| city_005 自由港 | CannonShell | 100 / 5 | 500 / 3 |
| city_005 自由港 | Wood | 100 / 5 | 500 / 3 |
| city_005 自由港 | Stone | 80 / 6 | 400 / 4 |
| city_005 自由港 | IronPlate | 60 / 11 | 300 / 6 |
| city_005 自由港 | Fuel | 100 / 9 | 500 / 5 |
| city_005 自由港 | SaltPork | 80 / 13 | 400 / 7 |
| city_006 勒阿弗尔 | Wood | 120 / 5 | 600 / 3 |
| city_006 勒阿弗尔 | IronPlate | 80 / 10 | 400 / 5 |
| city_007 马赛 | Wood | 120 / 5 | 600 / 3 |
| city_007 马赛 | IronPlate | 80 / 10 | 400 / 5 |
数据设计意图(仅供调参参考,实际生效情况见“已知限制”第 1 条):city_003 是弹药特化港(Bullet 1 金)、city_004 是建材特化港(Wood 2 金)、自由港全线高价(中立价差 + 高标价双重溢价)、city_006/007 是远海高危区的简化补给点。
GoodsOrigin.csv 全量(6 行)
Section titled “GoodsOrigin.csv 全量(6 行)”| 物品 | 产地 | 产地价(金) | 产地库存 | 价格涨幅/km | 库存降幅/km | 库存保底比例 |
|---|---|---|---|---|---|---|
| LuxuryWine | city_001 马德里 | 5 | 400 | 0.6 | 0.20 | 0.10 |
| LuxuryTobacco | city_002 塞维利亚 | 30 | 250 | 1.0 | 0.30 | 0.05 |
| LuxuryCloth | city_003 普利茅斯 | 12 | 300 | 0.7 | 0.20 | 0.10 |
| LuxuryArtillery | city_004 朴次茅斯 | 30 | 100 | 1.0 | 0.30 | 0.05 |
| LuxuryCoffee | city_006 勒阿弗尔 | 30 | 300 | 0.8 | 0.25 | 0.05 |
| LuxurySilk | city_007 马赛 | 80 | 150 | 1.2 | 0.30 | 0.05 |
自由港 city_005 没有产地行,也不上架任何奢侈品(代码硬编码)。城市坐标(距离计算输入,cities.csv):city_001 (200,150)、city_002 (−1500,1000)、city_003 (2000,500)、city_004 (2500,2000)、city_005 (500,2500)、city_006 (−1000,5000)、city_007 (2000,5500)。
TestTrader.json(仅 rich 变体,开发用)
Section titled “TestTrader.json(仅 rich 变体,开发用)”名称“测试商店”,位置 (10, 0, 10),类型 City,收购一切。9 种商品:SaltPork(上限 100/初始 50/日补 10)、CookedMeal(80/40/8)、DrinkableWater(200/100/20)、BeastOil(100/30/5,价格系数 1.2,全游戏唯一非 1 库存价格系数示例)、Bullet(500/200/50)、CannonShell(200/100/20)、AA_Gun_Bullet(300/150/30)、IronPlate(100/50/10)、Coal(200/100/20)。
其他数值速查
Section titled “其他数值速查”| 项 | 数值 | 单位 | 来源 |
|---|---|---|---|
| 新鲜度折扣(Stale) | 0.5,折后最低 1 金 | 倍率 | 代码 FreshnessState.cs |
| 世界新闻间隔 | 7 | 游戏日 | 代码 EventScheduler.cs |
| 通货变动概率 | 每条新闻 25%(4 类均分) | 百分比 | 代码 NewsTemplates.cs |
| 通货变动幅度 | ×0.95 或 ×1.05(各 50%),累乘 | 倍率 | 代码 NewsTemplates.cs |
| 通货变动物品池 | IronPlate / Wood / Coal / Bullet(各 25%) | — | 代码 NewsTemplates.cs |
| 流言价格误差 | ×0.7 ~ ×1.3 均匀随机 | 倍率 | 代码 RumorGenerator.cs |
| 流言邻城距离上限 | 3000 | 米 | 代码 RumorGenerator.cs |
| 单次简报流言上限 | 3 | 条 | 代码 RumorGenerator.cs |
| 买入箱容量 | 100 | 格 | CSV BuildingContainerConfig.csv |
| 堆叠数兜底 | 50 | 个/格 | 代码 TradingSession.cs、PlayerTrader.cs |
| 贸易大亨胜利 | 累计卖出收入 ≥ 50000 | 金 | 代码 VictoryManager.cs |
| 声望:首次交易 / 小额 / 中额 / 大额 | +10 / +3 / +5 / +8 | 点 | 代码 FactionManager.cs |
| 交易额分档 | 小 ≤ 100 < 中 ≤ 500 < 大 | 金(单次卖出收入) | 代码 FactionEventHandler.cs |
| UI 列表刷新 | 0.5 | 秒 | 代码 TradingPanel.cs |
与其他系统的交互
Section titled “与其他系统的交互”- 物品与库存:出售货源是全船 Storage 容器;购买入买入箱;新鲜度(Fresh/Stale)决定卖出折扣;过期转换物 RottenFood/StaleWater 只值 1 金。
- 生产与制作:生产链产物(CookedMeal、MonsterBrew 等)是主要卖出收入来源。
- 战利品与掉落:MonsterCorpse(30 金)等怪物掉落是前期重要变现物。
- 修建:买入箱(PurchaseBox)是 1×1 设施建筑,需先建好才能购买。
- 城市与国家:交易对象、触发半径、城市类型与坐标都来自城市系统;城市被占领会改变所属国家进而改变关系系数。
- 阵营与外交:关系状态决定 0.9~1.2 的价格系数与敌对禁入;交易反过来是涨声望最稳定的手段(+3~+10/次)。
- 探索与情报:靠港自动发现城市;港口简报推送通货变动新闻与奢侈品价格流言;产地价差是探索远港的经济动机。
- 时间与调度:城市补货与世界新闻都挂在游戏日变化事件上;默认 1 游戏日 = 24 现实秒。
- 游戏循环与胜负:累计卖出 50000 金达成贸易大亨胜利。
- 存档:全局价格修正器与新闻列表随世界状态存档;城市当前库存不存档(重新初始化为 CSV 初始值)。
- 界面总览:港口面板(MainRight 层)与交易弹窗(Modal 层)。
- 开发者工具:场景预设选择 standard/rich 数据集与测试商家。
配置与调参指南
Section titled “配置与调参指南”| 想调什么 | 改哪里 | 注意 |
|---|---|---|
| 某城市卖什么、卖多少、标价 | Assets/StreamingAssets/DataSets/trading/standard/CityInventory.csv(正常游玩)或 rich/(多数测试预设) |
改 buy_price;sell_price 列无效;库存上限/日补由 stock 自动派生(×2、÷5),不能独立调 |
| 物品兜底价格 | ItemPriceConfig.cs 代码默认表 |
需要程序改;只对不在任何 CSV 里的物品长期生效 |
| 奢侈品产地、价格/库存梯度 | Assets/StreamingAssets/GoodsOrigin.csv |
列:BasePrice、BaseStock、PriceRisePerKm、StockDropPerKm、MinStockRatio;产地城市改 OriginCityId |
| 关系价差幅度 | CityTrader.cs 的 GetRelationPriceModifier() |
代码常量 0.9 / 1.0 / 1.2 / 1.5,需程序改 |
| 陈旧货折扣 | FreshnessState.cs 常量 StaleValueMultiplier(0.5) |
注意此值同时影响食物饱食恢复量,不只交易 |
| 补货节奏 | TraderInventory.cs(每日一次)或 CSV stock(决定日补量) |
想改“每 N 天补货”需程序改 |
| 通货变动频率/幅度/物品池 | EventScheduler.cs(7 天)、NewsTemplates.cs(±5%、物品池) |
代码常量;当前对实际价格无效(见已知限制第 2 条) |
| 声望奖励数值 | FactionManager.cs 的 _reputationEvents 硬编码表 |
代码常量;分档阈值在 FactionEventHandler.cs |
| 贸易胜利门槛 | VictoryManager.cs 常量 TradeTycoonThreshold(50000) |
代码常量 |
| 交易触发距离 | cities.csv 的 radius 列 + DataSets/world/*.json 的 cityRadiusScale |
半径同时是城市 Gizmo/领地视觉半径 |
| 买入箱容量 | BuildingContainerConfig.csv PurchaseBox 行 StorageCapacity(100) |
CSV 可改 |
| 测试商家 | DataSets/trading/rich/TestTrader.json |
仅当预设 trading 轴指向含该文件的变体时生成 |
| 切换 standard/rich | Assets/StreamingAssets/Presets/*.json 的 "trading" 字段 |
不配 = standard |
已知限制与注意事项
Section titled “已知限制与注意事项”- 全局价格表互相覆盖,“每城一价”实际不成立:
ItemPriceConfig是全局静态表,每个城市初始化时把自己的 CSV 标价和奢侈品距离价写进同一张表,而商家报价在交易时实时查这张表。结果是:同一物品在所有城市同价,以最后初始化的城市写入值为准(如 SaltPork 在 CSV 里 city_001 标 8、自由港标 13,实际全图统一为后初始化者的值)。奢侈品的价格梯度同样被覆盖失效,只有库存梯度真实生效;流言播报的产地价用的是独立计算值,可能与商店实价不符。CSV 中的城市差异化标价目前主要剩“该城卖不卖、卖多少库存”的作用。 - 通货变动新闻无实际价格效果:
GlobalPriceModifier唯一的读取点是城市初始化时的奢侈品上架价,而通货变动新闻的物品池是 IronPlate/Wood/Coal/Bullet——两个集合不相交,所以该系数当前永远是 1.0 参与计算。且GlobalPriceModifier.cs注释声称“dock 入场重算 inventory 时应用”,实际代码只在城市 Start 时应用一次,会话中途的通货变动要等读档/重开场景才可能生效(前提是物品池修正)。 sell_price列是死配置:代码无任何读取(全工程无.SellPrice引用),买卖同价由代码结构决定。PlayerTrader.Buy把“剩余格子数”当“物品个数”用:单个买入箱单次结算最多入库“空格数”个物品(100 格空箱最多收 100 个),与预览的按格验证(100 格可装 100×50 个)不一致;超出部分自动按单价退款,不报错。大宗采购建议分多次确认或多建买入箱。- 预览的格子估算偏保守(
TradingSession.cs内 TODO):按“每种物品都需要全新格子”计算,未考虑买入箱已有同类未满格可合并,可能在实际放得下时提前拦截。 - 城市收购无限且无差别:
_buyAllItems默认 true,_specificBuyItemIds白名单机制存在但无任何配置使用;城市金币无限,卖出没有数量上限或价格递减,刷钱的唯一约束是货源和声望分档。 - 城市库存不存档:读档后城市库存回到 CSV 初始值;只有全局价格修正器、新闻、发现状态随世界状态存档。
- NPC 船交易未实现:
TraderType.NPCShip与设计文档(Docs/design/TradingSystem_Design.md)中“与其他船只交易”的设想均未落地,当前交易对象只有城市与开发用测试商家。 - 设计文档差异:设计文档中提到的价格随机性、供需动态、品质影响价格均未实现;现状是固定基础价 + 关系/新鲜度/产地三类修正。
TradingManager.cs的序列化字段_gameDayInSeconds(300 秒)与_defaultTriggerDistance(50 米)是早期设计遗留,当前无任何消费代码——补货实际走DateTimeManager的游戏日(24 现实秒/天),触发距离实际走城市半径。 Assets/StreamingAssets/CitySystem/CityInventory.csv是未被引用的旧副本(内容与 standard 变体完全相同):运行时加载路径是DataSets/trading/standard/,改 CitySystem 目录下那份不会生效。- 出售列表的鲜/陈分行共享同一待卖计数,且扣货按槽位顺序、不保证陈货先卖;玩家想“只卖陈货”时可能混卖鲜货(
PlayerTrader.cs注释明示的取舍)。