飞越物品系统

This commit is contained in:
Tourswen
2025-10-28 01:28:20 +08:00
parent 997f1ecaf8
commit 25531d663d
16 changed files with 1862 additions and 526 deletions

Binary file not shown.

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8" ?>
<?xml version="1.0" encoding="utf-8"?>
<Defs>
<!-- 变形技能的定义 -->
<AbilityDef>
@@ -39,4 +39,37 @@
</AbilityDef>
<!-- 能力定义 -->
<AbilityDef>
<defName>ARA_SpawnFlyOverTest</defName>
<label>召唤飞越物体</label>
<description>测试召唤不同类型的飞越物体</description>
<iconPath>ArachnaeSwarm/UI/Abilities/ARA_Ability_Morph</iconPath>
<cooldownTicksRange>1</cooldownTicksRange>
<hotKey>Misc12</hotKey>
<casterMustBeCapableOfViolence>false</casterMustBeCapableOfViolence>
<verbProperties>
<verbClass>Verb_CastAbility</verbClass>
<drawAimPie>false</drawAimPie>
<requireLineOfSight>false</requireLineOfSight>
<nonInterruptingSelfCast>true</nonInterruptingSelfCast>
<warmupTime>1</warmupTime>
<range>19.9</range>
<targetable>true</targetable>
<targetParams>
<canTargetSelf>True</canTargetSelf>
</targetParams>
</verbProperties>
<comps>
<li Class="ArachnaeSwarm.CompProperties_AbilitySpawnFlyOver">
<flyOverDef>ARA_FlyOverShip</flyOverDef>
<flyOverType>Standard</flyOverType>
<flightSpeed>0.01</flightSpeed>
<altitude>20</altitude>
<startPosition>MapEdge</startPosition>
<endPosition>OppositeMapEdge</endPosition>
<playFlyOverSound>true</playFlyOverSound>
</li>
</comps>
</AbilityDef>
</Defs>

View File

@@ -0,0 +1,97 @@
<?xml version="1.0" encoding="utf-8"?>
<Defs>
<ThingDef Parent="EtherealThingBase">
<defName>ARA_FlyOverShip</defName>
<label>飞越飞船</label>
<thingClass>ArachnaeSwarm.FlyOver</thingClass>
<tickerType>Normal</tickerType>
<drawerType>RealtimeOnly</drawerType>
<graphicData>
<!-- <texPath>ArachnaeSwarm/Weapon/ARA_Weapon_Empty</texPath> -->
<texPath>ArachnaeSwarm/FlyOverThing/ARA_HiveShip_Shadow</texPath>
<graphicClass>Graphic_Single</graphicClass>
<shaderType>TransparentPostLight</shaderType>
<drawSize>(75,170)</drawSize>
<color>(195,195,195,45)</color>
</graphicData>
<skyfaller>
<shadowSize>(0, 0)</shadowSize>
<motesPerCell>0</motesPerCell>
<floatingSound>FlyOver/Flying</floatingSound>
<impactSound>FlyOver/Landing</impactSound>
</skyfaller>
<modExtensions>
<li Class="ArachnaeSwarm.FlyOverShadowExtension">
<customShadowPath>ArachnaeSwarm/FlyOverThing/ARA_HiveShip_Shadow</customShadowPath>
<shadowIntensity>0.8</shadowIntensity>
<useCustomShadow>false</useCustomShadow>
<minShadowAlpha>0</minShadowAlpha>
<maxShadowAlpha>0</maxShadowAlpha>
<minShadowScale>15</minShadowScale>
<maxShadowScale>15</maxShadowScale>
</li>
</modExtensions>
<useHitPoints>false</useHitPoints>
<selectable>false</selectable>
<alwaysHaulable>false</alwaysHaulable>
<altitudeLayer>Projectile</altitudeLayer>
<comps>
<li Class="ArachnaeSwarm.CompProperties_FlyOverDropPods">
<!-- <dropProgress>0.5</dropProgress> -->
<useCyclicDrops>true</useCyclicDrops>
<cyclicDropIntervalHours>1</cyclicDropIntervalHours>
<dropCount>10</dropCount>
<scatterRadius>15</scatterRadius>
<useTradeDropSpot>false</useTradeDropSpot>
<allowFogged>false</allowFogged>
<dropAllInSamePod>false</dropAllInSamePod>
<leaveSlag>false</leaveSlag>
<sendStandardLetter>true</sendStandardLetter>
<customLetterLabel>虫群空调</customLetterLabel>
<customLetterText>虫巢舰正在空投一支虫族部队!</customLetterText>
<!-- 投掷物品 -->
<thingDefs>
<!-- <li>
<thingDef>MealPackaged</thingDef>
<count>20</count>
</li>
<li>
<thingDef>MedicineIndustrial</thingDef>
<count>10</count>
</li> -->
</thingDefs>
<!-- 特定 PawnKind 生成配置 -->
<pawnKinds>
<li>
<pawnKindDef>ArachnaeNode_Race_Fighter_Enermy</pawnKindDef>
<count>5</count>
</li>
<li>
<pawnKindDef>ArachnaeNode_Race_ShieldHead_Enermy</pawnKindDef>
<count>5</count>
</li>
</pawnKinds>
<!-- Pawn 派系配置 -->
<pawnFactionDef>ARA_Hostile_Hive</pawnFactionDef>
<generatePawnsOnDrop>true</generatePawnsOnDrop>
<!-- LordJob 配置 -->
<assignAssaultLordJob>true</assignAssaultLordJob>
<canKidnap>true</canKidnap>
<canTimeoutOrFlee>false</canTimeoutOrFlee>
<useSappers>false</useSappers>
<useAvoidGridSmart>false</useAvoidGridSmart>
<canSteal>false</canSteal>
<useBreachers>false</useBreachers>
<canPickUpOpportunisticWeapons>true</canPickUpOpportunisticWeapons>
<!-- 乘客行为 -->
<joinPlayer>false</joinPlayer>
<makePrisoners>false</makePrisoners>
</li>
</comps>
</ThingDef>
</Defs>

View File

@@ -71,7 +71,7 @@
</thingDefs>
</fuelFilter>
<fuelGizmoLabel>虫蜜</fuelGizmoLabel>
<showAllowAutoRefuelToggle>true</showAllowAutoRefuelToggle>
<showAllowAutoRefuelToggle>false</showAllowAutoRefuelToggle>
<targetFuelLevelConfigurable>true</targetFuelLevelConfigurable>
<canEjectFuel>true</canEjectFuel>
</li>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<Defs>
<ThingDef ParentName="ShellBase">
<ThingDef ParentName="ResourceBase">
<defName>ARA_DummyAmmo</defName>
<label>弹药</label>
<description>阿拉克涅虫族使用的弹药.</description>
@@ -697,7 +697,7 @@
</li>
</modExtensions>
</ThingDef>
<ThingDef ParentName="ShellBase">
<ThingDef ParentName="ResourceBase">
<defName>ARA_CatastropheMissile_Shell</defName>
<label>天灾酸烧导弹</label>
<description>阿拉克涅虫族使用的天灾酸烧导弹,只有天灾酸烧炮组织可以发射它。</description>
@@ -706,18 +706,10 @@
<texPath>ArachnaeSwarm/Mote/ARA_CatastropheMissile_Shell</texPath>
<graphicClass>Graphic_Single</graphicClass>
</graphicData>
<statBases>
<Mass>8</Mass>
</statBases>
<comps>
<li Class="CompProperties_Explosive">
<damageAmountBase>150</damageAmountBase>
<explosiveDamageType>ARA_AcidBurn</explosiveDamageType>
<explosiveRadius>10.9</explosiveRadius>
<postExplosionSpawnThingDef>ARA_Filth_SpentAcid</postExplosionSpawnThingDef>
<postExplosionSpawnChance>1</postExplosionSpawnChance>
<postExplosionSpawnThingCount>2</postExplosionSpawnThingCount>
<wickTicks>30~60</wickTicks>
<explosionSound>MortarBomb_Explode</explosionSound>
<explosionEffect>ARA_Shell_AcidSpitImpact</explosionEffect>
</li>
</comps>
</ThingDef>
<ThingDef ParentName="BaseBullet">

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

View File

@@ -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": ""
}
]
}

View File

@@ -154,7 +154,10 @@
<Compile Include="Storyteller\IncidentWorker_CustomRaid.cs" />
<Compile Include="Storyteller\RaidWaveDef.cs" />
<Compile Include="Storyteller\RaidWavePoolDef.cs" />
<Compile Include="Thing\CompAbilityEffect_SpawnFlyOver.cs" />
<Compile Include="Thing\CompProperties_AbilitySpawnFlyOver.cs" />
<Compile Include="Thing\ThingclassFlyOver.cs" />
<Compile Include="Thing_Comps\ARA_FlyOverDropPod\CompProperties_FlyOverDropPod.CS" />
<Compile Include="Verbs\Verb_ShootWithOffset.cs" />
<Compile Include="Abilities\ARA_ShowTemperatureRange\CompAbilityEffect_AbilityShowTemperatureRange.cs" />
<Compile Include="Abilities\ARA_ShowTemperatureRange\CompProperties_AbilityShowTemperatureRange.cs" />

View File

@@ -103,12 +103,58 @@ namespace ArachnaeSwarm
private const int AcidDamageInterval = 6000;
private Dictionary<Pawn, int> pawnTickCounters = new Dictionary<Pawn, int>();
// 安全字典操作方法
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<CompRefuelable>();
cachedRefuelingVatComp = this.TryGetComp<CompRefuelingVat>();
cachedAutoEjectorComp = this.TryGetComp<CompAutoEjector>();
// 确保字典不为null
pawnTickCounters ??= new Dictionary<Pawn, int>();
pawnsKilledByVat ??= new HashSet<Pawn>();
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<DamageDef>.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<FloatMenuOption> list = new List<FloatMenuOption>();
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<Pawn>();
pawnTickCounters ??= new Dictionary<Pawn, int>();
// 清理可能已销毁的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;
}
}
}

View File

@@ -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<ThingDef>.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);
}
}
}

View File

@@ -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<ThingDefCount> 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
}
}

View File

@@ -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;
}
}

View File

@@ -4,9 +4,11 @@ using UnityEngine;
using Verse;
using Verse.Sound;
[StaticConstructorOnStartup]
public class FlyOver : ThingWithComps, IThingHolder
namespace ArachnaeSwarm
{
[StaticConstructorOnStartup]
public class FlyOver : ThingWithComps, IThingHolder
{
// 核心字段
public ThingOwner innerContainer; // 内部物品容器
public IntVec3 startPosition; // 起始位置
@@ -14,11 +16,15 @@ public class FlyOver : ThingWithComps, IThingHolder
public float flightSpeed = 1f; // 飞行速度
public float currentProgress = 0f; // 当前进度 (0-1)
public float altitude = 10f; // 飞行高度
public float flightAngle = 0f; // 飞行角度
// 淡入效果相关
public float fadeInDuration = 1.5f; // 淡入持续时间(秒)
public float currentFadeInTime = 0f; // 当前淡入时间
public bool fadeInCompleted = false; // 淡入是否完成
// 状态标志
private bool hasStarted = false;
private bool hasCompleted = false;
public bool hasStarted = false;
public bool hasCompleted = false;
// 音效系统
private Sustainer flightSoundPlaying;
@@ -26,6 +32,7 @@ public class FlyOver : ThingWithComps, IThingHolder
// 视觉效果
private Material cachedShadowMaterial;
private static MaterialPropertyBlock shadowPropertyBlock = new MaterialPropertyBlock();
private static MaterialPropertyBlock fadePropertyBlock = new MaterialPropertyBlock();
// 配置字段
public bool spawnContentsOnImpact = false; // 是否在结束时生成内容物
@@ -74,6 +81,35 @@ public class FlyOver : ThingWithComps, IThingHolder
}
}
// 精确旋转 - 模仿原版 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<Thing>(this);
@@ -88,31 +124,37 @@ public class FlyOver : ThingWithComps, IThingHolder
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);
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)
{
// 计算飞行角度(从起点指向终点的方向)
Vector3 direction = (endPosition.ToVector3() - startPosition.ToVector3()).normalized;
flightAngle = Mathf.Atan2(direction.z, direction.x) * Mathf.Rad2Deg;
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");
}
}
}
@@ -124,8 +166,19 @@ public class FlyOver : ThingWithComps, IThingHolder
if (!hasStarted || hasCompleted)
return;
// 更新淡入效果
if (!fadeInCompleted)
{
currentFadeInTime += 1f / 60f; // 假设 60 FPS
if (currentFadeInTime >= fadeInDuration)
{
fadeInCompleted = true;
currentFadeInTime = fadeInDuration;
}
}
// 更新飞行进度
currentProgress += flightSpeed * 0.001f; // 调整速度系数
currentProgress += flightSpeed * 0.001f;
// 更新当前位置(用于碰撞检测等)
UpdatePosition();
@@ -176,6 +229,8 @@ public class FlyOver : ThingWithComps, IThingHolder
SoundInfo.InMap(new TargetInfo(endPosition, base.Map)));
}
Log.Message($"FlyOver completed at {endPosition}");
// 销毁自身
Destroy();
}
@@ -206,41 +261,102 @@ public class FlyOver : ThingWithComps, IThingHolder
}
}
// 关键修改:添加淡入效果的绘制方法
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);
// 绘制阴影
if (createShadow)
{
DrawFlightShadow();
}
// 绘制主体 - 使用带淡入效果的绘制方法
DrawFlyOverWithFade(finalDrawPos);
}
// 带淡入效果的主体绘制方法
protected virtual void DrawFlyOverWithFade(Vector3 drawPos)
{
Thing thingForGraphic = GetThingForGraphic();
Graphic graphic = thingForGraphic.Graphic;
if (graphic == null)
return;
// 获取原始材质
Material material = graphic.MatSingle;
if (material == null)
return;
// 计算淡入透明度
float alpha = FadeInAlpha;
// 如果已经完全淡入,使用原版绘制方法以获得最佳性能
if (alpha >= 0.999f)
{
graphic.Draw(drawPos, Rot4.North, thingForGraphic, ExactRotation.eulerAngles.y);
return;
}
// 创建带透明度的材质属性块
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)
{
scale = new Vector3(def.graphicData.drawSize.x, 1f, def.graphicData.drawSize.y);
}
// 使用自定义绘制实现淡入效果
Matrix4x4 matrix = Matrix4x4.TRS(drawPos, ExactRotation, scale);
Graphics.DrawMesh(MeshPool.plane10, matrix, material, 0, null, 0, fadePropertyBlock);
}
// 简化的阴影绘制方法 - 完全移除旋转(模仿原版)
protected virtual void DrawFlightShadow()
{
Material shadowMaterial = ShadowMaterial;
// 检查是否有 ModExtension
var shadowExtension = def.GetModExtension<FlyOverShadowExtension>();
Material shadowMaterial;
if (shadowExtension?.useCustomShadow == true && !shadowExtension.customShadowPath.NullOrEmpty())
{
shadowMaterial = MaterialPool.MatFrom(shadowExtension.customShadowPath, ShaderDatabase.Transparent);
}
else
{
shadowMaterial = ShadowMaterial;
}
if (shadowMaterial == null)
return;
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);
// 使用 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;
shadowPropertyBlock.SetColor(ShaderPropertyIDs.Color,
new Color(1f, 1f, 1f, shadowAlpha));
float shadowAlpha = Mathf.Lerp(minAlpha, maxAlpha, currentProgress) * shadowIntensity;
float shadowScale = Mathf.Lerp(minScale, maxScale, currentProgress);
Vector3 scale = new Vector3(shadowScale, 1f, shadowScale);
Matrix4x4 matrix = Matrix4x4.TRS(shadowPos, Quaternion.identity, scale);
// 阴影也应用淡入效果
shadowAlpha *= FadeInAlpha;
Graphics.DrawMesh(MeshPool.plane10, matrix, shadowMaterial,
0, null, 0, shadowPropertyBlock);
// 完全移除阴影旋转 - 始终使用默认朝向(模仿原版 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);
}
// IThingHolder 接口实现
@@ -265,13 +381,14 @@ public class FlyOver : ThingWithComps, IThingHolder
// 工具方法:创建飞越物体
public static FlyOver MakeFlyOver(ThingDef flyOverDef, IntVec3 start, IntVec3 end, Map map,
float speed = 1f, float height = 10f, ThingOwner contents = null)
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)
{
@@ -279,35 +396,52 @@ public class FlyOver : ThingWithComps, IThingHolder
}
GenSpawn.Spawn(flyOver, start, map);
Log.Message($"FlyOver created: {flyOver} from {start} to {end} at altitude {height} with {fadeInDuration}s fade-in");
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); // 右边中间
// 高空版本的工具方法
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);
// // 创建飞越物体
// 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); // 下边中间
// 高空特有的设置
flyOver.playFlyOverSound = false;
flyOver.createShadow = true;
flyOver.spawnContentsOnImpact = false;
// // 创建内容容器
// ThingOwner contents = new ThingOwner<Thing>();
// contents.TryAdd(ThingMaker.MakeThing(ThingDefOf.Steel, 20));
Log.Message($"HighAltitude FlyOver created: {flyOver} from {start} to {end} at altitude {height}");
return flyOver;
}
// FlyOver flyOver = FlyOver.MakeFlyOver(ThingDefOf.CargoFlyOver, start, end, map,
// speed: 1f, height: 12f, contents: contents);
// 快速淡入版本的工具方法
public static FlyOver MakeQuickFlyOver(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, 0.5f);
}
// flyOver.spawnContentsOnImpact = true;
//}
// 慢速淡入版本的工具方法(适合非常大的模型)
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);
}
}
// 更新的 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; // 实际高度
}
}

View File

@@ -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<ThingDefCountClass> thingDefs = new List<ThingDefCountClass>();
public bool dropAllContents = false; // 是否投掷所有内容物
// Pawn 生成配置
public List<PawnKindDefCountClass> pawnKinds = new List<PawnKindDefCountClass>();
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<Thing> items = new List<Thing>();
private List<Pawn> pawns = new List<Pawn>();
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<Thing> thingsToDrop = new List<Thing>();
// 添加预生成的内容物
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<Faction, List<Pawn>>();
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<Pawn>();
}
pawnsByFaction[pawn.Faction].Add(pawn);
}
// 为每个派系创建 LordJob
foreach (var factionGroup in pawnsByFaction)
{
Faction faction = factionGroup.Key;
List<Pawn> 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<Thing> thingsToDrop, IntVec3 dropCenter, Map map)
{
// 使用原版空投仓系统
if (Props.dropAllInSamePod)
{
// 所有物品在一个空投仓中
DropPodUtility.DropThingGroupsNear(
dropCenter,
map,
new List<List<Thing>> { 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<Thing> thingsToDrop, IntVec3 dropCenter, Map map)
{
List<List<Thing>> podGroups = new List<List<Thing>>();
if (Props.dropAllInSamePod)
{
// 所有物品在一个空投仓中,但生成多个相同的空投仓
for (int i = 0; i < Props.dropCount; i++)
{
// 为每个空投仓创建新的物品实例
List<Thing> podItems = new List<Thing>();
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<Thing> currentPod = new List<Thing>();
foreach (Thing thing in thingsToDrop)
{
currentPod.Add(thing);
if (currentPod.Count >= thingsToDrop.Count / Props.dropCount)
{
podGroups.Add(currentPod);
currentPod = new List<Thing>();
}
}
if (currentPod.Any())
{
podGroups.Add(currentPod);
}
}
// 投掷多个空投仓组
foreach (List<Thing> podGroup in podGroups)
{
IntVec3 scatterPos = GetScatteredDropPos(dropCenter, map);
DropPodUtility.DropThingGroupsNear(
scatterPos,
map,
new List<List<Thing>> { 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<Thing> 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<Gizmo> 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);
}
}
}
}