diff --git a/1.6/1.6/Assemblies/ArachnaeSwarm.dll b/1.6/1.6/Assemblies/ArachnaeSwarm.dll
index a1d8e77..53a448c 100644
Binary files a/1.6/1.6/Assemblies/ArachnaeSwarm.dll and b/1.6/1.6/Assemblies/ArachnaeSwarm.dll differ
diff --git a/1.6/1.6/Defs/AbilityDefs/Ability_Morph.xml b/1.6/1.6/Defs/AbilityDefs/Ability_Morph.xml
index 29dbbfa..29e392a 100644
--- a/1.6/1.6/Defs/AbilityDefs/Ability_Morph.xml
+++ b/1.6/1.6/Defs/AbilityDefs/Ability_Morph.xml
@@ -1,4 +1,4 @@
-
+
@@ -39,4 +39,37 @@
+
+
+ ARA_SpawnFlyOverTest
+
+ 测试召唤不同类型的飞越物体
+ ArachnaeSwarm/UI/Abilities/ARA_Ability_Morph
+ 1
+ Misc12
+ false
+
+ Verb_CastAbility
+ false
+ false
+ true
+ 1
+ 19.9
+ true
+
+ True
+
+
+
+
+ ARA_FlyOverShip
+ Standard
+ 0.01
+ 20
+ MapEdge
+ OppositeMapEdge
+ true
+
+
+
\ No newline at end of file
diff --git a/1.6/1.6/Defs/Thing_Misc/ARA_Flyover_Item.xml b/1.6/1.6/Defs/Thing_Misc/ARA_Flyover_Item.xml
new file mode 100644
index 0000000..8a36bdb
--- /dev/null
+++ b/1.6/1.6/Defs/Thing_Misc/ARA_Flyover_Item.xml
@@ -0,0 +1,97 @@
+
+
+
+ ARA_FlyOverShip
+
+ ArachnaeSwarm.FlyOver
+ Normal
+ RealtimeOnly
+
+
+ ArachnaeSwarm/FlyOverThing/ARA_HiveShip_Shadow
+ Graphic_Single
+ TransparentPostLight
+ (75,170)
+ (195,195,195,45)
+
+
+ (0, 0)
+ 0
+ FlyOver/Flying
+ FlyOver/Landing
+
+
+
+ ArachnaeSwarm/FlyOverThing/ARA_HiveShip_Shadow
+ 0.8
+ false
+ 0
+ 0
+ 15
+ 15
+
+
+ false
+ false
+ false
+ Projectile
+
+
+
+ true
+ 1
+ 10
+ 15
+ false
+ false
+ false
+ false
+ true
+ 虫群空调
+ 虫巢舰正在空投一支虫族部队!
+
+
+
+
+
+
+
+
+
+ ArachnaeNode_Race_Fighter_Enermy
+ 5
+
+
+ ArachnaeNode_Race_ShieldHead_Enermy
+ 5
+
+
+
+
+ ARA_Hostile_Hive
+ true
+
+
+ true
+ true
+ false
+ false
+ false
+ false
+ false
+ true
+
+
+ false
+ false
+
+
+
+
\ No newline at end of file
diff --git a/1.6/1.6/Defs/Thing_building/ARA_NutrientNetworkBuilding.xml b/1.6/1.6/Defs/Thing_building/ARA_NutrientNetworkBuilding.xml
index 272b99c..42f9d51 100644
--- a/1.6/1.6/Defs/Thing_building/ARA_NutrientNetworkBuilding.xml
+++ b/1.6/1.6/Defs/Thing_building/ARA_NutrientNetworkBuilding.xml
@@ -71,7 +71,7 @@
虫蜜
- true
+ false
true
true
diff --git a/1.6/1.6/Defs/Thing_building/ARA_SwarmTurret.xml b/1.6/1.6/Defs/Thing_building/ARA_SwarmTurret.xml
index 6165de2..0f71e61 100644
--- a/1.6/1.6/Defs/Thing_building/ARA_SwarmTurret.xml
+++ b/1.6/1.6/Defs/Thing_building/ARA_SwarmTurret.xml
@@ -1,6 +1,6 @@
-
+
ARA_DummyAmmo
阿拉克涅虫族使用的弹药.
@@ -697,7 +697,7 @@
-
+
ARA_CatastropheMissile_Shell
阿拉克涅虫族使用的天灾酸烧导弹,只有天灾酸烧炮组织可以发射它。
@@ -706,18 +706,10 @@
ArachnaeSwarm/Mote/ARA_CatastropheMissile_Shell
Graphic_Single
+
+ 8
+
-
- 150
- ARA_AcidBurn
- 10.9
- ARA_Filth_SpentAcid
- 1
- 2
- 30~60
- MortarBomb_Explode
- ARA_Shell_AcidSpitImpact
-
diff --git a/Content/Textures/ArachnaeSwarm/FlyOverThing/ARA_HiveShip_Shadow.png b/Content/Textures/ArachnaeSwarm/FlyOverThing/ARA_HiveShip_Shadow.png
new file mode 100644
index 0000000..1a40e74
Binary files /dev/null and b/Content/Textures/ArachnaeSwarm/FlyOverThing/ARA_HiveShip_Shadow.png differ
diff --git a/Source/ArachnaeSwarm/.vs/ArachnaeSwarm/v17/.suo b/Source/ArachnaeSwarm/.vs/ArachnaeSwarm/v17/.suo
index 7d4dade..ba9bcf9 100644
Binary files a/Source/ArachnaeSwarm/.vs/ArachnaeSwarm/v17/.suo and b/Source/ArachnaeSwarm/.vs/ArachnaeSwarm/v17/.suo differ
diff --git a/Source/ArachnaeSwarm/.vs/ArachnaeSwarm/v17/DocumentLayout.json b/Source/ArachnaeSwarm/.vs/ArachnaeSwarm/v17/DocumentLayout.json
index 8c60bea..e684f41 100644
--- a/Source/ArachnaeSwarm/.vs/ArachnaeSwarm/v17/DocumentLayout.json
+++ b/Source/ArachnaeSwarm/.vs/ArachnaeSwarm/v17/DocumentLayout.json
@@ -1,18 +1,22 @@
{
"Version": 1,
- "WorkspaceRootPath": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\",
+ "WorkspaceRootPath": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\",
"Documents": [
{
- "AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|d:\\steamlibrary\\steamapps\\common\\rimworld\\mods\\arachnaeswarm\\source\\arachnaeswarm\\building_comps\\ara_nutrientvat\\building_nutrientvat.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
- "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:building_comps\\ara_nutrientvat\\building_nutrientvat.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
+ "AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|e:\\steamlibrary\\steamapps\\common\\rimworld\\mods\\arachnaeswarm\\source\\arachnaeswarm\\thing_comps\\ara_flyoverdroppod\\compproperties_flyoverdroppod.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
+ "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:thing_comps\\ara_flyoverdroppod\\compproperties_flyoverdroppod.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
},
{
- "AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\building_comps\\ara_building_refuelingvat\\building_refuelingvat.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
- "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:building_comps\\ara_building_refuelingvat\\building_refuelingvat.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
+ "AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|e:\\steamlibrary\\steamapps\\common\\rimworld\\mods\\arachnaeswarm\\source\\arachnaeswarm\\thing\\thingclassflyover.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
+ "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:thing\\thingclassflyover.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
},
{
- "AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\powerarmor\\comppowerarmorstation.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
- "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:powerarmor\\comppowerarmorstation.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
+ "AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|e:\\steamlibrary\\steamapps\\common\\rimworld\\mods\\arachnaeswarm\\source\\arachnaeswarm\\thing\\compabilityeffect_spawnflyover.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
+ "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:thing\\compabilityeffect_spawnflyover.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
+ },
+ {
+ "AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|e:\\steamlibrary\\steamapps\\common\\rimworld\\mods\\arachnaeswarm\\source\\arachnaeswarm\\thing\\compproperties_abilityspawnflyover.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
+ "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:thing\\compproperties_abilityspawnflyover.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
}
],
"DocumentGroupContainers": [
@@ -31,39 +35,54 @@
{
"$type": "Document",
"DocumentIndex": 0,
- "Title": "Building_NutrientVat.cs",
- "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Building_Comps\\ARA_NutrientVat\\Building_NutrientVat.cs",
- "RelativeDocumentMoniker": "Building_Comps\\ARA_NutrientVat\\Building_NutrientVat.cs",
- "ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Building_Comps\\ARA_NutrientVat\\Building_NutrientVat.cs",
- "RelativeToolTip": "Building_Comps\\ARA_NutrientVat\\Building_NutrientVat.cs",
- "ViewState": "AgIAAMwAAAAAAAAAAAAswMoAAAAuAAAAAAAAAA==",
+ "Title": "CompProperties_FlyOverDropPod.CS",
+ "DocumentMoniker": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Thing_Comps\\ARA_FlyOverDropPod\\CompProperties_FlyOverDropPod.CS",
+ "RelativeDocumentMoniker": "Thing_Comps\\ARA_FlyOverDropPod\\CompProperties_FlyOverDropPod.CS",
+ "ToolTip": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Thing_Comps\\ARA_FlyOverDropPod\\CompProperties_FlyOverDropPod.CS",
+ "RelativeToolTip": "Thing_Comps\\ARA_FlyOverDropPod\\CompProperties_FlyOverDropPod.CS",
+ "ViewState": "AgIAAAAAAAAAAAAAAAAAAFoAAAAFAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
- "WhenOpened": "2025-10-27T07:14:35.288Z",
+ "WhenOpened": "2025-10-27T16:23:17.672Z",
+ "EditorCaption": ""
+ },
+ {
+ "$type": "Document",
+ "DocumentIndex": 3,
+ "Title": "CompProperties_AbilitySpawnFlyOver.cs",
+ "DocumentMoniker": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Thing\\CompProperties_AbilitySpawnFlyOver.cs",
+ "RelativeDocumentMoniker": "Thing\\CompProperties_AbilitySpawnFlyOver.cs",
+ "ToolTip": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Thing\\CompProperties_AbilitySpawnFlyOver.cs",
+ "RelativeToolTip": "Thing\\CompProperties_AbilitySpawnFlyOver.cs",
+ "ViewState": "AgIAAAAAAAAAAAAAAAAAACkAAAAFAAAAAAAAAA==",
+ "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
+ "WhenOpened": "2025-10-27T13:29:49.568Z",
+ "EditorCaption": ""
+ },
+ {
+ "$type": "Document",
+ "DocumentIndex": 1,
+ "Title": "ThingclassFlyOver.cs",
+ "DocumentMoniker": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Thing\\ThingclassFlyOver.cs",
+ "RelativeDocumentMoniker": "Thing\\ThingclassFlyOver.cs",
+ "ToolTip": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Thing\\ThingclassFlyOver.cs",
+ "RelativeToolTip": "Thing\\ThingclassFlyOver.cs",
+ "ViewState": "AgIAAAAAAAAAAAAAAAAAAB8AAAAPAAAAAAAAAA==",
+ "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
+ "WhenOpened": "2025-10-27T13:28:54.42Z",
"EditorCaption": ""
},
{
"$type": "Document",
"DocumentIndex": 2,
- "Title": "CompPowerArmorStation.cs",
- "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\PowerArmor\\CompPowerArmorStation.cs",
- "RelativeDocumentMoniker": "PowerArmor\\CompPowerArmorStation.cs",
- "ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\PowerArmor\\CompPowerArmorStation.cs",
- "RelativeToolTip": "PowerArmor\\CompPowerArmorStation.cs",
- "ViewState": "AgIAABoAAAAAAAAAAAAAwBoAAABhAAAAAAAAAA==",
+ "Title": "CompAbilityEffect_SpawnFlyOver.cs",
+ "DocumentMoniker": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Thing\\CompAbilityEffect_SpawnFlyOver.cs",
+ "RelativeDocumentMoniker": "Thing\\CompAbilityEffect_SpawnFlyOver.cs",
+ "ToolTip": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Thing\\CompAbilityEffect_SpawnFlyOver.cs",
+ "RelativeToolTip": "Thing\\CompAbilityEffect_SpawnFlyOver.cs",
+ "ViewState": "AgIAAO0AAAAAAAAAAAAiwDABAAAJAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
- "WhenOpened": "2025-10-27T06:39:11.711Z"
- },
- {
- "$type": "Document",
- "DocumentIndex": 1,
- "Title": "Building_RefuelingVat.cs",
- "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Building_Comps\\ARA_Building_RefuelingVat\\Building_RefuelingVat.cs",
- "RelativeDocumentMoniker": "Building_Comps\\ARA_Building_RefuelingVat\\Building_RefuelingVat.cs",
- "ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Building_Comps\\ARA_Building_RefuelingVat\\Building_RefuelingVat.cs",
- "RelativeToolTip": "Building_Comps\\ARA_Building_RefuelingVat\\Building_RefuelingVat.cs",
- "ViewState": "AgIAAMwAAAAAAAAAAAAAwJIAAAAXAAAAAAAAAA==",
- "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
- "WhenOpened": "2025-10-24T06:16:14.743Z"
+ "WhenOpened": "2025-10-27T13:06:24.762Z",
+ "EditorCaption": ""
}
]
}
diff --git a/Source/ArachnaeSwarm/ArachnaeSwarm.csproj b/Source/ArachnaeSwarm/ArachnaeSwarm.csproj
index 20c20b3..b73cb11 100644
--- a/Source/ArachnaeSwarm/ArachnaeSwarm.csproj
+++ b/Source/ArachnaeSwarm/ArachnaeSwarm.csproj
@@ -154,7 +154,10 @@
+
+
+
diff --git a/Source/ArachnaeSwarm/Building_Comps/ARA_Building_RefuelingVat/Building_RefuelingVat.cs b/Source/ArachnaeSwarm/Building_Comps/ARA_Building_RefuelingVat/Building_RefuelingVat.cs
index 12c9e5c..02b608c 100644
--- a/Source/ArachnaeSwarm/Building_Comps/ARA_Building_RefuelingVat/Building_RefuelingVat.cs
+++ b/Source/ArachnaeSwarm/Building_Comps/ARA_Building_RefuelingVat/Building_RefuelingVat.cs
@@ -103,12 +103,58 @@ namespace ArachnaeSwarm
private const int AcidDamageInterval = 6000;
private Dictionary pawnTickCounters = new Dictionary();
+ // 安全字典操作方法
+ private bool SafeTryGetTickCount(Pawn pawn, out int tickCount)
+ {
+ if (pawn == null || pawn.Destroyed)
+ {
+ tickCount = 0;
+ return false;
+ }
+ return pawnTickCounters.TryGetValue(pawn, out tickCount);
+ }
+
+ private void SafeSetTickCount(Pawn pawn, int tickCount)
+ {
+ if (pawn != null && !pawn.Destroyed)
+ {
+ pawnTickCounters[pawn] = tickCount;
+ }
+ }
+
+ private void SafeRemoveFromDictionaries(Pawn pawn)
+ {
+ if (pawn != null)
+ {
+ pawnTickCounters.Remove(pawn);
+ pawnsKilledByVat.Remove(pawn);
+ }
+ }
+
+ private void CleanupInvalidDictionaryEntries()
+ {
+ // 清理无效的pawn引用
+ var invalidPawns = pawnTickCounters.Keys.Where(pawn => pawn == null || pawn.Destroyed).ToList();
+ foreach (var invalidPawn in invalidPawns)
+ {
+ pawnTickCounters.Remove(invalidPawn);
+ }
+
+ pawnsKilledByVat.RemoveWhere(pawn => pawn == null || pawn.Destroyed);
+ }
+
public override void SpawnSetup(Map map, bool respawningAfterLoad)
{
base.SpawnSetup(map, respawningAfterLoad);
cachedRefuelableComp = this.TryGetComp();
cachedRefuelingVatComp = this.TryGetComp();
cachedAutoEjectorComp = this.TryGetComp();
+
+ // 确保字典不为null
+ pawnTickCounters ??= new Dictionary();
+ pawnsKilledByVat ??= new HashSet();
+
+ CleanupInvalidDictionaryEntries();
}
public override void DeSpawn(DestroyMode mode = DestroyMode.Vanish)
@@ -121,6 +167,9 @@ namespace ArachnaeSwarm
Finish(); // 使用修改后的Finish方法
}
}
+
+ // 清理字典
+ CleanupInvalidDictionaryEntries();
base.DeSpawn(mode);
}
@@ -128,6 +177,9 @@ namespace ArachnaeSwarm
{
try
{
+ if (pawn == null || pawn.Destroyed || pawn.Dead)
+ return;
+
BodyPartRecord targetPart = GetRandomVulnerablePart(pawn);
DamageDef acidDamageDef = DefDatabase.GetNamed("AcidBurn") ?? DamageDefOf.Burn;
@@ -164,6 +216,9 @@ namespace ArachnaeSwarm
{
try
{
+ if (pawn == null || pawn.Destroyed)
+ return;
+
// 检查是否是被建筑杀死的
if (pawnsKilledByVat.Contains(pawn))
{
@@ -182,8 +237,7 @@ namespace ArachnaeSwarm
}
// 从跟踪列表中移除
- pawnsKilledByVat.Remove(pawn);
- pawnTickCounters.Remove(pawn);
+ SafeRemoveFromDictionaries(pawn);
// 立即调用完成逻辑
if (selectedPawn == pawn)
@@ -203,6 +257,9 @@ namespace ArachnaeSwarm
private BodyPartRecord GetRandomVulnerablePart(Pawn pawn)
{
+ if (pawn == null || pawn.Destroyed)
+ return null;
+
// 优先选择外部身体部位
var vulnerableParts = pawn.health.hediffSet.GetNotMissingParts()
.Where(part => part.depth == BodyPartDepth.Outside &&
@@ -219,7 +276,8 @@ namespace ArachnaeSwarm
{
base.Tick();
- if (selectedPawn != null && (selectedPawn.Destroyed || !innerContainer.Contains(selectedPawn)))
+ // 更严格的空值检查
+ if (selectedPawn == null || selectedPawn.Destroyed || !innerContainer.Contains(selectedPawn))
{
OnStop();
return;
@@ -242,25 +300,29 @@ namespace ArachnaeSwarm
return;
}
- // 酸蚀伤害逻辑
- if (pawnTickCounters.TryGetValue(selectedPawn, out int tickCount))
+ // 酸蚀伤害逻辑 - 使用安全的方法
+ if (SafeTryGetTickCount(selectedPawn, out int tickCount))
{
tickCount++;
- pawnTickCounters[selectedPawn] = tickCount;
+ SafeSetTickCount(selectedPawn, tickCount);
if (tickCount >= AcidDamageInterval)
{
ApplyAcidDamage(selectedPawn);
- pawnTickCounters[selectedPawn] = 0;
+ // 检查pawn是否还存在(可能在ApplyAcidDamage中被销毁)
+ if (selectedPawn != null && !selectedPawn.Destroyed)
+ {
+ SafeSetTickCount(selectedPawn, 0);
+ }
}
}
else
{
- pawnTickCounters[selectedPawn] = 0;
+ SafeSetTickCount(selectedPawn, 0);
}
// 燃料生产逻辑
- if (HasFuelCapacity)
+ if (selectedPawn != null && HasFuelCapacity)
{
float fuelToAdd = FuelProductionPerTick;
RefuelableComp.Refuel(fuelToAdd);
@@ -270,6 +332,9 @@ namespace ArachnaeSwarm
public override AcceptanceReport CanAcceptPawn(Pawn pawn)
{
+ if (pawn == null || pawn.Destroyed)
+ return "Invalid pawn";
+
if (base.Working)
{
return "Occupied".Translate();
@@ -297,6 +362,9 @@ namespace ArachnaeSwarm
public override void TryAcceptPawn(Pawn pawn)
{
+ if (pawn == null || pawn.Destroyed)
+ return;
+
if (!CanAcceptPawn(pawn).Accepted)
{
return;
@@ -307,7 +375,7 @@ namespace ArachnaeSwarm
if (innerContainer.TryAddOrTransfer(pawn))
{
startTick = Find.TickManager.TicksGame;
- pawnTickCounters[pawn] = 0; // 初始化伤害计数器
+ SafeSetTickCount(pawn, 0); // 初始化伤害计数器
// 确保pawn不在死亡跟踪列表中
pawnsKilledByVat.Remove(pawn);
@@ -389,9 +457,8 @@ namespace ArachnaeSwarm
{
if (selectedPawn != null)
{
- // 从跟踪列表中移除
- pawnsKilledByVat.Remove(selectedPawn);
- pawnTickCounters.Remove(selectedPawn);
+ // 安全地从所有字典中移除
+ SafeRemoveFromDictionaries(selectedPawn);
// 确保pawn不在容器中(除非是被建筑杀死的)
if (innerContainer.Contains(selectedPawn) && !(selectedPawn.Dead && pawnsKilledByVat.Contains(selectedPawn)))
@@ -433,7 +500,7 @@ namespace ArachnaeSwarm
List list = new List();
foreach (Pawn p in base.Map.mapPawns.AllPawnsSpawned)
{
- if (CanAcceptPawn(p).Accepted)
+ if (p != null && !p.Destroyed && CanAcceptPawn(p).Accepted)
{
list.Add(new FloatMenuOption(p.LabelCap, () => SelectPawn(p), p, Color.white));
}
@@ -464,7 +531,7 @@ namespace ArachnaeSwarm
// 获取所有可接受的囚犯和奴隶
foreach (Pawn p in base.Map.mapPawns.AllPawnsSpawned)
{
- if (CanAcceptPawn(p).Accepted && (p.IsPrisonerOfColony || p.IsSlaveOfColony))
+ if (p != null && !p.Destroyed && CanAcceptPawn(p).Accepted && (p.IsPrisonerOfColony || p.IsSlaveOfColony))
{
list.Add(new FloatMenuOption(
p.LabelCap + " (" + (p.IsPrisonerOfColony ? "Prisoner" : "Slave") + ")",
@@ -485,7 +552,7 @@ namespace ArachnaeSwarm
// 检查是否有可用的囚犯/奴隶
bool hasPrisonersOrSlaves = base.Map.mapPawns.AllPawnsSpawned
- .Any(p => CanAcceptPawn(p).Accepted && (p.IsPrisonerOfColony || p.IsSlaveOfColony));
+ .Any(p => p != null && !p.Destroyed && CanAcceptPawn(p).Accepted && (p.IsPrisonerOfColony || p.IsSlaveOfColony));
if (!hasPrisonersOrSlaves)
{
@@ -501,7 +568,7 @@ namespace ArachnaeSwarm
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.Append(base.GetInspectString());
- if (base.Working && selectedPawn != null)
+ if (base.Working && selectedPawn != null && !selectedPawn.Destroyed)
{
stringBuilder.AppendLineIfNotEmpty().Append("CasketContains".Translate().ToString() + ": " + selectedPawn.NameShortColored.Resolve());
@@ -523,13 +590,13 @@ namespace ArachnaeSwarm
}
// 显示酸蚀伤害信息
- if (pawnTickCounters.TryGetValue(selectedPawn, out int tickCount))
+ if (SafeTryGetTickCount(selectedPawn, out int tickCount))
{
float damageProgress = (float)tickCount / AcidDamageInterval;
stringBuilder.AppendLineIfNotEmpty().Append("AcidDamageProgress".Translate() + ": " + damageProgress.ToStringPercent());
}
}
- else if (selectedPawn != null)
+ else if (selectedPawn != null && !selectedPawn.Destroyed)
{
stringBuilder.AppendLineIfNotEmpty().Append("WaitingForPawn".Translate(selectedPawn.Named("PAWN")).Resolve());
}
@@ -552,6 +619,10 @@ namespace ArachnaeSwarm
{
yield return floatMenuOption;
}
+
+ if (selPawn == null || selPawn.Destroyed)
+ yield break;
+
if (!selPawn.CanReach(this, PathEndMode.InteractionCell, Danger.Deadly))
{
yield return new FloatMenuOption("CannotEnterBuilding".Translate(this) + ": " + "NoPath".Translate().CapitalizeFirst(), null);
@@ -570,7 +641,7 @@ namespace ArachnaeSwarm
public override void DynamicDrawPhaseAt(DrawPhase phase, Vector3 drawLoc, bool flip = false)
{
- if (base.Working && selectedPawn != null && innerContainer.Contains(selectedPawn))
+ if (base.Working && selectedPawn != null && !selectedPawn.Destroyed && innerContainer.Contains(selectedPawn))
{
selectedPawn.Drawer.renderer.DynamicDrawPhaseAt(phase, drawLoc + PawnDrawOffset, null, neverAimWeapon: true);
}
@@ -589,7 +660,7 @@ namespace ArachnaeSwarm
public Job CreateCarryJobForPrisoner(Pawn prisoner, Pawn carrier)
{
- if (prisoner == null || carrier == null)
+ if (prisoner == null || prisoner.Destroyed || carrier == null || carrier.Destroyed)
return null;
if (!CanAcceptPawn(prisoner).Accepted)
return null;
@@ -609,6 +680,9 @@ namespace ArachnaeSwarm
{
foreach (Pawn pawn in base.Map.mapPawns.AllPawnsSpawned)
{
+ if (pawn == null || pawn.Destroyed)
+ continue;
+
// 检查是否是虫群成员
if (pawn.health.hediffSet.HasHediff(ARA_HediffDefOf.ARA_HiveMindDrone) ||
pawn.health.hediffSet.HasHediff(ARA_HediffDefOf.ARA_HiveMindWorker) ||
@@ -626,7 +700,7 @@ namespace ArachnaeSwarm
public void AssignCarrierForPrisoner(Pawn prisoner)
{
- if (prisoner == null)
+ if (prisoner == null || prisoner.Destroyed)
return;
// 获取可用的搬运者
var availableCarriers = GetAvailableCarriers().ToList();
@@ -641,6 +715,9 @@ namespace ArachnaeSwarm
foreach (Pawn carrier in availableCarriers)
{
+ if (carrier == null || carrier.Destroyed)
+ continue;
+
options.Add(new FloatMenuOption(
carrier.LabelCap,
() =>
@@ -679,12 +756,15 @@ namespace ArachnaeSwarm
pawnsKilledByVat ??= new HashSet();
pawnTickCounters ??= new Dictionary();
- // 清理可能已销毁的pawn引用
- pawnsKilledByVat.RemoveWhere(pawn => pawn == null || pawn.Destroyed);
- var deadPawns = pawnTickCounters.Keys.Where(pawn => pawn == null || pawn.Destroyed).ToList();
- foreach (var deadPawn in deadPawns)
+ // 更严格的清理可能已销毁的pawn引用
+ CleanupInvalidDictionaryEntries();
+
+ // 如果selectedPawn无效,重置状态
+ if (selectedPawn != null && (selectedPawn.Destroyed || selectedPawn.Dead))
{
- pawnTickCounters.Remove(deadPawn);
+ SafeRemoveFromDictionaries(selectedPawn);
+ selectedPawn = null;
+ startTick = -1;
}
}
}
diff --git a/Source/ArachnaeSwarm/Thing/CompAbilityEffect_SpawnFlyOver.cs b/Source/ArachnaeSwarm/Thing/CompAbilityEffect_SpawnFlyOver.cs
new file mode 100644
index 0000000..eb58f2b
--- /dev/null
+++ b/Source/ArachnaeSwarm/Thing/CompAbilityEffect_SpawnFlyOver.cs
@@ -0,0 +1,383 @@
+using RimWorld;
+using System.Collections.Generic;
+using UnityEngine;
+using Verse;
+
+namespace ArachnaeSwarm
+{
+ public class CompAbilityEffect_SpawnFlyOver : CompAbilityEffect
+ {
+ public new CompProperties_AbilitySpawnFlyOver Props => (CompProperties_AbilitySpawnFlyOver)props;
+
+ public override void Apply(LocalTargetInfo target, LocalTargetInfo dest)
+ {
+ base.Apply(target, dest);
+
+ if (parent.pawn == null || parent.pawn.Map == null)
+ return;
+
+ try
+ {
+ Log.Message($"FlyOver skill activated by {parent.pawn.Label} at position {parent.pawn.Position}");
+ Log.Message($"Target cell: {target.Cell}, Dest: {dest.Cell}");
+
+ // 计算起始和结束位置
+ IntVec3 startPos = CalculateStartPosition(target);
+ IntVec3 endPos = CalculateEndPosition(target, startPos);
+
+ Log.Message($"Final positions - Start: {startPos}, End: {endPos}");
+
+ // 验证位置是否有效
+ if (!startPos.InBounds(parent.pawn.Map))
+ {
+ Log.Warning($"Start position {startPos} is out of bounds, adjusting to map center");
+ startPos = parent.pawn.Map.Center;
+ }
+
+ if (!endPos.InBounds(parent.pawn.Map))
+ {
+ Log.Warning($"End position {endPos} is out of bounds, adjusting to map center");
+ endPos = parent.pawn.Map.Center;
+ }
+
+ // 根据类型创建不同的飞越物体
+ switch (Props.flyOverType)
+ {
+ case FlyOverType.Standard:
+ default:
+ CreateStandardFlyOver(startPos, endPos);
+ break;
+ }
+
+ // 显示效果消息
+ ShowEffectMessage();
+ }
+ catch (System.Exception ex)
+ {
+ Log.Error($"Error spawning fly over: {ex}");
+ }
+ }
+
+ private IntVec3 CalculateStartPosition(LocalTargetInfo target)
+ {
+ Map map = parent.pawn.Map;
+
+ switch (Props.startPosition)
+ {
+ case StartPosition.Caster:
+ return parent.pawn.Position;
+
+ case StartPosition.MapEdge:
+ return GetMapEdgePosition(map, GetDirectionFromCasterToTarget(target));
+
+ case StartPosition.CustomOffset:
+ return parent.pawn.Position + Props.customStartOffset;
+
+ case StartPosition.RandomMapEdge:
+ return GetRandomMapEdgePosition(map);
+
+ default:
+ return parent.pawn.Position;
+ }
+ }
+
+ private IntVec3 CalculateEndPosition(LocalTargetInfo target, IntVec3 startPos)
+ {
+ Map map = parent.pawn.Map;
+ IntVec3 endPos;
+
+ switch (Props.endPosition)
+ {
+ case EndPosition.TargetCell:
+ endPos = target.Cell;
+ break;
+
+ case EndPosition.OppositeMapEdge:
+ endPos = GetOppositeMapEdgeThroughCenter(map, startPos);
+ break;
+
+ case EndPosition.CustomOffset:
+ endPos = target.Cell + Props.customEndOffset;
+ break;
+
+ case EndPosition.FixedDistance:
+ endPos = GetFixedDistancePosition(startPos, target.Cell);
+ break;
+
+ case EndPosition.RandomMapEdge:
+ endPos = GetRandomMapEdgePosition(map);
+ Log.Message($"Random map edge selected as end position: {endPos}");
+ break;
+
+ default:
+ endPos = target.Cell;
+ break;
+ }
+
+ // 关键修复:确保起点和终点不同
+ if (startPos == endPos)
+ {
+ Log.Warning($"FlyOver start and end positions are the same: {startPos}. Adjusting end position.");
+
+ // 如果相同,将终点向随机方向移动
+ IntVec3 randomOffset = new IntVec3(Rand.Range(-10, 11), 0, Rand.Range(-10, 11));
+ endPos += randomOffset;
+
+ // 确保新位置在地图内
+ if (!endPos.InBounds(map))
+ {
+ endPos = map.Center;
+ }
+ }
+
+ Log.Message($"Calculated positions - Start: {startPos}, End: {endPos}, Distance: {startPos.DistanceTo(endPos)}");
+ return endPos;
+ }
+
+ // 修复的 OppositeMapEdge 逻辑:确保穿过地图中心
+ private IntVec3 GetOppositeMapEdgeThroughCenter(Map map, IntVec3 startPos)
+ {
+ IntVec3 center = map.Center;
+
+ // 计算从起点到中心的方向
+ Vector3 toCenter = (center.ToVector3() - startPos.ToVector3()).normalized;
+
+ // 如果方向为零向量,使用随机方向
+ if (toCenter == Vector3.zero)
+ {
+ toCenter = new Vector3(Rand.Range(-1f, 1f), 0, Rand.Range(-1f, 1f)).normalized;
+ Log.Message($"Using random direction to center: {toCenter}");
+ }
+
+ // 计算从中心到对面边缘的方向(与起点到中心的方向相同)
+ Vector3 fromCenter = toCenter;
+
+ Log.Message($"Calculating opposite edge: Start={startPos}, Center={center}, Direction={fromCenter}");
+
+ // 从中心出发,沿着相同方向找到对面的地图边缘
+ IntVec3 oppositeEdge = GetMapEdgePositionFromCenter(map, fromCenter);
+
+ Log.Message($"Found opposite edge through center: {oppositeEdge}");
+ return oppositeEdge;
+ }
+
+ // 从地图中心出发找到指定方向的地图边缘
+ private IntVec3 GetMapEdgePositionFromCenter(Map map, Vector3 direction)
+ {
+ IntVec3 center = map.Center;
+ float maxDist = Mathf.Max(map.Size.x, map.Size.z) * 0.6f;
+
+ // 从中心向指定方向延伸
+ for (int i = 1; i <= maxDist; i++)
+ {
+ IntVec3 testPos = center + new IntVec3(
+ Mathf.RoundToInt(direction.x * i),
+ 0,
+ Mathf.RoundToInt(direction.z * i));
+
+ if (!testPos.InBounds(map))
+ {
+ // 找到边界位置
+ IntVec3 edgePos = FindClosestValidPosition(testPos, map);
+ Log.Message($"Found map edge from center: {edgePos} (direction: {direction}, distance: {i})");
+ return edgePos;
+ }
+ }
+
+ // 如果没找到边界,使用随机边缘位置
+ Log.Warning("Could not find map edge from center, using random edge");
+ return GetRandomMapEdgePosition(map);
+ }
+
+ private IntVec3 GetMapEdgePosition(Map map, Vector3 direction)
+ {
+ if (direction == Vector3.zero)
+ {
+ direction = new Vector3(Rand.Range(-1f, 1f), 0, Rand.Range(-1f, 1f)).normalized;
+ Log.Message($"Using random direction: {direction}");
+ }
+
+ IntVec3 center = map.Center;
+ float maxDist = Mathf.Max(map.Size.x, map.Size.z) * 0.6f;
+
+ // 从中心向指定方向延伸
+ for (int i = 1; i <= maxDist; i++)
+ {
+ IntVec3 testPos = center + new IntVec3(
+ Mathf.RoundToInt(direction.x * i),
+ 0,
+ Mathf.RoundToInt(direction.z * i));
+
+ if (!testPos.InBounds(map))
+ {
+ // 找到边界位置
+ IntVec3 edgePos = FindClosestValidPosition(testPos, map);
+ Log.Message($"Found map edge position: {edgePos} (direction: {direction}, distance: {i})");
+ return edgePos;
+ }
+ }
+
+ // 如果没找到边界,使用随机边缘位置
+ Log.Warning("Could not find map edge in direction, using random edge");
+ return GetRandomMapEdgePosition(map);
+ }
+
+ private IntVec3 FindClosestValidPosition(IntVec3 invalidPos, Map map)
+ {
+ // 在无效位置周围寻找有效的边界位置
+ for (int radius = 1; radius <= 5; radius++)
+ {
+ foreach (IntVec3 pos in GenRadial.RadialPatternInRadius(radius))
+ {
+ IntVec3 testPos = invalidPos + pos;
+ if (testPos.InBounds(map))
+ {
+ return testPos;
+ }
+ }
+ }
+
+ return map.Center;
+ }
+
+ // 旧的 OppositeMapEdge 逻辑(保留作为参考)
+ private IntVec3 GetOppositeMapEdgePosition(Map map, IntVec3 startPos)
+ {
+ // 计算从起点指向地图中心的方向,然后取反
+ Vector3 toCenter = (map.Center.ToVector3() - startPos.ToVector3()).normalized;
+ Vector3 oppositeDirection = -toCenter;
+
+ // 如果方向为零向量,使用随机方向
+ if (oppositeDirection == Vector3.zero)
+ {
+ oppositeDirection = new Vector3(Rand.Range(-1f, 1f), 0, Rand.Range(-1f, 1f)).normalized;
+ }
+
+ Log.Message($"Opposite direction from {startPos}: {oppositeDirection}");
+ return GetMapEdgePosition(map, oppositeDirection);
+ }
+
+ private IntVec3 GetRandomMapEdgePosition(Map map)
+ {
+ // 随机选择一个地图边缘位置
+ int edge = Rand.Range(0, 4);
+ int x, z;
+
+ switch (edge)
+ {
+ case 0: // 上边
+ x = Rand.Range(0, map.Size.x);
+ z = 0;
+ break;
+ case 1: // 右边
+ x = map.Size.x - 1;
+ z = Rand.Range(0, map.Size.z);
+ break;
+ case 2: // 下边
+ x = Rand.Range(0, map.Size.x);
+ z = map.Size.z - 1;
+ break;
+ case 3: // 左边
+ default:
+ x = 0;
+ z = Rand.Range(0, map.Size.z);
+ break;
+ }
+
+ IntVec3 edgePos = new IntVec3(x, 0, z);
+ Log.Message($"Random map edge position: {edgePos}");
+ return edgePos;
+ }
+
+ private IntVec3 GetFixedDistancePosition(IntVec3 startPos, IntVec3 targetPos)
+ {
+ Vector3 direction = (targetPos.ToVector3() - startPos.ToVector3()).normalized;
+ IntVec3 endPos = startPos + new IntVec3(
+ (int)(direction.x * Props.flyOverDistance),
+ 0,
+ (int)(direction.z * Props.flyOverDistance));
+
+ Log.Message($"Fixed distance position: {endPos} (from {startPos}, distance: {Props.flyOverDistance})");
+ return endPos;
+ }
+
+ private Vector3 GetDirectionFromCasterToTarget(LocalTargetInfo target)
+ {
+ Vector3 direction = (target.Cell.ToVector3() - parent.pawn.Position.ToVector3()).normalized;
+
+ // 如果方向为零向量,使用随机方向
+ if (direction == Vector3.zero)
+ {
+ direction = new Vector3(Rand.Range(-1f, 1f), 0, Rand.Range(-1f, 1f)).normalized;
+ Log.Message($"Using random direction: {direction}");
+ }
+
+ return direction;
+ }
+
+ private void CreateStandardFlyOver(IntVec3 startPos, IntVec3 endPos)
+ {
+ // 确保有默认的飞越定义
+ ThingDef flyOverDef = Props.flyOverDef ?? DefDatabase.GetNamedSilentFail("ARA_FlyOverShip");
+ if (flyOverDef == null)
+ {
+ Log.Warning("No fly over def specified for standard fly over");
+ return;
+ }
+
+ FlyOver flyOver = FlyOver.MakeFlyOver(
+ flyOverDef,
+ startPos,
+ endPos,
+ parent.pawn.Map,
+ Props.flightSpeed,
+ Props.altitude
+ );
+
+ // 设置属性
+ flyOver.spawnContentsOnImpact = Props.dropContentsOnImpact;
+ flyOver.playFlyOverSound = Props.playFlyOverSound;
+
+ // 应用自定义音效
+ if (Props.customSound != null)
+ {
+ // 这里需要更复杂的逻辑来替换音效
+ }
+
+ Log.Message($"Standard FlyOver created: {flyOver} from {startPos} to {endPos}");
+ }
+
+ private void ShowEffectMessage()
+ {
+ string message = GetFlyOverMessage();
+ Messages.Message(message, parent.pawn, MessageTypeDefOf.NeutralEvent);
+ }
+
+ private string GetFlyOverMessage()
+ {
+ switch (Props.flyOverType)
+ {
+ case FlyOverType.HighAltitude:
+ return "HighAltitudeReconArrival".Translate(parent.pawn.LabelShort);
+ case FlyOverType.CargoDrop:
+ return "CargoDropIncoming".Translate(parent.pawn.LabelShort);
+ case FlyOverType.BombingRun:
+ return "BombingRunInitiated".Translate(parent.pawn.LabelShort);
+ case FlyOverType.Reconnaissance:
+ return "ReconnaissanceFlyOver".Translate(parent.pawn.LabelShort);
+ case FlyOverType.Standard:
+ default:
+ return "FlyOverInitiated".Translate(parent.pawn.LabelShort);
+ }
+ }
+
+ public override bool Valid(LocalTargetInfo target, bool throwMessages = false)
+ {
+ return base.Valid(target, throwMessages) &&
+ parent.pawn != null &&
+ parent.pawn.Map != null &&
+ target.Cell.IsValid &&
+ target.Cell.InBounds(parent.pawn.Map);
+ }
+ }
+}
diff --git a/Source/ArachnaeSwarm/Thing/CompProperties_AbilitySpawnFlyOver.cs b/Source/ArachnaeSwarm/Thing/CompProperties_AbilitySpawnFlyOver.cs
new file mode 100644
index 0000000..ae5be4d
--- /dev/null
+++ b/Source/ArachnaeSwarm/Thing/CompProperties_AbilitySpawnFlyOver.cs
@@ -0,0 +1,62 @@
+using RimWorld;
+using System.Collections.Generic;
+using Verse;
+
+namespace ArachnaeSwarm
+{
+ public class CompProperties_AbilitySpawnFlyOver : CompProperties_AbilityEffect
+ {
+ public ThingDef flyOverDef; // 飞越物体的 ThingDef
+ public FlyOverType flyOverType = FlyOverType.Standard; // 飞越类型
+ public float flightSpeed = 1f; // 飞行速度
+ public float altitude = 15f; // 飞行高度
+ public bool spawnContents = false; // 是否生成内容物
+ public List contents; // 内容物列表
+ public bool dropContentsOnImpact = true; // 是否在终点投放内容物
+ public SoundDef customSound; // 自定义音效
+ public bool playFlyOverSound = true; // 是否播放飞越音效
+
+ // 起始位置选项
+ public StartPosition startPosition = StartPosition.Caster;
+ public IntVec3 customStartOffset = IntVec3.Zero;
+
+ // 终点位置选项
+ public EndPosition endPosition = EndPosition.TargetCell;
+ public IntVec3 customEndOffset = IntVec3.Zero;
+ public int flyOverDistance = 30; // 飞越距离(当终点为自定义时)
+
+ public CompProperties_AbilitySpawnFlyOver()
+ {
+ this.compClass = typeof(CompAbilityEffect_SpawnFlyOver);
+ }
+ }
+
+ // 飞越类型枚举
+ public enum FlyOverType
+ {
+ Standard, // 标准飞越
+ HighAltitude, // 高空飞越
+ CargoDrop, // 货运飞越
+ BombingRun, // 轰炸飞越
+ Reconnaissance // 侦察飞越
+ }
+
+ // 起始位置枚举
+ public enum StartPosition
+ {
+ Caster, // 施法者位置
+ MapEdge, // 地图边缘
+ CustomOffset, // 自定义偏移
+ RandomMapEdge // 随机地图边缘
+ }
+
+ // 终点位置枚举
+ public enum EndPosition
+ {
+ TargetCell, // 目标单元格
+ OppositeMapEdge, // 对面地图边缘
+ CustomOffset, // 自定义偏移
+ FixedDistance, // 固定距离
+ RandomMapEdge
+ }
+}
diff --git a/Source/ArachnaeSwarm/Thing/HighAltitudeFlyOver.cs b/Source/ArachnaeSwarm/Thing/HighAltitudeFlyOver.cs
deleted file mode 100644
index e6ff40b..0000000
--- a/Source/ArachnaeSwarm/Thing/HighAltitudeFlyOver.cs
+++ /dev/null
@@ -1,171 +0,0 @@
-using System.Collections.Generic;
-using RimWorld;
-using UnityEngine;
-using Verse;
-using Verse.Sound;
-
-[StaticConstructorOnStartup]
-public class HighAltitudeFlyOver : FlyOver
-{
- // 超高空飞行物特有的字段
- private Material cachedHighAltitudeShadowMaterial;
- private bool useCustomShadowGraphic = true;
- private float shadowIntensity = 0.6f; // 阴影强度
-
- // 创建子类自己的 shadowPropertyBlock
- private static MaterialPropertyBlock highAltitudeShadowPropertyBlock = new MaterialPropertyBlock();
-
- // 静态构造器:预加载阴影材质
- static HighAltitudeFlyOver()
- {
- // 可以在这里预加载特殊阴影材质
- }
-
- // 属性:重写阴影材质
- protected new Material ShadowMaterial
- {
- get
- {
- if (cachedHighAltitudeShadowMaterial == null)
- {
- // 使用特殊的高空阴影材质
- var skyfallerShadow = def.skyfaller?.shadow;
- bool hasCustomShadow = skyfallerShadow != null && !skyfallerShadow.NullOrEmpty();
-
- if (hasCustomShadow && useCustomShadowGraphic)
- {
- cachedHighAltitudeShadowMaterial = MaterialPool.MatFrom(
- skyfallerShadow,
- ShaderDatabase.Transparent,
- Color.white);
- }
- else
- {
- // 默认使用圆形阴影
- cachedHighAltitudeShadowMaterial = MaterialPool.MatFrom(
- "Things/Skyfaller/SkyfallerShadowCircle",
- ShaderDatabase.Transparent);
- }
- }
- return cachedHighAltitudeShadowMaterial;
- }
- }
-
- public override void ExposeData()
- {
- base.ExposeData();
- Scribe_Values.Look(ref useCustomShadowGraphic, "useCustomShadowGraphic", true);
- Scribe_Values.Look(ref shadowIntensity, "shadowIntensity", 0.6f);
- }
-
- // 重写绘制方法:只绘制阴影,不绘制主体
- protected override void DrawAt(Vector3 drawLoc, bool flip = false)
- {
- // 完全跳过主体绘制,只绘制阴影
- DrawHighAltitudeShadow();
- }
-
- // 专门的高空阴影绘制方法
- protected virtual void DrawHighAltitudeShadow()
- {
- Material shadowMaterial = ShadowMaterial;
- if (shadowMaterial == null)
- return;
-
- Vector3 shadowPos = DrawPos;
- shadowPos.y = AltitudeLayer.Shadows.AltitudeFor();
-
- // 高空阴影的特殊参数
- float shadowAlpha = CalculateHighAltitudeShadowAlpha();
- float shadowScale = CalculateHighAltitudeShadowScale();
-
- // 设置阴影属性 - 使用子类自己的 property block
- highAltitudeShadowPropertyBlock.SetColor(ShaderPropertyIDs.Color,
- new Color(1f, 1f, 1f, shadowAlpha * shadowIntensity));
-
- Vector3 scale = new Vector3(shadowScale, 1f, shadowScale);
- Matrix4x4 matrix = Matrix4x4.TRS(shadowPos, Quaternion.identity, scale);
-
- // 绘制阴影
- Graphics.DrawMesh(MeshPool.plane10, matrix, shadowMaterial,
- 0, null, 0, highAltitudeShadowPropertyBlock);
- }
-
- // 计算高空阴影透明度
- protected virtual float CalculateHighAltitudeShadowAlpha()
- {
- // 高空阴影应该更淡
- float baseAlpha = Mathf.Lerp(0.1f, 0.4f, currentProgress);
-
- // 根据高度调整透明度(越高越淡)
- float heightFactor = Mathf.Clamp(1f - (altitude / 100f), 0.1f, 1f);
-
- return baseAlpha * heightFactor;
- }
-
- // 计算高空阴影大小
- protected virtual float CalculateHighAltitudeShadowScale()
- {
- // 高空阴影应该更大(透视效果)
- float baseScale = Mathf.Lerp(0.8f, 1.2f, currentProgress);
-
- // 根据高度调整大小(越高越大)
- float heightFactor = 1f + (altitude / 50f);
-
- return baseScale * heightFactor;
- }
-
- // 重写 Tick 方法,可以添加高空特有的效果
- protected override void Tick()
- {
- base.Tick();
-
- // 高空特有的效果:更稀少的粒子效果
- var motesPerCell = def.skyfaller?.motesPerCell;
- bool hasMotes = motesPerCell != null && motesPerCell > 0;
-
- if (Rand.MTBEventOccurs(2f, 1f, 1f) && hasMotes)
- {
- CreateHighAltitudeEffects();
- }
- }
-
- // 高空特效(更稀疏)
- protected virtual void CreateHighAltitudeEffects()
- {
- Vector3 effectPos = DrawPos;
- effectPos.y = AltitudeLayer.Weather.AltitudeFor(); // 使用天气层高度
-
- // 高空特有的粒子效果
- FleckMaker.ThrowAirPuffUp(effectPos, base.Map);
-
- // 偶尔生成云迹效果
- if (Rand.Chance(0.1f))
- {
- FleckMaker.ThrowSmoke(effectPos, base.Map, 3f);
- }
- }
-
- // 新的工具方法:创建超高空飞行物
- public static HighAltitudeFlyOver MakeHighAltitudeFlyOver(ThingDef flyOverDef, IntVec3 start, IntVec3 end, Map map,
- float speed = 1f, float height = 50f, ThingOwner contents = null) // 默认高度更高
- {
- HighAltitudeFlyOver flyOver = (HighAltitudeFlyOver)ThingMaker.MakeThing(flyOverDef);
- flyOver.startPosition = start;
- flyOver.endPosition = end;
- flyOver.flightSpeed = speed;
- flyOver.altitude = height;
-
- // 超高空特有的设置
- flyOver.playFlyOverSound = false; // 高空可能听不到声音
- flyOver.createShadow = true;
-
- if (contents != null)
- {
- flyOver.innerContainer.TryAddRangeOrTransfer(contents);
- }
-
- GenSpawn.Spawn(flyOver, start, map);
- return flyOver;
- }
-}
diff --git a/Source/ArachnaeSwarm/Thing/ThingclassFlyOver.cs b/Source/ArachnaeSwarm/Thing/ThingclassFlyOver.cs
index 8c4e7ed..bd3c11f 100644
--- a/Source/ArachnaeSwarm/Thing/ThingclassFlyOver.cs
+++ b/Source/ArachnaeSwarm/Thing/ThingclassFlyOver.cs
@@ -4,310 +4,444 @@ using UnityEngine;
using Verse;
using Verse.Sound;
-[StaticConstructorOnStartup]
-public class FlyOver : ThingWithComps, IThingHolder
+namespace ArachnaeSwarm
{
- // 核心字段
- public ThingOwner innerContainer; // 内部物品容器
- public IntVec3 startPosition; // 起始位置
- public IntVec3 endPosition; // 结束位置
- public float flightSpeed = 1f; // 飞行速度
- public float currentProgress = 0f; // 当前进度 (0-1)
- public float altitude = 10f; // 飞行高度
- public float flightAngle = 0f; // 飞行角度
-
- // 状态标志
- private bool hasStarted = false;
- private bool hasCompleted = false;
-
- // 音效系统
- private Sustainer flightSoundPlaying;
-
- // 视觉效果
- private Material cachedShadowMaterial;
- private static MaterialPropertyBlock shadowPropertyBlock = new MaterialPropertyBlock();
-
- // 配置字段
- public bool spawnContentsOnImpact = false; // 是否在结束时生成内容物
- public bool playFlyOverSound = true; // 是否播放飞越音效
- public bool createShadow = true; // 是否创建阴影
-
- // 属性
- public override Vector3 DrawPos
+ [StaticConstructorOnStartup]
+ public class FlyOver : ThingWithComps, IThingHolder
{
- get
+ // 核心字段
+ public ThingOwner innerContainer; // 内部物品容器
+ public IntVec3 startPosition; // 起始位置
+ public IntVec3 endPosition; // 结束位置
+ public float flightSpeed = 1f; // 飞行速度
+ public float currentProgress = 0f; // 当前进度 (0-1)
+ public float altitude = 10f; // 飞行高度
+
+ // 淡入效果相关
+ public float fadeInDuration = 1.5f; // 淡入持续时间(秒)
+ public float currentFadeInTime = 0f; // 当前淡入时间
+ public bool fadeInCompleted = false; // 淡入是否完成
+
+ // 状态标志
+ public bool hasStarted = false;
+ public bool hasCompleted = false;
+
+ // 音效系统
+ private Sustainer flightSoundPlaying;
+
+ // 视觉效果
+ private Material cachedShadowMaterial;
+ private static MaterialPropertyBlock shadowPropertyBlock = new MaterialPropertyBlock();
+ private static MaterialPropertyBlock fadePropertyBlock = new MaterialPropertyBlock();
+
+ // 配置字段
+ public bool spawnContentsOnImpact = false; // 是否在结束时生成内容物
+ public bool playFlyOverSound = true; // 是否播放飞越音效
+ public bool createShadow = true; // 是否创建阴影
+
+ // 属性
+ public override Vector3 DrawPos
{
- // 线性插值计算当前位置
- Vector3 start = startPosition.ToVector3();
- Vector3 end = endPosition.ToVector3();
-
- // 添加高度偏移
- Vector3 basePos = Vector3.Lerp(start, end, currentProgress);
- basePos.y = altitude;
-
- return basePos;
+ get
+ {
+ // 线性插值计算当前位置
+ Vector3 start = startPosition.ToVector3();
+ Vector3 end = endPosition.ToVector3();
+
+ // 添加高度偏移
+ Vector3 basePos = Vector3.Lerp(start, end, currentProgress);
+ basePos.y = altitude;
+
+ return basePos;
+ }
}
- }
-
- public override Graphic Graphic
- {
- get
+
+ public override Graphic Graphic
+ {
+ get
+ {
+ Thing thingForGraphic = GetThingForGraphic();
+ if (thingForGraphic == this)
+ {
+ return base.Graphic;
+ }
+ return thingForGraphic.Graphic.ExtractInnerGraphicFor(thingForGraphic);
+ }
+ }
+
+ protected Material ShadowMaterial
+ {
+ get
+ {
+ if (cachedShadowMaterial == null && createShadow)
+ {
+ cachedShadowMaterial = MaterialPool.MatFrom("Things/Skyfaller/SkyfallerShadowCircle", ShaderDatabase.Transparent);
+ }
+ return cachedShadowMaterial;
+ }
+ }
+
+ // 精确旋转 - 模仿原版 Projectile
+ public virtual Quaternion ExactRotation
+ {
+ get
+ {
+ Vector3 direction = (endPosition.ToVector3() - startPosition.ToVector3()).normalized;
+ return Quaternion.LookRotation(direction.Yto0());
+ }
+ }
+
+ // 简化的方向计算方法
+ public Vector3 MovementDirection
+ {
+ get
+ {
+ return (endPosition.ToVector3() - startPosition.ToVector3()).normalized;
+ }
+ }
+
+ // 淡入透明度(0-1)
+ public float FadeInAlpha
+ {
+ get
+ {
+ if (fadeInCompleted) return 1f;
+ return Mathf.Clamp01(currentFadeInTime / fadeInDuration);
+ }
+ }
+
+ public FlyOver()
+ {
+ innerContainer = new ThingOwner(this);
+ }
+
+ public override void ExposeData()
+ {
+ base.ExposeData();
+ Scribe_Deep.Look(ref innerContainer, "innerContainer", this);
+ Scribe_Values.Look(ref startPosition, "startPosition");
+ Scribe_Values.Look(ref endPosition, "endPosition");
+ Scribe_Values.Look(ref flightSpeed, "flightSpeed", 1f);
+ Scribe_Values.Look(ref currentProgress, "currentProgress", 0f);
+ Scribe_Values.Look(ref altitude, "altitude", 10f);
+ Scribe_Values.Look(ref hasStarted, "hasStarted", false);
+ Scribe_Values.Look(ref hasCompleted, "hasCompleted", false);
+ Scribe_Values.Look(ref spawnContentsOnImpact, "spawnContentsOnImpact", false);
+ Scribe_Values.Look(ref fadeInDuration, "fadeInDuration", 1.5f);
+ Scribe_Values.Look(ref currentFadeInTime, "currentFadeInTime", 0f);
+ Scribe_Values.Look(ref fadeInCompleted, "fadeInCompleted", false);
+ }
+
+ public override void SpawnSetup(Map map, bool respawningAfterLoad)
+ {
+ base.SpawnSetup(map, respawningAfterLoad);
+
+ Log.Message($"FlyOver Spawned - Start: {startPosition}, End: {endPosition}, Speed: {flightSpeed}, Altitude: {altitude}");
+ if (!respawningAfterLoad)
+ {
+ Log.Message($"FlyOver Direction - Vector: {MovementDirection}, Rotation: {ExactRotation.eulerAngles}");
+
+ // 设置初始位置
+ base.Position = startPosition;
+ hasStarted = true;
+
+ // 重置淡入状态
+ currentFadeInTime = 0f;
+ fadeInCompleted = false;
+
+ // 开始飞行音效
+ if (playFlyOverSound && def.skyfaller?.floatingSound != null)
+ {
+ flightSoundPlaying = def.skyfaller.floatingSound.TrySpawnSustainer(
+ SoundInfo.InMap(new TargetInfo(startPosition, map), MaintenanceType.PerTick));
+ Log.Message("FlyOver sound started");
+ }
+ }
+ }
+
+ protected override void Tick()
+ {
+ base.Tick();
+
+ if (!hasStarted || hasCompleted)
+ return;
+
+ // 更新淡入效果
+ if (!fadeInCompleted)
+ {
+ currentFadeInTime += 1f / 60f; // 假设 60 FPS
+ if (currentFadeInTime >= fadeInDuration)
+ {
+ fadeInCompleted = true;
+ currentFadeInTime = fadeInDuration;
+ }
+ }
+
+ // 更新飞行进度
+ currentProgress += flightSpeed * 0.001f;
+
+ // 更新当前位置(用于碰撞检测等)
+ UpdatePosition();
+
+ // 维持飞行音效
+ flightSoundPlaying?.Maintain();
+
+ // 检查是否到达终点
+ if (currentProgress >= 1f)
+ {
+ CompleteFlyOver();
+ }
+
+ // 可选:生成飞行轨迹特效
+ if (Rand.MTBEventOccurs(0.5f, 1f, 1f) && def.skyfaller?.motesPerCell > 0)
+ {
+ CreateFlightEffects();
+ }
+ }
+
+ private void UpdatePosition()
+ {
+ // 更新物体的网格位置(用于碰撞检测等)
+ Vector3 currentWorldPos = Vector3.Lerp(startPosition.ToVector3(), endPosition.ToVector3(), currentProgress);
+ IntVec3 newPos = currentWorldPos.ToIntVec3();
+
+ if (newPos != base.Position && newPos.InBounds(base.Map))
+ {
+ base.Position = newPos;
+ }
+ }
+
+ private void CompleteFlyOver()
+ {
+ hasCompleted = true;
+ currentProgress = 1f;
+
+ // 生成内容物(如果需要)
+ if (spawnContentsOnImpact && innerContainer.Any)
+ {
+ SpawnContents();
+ }
+
+ // 播放完成音效
+ if (def.skyfaller?.impactSound != null)
+ {
+ def.skyfaller.impactSound.PlayOneShot(
+ SoundInfo.InMap(new TargetInfo(endPosition, base.Map)));
+ }
+
+ Log.Message($"FlyOver completed at {endPosition}");
+
+ // 销毁自身
+ Destroy();
+ }
+
+ private void SpawnContents()
+ {
+ foreach (Thing thing in innerContainer)
+ {
+ if (thing != null && !thing.Destroyed)
+ {
+ GenPlace.TryPlaceThing(thing, endPosition, base.Map, ThingPlaceMode.Near);
+ }
+ }
+ innerContainer.Clear();
+ }
+
+ private void CreateFlightEffects()
+ {
+ // 在飞行轨迹上生成粒子效果
+ Vector3 effectPos = DrawPos;
+ effectPos.y = AltitudeLayer.MoteOverhead.AltitudeFor();
+ FleckMaker.ThrowSmoke(effectPos, base.Map, 1f);
+
+ // 可选:根据速度生成更多效果
+ if (flightSpeed > 2f)
+ {
+ FleckMaker.ThrowAirPuffUp(effectPos, base.Map);
+ }
+ }
+
+ // 关键修改:添加淡入效果的绘制方法
+ protected override void DrawAt(Vector3 drawLoc, bool flip = false)
+ {
+ Vector3 finalDrawPos = drawLoc;
+
+ // 绘制阴影
+ if (createShadow)
+ {
+ DrawFlightShadow();
+ }
+
+ // 绘制主体 - 使用带淡入效果的绘制方法
+ DrawFlyOverWithFade(finalDrawPos);
+ }
+
+ // 带淡入效果的主体绘制方法
+ protected virtual void DrawFlyOverWithFade(Vector3 drawPos)
{
Thing thingForGraphic = GetThingForGraphic();
- if (thingForGraphic == this)
+ Graphic graphic = thingForGraphic.Graphic;
+
+ if (graphic == null)
+ return;
+
+ // 获取原始材质
+ Material material = graphic.MatSingle;
+ if (material == null)
+ return;
+
+ // 计算淡入透明度
+ float alpha = FadeInAlpha;
+
+ // 如果已经完全淡入,使用原版绘制方法以获得最佳性能
+ if (alpha >= 0.999f)
{
- return base.Graphic;
+ graphic.Draw(drawPos, Rot4.North, thingForGraphic, ExactRotation.eulerAngles.y);
+ return;
}
- return thingForGraphic.Graphic.ExtractInnerGraphicFor(thingForGraphic);
- }
- }
-
- protected Material ShadowMaterial
- {
- get
- {
- if (cachedShadowMaterial == null && createShadow)
+
+ // 创建带透明度的材质属性块
+ fadePropertyBlock.SetColor(ShaderPropertyIDs.Color,
+ new Color(graphic.Color.r, graphic.Color.g, graphic.Color.b, graphic.Color.a * alpha));
+
+ // 计算缩放
+ Vector3 scale = Vector3.one;
+ if (def.graphicData != null)
{
- cachedShadowMaterial = MaterialPool.MatFrom("Things/Skyfaller/SkyfallerShadowCircle", ShaderDatabase.Transparent);
+ scale = new Vector3(def.graphicData.drawSize.x, 1f, def.graphicData.drawSize.y);
}
- return cachedShadowMaterial;
+
+ // 使用自定义绘制实现淡入效果
+ Matrix4x4 matrix = Matrix4x4.TRS(drawPos, ExactRotation, scale);
+ Graphics.DrawMesh(MeshPool.plane10, matrix, material, 0, null, 0, fadePropertyBlock);
}
- }
-
- public FlyOver()
- {
- innerContainer = new ThingOwner(this);
- }
-
- public override void ExposeData()
- {
- base.ExposeData();
- Scribe_Deep.Look(ref innerContainer, "innerContainer", this);
- Scribe_Values.Look(ref startPosition, "startPosition");
- Scribe_Values.Look(ref endPosition, "endPosition");
- Scribe_Values.Look(ref flightSpeed, "flightSpeed", 1f);
- Scribe_Values.Look(ref currentProgress, "currentProgress", 0f);
- Scribe_Values.Look(ref altitude, "altitude", 10f);
- Scribe_Values.Look(ref flightAngle, "flightAngle", 0f);
- Scribe_Values.Look(ref hasStarted, "hasStarted", false);
- Scribe_Values.Look(ref hasCompleted, "hasCompleted", false);
- Scribe_Values.Look(ref spawnContentsOnImpact, "spawnContentsOnImpact", false);
- }
-
- public override void SpawnSetup(Map map, bool respawningAfterLoad)
- {
- base.SpawnSetup(map, respawningAfterLoad);
-
- if (!respawningAfterLoad)
+
+ // 简化的阴影绘制方法 - 完全移除旋转(模仿原版)
+ protected virtual void DrawFlightShadow()
{
- // 计算飞行角度(从起点指向终点的方向)
- Vector3 direction = (endPosition.ToVector3() - startPosition.ToVector3()).normalized;
- flightAngle = Mathf.Atan2(direction.z, direction.x) * Mathf.Rad2Deg;
-
- // 设置初始位置
- base.Position = startPosition;
- hasStarted = true;
-
- // 开始飞行音效
- if (playFlyOverSound && def.skyfaller?.floatingSound != null)
+ // 检查是否有 ModExtension
+ var shadowExtension = def.GetModExtension();
+
+ Material shadowMaterial;
+ if (shadowExtension?.useCustomShadow == true && !shadowExtension.customShadowPath.NullOrEmpty())
{
- flightSoundPlaying = def.skyfaller.floatingSound.TrySpawnSustainer(
- SoundInfo.InMap(new TargetInfo(startPosition, map), MaintenanceType.PerTick));
+ shadowMaterial = MaterialPool.MatFrom(shadowExtension.customShadowPath, ShaderDatabase.Transparent);
}
- }
- }
-
- protected override void Tick()
- {
- base.Tick();
-
- if (!hasStarted || hasCompleted)
- return;
-
- // 更新飞行进度
- currentProgress += flightSpeed * 0.001f; // 调整速度系数
-
- // 更新当前位置(用于碰撞检测等)
- UpdatePosition();
-
- // 维持飞行音效
- flightSoundPlaying?.Maintain();
-
- // 检查是否到达终点
- if (currentProgress >= 1f)
- {
- CompleteFlyOver();
- }
-
- // 可选:生成飞行轨迹特效
- if (Rand.MTBEventOccurs(0.5f, 1f, 1f) && def.skyfaller?.motesPerCell > 0)
- {
- CreateFlightEffects();
- }
- }
-
- private void UpdatePosition()
- {
- // 更新物体的网格位置(用于碰撞检测等)
- Vector3 currentWorldPos = Vector3.Lerp(startPosition.ToVector3(), endPosition.ToVector3(), currentProgress);
- IntVec3 newPos = currentWorldPos.ToIntVec3();
-
- if (newPos != base.Position && newPos.InBounds(base.Map))
- {
- base.Position = newPos;
- }
- }
-
- private void CompleteFlyOver()
- {
- hasCompleted = true;
- currentProgress = 1f;
-
- // 生成内容物(如果需要)
- if (spawnContentsOnImpact && innerContainer.Any)
- {
- SpawnContents();
- }
-
- // 播放完成音效
- if (def.skyfaller?.impactSound != null)
- {
- def.skyfaller.impactSound.PlayOneShot(
- SoundInfo.InMap(new TargetInfo(endPosition, base.Map)));
- }
-
- // 销毁自身
- Destroy();
- }
-
- private void SpawnContents()
- {
- foreach (Thing thing in innerContainer)
- {
- if (thing != null && !thing.Destroyed)
+ else
{
- GenPlace.TryPlaceThing(thing, endPosition, base.Map, ThingPlaceMode.Near);
+ shadowMaterial = ShadowMaterial;
}
+
+ if (shadowMaterial == null)
+ return;
+
+ Vector3 shadowPos = DrawPos;
+ shadowPos.y = AltitudeLayer.Shadows.AltitudeFor();
+
+ // 使用 ModExtension 参数或默认值
+ float shadowIntensity = shadowExtension?.shadowIntensity ?? 1f;
+ float minAlpha = shadowExtension?.minShadowAlpha ?? 0.3f;
+ float maxAlpha = shadowExtension?.maxShadowAlpha ?? 1f;
+ float minScale = shadowExtension?.minShadowScale ?? 0.5f;
+ float maxScale = shadowExtension?.maxShadowScale ?? 1.5f;
+
+ float shadowAlpha = Mathf.Lerp(minAlpha, maxAlpha, currentProgress) * shadowIntensity;
+ float shadowScale = Mathf.Lerp(minScale, maxScale, currentProgress);
+
+ // 阴影也应用淡入效果
+ shadowAlpha *= FadeInAlpha;
+
+ // 完全移除阴影旋转 - 始终使用默认朝向(模仿原版 Projectile)
+ Vector3 s = new Vector3(shadowScale, 1f, shadowScale);
+ Vector3 vector = new Vector3(0f, -0.01f, 0f);
+ Matrix4x4 matrix = Matrix4x4.TRS(shadowPos + vector, Quaternion.identity, s);
+
+ Graphics.DrawMesh(MeshPool.plane10, matrix, shadowMaterial, 0);
}
- innerContainer.Clear();
- }
-
- private void CreateFlightEffects()
- {
- // 在飞行轨迹上生成粒子效果
- Vector3 effectPos = DrawPos;
- effectPos.y = AltitudeLayer.MoteOverhead.AltitudeFor();
- FleckMaker.ThrowSmoke(effectPos, base.Map, 1f);
-
- // 可选:根据速度生成更多效果
- if (flightSpeed > 2f)
+
+ // IThingHolder 接口实现
+ public ThingOwner GetDirectlyHeldThings()
{
- FleckMaker.ThrowAirPuffUp(effectPos, base.Map);
+ return innerContainer;
}
- }
-
- protected override void DrawAt(Vector3 drawLoc, bool flip = false)
- {
- // 获取绘制位置和旋转
- Vector3 finalDrawPos = drawLoc;
- float extraRotation = flightAngle;
-
- // 绘制主体
- Thing thingForGraphic = GetThingForGraphic();
- Graphic.Draw(finalDrawPos, thingForGraphic.Rotation, thingForGraphic, extraRotation);
-
- // 绘制阴影
- DrawFlightShadow();
- }
-
- protected virtual void DrawFlightShadow()
- {
- Material shadowMaterial = ShadowMaterial;
- if (shadowMaterial == null)
- return;
+
+ public void GetChildHolders(List outChildren)
+ {
+ ThingOwnerUtility.AppendThingHoldersFromThings(outChildren, GetDirectlyHeldThings());
+ }
+
+ private Thing GetThingForGraphic()
+ {
+ if (def.graphicData != null || !innerContainer.Any)
+ {
+ return this;
+ }
+ return innerContainer[0];
+ }
+
+ // 工具方法:创建飞越物体
+ public static FlyOver MakeFlyOver(ThingDef flyOverDef, IntVec3 start, IntVec3 end, Map map,
+ float speed = 1f, float height = 10f, ThingOwner contents = null, float fadeInDuration = 1.5f)
+ {
+ FlyOver flyOver = (FlyOver)ThingMaker.MakeThing(flyOverDef);
+ flyOver.startPosition = start;
+ flyOver.endPosition = end;
+ flyOver.flightSpeed = speed;
+ flyOver.altitude = height;
+ flyOver.fadeInDuration = fadeInDuration;
+
+ if (contents != null)
+ {
+ flyOver.innerContainer.TryAddRangeOrTransfer(contents);
+ }
+
+ GenSpawn.Spawn(flyOver, start, map);
- Vector3 shadowPos = DrawPos;
- shadowPos.y = AltitudeLayer.Shadows.AltitudeFor();
-
- // 根据进度调整阴影大小和透明度
- float shadowAlpha = Mathf.Lerp(0.3f, 1f, currentProgress);
- float shadowScale = Mathf.Lerp(0.5f, 1.5f, currentProgress);
-
- shadowPropertyBlock.SetColor(ShaderPropertyIDs.Color,
- new Color(1f, 1f, 1f, shadowAlpha));
+ Log.Message($"FlyOver created: {flyOver} from {start} to {end} at altitude {height} with {fadeInDuration}s fade-in");
+ return flyOver;
+ }
+
+ // 高空版本的工具方法
+ public static FlyOver MakeHighAltitudeFlyOver(ThingDef flyOverDef, IntVec3 start, IntVec3 end, Map map,
+ float speed = 2f, float height = 50f, ThingOwner contents = null, float fadeInDuration = 1.5f)
+ {
+ FlyOver flyOver = MakeFlyOver(flyOverDef, start, end, map, speed, height, contents, fadeInDuration);
- Vector3 scale = new Vector3(shadowScale, 1f, shadowScale);
- Matrix4x4 matrix = Matrix4x4.TRS(shadowPos, Quaternion.identity, scale);
-
- Graphics.DrawMesh(MeshPool.plane10, matrix, shadowMaterial,
- 0, null, 0, shadowPropertyBlock);
- }
-
- // IThingHolder 接口实现
- public ThingOwner GetDirectlyHeldThings()
- {
- return innerContainer;
- }
-
- public void GetChildHolders(List outChildren)
- {
- ThingOwnerUtility.AppendThingHoldersFromThings(outChildren, GetDirectlyHeldThings());
- }
-
- private Thing GetThingForGraphic()
- {
- if (def.graphicData != null || !innerContainer.Any)
- {
- return this;
+ // 高空特有的设置
+ flyOver.playFlyOverSound = false;
+ flyOver.createShadow = true;
+ flyOver.spawnContentsOnImpact = false;
+
+ Log.Message($"HighAltitude FlyOver created: {flyOver} from {start} to {end} at altitude {height}");
+ return flyOver;
}
- return innerContainer[0];
- }
-
- // 工具方法:创建飞越物体
- public static FlyOver MakeFlyOver(ThingDef flyOverDef, IntVec3 start, IntVec3 end, Map map,
- float speed = 1f, float height = 10f, ThingOwner contents = null)
- {
- FlyOver flyOver = (FlyOver)ThingMaker.MakeThing(flyOverDef);
- flyOver.startPosition = start;
- flyOver.endPosition = end;
- flyOver.flightSpeed = speed;
- flyOver.altitude = height;
-
- if (contents != null)
+
+ // 快速淡入版本的工具方法
+ public static FlyOver MakeQuickFlyOver(ThingDef flyOverDef, IntVec3 start, IntVec3 end, Map map,
+ float speed = 1f, float height = 10f, ThingOwner contents = null)
{
- flyOver.innerContainer.TryAddRangeOrTransfer(contents);
+ return MakeFlyOver(flyOverDef, start, end, map, speed, height, contents, 0.5f);
+ }
+
+ // 慢速淡入版本的工具方法(适合非常大的模型)
+ public static FlyOver MakeSlowFlyOver(ThingDef flyOverDef, IntVec3 start, IntVec3 end, Map map,
+ float speed = 1f, float height = 10f, ThingOwner contents = null)
+ {
+ return MakeFlyOver(flyOverDef, start, end, map, speed, height, contents, 3f);
}
-
- GenSpawn.Spawn(flyOver, start, map);
- return flyOver;
}
- //// 创建简单的飞越物体
- //public void CreateSimpleFlyOver()
- //{
- // // 获取地图边界
- // Map map = Find.CurrentMap;
- // IntVec3 start = new IntVec3(0, 0, map.Size.z / 2); // 左边中间
- // IntVec3 end = new IntVec3(map.Size.x, 0, map.Size.z / 2); // 右边中间
-
- // // 创建飞越物体
- // FlyOver.MakeFlyOver(ThingDefOf.FlyOverShip, start, end, map,
- // speed: 1.5f, height: 15f);
- //}
- //// 创建带内容的飞越物体
- //public void CreateFlyOverWithContents()
- //{
- // Map map = Find.CurrentMap;
- // IntVec3 start = new IntVec3(map.Size.x / 2, 0, 0); // 上边中间
- // IntVec3 end = new IntVec3(map.Size.x / 2, 0, map.Size.z); // 下边中间
-
- // // 创建内容容器
- // ThingOwner contents = new ThingOwner();
- // contents.TryAdd(ThingMaker.MakeThing(ThingDefOf.Steel, 20));
-
- // FlyOver flyOver = FlyOver.MakeFlyOver(ThingDefOf.CargoFlyOver, start, end, map,
- // speed: 1f, height: 12f, contents: contents);
-
- // flyOver.spawnContentsOnImpact = true;
- //}
+ // 更新的 ModExtension,添加淡入配置
+ public class FlyOverShadowExtension : DefModExtension
+ {
+ public string customShadowPath; // 自定义阴影材质路径
+ public float shadowIntensity = 0.6f; // 阴影强度
+ public bool useCustomShadow = false; // 是否使用自定义阴影
+ public float minShadowAlpha = 0.05f; // 最小阴影透明度
+ public float maxShadowAlpha = 0.2f; // 最大阴影透明度
+ public float minShadowScale = 0.5f; // 最小阴影大小
+ public float maxShadowScale = 1.0f; // 最大阴影大小
+ public float defaultFadeInDuration = 1.5f; // 默认淡入持续时间
+ public float ActuallyHeight = 150f; // 实际高度
+ }
}
diff --git a/Source/ArachnaeSwarm/Thing_Comps/ARA_FlyOverDropPod/CompProperties_FlyOverDropPod.CS b/Source/ArachnaeSwarm/Thing_Comps/ARA_FlyOverDropPod/CompProperties_FlyOverDropPod.CS
new file mode 100644
index 0000000..98ff8f0
--- /dev/null
+++ b/Source/ArachnaeSwarm/Thing_Comps/ARA_FlyOverDropPod/CompProperties_FlyOverDropPod.CS
@@ -0,0 +1,704 @@
+using System.Collections.Generic;
+using RimWorld;
+using Verse;
+using Verse.AI;
+using UnityEngine;
+using Verse.AI.Group;
+
+namespace ArachnaeSwarm
+{
+ // 扩展的空投仓配置属性 - 移除 dropPodDef
+ public class CompProperties_FlyOverDropPods : CompProperties
+ {
+ public IntVec3 dropOffset = IntVec3.Zero; // 投掷位置偏移
+
+ // 投掷时机配置
+ public float dropProgress = 0.5f; // 投掷进度 (0-1)
+ public bool useCyclicDrops = false; // 是否使用循环投掷
+ public float cyclicDropIntervalHours = 24f; // 循环投掷间隔(小时)
+ public bool waitForExternalSignal = false; // 是否等待外部信号
+ public string externalSignalTag; // 外部信号标签
+
+ public int dropCount = 1; // 投掷数量
+ public float scatterRadius = 3f; // 散布半径
+ public bool useTradeDropSpot; // 是否使用贸易空投点
+ public bool allowFogged; // 是否允许雾区
+ public bool dropAllInSamePod; // 是否在同一空投仓中
+ public bool leaveSlag; // 是否留下残骸
+
+ // 内容物配置
+ public List thingDefs = new List();
+ public bool dropAllContents = false; // 是否投掷所有内容物
+
+ // Pawn 生成配置
+ public List pawnKinds = new List();
+ public FactionDef pawnFactionDef; // Pawn 派系定义
+ public bool generatePawnsOnDrop = true; // 是否在投掷时生成 Pawn
+
+ // 乘客配置
+ public bool joinPlayer;
+ public bool makePrisoners;
+
+ // LordJob 配置 - 简化版本
+ public bool assignAssaultLordJob = false; // 是否分配袭击殖民地的 LordJob
+ public bool canKidnap = true; // 是否可以绑架
+ public bool canTimeoutOrFlee = true; // 是否可以超时或逃跑
+ public bool useSappers = false; // 是否使用工兵
+ public bool useAvoidGridSmart = false; // 是否使用智能回避网格
+ public bool canSteal = true; // 是否可以偷窃
+ public bool useBreachers = false; // 是否使用破墙者
+ public bool canPickUpOpportunisticWeapons = false; // 是否可以捡起机会性武器
+
+ // 信件通知
+ public bool sendStandardLetter = true;
+ public string customLetterText;
+ public string customLetterLabel;
+ public LetterDef customLetterDef;
+
+ // 派系
+ public Faction faction;
+
+ public CompProperties_FlyOverDropPods()
+ {
+ this.compClass = typeof(CompFlyOverDropPods);
+ }
+ }
+
+ // PawnKind 数量类
+ public class PawnKindDefCountClass : IExposable
+ {
+ public PawnKindDef pawnKindDef;
+ public int count;
+
+ public PawnKindDefCountClass() { }
+
+ public PawnKindDefCountClass(PawnKindDef pawnKindDef, int count)
+ {
+ this.pawnKindDef = pawnKindDef;
+ this.count = count;
+ }
+
+ public void ExposeData()
+ {
+ Scribe_Defs.Look(ref pawnKindDef, "pawnKindDef");
+ Scribe_Values.Look(ref count, "count", 1);
+ }
+
+ public override string ToString()
+ {
+ return $"{count}x {pawnKindDef?.label ?? "null"}";
+ }
+ }
+
+ // 空投仓投掷 Comp - 使用原版空投仓
+ public class CompFlyOverDropPods : ThingComp
+ {
+ public CompProperties_FlyOverDropPods Props => (CompProperties_FlyOverDropPods)props;
+
+ // 状态变量
+ private bool hasDropped = false;
+ private int ticksUntilNextDrop = 0;
+ private bool waitingForSignal = false;
+ private List items = new List();
+ private List pawns = new List();
+
+ public override void Initialize(CompProperties props)
+ {
+ base.Initialize(props);
+
+ // 预生成内容物
+ if (Props.thingDefs != null)
+ {
+ foreach (ThingDefCountClass thingDefCount in Props.thingDefs)
+ {
+ Thing thing = ThingMaker.MakeThing(thingDefCount.thingDef);
+ thing.stackCount = thingDefCount.count;
+ items.Add(thing);
+ }
+ }
+
+ // 如果不在投掷时生成 Pawn,则预生成 Pawn
+ if (!Props.generatePawnsOnDrop && Props.pawnKinds != null)
+ {
+ GeneratePawnsFromKinds();
+ }
+
+ // 初始化循环投掷计时器
+ if (Props.useCyclicDrops)
+ {
+ ticksUntilNextDrop = (int)(Props.cyclicDropIntervalHours * 2500f); // 1小时 = 2500 ticks
+ Log.Message($"Cyclic drops initialized: {Props.cyclicDropIntervalHours} hours interval");
+ }
+
+ // 初始化信号等待状态
+ if (Props.waitForExternalSignal)
+ {
+ waitingForSignal = true;
+ Log.Message($"Waiting for external signal: {Props.externalSignalTag}");
+ }
+ }
+
+ // 从 PawnKind 定义生成 Pawn
+ private void GeneratePawnsFromKinds()
+ {
+ if (Props.pawnKinds == null) return;
+
+ foreach (PawnKindDefCountClass pawnKindCount in Props.pawnKinds)
+ {
+ for (int i = 0; i < pawnKindCount.count; i++)
+ {
+ Pawn pawn = GeneratePawn(pawnKindCount.pawnKindDef);
+ if (pawn != null)
+ {
+ pawns.Add(pawn);
+ Log.Message($"Generated pawn: {pawn.Label} ({pawnKindCount.pawnKindDef.defName})");
+ }
+ }
+ }
+ }
+
+ // 生成单个 Pawn
+ private Pawn GeneratePawn(PawnKindDef pawnKindDef)
+ {
+ if (pawnKindDef == null)
+ {
+ Log.Error("Attempted to generate pawn with null PawnKindDef");
+ return null;
+ }
+
+ try
+ {
+ // 确定派系
+ Faction faction = DetermineFactionForPawn();
+
+ // 生成 Pawn
+ PawnGenerationRequest request = new PawnGenerationRequest(
+ pawnKindDef,
+ faction,
+ PawnGenerationContext.NonPlayer,
+ -1,
+ forceGenerateNewPawn: true,
+ allowDead: false,
+ allowDowned: false,
+ canGeneratePawnRelations: true,
+ mustBeCapableOfViolence: false,
+ colonistRelationChanceFactor: 1f,
+ forceAddFreeWarmLayerIfNeeded: false,
+ allowGay: true,
+ allowFood: true,
+ allowAddictions: true,
+ inhabitant: false,
+ certainlyBeenInCryptosleep: false,
+ forceRedressWorldPawnIfFormerColonist: false,
+ worldPawnFactionDoesntMatter: false,
+ biocodeWeaponChance: 0f,
+ biocodeApparelChance: 0f,
+ extraPawnForExtraRelationChance: null,
+ relationWithExtraPawnChanceFactor: 1f,
+ validatorPreGear: null,
+ validatorPostGear: null,
+ forcedTraits: null,
+ prohibitedTraits: null,
+ minChanceToRedressWorldPawn: 0f,
+ fixedBiologicalAge: null,
+ fixedChronologicalAge: null,
+ fixedGender: null
+ );
+
+ Pawn pawn = PawnGenerator.GeneratePawn(request);
+
+ // 设置 Pawn 的基本状态
+ if (pawn.mindState != null)
+ {
+ pawn.mindState.SetupLastHumanMeatTick();
+ }
+
+ Log.Message($"Successfully generated pawn: {pawn.LabelCap} from {pawnKindDef.defName}");
+ return pawn;
+ }
+ catch (System.Exception ex)
+ {
+ Log.Error($"Failed to generate pawn from {pawnKindDef.defName}: {ex}");
+ return null;
+ }
+ }
+
+ // 确定 Pawn 的派系
+ private Faction DetermineFactionForPawn()
+ {
+ // 优先使用指定的派系定义
+ if (Props.pawnFactionDef != null)
+ {
+ Faction faction = Find.FactionManager.FirstFactionOfDef(Props.pawnFactionDef);
+ if (faction != null) return faction;
+ }
+
+ // 使用 Comp 的派系
+ if (Props.faction != null) return Props.faction;
+
+ // 使用默认中立派系
+ return Faction.OfAncients;
+ }
+
+ public override void CompTick()
+ {
+ base.CompTick();
+
+ if (parent is FlyOver flyOver)
+ {
+ // 检查不同的投掷模式
+ if (!hasDropped && !waitingForSignal)
+ {
+ if (Props.useCyclicDrops)
+ {
+ CheckCyclicDrop(flyOver);
+ }
+ else
+ {
+ CheckProgressDrop(flyOver);
+ }
+ }
+ }
+ }
+
+ // 检查进度投掷
+ private void CheckProgressDrop(FlyOver flyOver)
+ {
+ if (flyOver.currentProgress >= Props.dropProgress && !hasDropped)
+ {
+ DropPods(flyOver);
+ hasDropped = true;
+ }
+ }
+
+ // 检查循环投掷
+ private void CheckCyclicDrop(FlyOver flyOver)
+ {
+ ticksUntilNextDrop--;
+
+ if (ticksUntilNextDrop <= 0)
+ {
+ DropPods(flyOver);
+
+ // 重置计时器
+ ticksUntilNextDrop = (int)(Props.cyclicDropIntervalHours * 2500f);
+ Log.Message($"Cyclic drop completed, next drop in {Props.cyclicDropIntervalHours} hours");
+ }
+ }
+
+ // 外部信号触发投掷
+ public void TriggerDropFromSignal()
+ {
+ if (parent is FlyOver flyOver && waitingForSignal)
+ {
+ Log.Message($"External signal received, triggering drop pods");
+ DropPods(flyOver);
+ waitingForSignal = false;
+ }
+ }
+
+ // 接收信号的方法
+ public override void ReceiveCompSignal(string signal)
+ {
+ base.ReceiveCompSignal(signal);
+
+ if (Props.waitForExternalSignal && signal == Props.externalSignalTag)
+ {
+ TriggerDropFromSignal();
+ }
+ }
+
+ private void DropPods(FlyOver flyOver)
+ {
+ Map map = flyOver.Map;
+ if (map == null)
+ {
+ Log.Error("FlyOver DropPods: Map is null");
+ return;
+ }
+
+ IntVec3 dropCenter = GetDropCenter(flyOver);
+
+ Log.Message($"DropPods triggered at progress {flyOver.currentProgress}, center: {dropCenter}");
+
+ // 如果在投掷时生成 Pawn,现在生成
+ if (Props.generatePawnsOnDrop && Props.pawnKinds != null)
+ {
+ GeneratePawnsFromKinds();
+ }
+
+ // 准备要投掷的物品列表
+ List thingsToDrop = new List();
+
+ // 添加预生成的内容物
+ thingsToDrop.AddRange(items);
+
+ // 添加生成的 Pawn
+ thingsToDrop.AddRange(pawns);
+
+ // 如果设置了投掷所有内容物,添加 FlyOver 的内容物
+ if (Props.dropAllContents && flyOver.innerContainer != null)
+ {
+ thingsToDrop.AddRange(flyOver.innerContainer);
+ }
+
+ if (!thingsToDrop.Any())
+ {
+ Log.Warning("No items to drop from FlyOver drop pods");
+ return;
+ }
+
+ // 移除已销毁的物品
+ thingsToDrop.RemoveAll(x => x.Destroyed);
+
+ // 设置乘客派系和行为
+ SetupPawnsForDrop();
+
+ // 执行投掷
+ if (Props.dropCount > 1)
+ {
+ DropMultiplePods(thingsToDrop, dropCenter, map);
+ }
+ else
+ {
+ DropSinglePod(thingsToDrop, dropCenter, map);
+ }
+
+ // 发送信件通知
+ if (Props.sendStandardLetter)
+ {
+ SendDropLetter(thingsToDrop, dropCenter, map);
+ }
+
+ Log.Message($"Drop pods completed: {thingsToDrop.Count} items dropped, including {pawns.Count} pawns");
+ }
+
+ // 设置 Pawn 的派系和行为
+ private void SetupPawnsForDrop()
+ {
+ foreach (Pawn pawn in pawns)
+ {
+ if (Props.joinPlayer)
+ {
+ if (pawn.Faction != Faction.OfPlayer)
+ {
+ pawn.SetFaction(Faction.OfPlayer);
+ }
+ }
+ else if (Props.makePrisoners)
+ {
+ if (pawn.RaceProps.Humanlike && !pawn.IsPrisonerOfColony)
+ {
+ pawn.guest.SetGuestStatus(Faction.OfPlayer, GuestStatus.Prisoner);
+ HealthUtility.TryAnesthetize(pawn);
+ }
+ }
+
+ // 设置初始状态
+ pawn.needs.SetInitialLevels();
+ pawn.mindState?.SetupLastHumanMeatTick();
+ }
+
+ // 分配 LordJob(如果启用)
+ if (Props.assignAssaultLordJob && pawns.Count > 0)
+ {
+ AssignAssaultLordJob();
+ }
+ }
+
+ // 分配袭击殖民地的 LordJob
+ private void AssignAssaultLordJob()
+ {
+ // 按派系分组 Pawn
+ var pawnsByFaction = new Dictionary>();
+
+ foreach (Pawn pawn in pawns)
+ {
+ // 跳过玩家派系和囚犯
+ if (pawn.Faction == Faction.OfPlayer || pawn.IsPrisonerOfColony)
+ continue;
+
+ // 跳过无效派系
+ if (pawn.Faction == null)
+ continue;
+
+ if (!pawnsByFaction.ContainsKey(pawn.Faction))
+ {
+ pawnsByFaction[pawn.Faction] = new List();
+ }
+ pawnsByFaction[pawn.Faction].Add(pawn);
+ }
+
+ // 为每个派系创建 LordJob
+ foreach (var factionGroup in pawnsByFaction)
+ {
+ Faction faction = factionGroup.Key;
+ List factionPawns = factionGroup.Value;
+
+ if (factionPawns.Count == 0)
+ continue;
+
+ // 创建 LordJob_AssaultColony
+ LordJob_AssaultColony lordJob = new LordJob_AssaultColony(
+ faction,
+ Props.canKidnap,
+ Props.canTimeoutOrFlee,
+ Props.useSappers,
+ Props.useAvoidGridSmart,
+ Props.canSteal,
+ Props.useBreachers,
+ Props.canPickUpOpportunisticWeapons
+ );
+
+ // 创建 Lord
+ Lord lord = LordMaker.MakeNewLord(faction, lordJob, Find.CurrentMap, factionPawns);
+
+ Log.Message($"Assigned assault lord job to {factionPawns.Count} pawns of faction {faction.Name}");
+ }
+ }
+
+ private void DropSinglePod(List thingsToDrop, IntVec3 dropCenter, Map map)
+ {
+ // 使用原版空投仓系统
+ if (Props.dropAllInSamePod)
+ {
+ // 所有物品在一个空投仓中
+ DropPodUtility.DropThingGroupsNear(
+ dropCenter,
+ map,
+ new List> { thingsToDrop },
+ openDelay: 110,
+ instaDrop: false,
+ leaveSlag: Props.leaveSlag,
+ canRoofPunch: !Props.useTradeDropSpot,
+ forbid: false,
+ allowFogged: Props.allowFogged,
+ canTransfer: false,
+ faction: Props.faction
+ );
+ }
+ else
+ {
+ // 每个物品单独空投仓
+ DropPodUtility.DropThingsNear(
+ dropCenter,
+ map,
+ thingsToDrop,
+ openDelay: 110,
+ canInstaDropDuringInit: false,
+ leaveSlag: Props.leaveSlag,
+ canRoofPunch: !Props.useTradeDropSpot,
+ forbid: false,
+ allowFogged: Props.allowFogged,
+ faction: Props.faction
+ );
+ }
+ }
+
+ private void DropMultiplePods(List thingsToDrop, IntVec3 dropCenter, Map map)
+ {
+ List> podGroups = new List>();
+
+ if (Props.dropAllInSamePod)
+ {
+ // 所有物品在一个空投仓中,但生成多个相同的空投仓
+ for (int i = 0; i < Props.dropCount; i++)
+ {
+ // 为每个空投仓创建新的物品实例
+ List podItems = new List();
+ foreach (Thing original in thingsToDrop)
+ {
+ if (original is Pawn originalPawn)
+ {
+ // 对于 Pawn,我们需要重新生成
+ Pawn newPawn = GeneratePawn(originalPawn.kindDef);
+ if (newPawn != null)
+ {
+ podItems.Add(newPawn);
+ }
+ }
+ else
+ {
+ Thing copy = ThingMaker.MakeThing(original.def, original.Stuff);
+ copy.stackCount = original.stackCount;
+ podItems.Add(copy);
+ }
+ }
+ podGroups.Add(podItems);
+ }
+ }
+ else
+ {
+ // 将物品分配到多个空投仓中
+ List currentPod = new List();
+ foreach (Thing thing in thingsToDrop)
+ {
+ currentPod.Add(thing);
+ if (currentPod.Count >= thingsToDrop.Count / Props.dropCount)
+ {
+ podGroups.Add(currentPod);
+ currentPod = new List();
+ }
+ }
+ if (currentPod.Any())
+ {
+ podGroups.Add(currentPod);
+ }
+ }
+
+ // 投掷多个空投仓组
+ foreach (List podGroup in podGroups)
+ {
+ IntVec3 scatterPos = GetScatteredDropPos(dropCenter, map);
+ DropPodUtility.DropThingGroupsNear(
+ scatterPos,
+ map,
+ new List> { podGroup },
+ openDelay: 110,
+ instaDrop: false,
+ leaveSlag: Props.leaveSlag,
+ canRoofPunch: !Props.useTradeDropSpot,
+ forbid: false,
+ allowFogged: Props.allowFogged,
+ canTransfer: false,
+ faction: Props.faction
+ );
+ }
+ }
+
+ private IntVec3 GetDropCenter(FlyOver flyOver)
+ {
+ // 计算投掷中心位置(基于当前飞行位置 + 偏移)
+ Vector3 currentPos = Vector3.Lerp(
+ flyOver.startPosition.ToVector3(),
+ flyOver.endPosition.ToVector3(),
+ flyOver.currentProgress
+ );
+
+ IntVec3 dropCenter = currentPos.ToIntVec3() + Props.dropOffset;
+
+ // 如果使用贸易空投点,找到贸易空投点
+ if (Props.useTradeDropSpot)
+ {
+ dropCenter = DropCellFinder.TradeDropSpot(flyOver.Map);
+ }
+
+ return dropCenter;
+ }
+
+ private IntVec3 GetScatteredDropPos(IntVec3 center, Map map)
+ {
+ if (Props.scatterRadius <= 0)
+ return center;
+
+ // 在散布半径内找到有效位置
+ for (int i = 0; i < 10; i++)
+ {
+ IntVec3 scatterPos = center + new IntVec3(
+ Rand.RangeInclusive(-(int)Props.scatterRadius, (int)Props.scatterRadius),
+ 0,
+ Rand.RangeInclusive(-(int)Props.scatterRadius, (int)Props.scatterRadius)
+ );
+
+ if (scatterPos.InBounds(map) &&
+ scatterPos.Standable(map) &&
+ !scatterPos.Roofed(map) &&
+ (Props.allowFogged || !scatterPos.Fogged(map)))
+ {
+ return scatterPos;
+ }
+ }
+
+ // 如果找不到有效位置,返回随机空投点
+ return DropCellFinder.RandomDropSpot(map);
+ }
+
+ private void SendDropLetter(List thingsToDrop, IntVec3 dropCenter, Map map)
+ {
+ TaggedString text = null;
+ TaggedString label = null;
+
+ // 生成信件内容(模仿原版逻辑)
+ if (Props.joinPlayer && pawns.Count == 1 && pawns[0].RaceProps.Humanlike)
+ {
+ text = "LetterRefugeeJoins".Translate(pawns[0].Named("PAWN"));
+ label = "LetterLabelRefugeeJoins".Translate(pawns[0].Named("PAWN"));
+ PawnRelationUtility.TryAppendRelationsWithColonistsInfo(ref text, ref label, pawns[0]);
+ }
+ else
+ {
+ text = "LetterQuestDropPodsArrived".Translate(GenLabel.ThingsLabel(thingsToDrop));
+ label = "LetterLabelQuestDropPodsArrived".Translate();
+ PawnRelationUtility.Notify_PawnsSeenByPlayer_Letter(
+ pawns,
+ ref label,
+ ref text,
+ "LetterRelatedPawnsNeutralGroup".Translate(Faction.OfPlayer.def.pawnsPlural),
+ informEvenIfSeenBefore: true
+ );
+ }
+
+ // 应用自定义文本
+ label = (Props.customLetterLabel.NullOrEmpty() ? label : Props.customLetterLabel.Formatted(label.Named("BASELABEL")));
+ text = (Props.customLetterText.NullOrEmpty() ? text : Props.customLetterText.Formatted(text.Named("BASETEXT")));
+
+ // 发送信件
+ Find.LetterStack.ReceiveLetter(
+ label,
+ text,
+ Props.customLetterDef ?? LetterDefOf.PositiveEvent,
+ new TargetInfo(dropCenter, map)
+ );
+ }
+
+ public override void PostExposeData()
+ {
+ base.PostExposeData();
+ Scribe_Values.Look(ref hasDropped, "hasDropped", false);
+ Scribe_Values.Look(ref ticksUntilNextDrop, "ticksUntilNextDrop", 0);
+ Scribe_Values.Look(ref waitingForSignal, "waitingForSignal", false);
+ Scribe_Collections.Look(ref items, "items", LookMode.Deep);
+ Scribe_Collections.Look(ref pawns, "pawns", LookMode.Reference);
+ }
+
+ public override IEnumerable CompGetGizmosExtra()
+ {
+ if (DebugSettings.ShowDevGizmos && parent is FlyOver)
+ {
+ yield return new Command_Action
+ {
+ defaultLabel = "Dev: Trigger Drop Pods",
+ action = () => DropPods(parent as FlyOver)
+ };
+
+ yield return new Command_Action
+ {
+ defaultLabel = "Dev: Generate Pawns Now",
+ action = () =>
+ {
+ GeneratePawnsFromKinds();
+ Messages.Message($"Generated {pawns.Count} pawns", MessageTypeDefOf.NeutralEvent);
+ }
+ };
+
+ if (Props.waitForExternalSignal)
+ {
+ yield return new Command_Action
+ {
+ defaultLabel = "Dev: Send External Signal",
+ action = () => TriggerDropFromSignal()
+ };
+ }
+ }
+ }
+
+ // 公共方法:供其他 Comps 调用以触发投掷
+ public void TriggerDropPods()
+ {
+ if (parent is FlyOver flyOver)
+ {
+ DropPods(flyOver);
+ }
+ }
+ }
+}
diff --git a/非公开资源/Content/Textures/FlyOverThing/ARA_HiveShip_Shadow.sai2 b/非公开资源/Content/Textures/FlyOverThing/ARA_HiveShip_Shadow.sai2
new file mode 100644
index 0000000..d162184
Binary files /dev/null and b/非公开资源/Content/Textures/FlyOverThing/ARA_HiveShip_Shadow.sai2 differ