zc
This commit is contained in:
@@ -51,6 +51,15 @@
|
|||||||
<WULA_InputItems>种输入物品</WULA_InputItems>
|
<WULA_InputItems>种输入物品</WULA_InputItems>
|
||||||
<WULA_OutputItems>种输出物品</WULA_OutputItems>
|
<WULA_OutputItems>种输出物品</WULA_OutputItems>
|
||||||
|
|
||||||
|
<WULA_AccessGlobalStorage>存取全局存储</WULA_AccessGlobalStorage>
|
||||||
|
<WULA_AccessGlobalStorageDesc>在轨道贸易信标范围和全局存储之间存取物品</WULA_AccessGlobalStorageDesc>
|
||||||
|
<WULA_NoPoweredTradeBeacon>没有已通电的轨道贸易信标</WULA_NoPoweredTradeBeacon>
|
||||||
|
<WULA_NoNegotiator>没有可用的殖民者</WULA_NoNegotiator>
|
||||||
|
<WULA_GlobalStorageTransferTitle>全局存储存取</WULA_GlobalStorageTransferTitle>
|
||||||
|
<WULA_GlobalStorageTransferHint>负数=存(信标->全局),正数=取(全局->信标空投)</WULA_GlobalStorageTransferHint>
|
||||||
|
<WULA_ResetTransfer>清零</WULA_ResetTransfer>
|
||||||
|
<WULA_ExecuteTransfer>执行存取</WULA_ExecuteTransfer>
|
||||||
|
|
||||||
<!-- 中文翻译 -->
|
<!-- 中文翻译 -->
|
||||||
<WULA_AirdropProducts>空投成品</WULA_AirdropProducts>
|
<WULA_AirdropProducts>空投成品</WULA_AirdropProducts>
|
||||||
<WULA_AirdropProductsDesc>将乌拉帝国母舰和工程舰上完成加工的所有物品通过空投舱投放到指定区域</WULA_AirdropProductsDesc>
|
<WULA_AirdropProductsDesc>将乌拉帝国母舰和工程舰上完成加工的所有物品通过空投舱投放到指定区域</WULA_AirdropProductsDesc>
|
||||||
|
|||||||
@@ -56,11 +56,145 @@ namespace WulaFallenEmpire
|
|||||||
|
|
||||||
if (CurrentlyUsableForGlobalBills())
|
if (CurrentlyUsableForGlobalBills())
|
||||||
{
|
{
|
||||||
|
TryAutoGatherFromBeaconsAndContainer();
|
||||||
globalOrderStack.ProcessOrders();
|
globalOrderStack.ProcessOrders();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal void TryAutoGatherFromBeaconsAndContainer()
|
||||||
|
{
|
||||||
|
var order = globalOrderStack?.orders?.FirstOrDefault(o =>
|
||||||
|
o != null &&
|
||||||
|
!o.paused &&
|
||||||
|
o.state == GlobalProductionOrder.ProductionState.Gathering);
|
||||||
|
if (order == null) return;
|
||||||
|
|
||||||
|
var storage = Find.World.GetComponent<GlobalStorageWorldComponent>();
|
||||||
|
if (storage == null) return;
|
||||||
|
|
||||||
|
Dictionary<ThingDef, int> required = GetRequiredMaterialsForOrder(order);
|
||||||
|
if (required.Count == 0) return;
|
||||||
|
|
||||||
|
bool changed = false;
|
||||||
|
foreach (var kvp in required)
|
||||||
|
{
|
||||||
|
ThingDef thingDef = kvp.Key;
|
||||||
|
int need = kvp.Value;
|
||||||
|
if (need <= 0) continue;
|
||||||
|
|
||||||
|
int inCloud = storage.GetInputStorageCount(thingDef);
|
||||||
|
int missing = need - inCloud;
|
||||||
|
if (missing <= 0) continue;
|
||||||
|
|
||||||
|
int uploadedFromBeacons = UploadFromPoweredTradeBeacons(storage, thingDef, missing);
|
||||||
|
if (uploadedFromBeacons > 0)
|
||||||
|
{
|
||||||
|
changed = true;
|
||||||
|
missing -= uploadedFromBeacons;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (missing <= 0) continue;
|
||||||
|
|
||||||
|
int uploadedFromContainer = UploadFromInnerContainer(storage, thingDef, missing);
|
||||||
|
if (uploadedFromContainer > 0)
|
||||||
|
{
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changed)
|
||||||
|
{
|
||||||
|
order.UpdateState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal Dictionary<ThingDef, int> GetRequiredMaterialsForOrder(GlobalProductionOrder order)
|
||||||
|
{
|
||||||
|
var required = order.GetProductCostList();
|
||||||
|
if (required.Count > 0) return required;
|
||||||
|
|
||||||
|
required = new Dictionary<ThingDef, int>();
|
||||||
|
if (order.recipe?.ingredients == null) return required;
|
||||||
|
|
||||||
|
foreach (var ingredient in order.recipe.ingredients)
|
||||||
|
{
|
||||||
|
ThingDef def = ingredient.filter?.AllowedThingDefs?.FirstOrDefault();
|
||||||
|
if (def == null) continue;
|
||||||
|
|
||||||
|
int count = ingredient.CountRequiredOfFor(def, order.recipe);
|
||||||
|
if (count <= 0) continue;
|
||||||
|
|
||||||
|
if (required.ContainsKey(def)) required[def] += count;
|
||||||
|
else required[def] = count;
|
||||||
|
}
|
||||||
|
|
||||||
|
return required;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal int UploadFromInnerContainer(GlobalStorageWorldComponent storage, ThingDef def, int count)
|
||||||
|
{
|
||||||
|
if (count <= 0) return 0;
|
||||||
|
|
||||||
|
int remaining = count;
|
||||||
|
int uploaded = 0;
|
||||||
|
|
||||||
|
while (remaining > 0)
|
||||||
|
{
|
||||||
|
Thing thing = innerContainer?.FirstOrDefault(t => t.def == def);
|
||||||
|
if (thing == null) break;
|
||||||
|
|
||||||
|
int take = Mathf.Min(thing.stackCount, remaining);
|
||||||
|
Thing split = thing.SplitOff(take);
|
||||||
|
split.Destroy(DestroyMode.Vanish);
|
||||||
|
|
||||||
|
storage.AddToInputStorage(def, take);
|
||||||
|
|
||||||
|
uploaded += take;
|
||||||
|
remaining -= take;
|
||||||
|
}
|
||||||
|
|
||||||
|
return uploaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal int UploadFromPoweredTradeBeacons(GlobalStorageWorldComponent storage, ThingDef def, int count)
|
||||||
|
{
|
||||||
|
if (count <= 0) return 0;
|
||||||
|
if (Map == null) return 0;
|
||||||
|
|
||||||
|
int remaining = count;
|
||||||
|
int uploaded = 0;
|
||||||
|
|
||||||
|
foreach (var beacon in Building_OrbitalTradeBeacon.AllPowered(Map))
|
||||||
|
{
|
||||||
|
foreach (var cell in beacon.TradeableCells)
|
||||||
|
{
|
||||||
|
if (remaining <= 0) break;
|
||||||
|
|
||||||
|
List<Thing> things = cell.GetThingList(Map);
|
||||||
|
for (int i = things.Count - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
if (remaining <= 0) break;
|
||||||
|
|
||||||
|
Thing t = things[i];
|
||||||
|
if (t?.def != def) continue;
|
||||||
|
|
||||||
|
int take = Mathf.Min(t.stackCount, remaining);
|
||||||
|
Thing split = t.SplitOff(take);
|
||||||
|
split.Destroy(DestroyMode.Vanish);
|
||||||
|
|
||||||
|
storage.AddToInputStorage(def, take);
|
||||||
|
uploaded += take;
|
||||||
|
remaining -= take;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (remaining <= 0) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return uploaded;
|
||||||
|
}
|
||||||
|
|
||||||
public bool CurrentlyUsableForGlobalBills()
|
public bool CurrentlyUsableForGlobalBills()
|
||||||
{
|
{
|
||||||
if (powerComp != null && !powerComp.PowerOn)
|
if (powerComp != null && !powerComp.PowerOn)
|
||||||
@@ -83,6 +217,15 @@ namespace WulaFallenEmpire
|
|||||||
{
|
{
|
||||||
yield return g;
|
yield return g;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
yield return new Command_Action
|
||||||
|
{
|
||||||
|
action = OpenGlobalStorageTransferDialog,
|
||||||
|
defaultLabel = "WULA_AccessGlobalStorage".Translate(),
|
||||||
|
defaultDesc = "WULA_AccessGlobalStorageDesc".Translate(),
|
||||||
|
icon = ContentFinder<Texture2D>.Get("UI/Commands/Trade", true),
|
||||||
|
};
|
||||||
|
|
||||||
// 白银转移按钮 - 检查输入端是否有白银
|
// 白银转移按钮 - 检查输入端是否有白银
|
||||||
var globalStorage = Find.World.GetComponent<GlobalStorageWorldComponent>();
|
var globalStorage = Find.World.GetComponent<GlobalStorageWorldComponent>();
|
||||||
int silverAmount = globalStorage?.GetInputStorageCount(ThingDefOf.Silver) ?? 0;
|
int silverAmount = globalStorage?.GetInputStorageCount(ThingDefOf.Silver) ?? 0;
|
||||||
@@ -122,6 +265,27 @@ namespace WulaFallenEmpire
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OpenGlobalStorageTransferDialog()
|
||||||
|
{
|
||||||
|
if (Map == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!Building_OrbitalTradeBeacon.AllPowered(Map).Any())
|
||||||
|
{
|
||||||
|
Messages.Message("WULA_NoPoweredTradeBeacon".Translate(), this, MessageTypeDefOf.RejectInput);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Pawn negotiator = Map.mapPawns?.FreeColonistsSpawned?.FirstOrDefault();
|
||||||
|
if (negotiator == null)
|
||||||
|
{
|
||||||
|
Messages.Message("WULA_NoNegotiator".Translate(), this, MessageTypeDefOf.RejectInput);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Find.WindowStack.Add(new Dialog_GlobalStorageTransfer(this, negotiator));
|
||||||
|
}
|
||||||
|
|
||||||
// 新增:将输入端白银转移到输出端的方法
|
// 新增:将输入端白银转移到输出端的方法
|
||||||
private void TransferSilverToOutput()
|
private void TransferSilverToOutput()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,416 @@
|
|||||||
|
using RimWorld;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using UnityEngine;
|
||||||
|
using Verse;
|
||||||
|
using Verse.Sound;
|
||||||
|
|
||||||
|
namespace WulaFallenEmpire
|
||||||
|
{
|
||||||
|
public class Dialog_GlobalStorageTransfer : Window
|
||||||
|
{
|
||||||
|
private const float RowHeight = 30f;
|
||||||
|
private const float TitleHeight = 45f;
|
||||||
|
private const float TopAreaHeight = 58f;
|
||||||
|
|
||||||
|
private readonly Building_GlobalWorkTable table;
|
||||||
|
private readonly Pawn negotiator;
|
||||||
|
private readonly GlobalStorageWorldComponent storage;
|
||||||
|
private readonly GlobalStorageTransferTrader trader;
|
||||||
|
|
||||||
|
private readonly QuickSearchWidget quickSearchWidget = new QuickSearchWidget();
|
||||||
|
private Vector2 scrollPosition;
|
||||||
|
private float viewHeight;
|
||||||
|
|
||||||
|
private List<Tradeable_StorageTransfer> tradeables = new List<Tradeable_StorageTransfer>();
|
||||||
|
|
||||||
|
private ITrader prevTrader;
|
||||||
|
private Pawn prevNegotiator;
|
||||||
|
private TradeDeal prevDeal;
|
||||||
|
private bool prevGiftMode;
|
||||||
|
|
||||||
|
public override Vector2 InitialSize => new Vector2(1024f, UI.screenHeight);
|
||||||
|
|
||||||
|
public Dialog_GlobalStorageTransfer(Building_GlobalWorkTable table, Pawn negotiator)
|
||||||
|
{
|
||||||
|
this.table = table;
|
||||||
|
this.negotiator = negotiator;
|
||||||
|
storage = Find.World.GetComponent<GlobalStorageWorldComponent>();
|
||||||
|
trader = new GlobalStorageTransferTrader(table?.Map, storage);
|
||||||
|
|
||||||
|
doCloseX = true;
|
||||||
|
closeOnAccept = false;
|
||||||
|
closeOnClickedOutside = true;
|
||||||
|
absorbInputAroundWindow = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void PostOpen()
|
||||||
|
{
|
||||||
|
base.PostOpen();
|
||||||
|
|
||||||
|
prevTrader = TradeSession.trader;
|
||||||
|
prevNegotiator = TradeSession.playerNegotiator;
|
||||||
|
prevDeal = TradeSession.deal;
|
||||||
|
prevGiftMode = TradeSession.giftMode;
|
||||||
|
|
||||||
|
TradeSession.trader = trader;
|
||||||
|
TradeSession.playerNegotiator = negotiator;
|
||||||
|
TradeSession.deal = null;
|
||||||
|
TradeSession.giftMode = false;
|
||||||
|
|
||||||
|
RebuildTradeables();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void PostClose()
|
||||||
|
{
|
||||||
|
base.PostClose();
|
||||||
|
|
||||||
|
TradeSession.trader = prevTrader;
|
||||||
|
TradeSession.playerNegotiator = prevNegotiator;
|
||||||
|
TradeSession.deal = prevDeal;
|
||||||
|
TradeSession.giftMode = prevGiftMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void DoWindowContents(Rect inRect)
|
||||||
|
{
|
||||||
|
if (table == null || table.DestroyedOrNull() || table.Map == null || storage == null)
|
||||||
|
{
|
||||||
|
Close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Text.Font = GameFont.Medium;
|
||||||
|
Widgets.Label(new Rect(0f, 0f, inRect.width, TitleHeight), "WULA_GlobalStorageTransferTitle".Translate());
|
||||||
|
Text.Font = GameFont.Small;
|
||||||
|
|
||||||
|
Rect topRect = new Rect(0f, TitleHeight, inRect.width, TopAreaHeight);
|
||||||
|
DrawTopArea(topRect);
|
||||||
|
|
||||||
|
float bottomAreaHeight = 45f;
|
||||||
|
Rect listRect = new Rect(0f, topRect.yMax + 6f, inRect.width, inRect.height - topRect.yMax - bottomAreaHeight - 8f);
|
||||||
|
DrawTradeablesList(listRect);
|
||||||
|
|
||||||
|
Rect bottomRect = new Rect(0f, inRect.height - bottomAreaHeight, inRect.width, bottomAreaHeight);
|
||||||
|
DrawBottomButtons(bottomRect);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawTopArea(Rect rect)
|
||||||
|
{
|
||||||
|
Rect searchRect = new Rect(rect.xMax - 260f, rect.y + 2f, 260f, 24f);
|
||||||
|
quickSearchWidget.OnGUI(searchRect, onFilterChange: () => { }, onClear: () => { });
|
||||||
|
|
||||||
|
Rect hintRect = new Rect(rect.x, rect.y, rect.width - 270f, rect.height);
|
||||||
|
Widgets.Label(hintRect,
|
||||||
|
"WULA_GlobalStorageTransferHint".Translate());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawTradeablesList(Rect rect)
|
||||||
|
{
|
||||||
|
Widgets.DrawMenuSection(rect);
|
||||||
|
Rect outRect = rect.ContractedBy(5f);
|
||||||
|
Rect viewRect = new Rect(0f, 0f, outRect.width - 16f, viewHeight);
|
||||||
|
|
||||||
|
Widgets.BeginScrollView(outRect, ref scrollPosition, viewRect);
|
||||||
|
float curY = 0f;
|
||||||
|
int drawnIndex = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < tradeables.Count; i++)
|
||||||
|
{
|
||||||
|
Tradeable_StorageTransfer trad = tradeables[i];
|
||||||
|
if (trad == null || trad.ThingDef == null) continue;
|
||||||
|
|
||||||
|
if (!quickSearchWidget.filter.Matches(trad.ThingDef))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
Rect rowRect = new Rect(0f, curY, viewRect.width, RowHeight);
|
||||||
|
TradeUI.DrawTradeableRow(rowRect, trad, drawnIndex);
|
||||||
|
curY += RowHeight;
|
||||||
|
drawnIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Event.current.type == EventType.Layout)
|
||||||
|
{
|
||||||
|
viewHeight = Mathf.Max(curY, outRect.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widgets.EndScrollView();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawBottomButtons(Rect rect)
|
||||||
|
{
|
||||||
|
float buttonWidth = 160f;
|
||||||
|
Rect executeRect = new Rect(rect.xMax - buttonWidth, rect.y + 2f, buttonWidth, rect.height - 4f);
|
||||||
|
Rect resetRect = new Rect(executeRect.x - buttonWidth - 10f, executeRect.y, buttonWidth, executeRect.height);
|
||||||
|
|
||||||
|
if (Widgets.ButtonText(resetRect, "WULA_ResetTransfer".Translate()))
|
||||||
|
{
|
||||||
|
foreach (var t in tradeables)
|
||||||
|
{
|
||||||
|
t?.ForceTo(0);
|
||||||
|
}
|
||||||
|
SoundDefOf.Tick_Low.PlayOneShotOnCamera();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Widgets.ButtonText(executeRect, "WULA_ExecuteTransfer".Translate()))
|
||||||
|
{
|
||||||
|
ExecuteTransfers();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ExecuteTransfers()
|
||||||
|
{
|
||||||
|
bool changed = false;
|
||||||
|
|
||||||
|
foreach (var trad in tradeables)
|
||||||
|
{
|
||||||
|
if (trad == null) continue;
|
||||||
|
if (trad.CountToTransfer == 0) continue;
|
||||||
|
|
||||||
|
changed = true;
|
||||||
|
trad.ResolveTrade();
|
||||||
|
trad.ForceTo(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changed)
|
||||||
|
{
|
||||||
|
SoundDefOf.ExecuteTrade.PlayOneShotOnCamera();
|
||||||
|
RebuildTradeables();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SoundDefOf.Tick_Low.PlayOneShotOnCamera();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RebuildTradeables()
|
||||||
|
{
|
||||||
|
tradeables.Clear();
|
||||||
|
|
||||||
|
var byDef = new Dictionary<ThingDef, Tradeable_StorageTransfer>();
|
||||||
|
|
||||||
|
foreach (Thing t in GetThingsInPoweredTradeBeaconRange(table.Map))
|
||||||
|
{
|
||||||
|
if (t?.def == null) continue;
|
||||||
|
if (t.def.category != ThingCategory.Item) continue;
|
||||||
|
|
||||||
|
if (!byDef.TryGetValue(t.def, out var trad))
|
||||||
|
{
|
||||||
|
trad = new Tradeable_StorageTransfer();
|
||||||
|
byDef[t.def] = trad;
|
||||||
|
}
|
||||||
|
trad.AddThing(t, Transactor.Colony);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var kvp in GetGlobalStorageCounts(storage))
|
||||||
|
{
|
||||||
|
ThingDef def = kvp.Key;
|
||||||
|
int count = kvp.Value;
|
||||||
|
if (def == null || count <= 0) continue;
|
||||||
|
|
||||||
|
if (!byDef.TryGetValue(def, out var trad))
|
||||||
|
{
|
||||||
|
trad = new Tradeable_StorageTransfer();
|
||||||
|
byDef[def] = trad;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (Thing dummy in MakeDummyStacks(def, count))
|
||||||
|
{
|
||||||
|
trad.AddThing(dummy, Transactor.Trader);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tradeables = byDef.Values
|
||||||
|
.Where(t => t != null && t.ThingDef != null)
|
||||||
|
.OrderBy(t => t.ThingDef.label)
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<Thing> GetThingsInPoweredTradeBeaconRange(Map map)
|
||||||
|
{
|
||||||
|
if (map == null) yield break;
|
||||||
|
|
||||||
|
HashSet<Thing> yielded = new HashSet<Thing>();
|
||||||
|
foreach (var beacon in Building_OrbitalTradeBeacon.AllPowered(map))
|
||||||
|
{
|
||||||
|
foreach (var cell in beacon.TradeableCells)
|
||||||
|
{
|
||||||
|
List<Thing> things = cell.GetThingList(map);
|
||||||
|
for (int i = 0; i < things.Count; i++)
|
||||||
|
{
|
||||||
|
Thing t = things[i];
|
||||||
|
if (t == null) continue;
|
||||||
|
if (!yielded.Add(t)) continue;
|
||||||
|
yield return t;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Dictionary<ThingDef, int> GetGlobalStorageCounts(GlobalStorageWorldComponent storage)
|
||||||
|
{
|
||||||
|
var counts = new Dictionary<ThingDef, int>();
|
||||||
|
if (storage == null) return counts;
|
||||||
|
|
||||||
|
foreach (var kvp in storage.outputStorage)
|
||||||
|
{
|
||||||
|
if (kvp.Key == null || kvp.Value <= 0) continue;
|
||||||
|
counts[kvp.Key] = counts.TryGetValue(kvp.Key, out var v) ? v + kvp.Value : kvp.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var kvp in storage.inputStorage)
|
||||||
|
{
|
||||||
|
if (kvp.Key == null || kvp.Value <= 0) continue;
|
||||||
|
counts[kvp.Key] = counts.TryGetValue(kvp.Key, out var v) ? v + kvp.Value : kvp.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return counts;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<Thing> MakeDummyStacks(ThingDef def, int count)
|
||||||
|
{
|
||||||
|
int remaining = count;
|
||||||
|
int stackLimit = Mathf.Max(1, def.stackLimit);
|
||||||
|
|
||||||
|
while (remaining > 0)
|
||||||
|
{
|
||||||
|
int stackCount = Mathf.Min(remaining, stackLimit);
|
||||||
|
Thing thing = MakeThingForDef(def, stackCount);
|
||||||
|
if (thing == null) yield break;
|
||||||
|
|
||||||
|
yield return thing;
|
||||||
|
remaining -= stackCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Thing MakeThingForDef(ThingDef def, int stackCount)
|
||||||
|
{
|
||||||
|
if (def == null) return null;
|
||||||
|
|
||||||
|
Thing thing;
|
||||||
|
if (def.MadeFromStuff)
|
||||||
|
{
|
||||||
|
ThingDef stuff = GenStuff.DefaultStuffFor(def) ?? GenStuff.AllowedStuffsFor(def).FirstOrDefault();
|
||||||
|
if (stuff == null) return null;
|
||||||
|
thing = ThingMaker.MakeThing(def, stuff);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
thing = ThingMaker.MakeThing(def);
|
||||||
|
}
|
||||||
|
|
||||||
|
thing.stackCount = stackCount;
|
||||||
|
return thing;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Tradeable_StorageTransfer : Tradeable
|
||||||
|
{
|
||||||
|
public override bool TraderWillTrade => true;
|
||||||
|
public override bool IsCurrency => false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class GlobalStorageTransferTrader : ITrader
|
||||||
|
{
|
||||||
|
private readonly Map map;
|
||||||
|
private readonly GlobalStorageWorldComponent storage;
|
||||||
|
private readonly TraderKindDef traderKind;
|
||||||
|
|
||||||
|
public GlobalStorageTransferTrader(Map map, GlobalStorageWorldComponent storage)
|
||||||
|
{
|
||||||
|
this.map = map;
|
||||||
|
this.storage = storage;
|
||||||
|
|
||||||
|
traderKind =
|
||||||
|
DefDatabase<TraderKindDef>.GetNamedSilentFail("Orbital_ExoticGoods") ??
|
||||||
|
DefDatabase<TraderKindDef>.GetNamedSilentFail("Orbital_BulkGoods") ??
|
||||||
|
DefDatabase<TraderKindDef>.AllDefs.FirstOrDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
public TraderKindDef TraderKind => traderKind;
|
||||||
|
public IEnumerable<Thing> Goods => Enumerable.Empty<Thing>();
|
||||||
|
public int RandomPriceFactorSeed => 0;
|
||||||
|
public string TraderName => "WULA_GlobalStorageTransferTitle".Translate();
|
||||||
|
public bool CanTradeNow => true;
|
||||||
|
public float TradePriceImprovementOffsetForPlayer => 0f;
|
||||||
|
public Faction Faction => Faction.OfPlayer;
|
||||||
|
public TradeCurrency TradeCurrency => TradeCurrency.Silver;
|
||||||
|
|
||||||
|
public IEnumerable<Thing> ColonyThingsWillingToBuy(Pawn playerNegotiator) => Enumerable.Empty<Thing>();
|
||||||
|
|
||||||
|
public void GiveSoldThingToTrader(Thing toGive, int countToGive, Pawn playerNegotiator)
|
||||||
|
{
|
||||||
|
if (storage == null) return;
|
||||||
|
if (toGive == null || countToGive <= 0) return;
|
||||||
|
|
||||||
|
Thing thing = toGive.SplitOff(countToGive);
|
||||||
|
thing.PreTraded(TradeAction.PlayerSells, playerNegotiator, this);
|
||||||
|
|
||||||
|
if (ShouldGoToOutputStorage(thing))
|
||||||
|
{
|
||||||
|
storage.AddToOutputStorage(thing.def, thing.stackCount);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
storage.AddToInputStorage(thing.def, thing.stackCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
thing.Destroy(DestroyMode.Vanish);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void GiveSoldThingToPlayer(Thing toGive, int countToGive, Pawn playerNegotiator)
|
||||||
|
{
|
||||||
|
if (storage == null) return;
|
||||||
|
if (map == null) return;
|
||||||
|
if (toGive == null || countToGive <= 0) return;
|
||||||
|
|
||||||
|
if (!TryRemoveFromAnyStorage(toGive.def, countToGive))
|
||||||
|
{
|
||||||
|
Log.Warning($"[WULA] Global storage changed while transfer dialog open; could not remove {countToGive}x {toGive.def?.defName}.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Thing thing = toGive.SplitOff(countToGive);
|
||||||
|
thing.PreTraded(TradeAction.PlayerBuys, playerNegotiator, this);
|
||||||
|
|
||||||
|
IntVec3 dropSpot = DropCellFinder.TradeDropSpot(map);
|
||||||
|
TradeUtility.SpawnDropPod(dropSpot, map, thing);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool ShouldGoToOutputStorage(Thing thing)
|
||||||
|
{
|
||||||
|
ThingDef def = thing?.def;
|
||||||
|
if (def == null) return false;
|
||||||
|
if (def.IsWeapon) return true;
|
||||||
|
if (def.IsApparel) return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryRemoveFromAnyStorage(ThingDef def, int count)
|
||||||
|
{
|
||||||
|
if (def == null || count <= 0) return false;
|
||||||
|
|
||||||
|
int available = storage.GetInputStorageCount(def) + storage.GetOutputStorageCount(def);
|
||||||
|
if (available < count) return false;
|
||||||
|
|
||||||
|
int remaining = count;
|
||||||
|
int fromOutput = Mathf.Min(remaining, storage.GetOutputStorageCount(def));
|
||||||
|
if (fromOutput > 0)
|
||||||
|
{
|
||||||
|
if (!storage.RemoveFromOutputStorage(def, fromOutput)) return false;
|
||||||
|
remaining -= fromOutput;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (remaining > 0)
|
||||||
|
{
|
||||||
|
if (!storage.RemoveFromInputStorage(def, remaining))
|
||||||
|
{
|
||||||
|
if (fromOutput > 0) storage.AddToOutputStorage(def, fromOutput);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -193,7 +193,7 @@ namespace WulaFallenEmpire
|
|||||||
{
|
{
|
||||||
recipe = recipe,
|
recipe = recipe,
|
||||||
targetCount = 1,
|
targetCount = 1,
|
||||||
paused = true
|
paused = false
|
||||||
};
|
};
|
||||||
SelTable.globalOrderStack.AddOrder(newOrder);
|
SelTable.globalOrderStack.AddOrder(newOrder);
|
||||||
SoundDefOf.Click.PlayOneShotOnCamera();
|
SoundDefOf.Click.PlayOneShotOnCamera();
|
||||||
|
|||||||
@@ -52,67 +52,18 @@ namespace WulaFallenEmpire
|
|||||||
private void CheckAndUpload()
|
private void CheckAndUpload()
|
||||||
{
|
{
|
||||||
var table = Table;
|
var table = Table;
|
||||||
var globalStorage = Find.World.GetComponent<GlobalStorageWorldComponent>();
|
|
||||||
|
|
||||||
// 找到当前正在进行的订单
|
// 找到当前正在进行的订单
|
||||||
var order = table.globalOrderStack.orders.FirstOrDefault(o => o.state == GlobalProductionOrder.ProductionState.Gathering && !o.paused);
|
var order = table.globalOrderStack.orders.FirstOrDefault(o => o.state == GlobalProductionOrder.ProductionState.Gathering && !o.paused);
|
||||||
if (order == null) return;
|
if (order == null) return;
|
||||||
|
|
||||||
// 检查是否满足需求
|
var beforeState = order.state;
|
||||||
var costList = order.GetProductCostList();
|
table.TryAutoGatherFromBeaconsAndContainer();
|
||||||
bool allSatisfied = true;
|
|
||||||
|
|
||||||
foreach (var kvp in costList)
|
if (beforeState == GlobalProductionOrder.ProductionState.Gathering &&
|
||||||
|
order.state == GlobalProductionOrder.ProductionState.Producing)
|
||||||
{
|
{
|
||||||
int needed = kvp.Value;
|
Messages.Message("WULA_OrderStarted".Translate(order.Label), table, MessageTypeDefOf.PositiveEvent);
|
||||||
int inCloud = globalStorage.GetInputStorageCount(kvp.Key);
|
|
||||||
int inContainer = table.innerContainer.TotalStackCountOfDef(kvp.Key);
|
|
||||||
|
|
||||||
if (inCloud + inContainer < needed)
|
|
||||||
{
|
|
||||||
allSatisfied = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (allSatisfied)
|
|
||||||
{
|
|
||||||
// 1. 消耗容器中的材料并上传到云端
|
|
||||||
foreach (var kvp in costList)
|
|
||||||
{
|
|
||||||
int needed = kvp.Value;
|
|
||||||
int inCloud = globalStorage.GetInputStorageCount(kvp.Key);
|
|
||||||
int missingInCloud = needed - inCloud;
|
|
||||||
|
|
||||||
if (missingInCloud > 0)
|
|
||||||
{
|
|
||||||
int toTake = missingInCloud;
|
|
||||||
while (toTake > 0)
|
|
||||||
{
|
|
||||||
Thing t = table.innerContainer.FirstOrDefault(x => x.def == kvp.Key);
|
|
||||||
if (t == null) break;
|
|
||||||
|
|
||||||
int num = UnityEngine.Mathf.Min(t.stackCount, toTake);
|
|
||||||
t.SplitOff(num).Destroy(); // 销毁实体
|
|
||||||
globalStorage.AddToInputStorage(kvp.Key, num); // 添加虚拟库存
|
|
||||||
toTake -= num;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. 立即尝试扣除资源并开始生产
|
|
||||||
// 这会从云端扣除刚刚上传的资源,防止其他订单抢占
|
|
||||||
if (order.TryDeductResources())
|
|
||||||
{
|
|
||||||
order.state = GlobalProductionOrder.ProductionState.Producing;
|
|
||||||
order.progress = 0f;
|
|
||||||
Messages.Message("WULA_OrderStarted".Translate(order.Label), table, MessageTypeDefOf.PositiveEvent);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// 理论上不应该发生,因为前面检查了 allSatisfied
|
|
||||||
Log.Error($"[WULA] Failed to deduct resources for {order.Label} immediately after upload.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,14 +41,16 @@ namespace WulaFallenEmpire
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 检查是否已经有足够的材料在容器中或云端
|
// 检查是否已经有足够的材料在容器中或云端
|
||||||
if (order.HasEnoughResources())
|
var globalStorage = Find.World.GetComponent<GlobalStorageWorldComponent>();
|
||||||
|
var neededMaterials = GetNeededMaterials(order, table, globalStorage);
|
||||||
|
if (neededMaterials.Count == 0)
|
||||||
{
|
{
|
||||||
if (forced) Log.Message($"[WULA_DEBUG] HasJobOnThing: Order has enough resources.");
|
if (forced) Log.Message($"[WULA_DEBUG] HasJobOnThing: Order has enough resources.");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查找所需材料
|
// 查找所需材料
|
||||||
var ingredients = FindBestIngredients(pawn, table, order);
|
var ingredients = FindBestIngredients(pawn, table, neededMaterials);
|
||||||
if (ingredients == null)
|
if (ingredients == null)
|
||||||
{
|
{
|
||||||
if (forced) Log.Message($"[WULA_DEBUG] HasJobOnThing: Could not find ingredients for {order.Label}.");
|
if (forced) Log.Message($"[WULA_DEBUG] HasJobOnThing: Could not find ingredients for {order.Label}.");
|
||||||
@@ -67,7 +69,11 @@ namespace WulaFallenEmpire
|
|||||||
if (order == null)
|
if (order == null)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
var ingredients = FindBestIngredients(pawn, table, order);
|
var globalStorage = Find.World.GetComponent<GlobalStorageWorldComponent>();
|
||||||
|
var neededMaterials = GetNeededMaterials(order, table, globalStorage);
|
||||||
|
if (neededMaterials.Count == 0) return null;
|
||||||
|
|
||||||
|
var ingredients = FindBestIngredients(pawn, table, neededMaterials);
|
||||||
if (ingredients == null)
|
if (ingredients == null)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
@@ -77,15 +83,11 @@ namespace WulaFallenEmpire
|
|||||||
return job;
|
return job;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<KeyValuePair<Thing, int>> FindBestIngredients(Pawn pawn, Building_GlobalWorkTable table, GlobalProductionOrder order)
|
private List<KeyValuePair<Thing, int>> FindBestIngredients(Pawn pawn, Building_GlobalWorkTable table, Dictionary<ThingDef, int> neededMaterials)
|
||||||
{
|
{
|
||||||
var result = new List<KeyValuePair<Thing, int>>();
|
var result = new List<KeyValuePair<Thing, int>>();
|
||||||
var globalStorage = Find.World.GetComponent<GlobalStorageWorldComponent>();
|
|
||||||
|
|
||||||
// 获取所需材料清单
|
Log.Message($"[WULA_DEBUG] Needed materials: {string.Join(", ", neededMaterials.Select(k => $"{k.Key.defName} x{k.Value}"))}");
|
||||||
var neededMaterials = GetNeededMaterials(order, table, globalStorage);
|
|
||||||
|
|
||||||
Log.Message($"[WULA_DEBUG] Needed materials for {order.Label}: {string.Join(", ", neededMaterials.Select(k => $"{k.Key.defName} x{k.Value}"))}");
|
|
||||||
|
|
||||||
foreach (var kvp in neededMaterials)
|
foreach (var kvp in neededMaterials)
|
||||||
{
|
{
|
||||||
@@ -155,6 +157,9 @@ namespace WulaFallenEmpire
|
|||||||
int containerCount = table.innerContainer.TotalStackCountOfDef(kvp.Key);
|
int containerCount = table.innerContainer.TotalStackCountOfDef(kvp.Key);
|
||||||
remaining -= containerCount;
|
remaining -= containerCount;
|
||||||
|
|
||||||
|
// 4. 减去轨道贸易信标(已通电)的范围内已有的
|
||||||
|
remaining -= CountInPoweredTradeBeaconRange(table.Map, kvp.Key);
|
||||||
|
|
||||||
if (remaining > 0)
|
if (remaining > 0)
|
||||||
{
|
{
|
||||||
needed[kvp.Key] = remaining;
|
needed[kvp.Key] = remaining;
|
||||||
@@ -163,5 +168,28 @@ namespace WulaFallenEmpire
|
|||||||
|
|
||||||
return needed;
|
return needed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private int CountInPoweredTradeBeaconRange(Map map, ThingDef def)
|
||||||
|
{
|
||||||
|
if (map == null || def == null) return 0;
|
||||||
|
|
||||||
|
int count = 0;
|
||||||
|
foreach (var beacon in Building_OrbitalTradeBeacon.AllPowered(map))
|
||||||
|
{
|
||||||
|
foreach (var cell in beacon.TradeableCells)
|
||||||
|
{
|
||||||
|
List<Thing> things = cell.GetThingList(map);
|
||||||
|
for (int i = 0; i < things.Count; i++)
|
||||||
|
{
|
||||||
|
Thing t = things[i];
|
||||||
|
if (t != null && t.def == def)
|
||||||
|
{
|
||||||
|
count += t.stackCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user