502 lines
27 KiB
C#
502 lines
27 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.Linq;
|
||
using System.Text.RegularExpressions;
|
||
using Decentralized.Domain;
|
||
using UnityEngine;
|
||
|
||
namespace Decentralized
|
||
{
|
||
/// <summary>
|
||
/// mod 物品:交易终端、矿架、显卡(每个型号一个物品 id)。显卡的健康度、假卡、翻新、烧毁存在物品标签上,随存档保存。
|
||
/// 读档时游戏按 id 重新调用工厂,再把存档里的标签与子物品(矿架里的显卡、电池)恢复回去。
|
||
/// </summary>
|
||
public static class Items
|
||
{
|
||
public const string Terminal = "pd_terminal";
|
||
public const string Rig = "pd_rig";
|
||
public const string Guide = "pd_gpu_guide";
|
||
public const string GpuPrefix = "pd_gpu_";
|
||
|
||
public const string HealthTag = "PD_HEALTH";
|
||
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", EditedTag = "PD_EDITED";
|
||
|
||
public static Action OpenTerminal;
|
||
|
||
public static void Subscribe() => ModHook.OnModItemDirectoryInit += Register;
|
||
public static void Unsubscribe() => ModHook.OnModItemDirectoryInit -= Register;
|
||
|
||
public static string GpuId(string model) => GpuPrefix + model.ToLowerInvariant();
|
||
|
||
public static bool IsGpu(GameItem item) => item != null && item.identifier != null && item.identifier.StartsWith(GpuPrefix);
|
||
|
||
private static void Register(ModItemDirectory dir)
|
||
{
|
||
dir.Add(Terminal, CreateTerminal);
|
||
dir.Add(Rig, CreateRig);
|
||
dir.Add(Guide, CreateGuide);
|
||
foreach (var spec in Hardware.Specs.Values.Where(s => !s.Asic))
|
||
{
|
||
var s = spec;
|
||
dir.Add(GpuId(s.Model), () => CreateGpu(s));
|
||
}
|
||
}
|
||
|
||
// ---------- 交易终端 ----------
|
||
|
||
private static GameItem CreateTerminal()
|
||
{
|
||
var item = ItemDirectory.CreateEmptyItem(Terminal);
|
||
item.SetSpriteAndShapeFromMod(ModInfo.Id, "terminal");
|
||
item.name = L.T("item.terminal.name");
|
||
item.shortDescription = L.T("item.terminal.desc");
|
||
item.unitValue = 0;
|
||
item.EnableTag(GeneralHelper.IMPORTANT_TAG);
|
||
item.EnableTag(TypeHelper.NOT_FOR_RESALE);
|
||
item.mayActivateSlotItemFunc = (_, _, _) => true;
|
||
item.canActivateSlotItemFunc = (_, _, _) => true;
|
||
item.onActivateSlotItemFunc = (_, _, _) => Log.Guard("打开交易终端", () => OpenTerminal?.Invoke());
|
||
ModHelper.SetSource(item, ModInfo.Id, ModInfo.Name);
|
||
return item;
|
||
}
|
||
|
||
/// <summary>显卡鉴定手册:档次对照、序列号规则、外观折扣。和游戏里的鉴定手册一样,是一件物品,说明里就是内容。</summary>
|
||
private static GameItem CreateGuide()
|
||
{
|
||
var item = ItemDirectory.CreateEmptyItem(Guide);
|
||
item.SetSpriteAndShapeFromMod(ModInfo.Id, "gpu_guide");
|
||
item.name = L.T("item.guide.name");
|
||
item.shortDescription = L.T("item.guide.desc");
|
||
item.unitValue = 0;
|
||
item.EnableTag(GeneralHelper.IMPORTANT_TAG);
|
||
item.EnableTag(TypeHelper.NOT_FOR_RESALE);
|
||
ModHelper.SetSource(item, ModInfo.Id, ModInfo.Name);
|
||
return item;
|
||
}
|
||
|
||
// ---------- 显卡 ----------
|
||
|
||
/// <summary>RZX70 → "RZX 70",RZ800 → "RZ 800"。</summary>
|
||
public static string DisplayModel(string model) => Regex.Replace(Regex.Replace(model, @"(?<=[A-Za-z])(?=\d)", " "), @"(?<=\d)(?=[A-Z])", " ");
|
||
|
||
private static GameItem CreateGpu(HardwareSpec spec)
|
||
{
|
||
var item = ItemDirectory.CreateEmptyItem(GpuId(spec.Model));
|
||
item.SetGameItemType("MATERIAL");
|
||
InspectableHelper.InitInspectableItem(item);
|
||
item.onLoaded = Refresh;
|
||
// 右键"切换"翻面;外观差异用叠加图画在同一张底图上
|
||
item.EnableTag(ToggleAppearanceHelper.TOGGLE_APPEARANCE_TAG);
|
||
item.mayToggleSlotItemFunc = (_, _, _) => true;
|
||
item.canToggleSlotItemFunc = (_, _, _) => true;
|
||
item.onToggleSlotItemFunc = (toggled, _, _) => Log.Guard("显卡翻面", () =>
|
||
{
|
||
Flip(toggled);
|
||
});
|
||
item.getItemSpriteModifierForItemFunc = Overlays;
|
||
// 摆给顾客的时候,顾客可能看出说法与实物对不上
|
||
item.onSlotAddItemFunc = (Action<GameItem, GameInventory, SlotMarker>)Delegate.Combine(item.onSlotAddItemFunc,
|
||
(Action<GameItem, GameInventory, SlotMarker>)((it, _, __) => Log.Guard("顾客验货", () => OnOffered(it))));
|
||
ModHelper.SetSource(item, ModInfo.Id, ModInfo.Name);
|
||
Refresh(item);
|
||
return item;
|
||
}
|
||
|
||
/// <summary>按给定的状态造一张卡(二手卡、假卡):写入标签并生成外观。</summary>
|
||
public static GameItem NewCard(string model, double health, string fakeOf, Rng rng, DealerKind? seller = null, int markupPct = 0)
|
||
{
|
||
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)
|
||
{
|
||
GeneralHelper.SetItemOwned(item, false); // 卖家的货:买下之前不属于玩家,说法才有效
|
||
SetClaim(item, CardLabels.DealerClaim(u, seller.Value, rng), 0, markupPct);
|
||
}
|
||
return item;
|
||
}
|
||
|
||
// ---------- 标签栏:卖家的说法与指出 ----------
|
||
|
||
private static object checkedClient;
|
||
private static readonly HashSet<int> checkedItems = new HashSet<int>();
|
||
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>
|
||
/// 自己的卡摆给想买的顾客:说法里隐瞒的每一项,顾客有 50% 的概率看出来;看出来就拆穿这一项(价格回到真实情况),
|
||
/// 并且掉一点信誉。每位顾客对每张卡只看一次。说法比实际更差(把好卡说差了)顾客不会去指。
|
||
/// </summary>
|
||
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) || (!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;
|
||
var claim = GetClaim(item);
|
||
if (claim == null) return;
|
||
if (!ReferenceEquals(checkedClient, ci)) { checkedClient = ci; checkedItems.Clear(); }
|
||
if (!checkedItems.Add(item.uniqueId)) return;
|
||
var u = ToUnit(item);
|
||
int exposed = Exposed(item);
|
||
int lies = CardLabels.Lies(u, claim.Value, exposed);
|
||
if (lies == 0) return;
|
||
var rng = Session.Data.Rng;
|
||
int found = 0;
|
||
foreach (int r in CardLabels.Rows)
|
||
if ((lies & r) != 0 && rng.Chance(CustomerSpotChance))
|
||
{
|
||
found |= r;
|
||
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, -rep);
|
||
EmporiumAudioPlayer.Instance?.audioFail?.Play();
|
||
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))));
|
||
}
|
||
|
||
/// <summary>标签上的说法;没有说法的卡(自己挖出来、正规渠道的新卡)一律按真实情况算。</summary>
|
||
public static Claim? GetClaim(GameItem item)
|
||
{
|
||
if (!item.IsTag(ClaimTag)) return null;
|
||
var c = Claim.Decode(item.GetTagReadonly(ClaimTag).GetString());
|
||
return string.IsNullOrEmpty(c.Model) ? (Claim?)null : c;
|
||
}
|
||
|
||
/// <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)
|
||
{
|
||
item.ModifyTag(ClaimTag, t => t.Enable().SetString(claim.Encode()));
|
||
item.ModifyTag(ExposedTag, t => t.Enable().SetInt(exposed));
|
||
if (markupPct != 0) item.ModifyTag(MarkupTag, t => t.Enable().SetInt(markupPct));
|
||
Refresh(item);
|
||
}
|
||
|
||
/// <summary>现在标签上写的型号:说法里的型号,被拆穿之后是真实芯片。</summary>
|
||
public static string BelievedModel(GameItem item, Unit u)
|
||
{
|
||
var c = GetClaim(item);
|
||
if (c == null) return u.Model;
|
||
return (Exposed(item) & CardLabels.RowSpec) != 0 ? (u.FakeOf ?? u.Model) : c.Value.Model;
|
||
}
|
||
|
||
/// <summary>玩家指出一个标签:记下来,说法改回真实情况;返回 0 说法属实、1 隐瞒了不利的情况、2 拆穿假货。</summary>
|
||
public static int Point(GameItem item, int row)
|
||
{
|
||
var u = ToUnit(item);
|
||
var claim = GetClaim(item) ?? Claim.Truth(u);
|
||
int exposed = Exposed(item);
|
||
int kind = CardLabels.Point(u, claim, row, ref exposed);
|
||
item.ModifyTag(ExposedTag, t => t.Enable().SetInt(exposed));
|
||
if (!item.IsTag(ClaimTag)) item.ModifyTag(ClaimTag, t => t.Enable().SetString(claim.Encode()));
|
||
Refresh(item);
|
||
if (item.IsTag("APPRAISAL_ITEM")) AppraisalHelper.SetDefaultAppraisalValue(item, (int)item.unitValue);
|
||
return kind;
|
||
}
|
||
|
||
/// <summary>用标签机改说法(随便改):这一项重新算作"没人指出过"。</summary>
|
||
public static void EditClaim(GameItem item, int row, int value, string model = null)
|
||
{
|
||
var u = ToUnit(item);
|
||
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);
|
||
}
|
||
|
||
/// <summary>说明:算力和耗电按标称写;成色在归玩家之前只给粗略等级(要看外观自己判断),归玩家之后给精确健康度。</summary>
|
||
private static string Desc(GameItem item, Unit u)
|
||
{
|
||
var label = Hardware.Specs[BelievedModel(item, u)];
|
||
string cond = GeneralHelper.IsItemOwned(item)
|
||
? Mathf.RoundToInt((float)u.Health) + "%"
|
||
: L.T(u.Health >= 85 ? "item.gpu.grade.1" : u.Health >= 60 ? "item.gpu.grade.2" : u.Health >= 35 ? "item.gpu.grade.3" : "item.gpu.grade.4");
|
||
return L.T("item.gpu.desc", Fmt.Num(label.DisplayHash), Fmt.Num(label.Kj), cond);
|
||
}
|
||
|
||
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;
|
||
string name = L.T("item.gpu.name", Hardware.Specs[BelievedModel(item, u)].Brand, DisplayModel(BelievedModel(item, u)));
|
||
if (item.name != name) item.name = name;
|
||
long value = CardLabels.Value(u, GetClaim(item), Exposed(item));
|
||
if (!GeneralHelper.IsItemOwned(item) && item.IsTag(MarkupTag)) value = Math.Max(1, (long)Math.Round(value * (1 + item.GetTagReadonly(MarkupTag).GetInt() / 100.0)));
|
||
if (item.unitValue != value) item.unitValue = value;
|
||
}
|
||
|
||
public static Unit ToUnit(GameItem item)
|
||
{
|
||
string model = item.identifier.Substring(GpuPrefix.Length);
|
||
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();
|
||
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;
|
||
}
|
||
|
||
public static void Store(GameItem item, Unit u)
|
||
{
|
||
item.ModifyTag(HealthTag, s => s.Enable().SetDouble(u.Health));
|
||
if (u.FakeOf != null) item.ModifyTag(FakeTag, s => s.Enable().SetString(u.FakeOf));
|
||
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);
|
||
}
|
||
|
||
/// <summary>按标签更新名字、说明、价值和图:显示的是标签上的型号(假卡也显示被冒充的型号)。</summary>
|
||
public static void Refresh(GameItem item)
|
||
{
|
||
var u = ToUnit(item);
|
||
var label = u.Label;
|
||
if (u.Dead)
|
||
{
|
||
item.name = L.T("item.gpu.burnt.name", label.Brand, DisplayModel(label.Model));
|
||
item.shortDescription = L.T("item.gpu.burnt.desc");
|
||
item.unitValue = Math.Max(1, label.Price / 20);
|
||
item.SetSpriteAndShapeFromMod(ModInfo.Id, item.IsTag(BackTag) ? "gpu_burnt_b" : "gpu_burnt_f");
|
||
return;
|
||
}
|
||
item.SetSpriteAndShapeFromMod(ModInfo.Id, LookKeys(item, new List<string>()));
|
||
SyncValue(item);
|
||
}
|
||
|
||
public static bool IsBack(GameItem item) => item.IsTag(BackTag);
|
||
|
||
public static void Flip(GameItem item)
|
||
{
|
||
if (IsBack(item)) item.ModifyTag(BackTag, t => t.Disable()); else item.EnableTag(BackTag);
|
||
Refresh(item);
|
||
}
|
||
|
||
/// <summary>当前面的底图名与叠加图名:正面三个维度,背面丝印与贴纸;烧毁的卡不叠。</summary>
|
||
/// <summary>实物上的风扇数:由真实芯片的档次决定(低档次的板子套高档次的壳,风扇数露馅)。</summary>
|
||
public static int FansOf(Unit u) => Hardware.FanCount[Hardware.Specs[u.FakeOf ?? u.Model].Tier];
|
||
|
||
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 (l.Fan > 0) overlays.Add($"gpu_ov_fan{l.Fan}_{FansOf(u)}");
|
||
if (l.Fingers > 0) overlays.Add("gpu_ov_fin" + l.Fingers);
|
||
if (l.Bracket > 0) overlays.Add("gpu_ov_brk1");
|
||
}
|
||
else
|
||
{
|
||
// 丝印:0 标称型号;1 印的是真实芯片型号;2 标称型号但字歪(重印过)
|
||
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 == 2) overlays.Add("gpu_ov_stk2");
|
||
}
|
||
return brand + (back ? "_b" : "_f_t" + Hardware.Specs[u.FakeOf ?? u.Model].Tier);
|
||
}
|
||
|
||
public static Sprite ModSprite(string key)
|
||
{
|
||
var mod = ModLoader.Mods.Find(m => m.Manifest.ID.Equals(ModInfo.Id));
|
||
return mod != null && mod.Sprites.TryGetValue(key, out var sprite) ? sprite : null;
|
||
}
|
||
|
||
/// <summary>物品栏里的外观叠加图。</summary>
|
||
private static List<ItemSpriteModifier> Overlays(GameItem item)
|
||
{
|
||
var list = new List<ItemSpriteModifier>();
|
||
SyncValue(item); // 归属变化(买下)后价格随之变回真实价值
|
||
if (item.IsTag(DeadTag)) return list;
|
||
var keys = new List<string>();
|
||
LookKeys(item, keys);
|
||
int priority = 0;
|
||
foreach (var k in keys)
|
||
{
|
||
var sprite = ModSprite(k);
|
||
if (sprite != null) list.Add(new ItemSpriteModifier(sprite, Vector2.zero, 0, false, priority++, false, Color.white));
|
||
}
|
||
return list;
|
||
}
|
||
|
||
// ---------- 矿架 ----------
|
||
|
||
public const string SlotsTag = "PD_SLOTS", ProgressTag = "PD_SLOT_PROGRESS";
|
||
|
||
private static int SlotsOf(GameItem rig) => rig.IsTag(SlotsTag) ? rig.GetTagReadonly(SlotsTag).GetInt() : RigSlots.Base;
|
||
|
||
private static GameItem CreateRig()
|
||
{
|
||
var battery = new GameSlotInventory();
|
||
var upgrade = new GameSlotInventory();
|
||
var modules = new GameGridInventory(MachineHelper.DEFAULT_MODULE_BAY_CONFIG, 7);
|
||
// 显卡每张 3×2 格:3 格宽的格子,每张卡 3×2 格,每个槽两行;未解锁的行不可放
|
||
var cards = new GameGridInventory(RigSlots.Shape(RigSlots.Base), RigSlots.CellWidth);
|
||
var window = MachineHelper.CreateMachineryWindow(true,
|
||
MachineHelper.PowerPanel(battery),
|
||
MachineHelper.Panel(L.T("rig.label.modules"), modules, "module"),
|
||
MachineHelper.Panel(L.T("rig.label.cards"), cards, "cards"),
|
||
MachineHelper.Panel(L.T("rig.label.upgrade"), upgrade, "upgrade"));
|
||
var rig = ItemDirectory.CreateEmptyItem(Rig).SetContentWindow(window);
|
||
rig.SetSpriteAndShapeFromMod(ModInfo.Id, "rig");
|
||
rig.name = L.T("item.rig.name");
|
||
rig.unitValue = Tuning.RigPrice;
|
||
MachineHelper.SetupBatterySlot(battery, rig);
|
||
rig.EnableTag("CONTAINER_TAG");
|
||
rig.EnableTag("STANDARD_MACHINE_TAG");
|
||
MachineryHelper.InitMachinery(rig, Tuning.RigKj);
|
||
// 游戏的通用模块槽(含模块相邻效果);模块变动后游戏会按原版公式改写耗电,再由 UpdateRig 按本 mod 的规则重算
|
||
MachineHelper.SetupModuleBay(modules, rig, () => UpdateRig(rig, modules, cards));
|
||
cards.mayInventoryAddItemFunc = (item, _) => GeneralHelper.IsItemOwned(item) && IsGpu(item) && !item.IsTag(DeadTag);
|
||
cards.onSlotAddItemEndFunc = (_, _, _) => UpdateRig(rig, modules, cards);
|
||
cards.onSlotRemoveItemEndFunc = (_, _, _) => UpdateRig(rig, modules, cards);
|
||
// 升级槽:只收下一级需要的电子元件,放进去立刻投入(计入进度),攒够解锁一个显卡槽
|
||
upgrade.mayInventoryAddItemFunc = (item, _) =>
|
||
{
|
||
var need = RigSlots.Next(SlotsOf(rig));
|
||
return GeneralHelper.IsItemOwned(item) && need != null && item.identifier == need.Value.Item;
|
||
};
|
||
upgrade.onSlotAddItemEndFunc = (item, _, _) => Log.Guard("矿架升级槽位", () => TakeUpgrade(rig, upgrade, cards, modules, item));
|
||
rig.onLoaded = _ => { ApplySlots(rig, cards); UpdateRig(rig, modules, cards); };
|
||
rig.onCycleEndEarlySlotItemFunc = (_, _, _) => Log.Guard("矿架扣电", () => NightDraw(rig, battery, modules, cards));
|
||
ModHelper.SetSource(rig, ModInfo.Id, ModInfo.Name);
|
||
ApplySlots(rig, cards);
|
||
UpdateRig(rig, modules, cards);
|
||
return rig;
|
||
}
|
||
|
||
/// <summary>按标签里的槽数设置显卡格子的形状(游戏自己扩展模块槽也是 SetShape 加 Validate)。</summary>
|
||
private static void ApplySlots(GameItem rig, GameGridInventory cards)
|
||
{
|
||
cards.SetShape(RigSlots.Shape(SlotsOf(rig)), RigSlots.CellWidth);
|
||
rig.contentWindow?.Validate();
|
||
}
|
||
|
||
private static void TakeUpgrade(GameItem rig, GameSlotInventory upgradeSlot, GameGridInventory cards, GameInventory modules, GameItem item)
|
||
{
|
||
int slots = SlotsOf(rig);
|
||
int progress = rig.IsTag(ProgressTag) ? rig.GetTagReadonly(ProgressTag).GetInt() : 0;
|
||
int before = slots;
|
||
int took = RigSlots.Insert(ref slots, ref progress, item.identifier, item.unitCount);
|
||
if (took <= 0) return;
|
||
rig.ModifyTag(SlotsTag, s => s.Enable().SetInt(slots));
|
||
rig.ModifyTag(ProgressTag, s => s.Enable().SetInt(progress));
|
||
// 消耗收下的件数;放不下的部分留在升级槽里(玩家取回)
|
||
if (took >= item.unitCount) { upgradeSlot.Expel(item); item.Destroy(); }
|
||
else item.GetSplitItem(took).Destroy();
|
||
if (slots != before)
|
||
{
|
||
ApplySlots(rig, cards);
|
||
EmporiumAudioPlayer.Instance?.ratchet?.Play();
|
||
}
|
||
UpdateRig(rig, (GameGridInventory)modules, cards);
|
||
}
|
||
|
||
public static List<GameItem> Cards(GameInventory cards) => cards.childItems.Where(IsGpu).ToList();
|
||
|
||
/// <summary>按模块槽里的模块重新汇总三项百分比(游戏自己的公式,含相邻效果),换算成本 mod 的模块效果。</summary>
|
||
public static ModuleBonus Bonus(GameItem rig, GameInventory modules)
|
||
{
|
||
ModuleHelper.ComputeModuleEffect(modules.childItems, rig);
|
||
return ModuleBonus.From(
|
||
rig.GetTagReadonly(ModuleHelper.TOTAL_PERCENTAGE_PERFORMANCE_BONUS_INT).GetInt(),
|
||
rig.GetTagReadonly(ModuleHelper.TOTAL_PERCENTAGE_POWER_BONUS_INT).GetInt(),
|
||
rig.GetTagReadonly(ModuleHelper.TOTAL_PERCENTAGE_QUALITY_BONUS_INT).GetInt());
|
||
}
|
||
|
||
private static int RigKj(GameInventory cards, ModuleBonus b) =>
|
||
Tuning.RigKj + Mathf.CeilToInt((float)Cards(cards).Select(ToUnit).Sum(u => u.Kj(b)));
|
||
|
||
private static void UpdateRig(GameItem rig, GameInventory modules, GameInventory cards)
|
||
{
|
||
var b = Bonus(rig, modules);
|
||
int kj = RigKj(cards, b);
|
||
// 基础耗电 = 机身 + 所有显卡(不含模块)。游戏装卸模块时用这个基础值重算耗电,所以它必须随显卡变化,
|
||
// 否则游戏会把耗电改回"只有机身的 2 kJ"再按百分比折算。
|
||
int baseKj = RigKj(cards, ModuleBonus.None);
|
||
rig.ModifyTag("MACHINERY_DRAWN_POWER_BASE", s => s.Enable().SetInt(baseKj));
|
||
rig.ModifyTag(MachineryHelper.MACHINERY_DRAWN_POWER, s => s.Enable().SetInt(kj));
|
||
double hash = Cards(cards).Select(ToUnit).Sum(u => u.Hash(b));
|
||
int slots = SlotsOf(rig);
|
||
rig.shortDescription = L.T("item.rig.desc", slots, Fmt.Num(hash * 10), kj);
|
||
var next = RigSlots.Next(slots);
|
||
if (next != null)
|
||
{
|
||
int progress = rig.IsTag(ProgressTag) ? rig.GetTagReadonly(ProgressTag).GetInt() : 0;
|
||
rig.shortDescription += " " + L.T("item.rig.next", slots + 1, L.T("mat." + next.Value.Item), progress, next.Value.Count);
|
||
}
|
||
if (b.Hash != 1 || b.Kj != 1 || b.Wear != 1)
|
||
rig.shortDescription += " " + L.T("item.rig.modules", Pct(b.Hash), Pct(b.Kj), Pct(b.Wear));
|
||
}
|
||
|
||
private static string Pct(double mult)
|
||
{
|
||
int p = (int)Math.Round((mult - 1) * 100);
|
||
return p > 0 ? "+" + p + "%" : p + "%";
|
||
}
|
||
|
||
/// <summary>夜间早段:按矿架上的显卡总耗电(含模块效果)从电池扣电;扣电成功,登记今晚参与挖矿的显卡和模块效果。</summary>
|
||
private static void NightDraw(GameItem rig, GameSlotInventory battery, GameInventory modules, GameInventory cards)
|
||
{
|
||
var list = Cards(cards).Where(c => !c.IsTag(DeadTag)).ToList();
|
||
Session.TonightRigs++;
|
||
if (list.Count == 0) return;
|
||
var b = Bonus(rig, modules);
|
||
int kj = RigKj(cards, b);
|
||
if (battery.childItems.Count == 0 || !PowerHelper.DrawPowerSource(battery.childItem, kj)) return;
|
||
Session.TonightPoweredRigs++;
|
||
Session.TonightKj += kj;
|
||
foreach (var c in list) Session.TonightCards.Add(new KeyValuePair<GameItem, ModuleBonus>(c, b));
|
||
UpdateRig(rig, modules, cards);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 放进储藏区(游戏 EmporiumTryAdd 的默认目标,放不下时游戏自己找位置)。
|
||
/// 不放柜台:测试中柜台上的物品过夜后会消失(未在正常流程中确认);ModHelper.MoveItemTo* 放满时会静默失败。
|
||
/// </summary>
|
||
public static void Give(GameItem item)
|
||
{
|
||
GeneralHelper.SetItemOwned(item, true);
|
||
GraphUtils.EmporiumTryAdd(item);
|
||
}
|
||
}
|
||
}
|