feat(buildings): implement material consumption for skyfaller caller

- Add material requirement checks before calling skyfallers
- Implement material consumption logic from orbital trade beacons
- Add cost display in building descriptions and inspector
- Set WorkToBuild to 0 for all drop buildings
- Add cancel command for active skyfaller calls
- Improve condition checking and error messaging
This commit is contained in:
2025-11-26 16:27:36 +08:00
parent ed280f3fef
commit 6f7e372d23
4 changed files with 248 additions and 42 deletions

View File

@@ -37,7 +37,7 @@
<terrainAffordanceNeeded>Light</terrainAffordanceNeeded>
<statBases>
<MaxHitPoints>1</MaxHitPoints>
<WorkToBuild>1</WorkToBuild>
<WorkToBuild>0</WorkToBuild>
<Mass>1</Mass>
<Flammability>0</Flammability>
</statBases>
@@ -195,7 +195,7 @@
<MarketValue>1</MarketValue>
<MaxHitPoints>1</MaxHitPoints>
<Flammability>0</Flammability>
<WorkToBuild>1</WorkToBuild>
<WorkToBuild>0</WorkToBuild>
</statBases>
<costList>
<WULA_Alloy>10</WULA_Alloy>
@@ -439,7 +439,7 @@
</placeWorkers>
<statBases>
<MaxHitPoints>1</MaxHitPoints>
<WorkToBuild>1</WorkToBuild>
<WorkToBuild>0</WorkToBuild>
<Mass>1</Mass>
<Flammability>0</Flammability>
</statBases>
@@ -639,7 +639,7 @@
</researchPrerequisites>
<statBases>
<MaxHitPoints>1</MaxHitPoints>
<WorkToBuild>1</WorkToBuild>
<WorkToBuild>0</WorkToBuild>
<Mass>1</Mass>
<Flammability>0</Flammability>
</statBases>
@@ -869,7 +869,7 @@
</researchPrerequisites>
<statBases>
<MaxHitPoints>1</MaxHitPoints>
<WorkToBuild>1</WorkToBuild>
<WorkToBuild>0</WorkToBuild>
<Mass>1</Mass>
<Flammability>0</Flammability>
</statBases>
@@ -1037,7 +1037,7 @@
</researchPrerequisites>
<statBases>
<MaxHitPoints>1</MaxHitPoints>
<WorkToBuild>1</WorkToBuild>
<WorkToBuild>0</WorkToBuild>
<Mass>1</Mass>
<Flammability>0</Flammability>
</statBases>
@@ -1197,7 +1197,7 @@
</researchPrerequisites>
<statBases>
<MaxHitPoints>1</MaxHitPoints>
<WorkToBuild>1</WorkToBuild>
<WorkToBuild>0</WorkToBuild>
<Mass>1</Mass>
<Flammability>0</Flammability>
</statBases>
@@ -1371,7 +1371,7 @@
</researchPrerequisites>
<statBases>
<MaxHitPoints>1</MaxHitPoints>
<WorkToBuild>1</WorkToBuild>
<WorkToBuild>0</WorkToBuild>
<Mass>1</Mass>
<Flammability>0</Flammability>
</statBases>

View File

@@ -114,4 +114,13 @@
<WULA_GatheringMaterials>正在收集材料</WULA_GatheringMaterials>
<WULA_Paused>已暂停</WULA_Paused>
<!-- CompSkyfallerCaller new keys -->
<WULA_RequiredMaterials>所需材料:</WULA_RequiredMaterials>
<WULA_InsufficientMaterials>轨道信标范围内材料不足</WULA_InsufficientMaterials>
<!-- Cancel Skyfaller -->
<WULA_CancelSkyfaller>取消呼叫</WULA_CancelSkyfaller>
<WULA_CancelSkyfallerDesc>中止正在进行的空投呼叫。</WULA_CancelSkyfallerDesc>
<WULA_SkyfallerCallCancelled>空投呼叫已取消。</WULA_SkyfallerCallCancelled>
</LanguageData>

View File

@@ -146,6 +146,12 @@ namespace WulaFallenEmpire
Log.Message($"[SkyfallerCaller] Cannot call: roof conditions not met");
return false;
}
if (!HasEnoughMaterials())
{
Log.Message($"[SkyfallerCaller] Cannot call: insufficient materials");
return false;
}
Log.Message($"[SkyfallerCaller] All conditions met for skyfaller call");
return true;
@@ -229,6 +235,17 @@ namespace WulaFallenEmpire
return;
}
if (!HasEnoughMaterials())
{
Log.Message($"[SkyfallerCaller] Aborting skyfaller call due to insufficient materials.");
calling = false;
used = false;
callTick = -1;
return;
}
ConsumeMaterials();
// 检查屋顶并处理
HandleRoofDestruction();
@@ -272,21 +289,177 @@ namespace WulaFallenEmpire
}
}
private List<ThingDefCountClass> CostList
{
get
{
if (parent.def?.costList.NullOrEmpty() ?? true)
{
return null;
}
return parent.def.costList;
}
}
private bool HasEnoughMaterials()
{
var costList = CostList;
if (costList.NullOrEmpty())
{
return true;
}
var availableThings = new List<Thing>();
if (parent.Map == null) return false;
foreach (Building_OrbitalTradeBeacon beacon in Building_OrbitalTradeBeacon.AllPowered(parent.Map))
{
foreach (IntVec3 cell in beacon.TradeableCells)
{
List<Thing> thingList = parent.Map.thingGrid.ThingsListAt(cell);
for (int i = 0; i < thingList.Count; i++)
{
Thing thing = thingList[i];
if (thing.def.EverHaulable)
{
availableThings.Add(thing);
}
}
}
}
availableThings = availableThings.Distinct().ToList();
foreach (var cost in costList)
{
int count = 0;
foreach (var thing in availableThings)
{
if (thing.def == cost.thingDef)
{
count += thing.stackCount;
}
}
if (count < cost.count)
{
return false;
}
}
return true;
}
private void ConsumeMaterials()
{
var costList = CostList;
if (costList.NullOrEmpty())
{
return;
}
var tradeableThings = new List<Thing>();
if (parent.Map == null) return;
foreach (Building_OrbitalTradeBeacon beacon in Building_OrbitalTradeBeacon.AllPowered(parent.Map))
{
foreach (IntVec3 cell in beacon.TradeableCells)
{
List<Thing> thingList = parent.Map.thingGrid.ThingsListAt(cell);
for (int i = 0; i < thingList.Count; i++)
{
Thing thing = thingList[i];
if (thing.def.EverHaulable)
{
tradeableThings.Add(thing);
}
}
}
}
tradeableThings = tradeableThings.Distinct().ToList();
foreach (var cost in costList)
{
int remaining = cost.count;
for (int i = tradeableThings.Count - 1; i >= 0; i--)
{
var thing = tradeableThings[i];
if (thing.def == cost.thingDef)
{
if (thing.stackCount > remaining)
{
thing.SplitOff(remaining);
remaining = 0;
}
else
{
remaining -= thing.stackCount;
thing.Destroy();
}
if (remaining <= 0)
{
break;
}
}
}
}
}
private string GetCostString()
{
var costList = CostList;
if (costList.NullOrEmpty())
{
return "";
}
var sb = new System.Text.StringBuilder();
foreach (var cost in costList)
{
sb.AppendLine($" - {cost.thingDef.LabelCap}: {cost.count}");
}
return sb.ToString();
}
private void CancelCall()
{
calling = false;
used = false;
callTick = -1;
Messages.Message("WULA_SkyfallerCallCancelled".Translate(), parent, MessageTypeDefOf.NeutralEvent);
}
public override IEnumerable<Gizmo> CompGetGizmosExtra()
{
foreach (var gizmo in base.CompGetGizmosExtra())
yield return gizmo;
if (calling)
{
Command_Action cancelCommand = new Command_Action
{
defaultLabel = "WULA_CancelSkyfaller".Translate(),
defaultDesc = "WULA_CancelSkyfallerDesc".Translate(),
icon = ContentFinder<Texture2D>.Get("UI/Designators/Cancel"),
action = CancelCall
};
yield return cancelCommand;
}
if (CanCall)
{
string reason = GetDisabledReason();
Command_Action callCommand = new Command_Action
{
defaultLabel = "WULA_CallSkyfaller".Translate(),
defaultDesc = GetCallDescription(),
icon = ContentFinder<Texture2D>.Get("Wula/UI/Commands/WULA_DropBuilding"),
action = () => CallSkyfaller(false),
disabledReason = GetDisabledReason()
disabledReason = reason
};
if (!string.IsNullOrEmpty(reason))
{
callCommand.Disable(reason);
}
yield return callCommand;
}
@@ -317,15 +490,14 @@ namespace WulaFallenEmpire
private string GetCallDescription()
{
string desc = "WULA_CallSkyfallerDesc".Translate();
// 只在需要 FlyOver 时显示相关描述
var sb = new System.Text.StringBuilder();
sb.Append("WULA_CallSkyfallerDesc".Translate());
if (Props.requireFlyOver && !HasRequiredFlyOver)
{
desc += $"\n{"WULA_RequiresBuildingDropperFlyOver".Translate()}";
sb.AppendLine().Append("WULA_RequiresBuildingDropperFlyOver".Translate());
}
// 添加 null 检查
if (parent?.Map != null)
{
RoofDef roof = parent.Position.GetRoof(parent.Map);
@@ -333,16 +505,23 @@ namespace WulaFallenEmpire
{
if (roof.isThickRoof && !Props.allowThickRoof)
{
desc += $"\n{"WULA_ThickRoofBlockingDesc".Translate()}";
sb.AppendLine().Append("WULA_ThickRoofBlockingDesc".Translate());
}
else if (!roof.isThickRoof && !Props.allowThinRoof)
{
desc += $"\n{"WULA_RoofBlockingDesc".Translate()}";
sb.AppendLine().Append("WULA_RoofBlockingDesc".Translate());
}
}
}
return desc;
string costString = GetCostString();
if (!string.IsNullOrEmpty(costString))
{
sb.AppendLine().AppendLine().Append("WULA_RequiredMaterials".Translate());
sb.Append(costString);
}
return sb.ToString();
}
private string GetDisabledReason()
@@ -369,55 +548,73 @@ namespace WulaFallenEmpire
}
}
}
if (!HasEnoughMaterials())
{
return "WULA_InsufficientMaterials".Translate();
}
return null;
}
public override string CompInspectStringExtra()
{
// 添加 null 检查,防止在小型化建筑上出现异常
if (parent?.Map == null)
{
return base.CompInspectStringExtra();
}
var sb = new System.Text.StringBuilder();
if (calling)
{
int ticksLeft = callTick - Find.TickManager.TicksGame;
if (ticksLeft > 0)
{
return "WULA_SkyfallerArrivingIn".Translate(ticksLeft.ToStringTicksToPeriod());
sb.Append("WULA_SkyfallerArrivingIn".Translate(ticksLeft.ToStringTicksToPeriod()));
}
}
else if (!used)
{
string status = "WULA_ReadyToCallSkyfaller".Translate();
// 只在需要 FlyOver 时显示相关条件信息
sb.Append("WULA_ReadyToCallSkyfaller".Translate());
if (Props.requireFlyOver && !HasRequiredFlyOver)
{
status += $"\n{"WULA_MissingBuildingDropperFlyOver".Translate()}";
sb.AppendLine().Append("WULA_MissingBuildingDropperFlyOver".Translate());
}
// 添加 null 检查
if (parent?.Map != null)
RoofDef roof = parent.Position.GetRoof(parent.Map);
if (roof != null)
{
RoofDef roof = parent.Position.GetRoof(parent.Map);
if (roof != null)
if (roof.isThickRoof && !Props.allowThickRoof)
{
if (roof.isThickRoof && !Props.allowThickRoof)
{
status += $"\n{"WULA_BlockedByThickRoof".Translate()}";
}
else if (!roof.isThickRoof && !Props.allowThinRoof)
{
status += $"\n{"WULA_BlockedByRoof".Translate()}";
}
sb.AppendLine().Append("WULA_BlockedByThickRoof".Translate());
}
else if (!roof.isThickRoof && !Props.allowThinRoof)
{
sb.AppendLine().Append("WULA_BlockedByRoof".Translate());
}
}
return status;
string costString = GetCostString();
if (!string.IsNullOrEmpty(costString))
{
sb.AppendLine().AppendLine("WULA_RequiredMaterials".Translate());
sb.Append(costString);
}
}
return base.CompInspectStringExtra();
string baseInspectString = base.CompInspectStringExtra();
if (!string.IsNullOrEmpty(baseInspectString))
{
if (sb.Length > 0)
{
sb.AppendLine();
}
sb.Append(baseInspectString);
}
return sb.Length > 0 ? sb.ToString().TrimEnd() : null;
}
}
}