飞越物品系统
This commit is contained in:
Binary file not shown.
@@ -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>
|
||||
97
1.6/1.6/Defs/Thing_Misc/ARA_Flyover_Item.xml
Normal file
97
1.6/1.6/Defs/Thing_Misc/ARA_Flyover_Item.xml
Normal 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>
|
||||
@@ -71,7 +71,7 @@
|
||||
</thingDefs>
|
||||
</fuelFilter>
|
||||
<fuelGizmoLabel>虫蜜</fuelGizmoLabel>
|
||||
<showAllowAutoRefuelToggle>true</showAllowAutoRefuelToggle>
|
||||
<showAllowAutoRefuelToggle>false</showAllowAutoRefuelToggle>
|
||||
<targetFuelLevelConfigurable>true</targetFuelLevelConfigurable>
|
||||
<canEjectFuel>true</canEjectFuel>
|
||||
</li>
|
||||
|
||||
@@ -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 |
Binary file not shown.
@@ -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": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
383
Source/ArachnaeSwarm/Thing/CompAbilityEffect_SpawnFlyOver.cs
Normal file
383
Source/ArachnaeSwarm/Thing/CompAbilityEffect_SpawnFlyOver.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,8 @@ using UnityEngine;
|
||||
using Verse;
|
||||
using Verse.Sound;
|
||||
|
||||
namespace ArachnaeSwarm
|
||||
{
|
||||
[StaticConstructorOnStartup]
|
||||
public class FlyOver : ThingWithComps, IThingHolder
|
||||
{
|
||||
@@ -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));
|
||||
|
||||
// FlyOver flyOver = FlyOver.MakeFlyOver(ThingDefOf.CargoFlyOver, start, end, map,
|
||||
// speed: 1f, height: 12f, contents: contents);
|
||||
|
||||
// flyOver.spawnContentsOnImpact = true;
|
||||
//}
|
||||
Log.Message($"HighAltitude FlyOver created: {flyOver} from {start} to {end} at altitude {height}");
|
||||
return flyOver;
|
||||
}
|
||||
|
||||
// 快速淡入版本的工具方法
|
||||
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);
|
||||
}
|
||||
|
||||
// 慢速淡入版本的工具方法(适合非常大的模型)
|
||||
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; // 实际高度
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
非公开资源/Content/Textures/FlyOverThing/ARA_HiveShip_Shadow.sai2
Normal file
BIN
非公开资源/Content/Textures/FlyOverThing/ARA_HiveShip_Shadow.sai2
Normal file
Binary file not shown.
Reference in New Issue
Block a user