diff --git a/1.6/1.6/Assemblies/WulaFallenEmpire.dll b/1.6/1.6/Assemblies/WulaFallenEmpire.dll
index 6ad6fb6f..6f03769a 100644
Binary files a/1.6/1.6/Assemblies/WulaFallenEmpire.dll and b/1.6/1.6/Assemblies/WulaFallenEmpire.dll differ
diff --git a/1.6/1.6/Defs/QuestScriptDefs/WULA_Attack_Robber_Camp.xml b/1.6/1.6/Defs/QuestScriptDefs/WULA_Attack_Robber_Camp.xml
index cfa384da..f97b9cdb 100644
--- a/1.6/1.6/Defs/QuestScriptDefs/WULA_Attack_Robber_Camp.xml
+++ b/1.6/1.6/Defs/QuestScriptDefs/WULA_Attack_Robber_Camp.xml
@@ -5,25 +5,11 @@
0
1
true
- true
+ true
- questName->The [bandit] [camp]
- questName->[bandit] [camp]
- questName->[asker_nameDef] and the [camp]
- camp->Camp
- camp->Outpost
- camp->Lair
- camp->Encampment
- bandit->Bandit
- bandit->Raider
- bandit->Outlaw
- bandit->Desperado
- bandit->Fugitive
- bandit->Marauder
- bandit->Robber
- bandit->Brigand
+ questName->WULA_Attack_Robber_Camp_questName
@@ -46,109 +32,44 @@
- true
+ false
-
- asker
- true
- false
- 0.15
-
-
-
- siteTile
- true
- 0.5
-
- Oasis
- Lake
- LakeWithIsland
- LakeWithIslands
- Pond
- DryLake
- ToxicLake
- Wetland
- HotSprings
- Archipelago
- CoastalIsland
- Peninsula
- Bay
- Valley
- Cavern
- Chasm
- Cliffs
- Hollow
- TerraformingScar
- LavaLake
- Dunes
- AncientHeatVent
-
-
-
-
- sitePartDefs
- siteFaction
-
- BanditCamp
-
+
+ siteFaction
+ true
$asker
+ true
-
- $siteTile
+
+
+
+ WULA_Camp_Captured_By_Robber
+ 1
+
+
+ 5
+ 10
+ Things/Building/Natural/Hive/HiveC
+ Things/Building/Natural/Hive/HiveC
+ site
$siteFaction
- $sitePartDefs
- sitePartsParams
+
+ 5
+ 10
+
+
-
- sitePoints
- $sitePartsParams
-
Util_GetDefaultRewardValueFromPoints
- $sitePoints
+ $points
-
-
- $rewardValue
- 1.75
- rewardValue
-
-
-
- Util_GenerateSite
-
-
-
- $site
-
-
-
- $site
- true
- $(randInt(12,28)*60000)
- site.MapGenerated
- true
-
-
-
- Quest expired: [resolvedQuestName]
- The bandit camp has packed up and moved on. The quest [resolvedQuestName] has expired.
-
-
- Fail
-
-
-
-
-
site.Destroyed
@@ -199,4 +120,138 @@
+
+ WULA_Camp_Captured_By_Robber
+ 被异族占据的哨站
+ (44, 1, 44)
+ true
+ 0
+
+
+ (21, 0, 24)
+
+
+ Robber
+ true
+ RobberGroup
+ MapFaction
+ DefendBase
+ MapGeneration
+ 1000~2000
+
+
+
+
+
+
+ WULA_TransportPod
+
+ (24,20,24,20)
+ (24,23,24,23)
+
+
+
+ Brazier
+ Steel
+ (16,0,22)
+
+
+ Bedroll
+ Cloth
+ 3
+
+ (23,14,23,16)
+
+
+
+ Wula_Fusion_Generators
+ 29.99023
+ (30,0,27)
+
+
+ WULA_Wall_Flag_Building
+
+ (16,13,16,13)
+ (25,30,25,30)
+ (27,30,27,30)
+
+
+
+ WULA_Machine_Recharger
+ (14,0,16)
+
+
+ WulaWall
+
+ (12,14,16,14)
+ (12,15,12,18)
+ (11,18,11,18)
+ (10,20,10,21)
+ (9,21,9,27)
+ (20,12,22,12)
+ (22,13,23,13)
+ (25,13,29,13)
+ (10,27,11,27)
+ (11,28,11,29)
+ (12,29,12,29)
+ (29,14,30,14)
+ (30,15,31,16)
+ (14,32,14,35)
+ (31,17,31,21)
+ (33,16,35,16)
+ (15,35,21,35)
+ (35,17,35,18)
+ (21,33,24,33)
+ (24,31,25,31)
+ (21,34,21,34)
+ (24,32,24,32)
+ (36,18,37,18)
+ (32,25,34,25)
+ (27,31,27,32)
+ (32,26,32,29)
+ (37,19,37,22)
+ (34,24,34,24)
+ (31,29,31,32)
+ (28,32,30,32)
+
+
+
+ WulaDoor
+
+ (32,16,32,16)
+ (26,31,26,31)
+
+
+
+ Wula_Sonar_Mine
+
+ (10,22,10,22)
+ (28,14,28,14)
+ (30,31,30,31)
+
+
+
+ Bedroll
+ Cloth
+ 1
+
+ (10,24,10,26)
+
+
+
+ WulaShelter
+
+ (12,6,15,6)
+ (9,10,12,10)
+ (12,7,12,9)
+ (7,32,7,36)
+ (34,9,38,9)
+ (8,36,10,36)
+ (38,10,38,12)
+ (35,35,38,35)
+ (38,32,38,34)
+
+
+
+
\ No newline at end of file
diff --git a/1.6/1.6/Defs/ResearchProjectDefs/WULA_ResearchProjects_Remake.xml b/1.6/1.6/Defs/ResearchProjectDefs/WULA_ResearchProjects_Remake.xml
index beaa8e47..2853448e 100644
--- a/1.6/1.6/Defs/ResearchProjectDefs/WULA_ResearchProjects_Remake.xml
+++ b/1.6/1.6/Defs/ResearchProjectDefs/WULA_ResearchProjects_Remake.xml
@@ -668,4 +668,17 @@
WULA_Colony_License_LV3_Technology
+
+
+
+ WULA_Building_Teleporter_Technology
+ 10.00
+ 2.70
+ 建筑传送许可
+ 允许通过传送将乌拉帝国的建筑直接传送到空投区,这使得建筑可以立刻部署到目标地点,且无视厚岩顶阻挡。
+ 2000
+
+ WULA_Colony_License_LV3_Technology
+
+
\ No newline at end of file
diff --git a/1.6/1.6/Defs/ThingDefs_Buildings/WULA_Drop_Buildings.xml b/1.6/1.6/Defs/ThingDefs_Buildings/WULA_Drop_Buildings.xml
index 7eb5e71e..ac5acc06 100644
--- a/1.6/1.6/Defs/ThingDefs_Buildings/WULA_Drop_Buildings.xml
+++ b/1.6/1.6/Defs/ThingDefs_Buildings/WULA_Drop_Buildings.xml
@@ -71,6 +71,14 @@
true
false
+
+ WulaWall
+ true
+ WULA_Psi_Skip_Entry
+ VoidStructure_Emerge
+ true
+ WULA_Building_Teleporter_Technology
+
@@ -248,6 +256,14 @@
true
false
+
+ WulaDoor
+ true
+ WULA_Psi_Skip_Entry
+ VoidStructure_Emerge
+ true
+ WULA_Building_Teleporter_Technology
+
@@ -467,6 +483,14 @@
true
false
+
+ WulaShelter
+ true
+ WULA_Psi_Skip_Entry
+ VoidStructure_Emerge
+ true
+ WULA_Building_Teleporter_Technology
+
@@ -634,6 +658,14 @@
true
false
+
+ WULA_MaintenancePod
+ true
+ WULA_Psi_Skip_Entry
+ VoidStructure_Emerge
+ true
+ WULA_Building_Teleporter_Technology
+
@@ -831,6 +863,14 @@
true
false
+
+ WULA_WeaponArmor_Productor
+ true
+ WULA_Psi_Skip_Entry
+ VoidStructure_Emerge
+ true
+ WULA_Building_Teleporter_Technology
+
@@ -1062,7 +1102,7 @@
Misc12
0.5
2200
- Medium
+ Light
250
1600
@@ -1426,6 +1466,14 @@
true
false
+
+ WULA_Machine_Recharger
+ true
+ WULA_Psi_Skip_Entry
+ VoidStructure_Emerge
+ true
+ WULA_Building_Teleporter_Technology
+
@@ -1596,6 +1644,14 @@
true
false
+
+ WULA_Charging_Station_Synth
+ true
+ WULA_Psi_Skip_Entry
+ VoidStructure_Emerge
+ true
+ WULA_Building_Teleporter_Technology
+
@@ -1761,6 +1817,14 @@
true
false
+
+ WULA_Cube_Productor
+ true
+ WULA_Psi_Skip_Entry
+ VoidStructure_Emerge
+ true
+ WULA_Building_Teleporter_Technology
+
@@ -1948,6 +2012,14 @@
true
false
+
+ Wula_DarkEnergy_Generators
+ true
+ WULA_Psi_Skip_Entry
+ VoidStructure_Emerge
+ true
+ WULA_Building_Teleporter_Technology
+
@@ -2150,6 +2222,14 @@
true
false
+
+ Wula_Fusion_Generators
+ true
+ WULA_Psi_Skip_Entry
+ VoidStructure_Emerge
+ true
+ WULA_Building_Teleporter_Technology
+
diff --git a/1.6/1.6/Defs/ThingDefs_Buildings/WULA_Turret_Buildings.xml b/1.6/1.6/Defs/ThingDefs_Buildings/WULA_Turret_Buildings.xml
index e6141685..6da19eac 100644
--- a/1.6/1.6/Defs/ThingDefs_Buildings/WULA_Turret_Buildings.xml
+++ b/1.6/1.6/Defs/ThingDefs_Buildings/WULA_Turret_Buildings.xml
@@ -73,6 +73,14 @@
true
false
+
+ Wula_Sonar_Mine
+ true
+ WULA_Psi_Skip_Entry
+ VoidStructure_Emerge
+ true
+ WULA_Building_Teleporter_Technology
+
@@ -328,6 +336,14 @@
true
false
+
+ WULA_Cat_Bunker
+ true
+ WULA_Psi_Skip_Entry
+ VoidStructure_Emerge
+ true
+ WULA_Building_Teleporter_Technology
+
@@ -588,6 +604,14 @@
true
false
+
+ Wula_Base_ATGun_Turret
+ true
+ WULA_Psi_Skip_Entry
+ VoidStructure_Emerge
+ true
+ WULA_Building_Teleporter_Technology
+
@@ -881,6 +905,14 @@
true
false
+
+ Wula_Base_Laser_Turret
+ true
+ WULA_Psi_Skip_Entry
+ VoidStructure_Emerge
+ true
+ WULA_Building_Teleporter_Technology
+
@@ -1176,6 +1208,14 @@
true
false
+
+ Wula_Base_Mortar_Turret
+ true
+ WULA_Psi_Skip_Entry
+ VoidStructure_Emerge
+ true
+ WULA_Building_Teleporter_Technology
+
diff --git a/1.6/1.6/Languages/ChineseSimplified (简体中文)/DefInjected/QuestScriptDef/WULA_Attack_Robber_Camp.xml b/1.6/1.6/Languages/ChineseSimplified (简体中文)/DefInjected/QuestScriptDef/WULA_Attack_Robber_Camp.xml
index 7d1f4bf4..2b87d3bf 100644
--- a/1.6/1.6/Languages/ChineseSimplified (简体中文)/DefInjected/QuestScriptDef/WULA_Attack_Robber_Camp.xml
+++ b/1.6/1.6/Languages/ChineseSimplified (简体中文)/DefInjected/QuestScriptDef/WULA_Attack_Robber_Camp.xml
@@ -1,12 +1,9 @@
- questName->[bandit][camp]
- questName->[bandit][camp]
- questName->[asker_nameDef]与[camp]
+ questName->被[bandit]占据的乌拉帝国[camp]
camp->营地
camp->哨所
- camp->窝点
camp->据点
bandit->土匪
bandit->劫掠者
@@ -18,6 +15,6 @@
bandit->劫匪
- questDescription->[asker_faction_name]的[asker_faction_leaderTitle],[asker_nameFull],向我们发来了一条讯息。根据他们掌握的情报,一个属于[siteFaction_name]派系,由[siteFaction_pawnsPlural]建立的营地一直在袭击他们的远行队。\n\n[asker_nameDef]请求我们帮助他们摧毁这个营地,彻底消灭敌人的成员和炮塔。[asker_label]还提醒我们:\n\n[sitePart0_description]。
+ questDescription->乌拉帝国行星封锁机关的总控AI向殖民地发送了一个请求。[siteFaction_name]的一群战士控制了一个废弃的乌拉帝国空投前哨站。可能是这个前哨站没有按照标准流程启动报废程序,导致其被异族占据,里面有一些科技造物是不能外泄到异族手中的。\n\n这个营地必须被抹去,其中的乌拉帝国科技造物可以回收,也可以就地销毁。现场人数无法评估,仍然有可能有一些未触发的<color=#AA3020><i>弹射地雷</i></color>,但是未发现已经启动的乌拉帝国炮塔。
\ No newline at end of file
diff --git a/1.6/1.6/Languages/ChineseSimplified (简体中文)/DefInjected/QuestScriptDef/WULA_Recycle_PIA_Legion_File.xml b/1.6/1.6/Languages/ChineseSimplified (简体中文)/DefInjected/QuestScriptDef/WULA_Recycle_PIA_Legion_File.xml
index 8ba7d1fe..72ef50a7 100644
--- a/1.6/1.6/Languages/ChineseSimplified (简体中文)/DefInjected/QuestScriptDef/WULA_Recycle_PIA_Legion_File.xml
+++ b/1.6/1.6/Languages/ChineseSimplified (简体中文)/DefInjected/QuestScriptDef/WULA_Recycle_PIA_Legion_File.xml
@@ -4,6 +4,6 @@
questName->晋级任务:回收皇家机密
- questDescription->乌拉帝国行星封锁机关的总控AI向殖民地发送了一个特殊的请求,有一个装着皇室机密的保险箱被乌拉帝国的进步派叛军控制了,殖民地必须将其回收,随后将其交回乌拉帝国舰队。如果保险箱已经被打开,就要杀光在场的所有人。\n\n目前叛军很可能在试图打开这个保险箱,你需要检查其哨站内的工作台。\n\n你只有5天的时间处理这个任务,取得保险箱后,你可以建造<color=#6BB7B7><i>乌拉帝国物资输送舱</i></color>来将保险箱输送到位于轨道上的舰队。
+ questDescription->乌拉帝国行星封锁机关的总控AI向殖民地发送了一个特殊的请求,有一个装着皇室机密的保险箱被乌拉帝国的进步派叛军控制了,殖民地必须将其回收,随后将其交回乌拉帝国舰队。如果保险箱已经被打开,就要杀光在场的所有人。\n\n目前叛军很可能在试图打开这个保险箱,你需要检查其哨站内的工作台。大约有<color=#AA3020><i>10~20个叛军合成人</i></color>被拍摄到,现场同时发现了正在运作的<color=#AA3020><i>若干炮台</i></color>。\n\n你只有5天的时间处理这个任务,取得保险箱后,你可以建造<color=#6BB7B7><i>乌拉帝国物资输送舱</i></color>来将保险箱输送到位于轨道上的舰队。
\ No newline at end of file
diff --git a/Source/WulaFallenEmpire/BuildingComp/WULA_BuildingSpawner/CompBuildingSpawner.cs b/Source/WulaFallenEmpire/BuildingComp/WULA_BuildingSpawner/CompBuildingSpawner.cs
new file mode 100644
index 00000000..b10e7221
--- /dev/null
+++ b/Source/WulaFallenEmpire/BuildingComp/WULA_BuildingSpawner/CompBuildingSpawner.cs
@@ -0,0 +1,1048 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using RimWorld;
+using RimWorld.Planet;
+using UnityEngine;
+using Verse;
+using Verse.AI;
+using Verse.Sound;
+
+namespace WulaFallenEmpire
+{
+ public class CompBuildingSpawner : ThingComp
+ {
+ public CompProperties_BuildingSpawner Props => (CompProperties_BuildingSpawner)props;
+
+ private GlobalStorageWorldComponent _globalStorage;
+ private GlobalStorageWorldComponent GlobalStorage
+ {
+ get
+ {
+ if (_globalStorage == null)
+ {
+ _globalStorage = Find.World.GetComponent();
+ }
+ return _globalStorage;
+ }
+ }
+
+ // 状态变量
+ public bool used = false;
+ private int callTick = -1;
+ public bool calling = false;
+ private bool usedGlobalStorage = false;
+ public bool autoCallScheduled = false;
+
+ // 非玩家派系检查
+ public bool IsNonPlayerFaction => parent.Faction != null && parent.Faction != Faction.OfPlayer;
+
+ // 自动呼叫条件
+ private bool ShouldAutoCall => IsNonPlayerFaction && Props.canAutoCall && !autoCallScheduled && !used;
+
+ // 科技检查
+ public bool HasRequiredResearch
+ {
+ get
+ {
+ if (Props.requiredResearch == null)
+ return true;
+
+ if (IsNonPlayerFaction)
+ return true; // 非玩家派系不需要科技
+
+ return Props.requiredResearch.IsFinished;
+ }
+ }
+
+ // FlyOver 检查
+ public bool HasRequiredFlyOver
+ {
+ get
+ {
+ if (!Props.requireFlyOver)
+ return true;
+
+ if (parent?.Map == null)
+ return false;
+
+ try
+ {
+ // 检查所有FlyOver类型的物体
+ var allFlyOvers = new List();
+ var dynamicObjects = parent.Map.dynamicDrawManager.DrawThings;
+ foreach (var thing in dynamicObjects)
+ {
+ if (thing is FlyOver)
+ {
+ allFlyOvers.Add(thing);
+ }
+ }
+
+ Log.Message($"[BuildingSpawner] Found {allFlyOvers.Count} FlyOvers on map");
+
+ foreach (var thing in allFlyOvers)
+ {
+ if (thing is FlyOver flyOver && !flyOver.Destroyed)
+ {
+ var facilitiesComp = flyOver.GetComp();
+ if (facilitiesComp == null)
+ {
+ Log.Warning($"[BuildingSpawner] FlyOver at {flyOver.Position} has no CompFlyOverFacilities");
+ continue;
+ }
+
+ if (facilitiesComp.HasFacility("BuildingdropperFacility"))
+ {
+ Log.Message($"[BuildingSpawner] Found valid FlyOver at {flyOver.Position} with BuildingdropperFacility");
+ return true;
+ }
+ }
+ }
+
+ Log.Message("[BuildingSpawner] No FlyOver with BuildingdropperFacility found");
+ return false;
+ }
+ catch (System.Exception ex)
+ {
+ Log.Error($"[BuildingSpawner] Exception while checking for FlyOver: {ex}");
+ return false;
+ }
+ }
+ }
+
+ // 屋顶检查
+ public bool CheckRoofConditions
+ {
+ get
+ {
+ if (parent?.Map == null)
+ return true;
+
+ RoofDef roof = parent.Position.GetRoof(parent.Map);
+ if (roof == null)
+ return true;
+
+ if (roof.isThickRoof && !Props.allowThickRoof)
+ return false;
+ if (!roof.isThickRoof && !Props.allowThinRoof)
+ return false;
+
+ return true;
+ }
+ }
+
+ // 总体可调用检查
+ public bool CanCallBuilding => !used && !calling && HasRequiredResearch &&
+ HasRequiredFlyOver && CheckRoofConditions;
+
+ // 初始化和保存
+ public override void PostSpawnSetup(bool respawningAfterLoad)
+ {
+ base.PostSpawnSetup(respawningAfterLoad);
+
+ if (!respawningAfterLoad && ShouldAutoCall)
+ {
+ autoCallScheduled = true;
+ callTick = Find.TickManager.TicksGame + Props.autoCallDelayTicks;
+ calling = true;
+
+ Log.Message($"[BuildingSpawner] Scheduled auto-call for non-player building {parent.Label} at tick {callTick}");
+ }
+ }
+
+ public override void PostExposeData()
+ {
+ base.PostExposeData();
+ Scribe_Values.Look(ref used, "used", false);
+ Scribe_Values.Look(ref callTick, "callTick", -1);
+ Scribe_Values.Look(ref calling, "calling", false);
+ Scribe_Values.Look(ref usedGlobalStorage, "usedGlobalStorage", false);
+ Scribe_Values.Look(ref autoCallScheduled, "autoCallScheduled", false);
+ }
+
+ // 定时更新
+ public override void CompTick()
+ {
+ base.CompTick();
+
+ if (calling && callTick >= 0 && Find.TickManager.TicksGame >= callTick)
+ {
+ if (autoCallScheduled)
+ {
+ ExecuteAutoBuildingSpawn();
+ }
+ else
+ {
+ ExecuteBuildingSpawn();
+ }
+ }
+ }
+
+ // 计算生成位置
+ private IntVec3 CalculateSpawnPosition()
+ {
+ IntVec3 basePos = parent.Position;
+
+ // 应用偏移
+ IntVec3 offsetPos = basePos + new IntVec3(Props.spawnOffset.x, 0, Props.spawnOffset.z);
+
+ return offsetPos;
+ }
+
+ // 自动生成建筑(非玩家派系)
+ protected virtual void ExecuteAutoBuildingSpawn()
+ {
+ try
+ {
+ Log.Message($"[BuildingSpawner] Executing auto building spawn for non-player building at {parent.Position}");
+
+ if (Props.buildingToSpawn == null)
+ {
+ Log.Error("[BuildingSpawner] Building def is null!");
+ ResetCall();
+ return;
+ }
+
+ // 处理屋顶
+ HandleRoofDestruction();
+
+ // 获取生成位置
+ IntVec3 spawnPos = CalculateSpawnPosition();
+
+ // 检查位置是否可用
+ if (!CanSpawnAtPosition(spawnPos))
+ {
+ Log.Error($"[BuildingSpawner] Cannot spawn building at {spawnPos}");
+ ResetCall();
+ return;
+ }
+
+ // 播放效果器
+ PlaySpawnEffects(spawnPos);
+
+ // 创建建筑
+ Thing newBuilding = CreateBuilding(spawnPos);
+
+ if (newBuilding == null)
+ {
+ Log.Error("[BuildingSpawner] Failed to create building!");
+ ResetCall();
+ return;
+ }
+
+ // 生成建筑
+ GenSpawn.Spawn(newBuilding, spawnPos, parent.Map, Props.buildingRotation);
+
+ // 后处理
+ PostSpawnProcessing(newBuilding);
+
+ // 销毁原建筑
+ if (Props.destroyBuilding)
+ {
+ parent.Destroy(DestroyMode.Vanish);
+ }
+
+ // 重置状态
+ ResetCall();
+ autoCallScheduled = false;
+
+ // 显示消息
+ Messages.Message("WULA_AutoBuildingSpawned".Translate(Props.buildingToSpawn.label, parent.Faction.Name),
+ MessageTypeDefOf.NeutralEvent);
+ }
+ catch (System.Exception ex)
+ {
+ Log.Error($"[BuildingSpawner] Error in ExecuteAutoBuildingSpawn: {ex}");
+ ResetCall();
+ }
+ }
+
+ // 手动生成建筑
+ public void CallBuilding(bool isAutoCall = false)
+ {
+ // 非玩家派系不能手动呼叫
+ if (IsNonPlayerFaction && !isAutoCall)
+ {
+ Messages.Message("WULA_NonPlayerCannotCall".Translate(), parent, MessageTypeDefOf.RejectInput);
+ return;
+ }
+
+ if (!CanCallBuilding)
+ {
+ ShowDisabledReason();
+ return;
+ }
+
+ Log.Message($"[BuildingSpawner] Starting building spawn from {parent.Label} at {parent.Position}");
+
+ calling = true;
+ used = true;
+ int delay = isAutoCall ? Props.autoCallDelayTicks : Props.delayTicks;
+ callTick = Find.TickManager.TicksGame + delay;
+
+ if (delay <= 0)
+ {
+ ExecuteBuildingSpawn();
+ }
+ else
+ {
+ string messageKey = usedGlobalStorage ?
+ "WULA_BuildingIncomingFromGlobal" :
+ "WULA_BuildingIncoming";
+ Messages.Message(messageKey.Translate(delay.ToStringTicksToPeriod()), parent, MessageTypeDefOf.ThreatBig);
+ }
+ }
+
+ // 显示禁用原因
+ private void ShowDisabledReason()
+ {
+ if (!HasRequiredResearch)
+ {
+ Messages.Message("WULA_MissingResearch".Translate(Props.requiredResearch.label),
+ parent, MessageTypeDefOf.RejectInput);
+ }
+ else if (!HasRequiredFlyOver)
+ {
+ Messages.Message("WULA_NoBuildingDropperFlyOver".Translate(),
+ parent, MessageTypeDefOf.RejectInput);
+ }
+ else if (!CheckRoofConditions)
+ {
+ string roofType = parent.Position.GetRoof(parent.Map)?.isThickRoof == true ?
+ "thick" : "thin";
+ Messages.Message($"WULA_RoofBlocking_{roofType}".Translate(),
+ parent, MessageTypeDefOf.RejectInput);
+ }
+ }
+
+ // 重置状态
+ protected void ResetCall()
+ {
+ calling = false;
+ used = false;
+ callTick = -1;
+ usedGlobalStorage = false;
+ autoCallScheduled = false;
+ }
+
+ // 执行建筑生成
+ protected virtual void ExecuteBuildingSpawn()
+ {
+ Log.Message($"[BuildingSpawner] Executing building spawn at {parent.Position}");
+
+ if (Props.buildingToSpawn == null)
+ {
+ Log.Error("[BuildingSpawner] Building def is null!");
+ return;
+ }
+
+ // 检查资源
+ var resourceCheck = CheckAndConsumeMaterials();
+ if (!resourceCheck.HasEnoughMaterials)
+ {
+ Log.Message($"[BuildingSpawner] Aborting building spawn due to insufficient materials.");
+ ResetCall();
+ return;
+ }
+
+ usedGlobalStorage = resourceCheck.UsedGlobalStorage;
+
+ // 处理屋顶
+ HandleRoofDestruction();
+
+ // 获取生成位置
+ IntVec3 spawnPos = CalculateSpawnPosition();
+
+ // 检查位置是否可用
+ if (!CanSpawnAtPosition(spawnPos))
+ {
+ Log.Error($"[BuildingSpawner] Cannot spawn building at {spawnPos}");
+ ResetCall();
+ return;
+ }
+
+ // 播放效果器
+ PlaySpawnEffects(spawnPos);
+
+ // 创建建筑
+ Thing newBuilding = CreateBuilding(spawnPos);
+
+ if (newBuilding == null)
+ {
+ Log.Error("[BuildingSpawner] Failed to create building!");
+ return;
+ }
+
+ // 生成建筑
+ GenSpawn.Spawn(newBuilding, spawnPos, parent.Map, Props.buildingRotation);
+
+ // 后处理
+ PostSpawnProcessing(newBuilding);
+
+ // 销毁原建筑
+ if (Props.destroyBuilding)
+ {
+ parent.Destroy(DestroyMode.Vanish);
+ }
+
+ calling = false;
+ callTick = -1;
+ }
+
+ // 检查位置是否可用
+ private bool CanSpawnAtPosition(IntVec3 spawnPos)
+ {
+ if (parent?.Map == null)
+ return false;
+
+ // 检查是否在地图范围内
+ if (!spawnPos.InBounds(parent.Map))
+ return false;
+
+ // 检查是否有阻挡物
+ if (!Props.canReplaceExisting)
+ {
+ List thingsAtPos = spawnPos.GetThingList(parent.Map);
+ foreach (var thing in thingsAtPos)
+ {
+ // 跳过不可穿透的建筑和植物
+ if (thing.def.passability == Traversability.Impassable)
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ // 处理屋顶破坏
+ private void HandleRoofDestruction()
+ {
+ if (parent?.Map == null)
+ return;
+
+ IntVec3 targetPos = parent.Position;
+ RoofDef roof = targetPos.GetRoof(parent.Map);
+
+ if (roof != null && !roof.isThickRoof && Props.allowThinRoof)
+ {
+ Log.Message($"[BuildingSpawner] Destroying thin roof at {targetPos}");
+ parent.Map.roofGrid.SetRoof(targetPos, null);
+
+ // 生成屋顶破坏效果
+ FleckMaker.ThrowDustPuffThick(targetPos.ToVector3Shifted(), parent.Map, 2f,
+ new Color(1f, 1f, 1f, 2f));
+ }
+ }
+
+ // 播放生成效果
+ private void PlaySpawnEffects(IntVec3 spawnPos)
+ {
+ if (parent?.Map == null)
+ return;
+
+ // 播放效果器
+ if (Props.spawnEffecter != null)
+ {
+ Effecter effecter = Props.spawnEffecter.Spawn();
+ effecter.Trigger(new TargetInfo(spawnPos, parent.Map),
+ new TargetInfo(spawnPos, parent.Map));
+ effecter.Cleanup();
+ }
+
+ // 播放音效
+ if (Props.spawnSound != null)
+ {
+ Props.spawnSound.PlayOneShot(new TargetInfo(spawnPos, parent.Map));
+ }
+
+ // 播放粒子效果
+ FleckMaker.ThrowSmoke(spawnPos.ToVector3Shifted(), parent.Map, 1.5f);
+ FleckMaker.ThrowLightningGlow(spawnPos.ToVector3Shifted(), parent.Map, 2f);
+ }
+
+ // 创建建筑实例
+ private Thing CreateBuilding(IntVec3 spawnPos)
+ {
+ if (Props.buildingToSpawn == null)
+ return null;
+
+ // 构建参数
+ ThingDef stuff = null;
+ if (Props.buildingToSpawn.MadeFromStuff)
+ {
+ // 如果需要材料,可以从配置中获取或使用默认材料
+ stuff = ThingDefOf.Steel;
+ }
+
+ // 创建建筑
+ Thing newBuilding = ThingMaker.MakeThing(Props.buildingToSpawn, stuff);
+
+ // 设置派系
+ if (Props.inheritFaction && parent.Faction != null)
+ {
+ newBuilding.SetFaction(parent.Faction);
+ }
+
+ // 设置燃料(如果有)
+ CompRefuelable refuelable = newBuilding.TryGetComp();
+ if (refuelable != null)
+ {
+ float fuelPercent = Props.fuelRange.RandomInRange;
+ refuelable.Refuel(refuelable.Props.fuelCapacity * fuelPercent);
+ }
+
+ return newBuilding;
+ }
+
+ // 生成后处理
+ private void PostSpawnProcessing(Thing newBuilding)
+ {
+ // 可以添加额外的后处理逻辑
+ // 例如:启动炮塔、设置工作模式等
+
+ // 如果生成的是炮塔,自动启动
+ if (newBuilding is Building_Turret turret)
+ {
+ CompPowerTrader powerComp = turret.GetComp();
+ if (powerComp != null)
+ {
+ powerComp.PowerOn = true;
+ }
+ }
+
+ // 记录日志
+ Log.Message($"[BuildingSpawner] Successfully spawned {Props.buildingToSpawn.label} at {newBuilding.Position}");
+ }
+
+ // 资源管理(与SkyfallerCaller类似)
+ protected virtual List CostList
+ {
+ get
+ {
+ if (parent.def?.costList.NullOrEmpty() ?? true)
+ {
+ return null;
+ }
+ return parent.def.costList;
+ }
+ }
+
+ protected struct ResourceCheckResult
+ {
+ public bool HasEnoughMaterials;
+ public bool UsedGlobalStorage;
+ public Dictionary BeaconMaterials;
+ public Dictionary GlobalMaterials;
+ }
+
+ protected ResourceCheckResult CheckAndConsumeMaterials()
+ {
+ var result = new ResourceCheckResult
+ {
+ HasEnoughMaterials = false,
+ UsedGlobalStorage = false,
+ BeaconMaterials = new Dictionary(),
+ GlobalMaterials = new Dictionary()
+ };
+
+ if (DebugSettings.godMode)
+ {
+ result.HasEnoughMaterials = true;
+ return result;
+ }
+
+ var costList = CostList;
+ if (costList.NullOrEmpty())
+ {
+ result.HasEnoughMaterials = true;
+ return result;
+ }
+
+ if (parent.Map == null)
+ {
+ return result;
+ }
+
+ // 收集信标附近的物资
+ var beaconMaterials = CollectBeaconMaterials();
+ result.BeaconMaterials = beaconMaterials;
+
+ // 检查信标附近物资是否足够
+ bool beaconHasEnough = true;
+ foreach (var cost in costList)
+ {
+ int availableInBeacon = beaconMaterials.ContainsKey(cost.thingDef) ? beaconMaterials[cost.thingDef] : 0;
+ if (availableInBeacon < cost.count)
+ {
+ beaconHasEnough = false;
+ break;
+ }
+ }
+
+ if (beaconHasEnough)
+ {
+ ConsumeBeaconMaterials(beaconMaterials, costList);
+ result.HasEnoughMaterials = true;
+ result.UsedGlobalStorage = false;
+ return result;
+ }
+
+ // 检查全局储存器
+ var globalMaterials = CheckGlobalStorageMaterials();
+ result.GlobalMaterials = globalMaterials;
+
+ bool globalHasEnough = true;
+ foreach (var cost in costList)
+ {
+ int availableInBeacon = beaconMaterials.ContainsKey(cost.thingDef) ? beaconMaterials[cost.thingDef] : 0;
+ int availableInGlobal = globalMaterials.ContainsKey(cost.thingDef) ? globalMaterials[cost.thingDef] : 0;
+
+ if (availableInBeacon + availableInGlobal < cost.count)
+ {
+ globalHasEnough = false;
+ break;
+ }
+ }
+
+ if (globalHasEnough)
+ {
+ ConsumeMixedMaterials(beaconMaterials, globalMaterials, costList);
+ result.HasEnoughMaterials = true;
+ result.UsedGlobalStorage = true;
+ return result;
+ }
+
+ result.HasEnoughMaterials = false;
+ return result;
+ }
+
+ // 资源收集和消耗方法(与SkyfallerCaller相同)
+ private Dictionary CollectBeaconMaterials()
+ {
+ var materials = new Dictionary();
+
+ if (parent.Map == null) return materials;
+
+ foreach (Building_OrbitalTradeBeacon beacon in Building_OrbitalTradeBeacon.AllPowered(parent.Map))
+ {
+ foreach (IntVec3 cell in beacon.TradeableCells)
+ {
+ List thingList = parent.Map.thingGrid.ThingsListAt(cell);
+ for (int i = 0; i < thingList.Count; i++)
+ {
+ Thing thing = thingList[i];
+ if (thing.def.EverHaulable)
+ {
+ if (materials.ContainsKey(thing.def))
+ {
+ materials[thing.def] += thing.stackCount;
+ }
+ else
+ {
+ materials[thing.def] = thing.stackCount;
+ }
+ }
+ }
+ }
+ }
+
+ return materials;
+ }
+
+ // 新增:检查全局储存器中的物资
+ private Dictionary CheckGlobalStorageMaterials()
+ {
+ var materials = new Dictionary();
+
+ if (GlobalStorage == null) return materials;
+
+ var costList = CostList;
+ if (costList.NullOrEmpty()) return materials;
+
+ foreach (var cost in costList)
+ {
+ int globalCount = GlobalStorage.GetInputStorageCount(cost.thingDef);
+ if (globalCount > 0)
+ {
+ materials[cost.thingDef] = globalCount;
+ }
+ }
+
+ return materials;
+ }
+
+ // 新增:只消耗信标附近的物资
+ private void ConsumeBeaconMaterials(Dictionary beaconMaterials, List costList)
+ {
+ var tradeableThings = new List();
+
+ foreach (Building_OrbitalTradeBeacon beacon in Building_OrbitalTradeBeacon.AllPowered(parent.Map))
+ {
+ foreach (IntVec3 cell in beacon.TradeableCells)
+ {
+ List 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);
+ }
+ }
+ }
+ }
+
+ foreach (var cost in costList)
+ {
+ int remaining = cost.count;
+ for (int i = tradeableThings.Count - 1; i >= 0 && remaining > 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();
+ tradeableThings.RemoveAt(i);
+ }
+ }
+ }
+ }
+ }
+
+ // 新增:混合消耗信标和全局储存器的物资
+ private void ConsumeMixedMaterials(Dictionary beaconMaterials, Dictionary globalMaterials, List costList)
+ {
+ // 先消耗信标附近的物资
+ var tradeableThings = new List();
+
+ foreach (Building_OrbitalTradeBeacon beacon in Building_OrbitalTradeBeacon.AllPowered(parent.Map))
+ {
+ foreach (IntVec3 cell in beacon.TradeableCells)
+ {
+ List 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);
+ }
+ }
+ }
+ }
+
+ // 对每种所需材料进行处理
+ foreach (var cost in costList)
+ {
+ int remaining = cost.count;
+
+ // 第一步:消耗信标附近的物资
+ for (int i = tradeableThings.Count - 1; i >= 0 && remaining > 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();
+ tradeableThings.RemoveAt(i);
+ }
+ }
+ }
+
+ // 第二步:如果还有剩余,从全局储存器扣除
+ if (remaining > 0 && GlobalStorage != null)
+ {
+ GlobalStorage.RemoveFromInputStorage(cost.thingDef, remaining);
+ }
+ }
+ }
+
+ // 保留原有的 HasEnoughMaterials 方法用于 Gizmo 显示
+ public bool HasEnoughMaterials()
+ {
+ if (DebugSettings.godMode) return true;
+
+ var costList = CostList;
+ if (costList.NullOrEmpty())
+ {
+ return true;
+ }
+
+ // 第一步:检查信标附近物资
+ var beaconMaterials = CollectBeaconMaterials();
+ bool beaconHasEnough = true;
+
+ foreach (var cost in costList)
+ {
+ int availableInBeacon = beaconMaterials.ContainsKey(cost.thingDef) ? beaconMaterials[cost.thingDef] : 0;
+ if (availableInBeacon < cost.count)
+ {
+ beaconHasEnough = false;
+ break;
+ }
+ }
+
+ if (beaconHasEnough) return true;
+
+ // 第二步:检查全局储存器(如果信标附近不够)
+ if (GlobalStorage == null) return false;
+
+ foreach (var cost in costList)
+ {
+ int availableInBeacon = beaconMaterials.ContainsKey(cost.thingDef) ? beaconMaterials[cost.thingDef] : 0;
+ int availableInGlobal = GlobalStorage.GetInputStorageCount(cost.thingDef);
+
+ if (availableInBeacon + availableInGlobal < cost.count)
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ // Gizmos
+ private void CancelCall()
+ {
+ calling = false;
+ used = false;
+ callTick = -1;
+ usedGlobalStorage = false;
+ autoCallScheduled = false;
+ Messages.Message("WULA_BuildingCallCancelled".Translate(), parent, MessageTypeDefOf.NeutralEvent);
+ }
+
+ public override IEnumerable CompGetGizmosExtra()
+ {
+ foreach (var gizmo in base.CompGetGizmosExtra())
+ yield return gizmo;
+
+ // 非玩家派系不显示呼叫按钮
+ if (IsNonPlayerFaction)
+ yield break;
+
+ if (calling)
+ {
+ Command_Action cancelCommand = new Command_Action
+ {
+ defaultLabel = "WULA_CancelBuilding".Translate(),
+ defaultDesc = "WULA_CancelBuildingDesc".Translate(),
+ icon = ContentFinder.Get("UI/Designators/Cancel"),
+ action = CancelCall
+ };
+ yield return cancelCommand;
+ }
+
+ if (CanCallBuilding)
+ {
+ string reason = GetDisabledReason();
+ Command_Action callCommand = new Command_Action
+ {
+ defaultLabel = "WULA_CallBuilding".Translate(),
+ defaultDesc = GetCallDescription(),
+ icon = ContentFinder.Get("Wula/UI/Commands/WULA_SpawnBuilding"),
+ action = () => CallBuilding(false),
+ disabledReason = reason
+ };
+ if (!string.IsNullOrEmpty(reason))
+ {
+ callCommand.Disable(reason);
+ }
+ yield return callCommand;
+ }
+ }
+
+ // 获取禁用原因
+ private string GetDisabledReason()
+ {
+ if (IsNonPlayerFaction)
+ {
+ return "WULA_NonPlayerCannotCall".Translate();
+ }
+
+ if (!HasRequiredResearch)
+ {
+ return "WULA_MissingResearch".Translate(Props.requiredResearch.label);
+ }
+
+ if (Props.requireFlyOver && !HasRequiredFlyOver)
+ {
+ return "WULA_NoBuildingDropperFlyOver".Translate();
+ }
+
+ if (!CheckRoofConditions)
+ {
+ string roofType = parent.Position.GetRoof(parent.Map)?.isThickRoof == true ?
+ "thick" : "thin";
+ return $"WULA_RoofBlocking_{roofType}".Translate();
+ }
+
+ if (!HasEnoughMaterials())
+ {
+ return "WULA_InsufficientMaterials".Translate();
+ }
+
+ return null;
+ }
+
+ // 获取呼叫描述
+ private string GetCallDescription()
+ {
+ var sb = new StringBuilder();
+ sb.Append("WULA_CallBuildingDesc".Translate(Props.buildingToSpawn.label));
+
+ if (Props.requiredResearch != null)
+ {
+ sb.AppendLine().Append("WULA_RequiresResearch".Translate(Props.requiredResearch.label));
+ }
+
+ if (Props.requireFlyOver && !HasRequiredFlyOver)
+ {
+ sb.AppendLine().Append("WULA_RequiresBuildingDropperFlyOver".Translate());
+ }
+
+ if (parent?.Map != null)
+ {
+ RoofDef roof = parent.Position.GetRoof(parent.Map);
+ if (roof != null)
+ {
+ if (roof.isThickRoof && !Props.allowThickRoof)
+ {
+ sb.AppendLine().Append("WULA_ThickRoofBlockingDesc".Translate());
+ }
+ else if (!roof.isThickRoof && !Props.allowThinRoof)
+ {
+ sb.AppendLine().Append("WULA_RoofBlockingDesc".Translate());
+ }
+ }
+ }
+
+ string costString = GetCostString();
+ if (!string.IsNullOrEmpty(costString))
+ {
+ sb.AppendLine().AppendLine().Append("WULA_RequiredMaterials".Translate());
+ sb.Append(costString);
+ }
+
+ return sb.ToString();
+ }
+
+ // 获取成本字符串
+ private string GetCostString()
+ {
+ var costList = CostList;
+ if (costList.NullOrEmpty())
+ {
+ return "";
+ }
+
+ var sb = new StringBuilder();
+ foreach (var cost in costList)
+ {
+ sb.AppendLine($" - {cost.thingDef.LabelCap}: {cost.count}");
+ }
+ return sb.ToString();
+ }
+
+ // 检查字符串
+ public override string CompInspectStringExtra()
+ {
+ if (parent?.Map == null)
+ {
+ return base.CompInspectStringExtra();
+ }
+
+ var sb = new StringBuilder();
+
+ // 显示自动呼叫状态
+ if (autoCallScheduled && calling)
+ {
+ int ticksLeft = callTick - Find.TickManager.TicksGame;
+ sb.Append("WULA_AutoBuildingArrivingIn".Translate(ticksLeft.ToStringTicksToPeriod()));
+ }
+ else if (calling)
+ {
+ int ticksLeft = callTick - Find.TickManager.TicksGame;
+ if (ticksLeft > 0)
+ {
+ string messageKey = usedGlobalStorage ?
+ "WULA_BuildingArrivingInFromGlobal" :
+ "WULA_BuildingArrivingIn";
+ sb.Append(messageKey.Translate(ticksLeft.ToStringTicksToPeriod()));
+ }
+ }
+ else if (!used)
+ {
+ if (IsNonPlayerFaction && Props.canAutoCall)
+ {
+ sb.Append("WULA_AutoBuildingReady".Translate());
+ }
+ else
+ {
+ sb.Append("WULA_ReadyToCallBuilding".Translate(Props.buildingToSpawn.label));
+ }
+
+ // 显示科技需求
+ if (Props.requiredResearch != null && !HasRequiredResearch)
+ {
+ sb.AppendLine().Append("WULA_MissingResearch".Translate(Props.requiredResearch.label));
+ }
+
+ // 显示FlyOver需求
+ if (Props.requireFlyOver && !HasRequiredFlyOver)
+ {
+ sb.AppendLine().Append("WULA_MissingBuildingDropperFlyOver".Translate());
+ }
+
+ // 显示屋顶状态
+ RoofDef roof = parent.Position.GetRoof(parent.Map);
+ if (roof != null)
+ {
+ if (roof.isThickRoof && !Props.allowThickRoof)
+ {
+ sb.AppendLine().Append("WULA_BlockedByThickRoof".Translate());
+ }
+ else if (!roof.isThickRoof && !Props.allowThinRoof)
+ {
+ sb.AppendLine().Append("WULA_BlockedByRoof".Translate());
+ }
+ }
+
+ // 显示成本
+ string costString = GetCostString();
+ if (!string.IsNullOrEmpty(costString))
+ {
+ sb.AppendLine().AppendLine("WULA_RequiredMaterials".Translate());
+ sb.Append(costString);
+ }
+ }
+
+ 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;
+ }
+ }
+}
diff --git a/Source/WulaFallenEmpire/BuildingComp/WULA_BuildingSpawner/CompProperties_BuildingSpawner.cs b/Source/WulaFallenEmpire/BuildingComp/WULA_BuildingSpawner/CompProperties_BuildingSpawner.cs
new file mode 100644
index 00000000..ce6f5984
--- /dev/null
+++ b/Source/WulaFallenEmpire/BuildingComp/WULA_BuildingSpawner/CompProperties_BuildingSpawner.cs
@@ -0,0 +1,82 @@
+using RimWorld;
+using System.Collections.Generic;
+using Verse;
+
+namespace WulaFallenEmpire
+{
+ public class CompProperties_BuildingSpawner : CompProperties
+ {
+ public ThingDef buildingToSpawn;
+ public bool destroyBuilding = true;
+ public int delayTicks = 0;
+
+ // 自动召唤设置
+ public bool canAutoCall = true;
+ public int autoCallDelayTicks = 0; // 默认10秒
+
+ // FlyOver 前提条件
+ public bool requireFlyOver = false;
+
+ // 屋顶限制
+ public bool allowThinRoof = true;
+ public bool allowThickRoof = true;
+
+ // 新增:科技需求
+ public ResearchProjectDef requiredResearch;
+
+ // 新增:生成时的效果器
+ public EffecterDef spawnEffecter;
+
+ // 新增:音效
+ public SoundDef spawnSound;
+
+ // 新增:建筑朝向设置
+ public Rot4 buildingRotation = Rot4.North;
+
+ // 新增:位置偏移
+ public IntVec2 spawnOffset = IntVec2.Zero;
+
+ // 新增:允许替换现有建筑
+ public bool canReplaceExisting = false;
+
+ // 新增:是否继承原建筑的派系
+ public bool inheritFaction = true;
+
+ // 新增:建筑生成后的燃料量(如果适用)
+ public FloatRange fuelRange = new FloatRange(1f, 1f);
+
+ public CompProperties_BuildingSpawner()
+ {
+ compClass = typeof(CompBuildingSpawner);
+ }
+
+ public override void ResolveReferences(ThingDef parentDef)
+ {
+ base.ResolveReferences(parentDef);
+
+ // 验证buildingToSpawn
+ if (buildingToSpawn != null && buildingToSpawn.category != ThingCategory.Building)
+ {
+ Log.Error($"CompProperties_BuildingSpawner: buildingToSpawn must be a building, but got {buildingToSpawn.defName}");
+ buildingToSpawn = null;
+ }
+ }
+
+ public override IEnumerable ConfigErrors(ThingDef parentDef)
+ {
+ foreach (string item in base.ConfigErrors(parentDef))
+ {
+ yield return item;
+ }
+
+ if (buildingToSpawn == null)
+ {
+ yield return "buildingToSpawn is not set";
+ }
+ else if (buildingToSpawn.category != ThingCategory.Building)
+ {
+ yield return $"buildingToSpawn must be a building, but got {buildingToSpawn.defName}";
+ }
+ }
+ }
+}
diff --git a/Source/WulaFallenEmpire/Designator/Designator_CallSkyfallerInArea.cs b/Source/WulaFallenEmpire/Designator/Designator_CallSkyfallerInArea.cs
index 2eb3adca..0dd231e0 100644
--- a/Source/WulaFallenEmpire/Designator/Designator_CallSkyfallerInArea.cs
+++ b/Source/WulaFallenEmpire/Designator/Designator_CallSkyfallerInArea.cs
@@ -11,17 +11,21 @@ namespace WulaFallenEmpire
// 记录已经处理过的建筑(避免重复)
private HashSet processedBuildings = new HashSet();
+ // 组件类型过滤
+ public bool includeBuildingSpawner = true;
+ public bool includeSkyfallerCaller = true;
+
public Designator_CallSkyfallerInArea()
{
- defaultLabel = "WULA_Designator_CallSkyfallerInArea".Translate();
- defaultDesc = "WULA_Designator_CallSkyfallerInAreaDesc".Translate();
- icon = ContentFinder.Get("Wula/UI/Designators/Designator_CallSkyfallerInArea");
+ defaultLabel = "WULA_Designator_CallInArea".Translate();
+ defaultDesc = "WULA_Designator_CallInAreaDesc".Translate();
+ icon = ContentFinder.Get("Wula/UI/Designators/Designator_CallInArea");
soundDragSustain = SoundDefOf.Designate_DragStandard;
soundDragChanged = SoundDefOf.Designate_DragStandard_Changed;
useMouseIcon = true;
soundSucceeded = SoundDefOf.Designate_Claim;
hotKey = KeyBindingDefOf.Misc12;
- tutorTag = "CallSkyfallerInArea";
+ tutorTag = "CallInArea";
}
public override DrawStyleCategoryDef DrawStyleCategory => DrawStyleCategoryDefOf.FilledRectangle;
@@ -35,13 +39,13 @@ namespace WulaFallenEmpire
if (c.Fogged(Map))
return false;
- // 只要单元格内有玩家建筑,就允许选择
+ // 检查单元格内是否有符合条件的玩家建筑
var things = Map.thingGrid.ThingsListAt(c);
foreach (var thing in things)
{
if (thing.def.category == ThingCategory.Building &&
thing.Faction == Faction.OfPlayer &&
- thing.TryGetComp() != null)
+ HasValidComponent(thing))
{
return true;
}
@@ -50,6 +54,28 @@ namespace WulaFallenEmpire
// 即使单元格内没有符合条件的建筑,也允许选择(这样用户可以拖动区域)
return true;
}
+
+ // 检查建筑是否有有效的组件
+ private bool HasValidComponent(Thing thing)
+ {
+ // 检查 Building Spawner 组件
+ if (includeBuildingSpawner)
+ {
+ var buildingSpawner = thing.TryGetComp();
+ if (buildingSpawner != null && buildingSpawner.CanCallBuilding)
+ return true;
+ }
+
+ // 检查 Skyfaller Caller 组件
+ if (includeSkyfallerCaller)
+ {
+ var skyfallerCaller = thing.TryGetComp();
+ if (skyfallerCaller != null && skyfallerCaller.CanCallSkyfaller)
+ return true;
+ }
+
+ return false;
+ }
public override void DesignateSingleCell(IntVec3 c)
{
@@ -63,30 +89,53 @@ namespace WulaFallenEmpire
processedBuildings.Clear();
int totalBuildings = 0;
+ int buildingSpawnerCount = 0;
+ int skyfallerCallerCount = 0;
// 处理所有选中的单元格
foreach (var cell in cells)
{
if (cell.InBounds(Map))
{
- // 统计该单元格处理的建筑数量
int cellCount = processedBuildings.Count;
ProcessCell(cell);
int newBuildings = processedBuildings.Count - cellCount;
+
+ // 统计每个组件类型的调用数量
+ foreach (var building in processedBuildings)
+ {
+ if (building.Destroyed) continue;
+
+ if (building.TryGetComp()?.calling == true)
+ buildingSpawnerCount++;
+ else if (building.TryGetComp()?.calling == true)
+ skyfallerCallerCount++;
+ }
+
totalBuildings += newBuildings;
}
}
- // 计算成功和失败的数量
- // 这里需要跟踪每个建筑的调用结果
- // 由于我们直接调用CallSkyfaller,需要知道哪些失败了
- // 简化处理:在ProcessCell中统计
-
- // 显示简单的结果消息
+ // 显示结果消息
if (totalBuildings > 0)
{
- Messages.Message("WULA_AreaCallInitiated".Translate(totalBuildings),
- MessageTypeDefOf.PositiveEvent);
+ string message = "WULA_AreaCallInitiated".Translate(totalBuildings);
+
+ if (buildingSpawnerCount > 0 && skyfallerCallerCount > 0)
+ {
+ message += "\n" + "WULA_BothComponentsCalled".Translate(
+ buildingSpawnerCount, skyfallerCallerCount);
+ }
+ else if (buildingSpawnerCount > 0)
+ {
+ message += "\n" + "WULA_BuildingSpawnerCalled".Translate(buildingSpawnerCount);
+ }
+ else if (skyfallerCallerCount > 0)
+ {
+ message += "\n" + "WULA_SkyfallerCallerCalled".Translate(skyfallerCallerCount);
+ }
+
+ Messages.Message(message, MessageTypeDefOf.PositiveEvent);
}
else
{
@@ -113,21 +162,44 @@ namespace WulaFallenEmpire
if (processedBuildings.Contains(thing))
continue;
- // 获取空投组件
- var comp = thing.TryGetComp();
- if (comp == null)
- continue;
+ // 标记为已处理
+ processedBuildings.Add(thing);
- // 尝试呼叫空投
- if (comp.CanCallSkyfaller)
+ // 尝试调用两种组件(如果有且可以调用)
+ bool anyCalled = false;
+
+ // 1. 先尝试 Building Spawner
+ if (includeBuildingSpawner)
{
- comp.CallSkyfaller(false);
- processedBuildings.Add(thing);
+ var buildingSpawner = thing.TryGetComp();
+ if (buildingSpawner != null && buildingSpawner.CanCallBuilding)
+ {
+ buildingSpawner.CallBuilding(false);
+ anyCalled = true;
+
+ // 如果建筑被销毁,记录日志
+ if (thing.Destroyed)
+ {
+ Log.Message($"[Designator] Building destroyed after BuildingSpawner call at {cell}");
+ }
+ }
}
- // 即使不能呼叫,也添加到已处理列表,避免重复尝试
- else
+
+ // 2. 尝试 Skyfaller Caller(如果建筑还存在)
+ if (!thing.Destroyed && includeSkyfallerCaller)
{
- processedBuildings.Add(thing);
+ var skyfallerCaller = thing.TryGetComp();
+ if (skyfallerCaller != null && skyfallerCaller.CanCallSkyfaller)
+ {
+ skyfallerCaller.CallSkyfaller(false);
+ anyCalled = true;
+ }
+ }
+
+ // 如果没有任何组件被调用,从处理列表中移除(防止重复尝试)
+ if (!anyCalled)
+ {
+ processedBuildings.Remove(thing);
}
}
}
@@ -141,20 +213,43 @@ namespace WulaFallenEmpire
if (t.Faction != Faction.OfPlayer)
return false;
- var comp = t.TryGetComp();
- if (comp == null)
- return false;
-
- return true;
+ return HasValidComponent(t);
}
public override void DesignateThing(Thing t)
{
// 用于反向设计器(右键菜单)
- var comp = t.TryGetComp();
- if (comp != null && comp.CanCallSkyfaller)
+ processedBuildings.Add(t);
+
+ // 尝试调用两种组件
+ bool anyCalled = false;
+
+ // 1. 先尝试 Building Spawner
+ if (includeBuildingSpawner)
{
- comp.CallSkyfaller(false);
+ var buildingSpawner = t.TryGetComp();
+ if (buildingSpawner != null && buildingSpawner.CanCallBuilding)
+ {
+ buildingSpawner.CallBuilding(false);
+ anyCalled = true;
+ }
+ }
+
+ // 2. 尝试 Skyfaller Caller(如果建筑还存在)
+ if (!t.Destroyed && includeSkyfallerCaller)
+ {
+ var skyfallerCaller = t.TryGetComp();
+ if (skyfallerCaller != null && skyfallerCaller.CanCallSkyfaller)
+ {
+ skyfallerCaller.CallSkyfaller(false);
+ anyCalled = true;
+ }
+ }
+
+ if (!anyCalled)
+ {
+ Messages.Message("WULA_NoComponentCanCall".Translate(),
+ t, MessageTypeDefOf.RejectInput);
}
}
@@ -162,8 +257,32 @@ namespace WulaFallenEmpire
{
// 参考Designator_Deconstruct,只绘制鼠标悬停方框
GenUI.RenderMouseoverBracket();
+
+ // 可以添加额外的视觉效果来显示哪些建筑将被影响
+ if (Find.DesignatorManager.SelectedDesignator == this)
+ {
+ DrawAffectedBuildings();
+ }
+ }
+
+ // 绘制受影响的建筑
+ private void DrawAffectedBuildings()
+ {
+ if (Map == null) return;
+
+ // 这里可以绘制高亮显示哪些建筑会被影响
+ // 但由于性能考虑,只在特定条件下绘制
+ if (DebugSettings.godMode)
+ {
+ foreach (var building in Map.listerBuildings.allBuildingsColonist)
+ {
+ if (HasValidComponent(building))
+ {
+ GenDraw.DrawFieldEdges(new List { building.Position },
+ building.Destroyed ? Color.red : Color.green);
+ }
+ }
+ }
}
}
-
-
}
diff --git a/Source/WulaFallenEmpire/WulaFallenEmpire.csproj b/Source/WulaFallenEmpire/WulaFallenEmpire.csproj
index c767427d..6259969a 100644
--- a/Source/WulaFallenEmpire/WulaFallenEmpire.csproj
+++ b/Source/WulaFallenEmpire/WulaFallenEmpire.csproj
@@ -106,6 +106,8 @@
+
+