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