diff --git a/1.6/1.6/Assemblies/ArachnaeSwarm.dll b/1.6/1.6/Assemblies/ArachnaeSwarm.dll
index 3a8885d..32f0661 100644
Binary files a/1.6/1.6/Assemblies/ArachnaeSwarm.dll and b/1.6/1.6/Assemblies/ArachnaeSwarm.dll differ
diff --git a/1.6/1.6/Defs/Thing_Misc/ARA_Flyover_Item.xml b/1.6/1.6/Defs/Thing_Misc/ARA_Flyover_Item.xml
index 1917351..71c4230 100644
--- a/1.6/1.6/Defs/Thing_Misc/ARA_Flyover_Item.xml
+++ b/1.6/1.6/Defs/Thing_Misc/ARA_Flyover_Item.xml
@@ -34,8 +34,9 @@
false
false
false
- LightingOverlay
+ FogOfWar
+
60
虫巢母舰
@@ -45,6 +46,7 @@
true
false
+
true
@@ -102,13 +104,13 @@
false
false
+
600
600
60
60
3
- 60
0.1
ARA_HiveShip_Fire_Incoming
@@ -120,13 +122,78 @@
true
10
- true
+ false
战舰炮击警告
一艘敌方战舰正在对殖民地进行炮击!立即寻找掩护!
ThreatBig
+
+
+ ARA_HiveCorvette
+
+
+ 250
+ 60
+ 1
+
+
+ 15
+ 200
+ 5
+ true
+
+
+ 20
+ 10
+ false
+
+
+ true
+ false
+ true
+
+
+ 0.6
+ true
+
+
+ ARA_HiveCorvette
+
+ ArachnaeSwarm.FlyOver
+ Normal
+ RealtimeOnly
+
+
+ ArachnaeSwarm/FlyOverThing/ARA_HiveShip_Shadow
+ Graphic_Single
+ TransparentPostLight
+ (20,30)
+ (195,195,195,45)
+
+
+ (0, 0)
+ 0
+ FlyOver/Flying
+ FlyOver/Landing
+
+
+
+ ArachnaeSwarm/FlyOverThing/ARA_HiveShip_Shadow
+ false
+
+
+
+ false
+ false
+ false
+ FogOfWar
+
ARA_HiveShip_Fire_Incoming
@@ -140,6 +207,12 @@
ARA_AcidBurn
0.5
1
+
+
+ (0,0)
+ (1,-1)
+
+
\ No newline at end of file
diff --git a/Source/ArachnaeSwarm/.vs/ArachnaeSwarm/v17/.suo b/Source/ArachnaeSwarm/.vs/ArachnaeSwarm/v17/.suo
index fa1964a..a551e25 100644
Binary files a/Source/ArachnaeSwarm/.vs/ArachnaeSwarm/v17/.suo and b/Source/ArachnaeSwarm/.vs/ArachnaeSwarm/v17/.suo differ
diff --git a/Source/ArachnaeSwarm/.vs/ArachnaeSwarm/v17/DocumentLayout.json b/Source/ArachnaeSwarm/.vs/ArachnaeSwarm/v17/DocumentLayout.json
index 990db67..3308eeb 100644
--- a/Source/ArachnaeSwarm/.vs/ArachnaeSwarm/v17/DocumentLayout.json
+++ b/Source/ArachnaeSwarm/.vs/ArachnaeSwarm/v17/DocumentLayout.json
@@ -1,7 +1,28 @@
{
"Version": 1,
"WorkspaceRootPath": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\",
- "Documents": [],
+ "Documents": [
+ {
+ "AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|d:\\steamlibrary\\steamapps\\common\\rimworld\\mods\\arachnaeswarm\\source\\arachnaeswarm\\flyover\\thingclassflyover.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
+ "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:flyover\\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\\flyover\\ara_flyoverescort\\compflyoverescort.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
+ "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:flyover\\ara_flyoverescort\\compflyoverescort.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\\flyover\\ara_flyoverescort\\compproperties_flyoverescort.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
+ "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:flyover\\ara_flyoverescort\\compproperties_flyoverescort.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\\flyover\\ara_shipartillery\\compshipartillery.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
+ "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:flyover\\ara_shipartillery\\compshipartillery.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\\flyover\\ara_shipartillery\\compproperties_shipartillery.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
+ "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:flyover\\ara_shipartillery\\compproperties_shipartillery.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
+ }
+ ],
"DocumentGroupContainers": [
{
"Orientation": 0,
@@ -9,11 +30,76 @@
"DocumentGroups": [
{
"DockedWidth": 200,
- "SelectedChildIndex": -1,
+ "SelectedChildIndex": 1,
"Children": [
{
"$type": "Bookmark",
"Name": "ST:0:0:{1c4feeaa-4718-4aa9-859d-94ce25d182ba}"
+ },
+ {
+ "$type": "Document",
+ "DocumentIndex": 0,
+ "Title": "ThingclassFlyOver.cs",
+ "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Flyover\\ThingclassFlyOver.cs",
+ "RelativeDocumentMoniker": "Flyover\\ThingclassFlyOver.cs",
+ "ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Flyover\\ThingclassFlyOver.cs",
+ "RelativeToolTip": "Flyover\\ThingclassFlyOver.cs",
+ "ViewState": "AgIAAAMCAAAAAAAAAAAAABcCAAAeAAAAAAAAAA==",
+ "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
+ "WhenOpened": "2025-10-28T09:09:22.03Z",
+ "EditorCaption": ""
+ },
+ {
+ "$type": "Document",
+ "DocumentIndex": 1,
+ "Title": "CompFlyOverEscort.cs",
+ "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Flyover\\ARA_FlyOverEscort\\CompFlyOverEscort.cs",
+ "RelativeDocumentMoniker": "Flyover\\ARA_FlyOverEscort\\CompFlyOverEscort.cs",
+ "ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Flyover\\ARA_FlyOverEscort\\CompFlyOverEscort.cs",
+ "RelativeToolTip": "Flyover\\ARA_FlyOverEscort\\CompFlyOverEscort.cs",
+ "ViewState": "AgIAAAAAAAAAAAAAAAAAAAMAAAAMAAAAAAAAAA==",
+ "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
+ "WhenOpened": "2025-10-28T07:30:55.268Z",
+ "EditorCaption": ""
+ },
+ {
+ "$type": "Document",
+ "DocumentIndex": 2,
+ "Title": "CompProperties_FlyOverEscort.cs",
+ "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Flyover\\ARA_FlyOverEscort\\CompProperties_FlyOverEscort.cs",
+ "RelativeDocumentMoniker": "Flyover\\ARA_FlyOverEscort\\CompProperties_FlyOverEscort.cs",
+ "ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Flyover\\ARA_FlyOverEscort\\CompProperties_FlyOverEscort.cs",
+ "RelativeToolTip": "Flyover\\ARA_FlyOverEscort\\CompProperties_FlyOverEscort.cs",
+ "ViewState": "AgIAAAMAAAAAAAAAAAAAABUAAAAjAAAAAAAAAA==",
+ "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
+ "WhenOpened": "2025-10-28T07:30:47.27Z",
+ "EditorCaption": ""
+ },
+ {
+ "$type": "Document",
+ "DocumentIndex": 4,
+ "Title": "CompProperties_ShipArtillery.cs",
+ "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Flyover\\ARA_ShipArtillery\\CompProperties_ShipArtillery.cs",
+ "RelativeDocumentMoniker": "Flyover\\ARA_ShipArtillery\\CompProperties_ShipArtillery.cs",
+ "ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Flyover\\ARA_ShipArtillery\\CompProperties_ShipArtillery.cs",
+ "RelativeToolTip": "Flyover\\ARA_ShipArtillery\\CompProperties_ShipArtillery.cs",
+ "ViewState": "AgIAAAAAAAAAAAAAAAAAAA8AAAAhAAAAAAAAAA==",
+ "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
+ "WhenOpened": "2025-10-28T06:21:06.271Z",
+ "EditorCaption": ""
+ },
+ {
+ "$type": "Document",
+ "DocumentIndex": 3,
+ "Title": "CompShipArtillery.cs",
+ "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Flyover\\ARA_ShipArtillery\\CompShipArtillery.cs",
+ "RelativeDocumentMoniker": "Flyover\\ARA_ShipArtillery\\CompShipArtillery.cs",
+ "ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Flyover\\ARA_ShipArtillery\\CompShipArtillery.cs",
+ "RelativeToolTip": "Flyover\\ARA_ShipArtillery\\CompShipArtillery.cs",
+ "ViewState": "AgIAALEAAAAAAAAAAAAywNEAAABcAAAAAAAAAA==",
+ "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
+ "WhenOpened": "2025-10-28T06:21:04.222Z",
+ "EditorCaption": ""
}
]
}
diff --git a/Source/ArachnaeSwarm/ArachnaeSwarm.csproj b/Source/ArachnaeSwarm/ArachnaeSwarm.csproj
index 4be990d..672f645 100644
--- a/Source/ArachnaeSwarm/ArachnaeSwarm.csproj
+++ b/Source/ArachnaeSwarm/ArachnaeSwarm.csproj
@@ -138,6 +138,8 @@
+
+
diff --git a/Source/ArachnaeSwarm/Flyover/ARA_FlyOverEscort/CompFlyOverEscort.cs b/Source/ArachnaeSwarm/Flyover/ARA_FlyOverEscort/CompFlyOverEscort.cs
new file mode 100644
index 0000000..a6e7038
--- /dev/null
+++ b/Source/ArachnaeSwarm/Flyover/ARA_FlyOverEscort/CompFlyOverEscort.cs
@@ -0,0 +1,338 @@
+using RimWorld;
+using System.Collections.Generic;
+using UnityEngine;
+using Verse;
+
+namespace ArachnaeSwarm
+{
+ public class CompFlyOverEscort : ThingComp
+ {
+ public CompProperties_FlyOverEscort Props => (CompProperties_FlyOverEscort)props;
+
+ // 状态变量
+ private float ticksUntilNextSpawn = 0f;
+ private List activeEscorts = new List();
+ private bool hasInitialized = false;
+
+ public override void Initialize(CompProperties props)
+ {
+ base.Initialize(props);
+
+ if (Props.spawnOnStart)
+ {
+ ticksUntilNextSpawn = 0f;
+ }
+ else
+ {
+ ticksUntilNextSpawn = Props.spawnIntervalTicks;
+ }
+
+ Log.Message($"FlyOver Escort initialized: {Props.spawnIntervalTicks} ticks interval, max {Props.maxEscorts} escorts");
+ }
+
+ public override void CompTick()
+ {
+ base.CompTick();
+
+ if (parent is not FlyOver mainFlyOver || !mainFlyOver.Spawned || mainFlyOver.Map == null)
+ return;
+
+ // 初始化检查
+ if (!hasInitialized && mainFlyOver.hasStarted)
+ {
+ hasInitialized = true;
+ Log.Message($"FlyOver Escort: Main FlyOver started at {mainFlyOver.startPosition}");
+ }
+
+ // 清理已销毁的伴飞
+ activeEscorts.RemoveAll(escort => escort == null || escort.Destroyed || !escort.Spawned);
+
+ // 检查是否应该生成新伴飞
+ if (ShouldSpawnEscort(mainFlyOver))
+ {
+ ticksUntilNextSpawn -= 1f;
+
+ if (ticksUntilNextSpawn <= 0f)
+ {
+ SpawnEscorts(mainFlyOver);
+ ticksUntilNextSpawn = Props.spawnIntervalTicks;
+ }
+ }
+
+ // 更新现有伴飞的位置(如果需要)
+ UpdateEscortPositions(mainFlyOver);
+ }
+
+ private bool ShouldSpawnEscort(FlyOver mainFlyOver)
+ {
+ if (!mainFlyOver.hasStarted || mainFlyOver.hasCompleted)
+ return false;
+
+ if (!Props.continuousSpawning && activeEscorts.Count >= Props.spawnCount)
+ return false;
+
+ if (activeEscorts.Count >= Props.maxEscorts)
+ return false;
+
+ return true;
+ }
+
+ private void SpawnEscorts(FlyOver mainFlyOver)
+ {
+ int escortsToSpawn = Mathf.Min(Props.spawnCount, Props.maxEscorts - activeEscorts.Count);
+
+ for (int i = 0; i < escortsToSpawn; i++)
+ {
+ FlyOver escort = CreateEscort(mainFlyOver);
+ if (escort != null)
+ {
+ activeEscorts.Add(escort);
+ Log.Message($"Spawned escort #{i+1} for FlyOver at {mainFlyOver.DrawPos}");
+ }
+ }
+ }
+
+ private FlyOver CreateEscort(FlyOver mainFlyOver)
+ {
+ try
+ {
+ // 选择伴飞定义
+ ThingDef escortDef = SelectEscortDef();
+ if (escortDef == null)
+ {
+ Log.Error("FlyOver Escort: No valid escort def found");
+ return null;
+ }
+
+ // 计算伴飞的起点和终点
+ IntVec3 escortStart = CalculateEscortStart(mainFlyOver);
+ IntVec3 escortEnd = CalculateEscortEnd(mainFlyOver, escortStart);
+
+ if (!escortStart.InBounds(mainFlyOver.Map) || !escortEnd.InBounds(mainFlyOver.Map))
+ {
+ Log.Warning("FlyOver Escort: Escort start or end position out of bounds");
+ return null;
+ }
+
+ // 计算伴飞参数
+ float escortSpeed = mainFlyOver.flightSpeed * Props.escortSpeedMultiplier;
+ float escortAltitude = mainFlyOver.altitude + Props.escortAltitudeOffset;
+
+ // 创建伴飞
+ FlyOver escort = FlyOver.MakeFlyOver(
+ escortDef,
+ escortStart,
+ escortEnd,
+ mainFlyOver.Map,
+ escortSpeed,
+ escortAltitude,
+ null, // 没有内容物
+ mainFlyOver.fadeInDuration
+ );
+
+ // 设置伴飞属性
+ SetupEscortProperties(escort, mainFlyOver);
+
+ Log.Message($"Created escort: {escortStart} -> {escortEnd}, speed: {escortSpeed}, altitude: {escortAltitude}");
+
+ return escort;
+ }
+ catch (System.Exception ex)
+ {
+ Log.Error($"Error creating FlyOver escort: {ex}");
+ return null;
+ }
+ }
+
+ private ThingDef SelectEscortDef()
+ {
+ if (Props.escortFlyOverDefs != null && Props.escortFlyOverDefs.Count > 0)
+ {
+ return Props.escortFlyOverDefs.RandomElement();
+ }
+
+ return Props.escortFlyOverDef;
+ }
+
+ private IntVec3 CalculateEscortStart(FlyOver mainFlyOver)
+ {
+ Vector3 mainDirection = mainFlyOver.MovementDirection;
+ Vector3 mainPosition = mainFlyOver.DrawPos;
+
+ // 计算横向偏移方向(垂直于飞行方向)
+ Vector3 lateralDirection = GetLateralOffsetDirection(mainDirection);
+
+ // 计算偏移量
+ float lateralOffset = Props.useRandomOffset ?
+ Rand.Range(-Props.lateralOffset, Props.lateralOffset) :
+ Props.lateralOffset;
+
+ float spawnDistance = Props.useRandomOffset ?
+ Rand.Range(Props.spawnDistance * 0.5f, Props.spawnDistance * 1.5f) :
+ Props.spawnDistance;
+
+ // 计算起点位置(从主FlyOver后方偏移)
+ Vector3 offset = (-mainDirection * spawnDistance) + (lateralDirection * lateralOffset);
+ Vector3 escortStartPos = mainPosition + offset;
+
+ // 确保位置在地图边界内
+ IntVec3 escortStart = escortStartPos.ToIntVec3();
+ if (!escortStart.InBounds(mainFlyOver.Map))
+ {
+ // 如果超出边界,调整到边界内
+ escortStart = ClampToMap(escortStart, mainFlyOver.Map);
+ }
+
+ return escortStart;
+ }
+
+ private IntVec3 CalculateEscortEnd(FlyOver mainFlyOver, IntVec3 escortStart)
+ {
+ Vector3 mainDirection = mainFlyOver.MovementDirection;
+ Vector3 mainEndPos = mainFlyOver.endPosition.ToVector3();
+
+ // 如果镜像移动,使用相反方向
+ if (Props.mirrorMovement)
+ {
+ mainDirection = -mainDirection;
+ }
+
+ // 计算从起点沿飞行方向延伸的终点
+ float flightDistance = mainFlyOver.startPosition.DistanceTo(mainFlyOver.endPosition);
+ Vector3 escortEndPos = escortStart.ToVector3() + (mainDirection * flightDistance);
+
+ // 确保终点在地图边界内
+ IntVec3 escortEnd = escortEndPos.ToIntVec3();
+ if (!escortEnd.InBounds(mainFlyOver.Map))
+ {
+ escortEnd = ClampToMap(escortEnd, mainFlyOver.Map);
+ }
+
+ return escortEnd;
+ }
+
+ private Vector3 GetLateralOffsetDirection(Vector3 mainDirection)
+ {
+ // 获取垂直于飞行方向的向量(随机选择左侧或右侧)
+ Vector3 lateral = new Vector3(-mainDirection.z, 0f, mainDirection.x);
+
+ // 随机选择方向
+ if (Rand.Value > 0.5f)
+ {
+ lateral = -lateral;
+ }
+
+ return lateral.normalized;
+ }
+
+ private IntVec3 ClampToMap(IntVec3 position, Map map)
+ {
+ CellRect mapRect = CellRect.WholeMap(map);
+ return new IntVec3(
+ Mathf.Clamp(position.x, mapRect.minX, mapRect.maxX),
+ 0,
+ Mathf.Clamp(position.z, mapRect.minZ, mapRect.maxZ)
+ );
+ }
+
+ private void SetupEscortProperties(FlyOver escort, FlyOver mainFlyOver)
+ {
+ // 设置缩放
+ if (Props.escortScale != 1f)
+ {
+ // 这里可能需要通过ModExtension或其他方式设置缩放
+ }
+
+ // 禁用阴影(如果需要)
+ if (!mainFlyOver.createShadow)
+ {
+ escort.createShadow = false;
+ }
+
+ // 禁用音效(如果需要)
+ if (!mainFlyOver.playFlyOverSound)
+ {
+ escort.playFlyOverSound = false;
+ }
+ }
+
+ private void UpdateEscortPositions(FlyOver mainFlyOver)
+ {
+ // 如果需要实时更新伴飞位置,可以在这里实现
+ // 目前伴飞会按照自己的路径飞行
+ }
+
+ public override void PostDestroy(DestroyMode mode, Map previousMap)
+ {
+ base.PostDestroy(mode, previousMap);
+
+ // 销毁所有伴飞
+ if (Props.destroyWithParent)
+ {
+ foreach (FlyOver escort in activeEscorts)
+ {
+ if (escort != null && escort.Spawned)
+ {
+ escort.Destroy();
+ }
+ }
+ activeEscorts.Clear();
+ }
+ }
+
+ public override void PostExposeData()
+ {
+ base.PostExposeData();
+ Scribe_Values.Look(ref ticksUntilNextSpawn, "ticksUntilNextSpawn", 0f);
+ Scribe_Collections.Look(ref activeEscorts, "activeEscorts", LookMode.Reference);
+ Scribe_Values.Look(ref hasInitialized, "hasInitialized", false);
+ }
+
+ public override IEnumerable CompGetGizmosExtra()
+ {
+ if (DebugSettings.ShowDevGizmos && parent is FlyOver)
+ {
+ yield return new Command_Action
+ {
+ defaultLabel = "Dev: Spawn Escort",
+ action = () => SpawnEscorts(parent as FlyOver)
+ };
+
+ yield return new Command_Action
+ {
+ defaultLabel = $"Dev: Escort Status - Active: {activeEscorts.Count}/{Props.maxEscorts}",
+ action = () => {}
+ };
+
+ yield return new Command_Action
+ {
+ defaultLabel = "Dev: Clear All Escorts",
+ action = () =>
+ {
+ foreach (var escort in activeEscorts)
+ {
+ if (escort != null && escort.Spawned)
+ escort.Destroy();
+ }
+ activeEscorts.Clear();
+ }
+ };
+ }
+ }
+
+ // 公共方法:强制生成伴飞
+ public void SpawnEscortNow()
+ {
+ if (parent is FlyOver flyOver)
+ {
+ SpawnEscorts(flyOver);
+ }
+ }
+
+ // 公共方法:获取活跃伴飞数量
+ public int GetActiveEscortCount()
+ {
+ return activeEscorts.Count;
+ }
+ }
+}
diff --git a/Source/ArachnaeSwarm/Flyover/ARA_FlyOverEscort/CompProperties_FlyOverEscort.cs b/Source/ArachnaeSwarm/Flyover/ARA_FlyOverEscort/CompProperties_FlyOverEscort.cs
new file mode 100644
index 0000000..6b23e49
--- /dev/null
+++ b/Source/ArachnaeSwarm/Flyover/ARA_FlyOverEscort/CompProperties_FlyOverEscort.cs
@@ -0,0 +1,43 @@
+using RimWorld;
+using System.Collections.Generic;
+using Verse;
+
+namespace ArachnaeSwarm
+{
+ public class CompProperties_FlyOverEscort : CompProperties
+ {
+ // 伴飞配置
+ public ThingDef escortFlyOverDef; // 伴飞FlyOver定义
+ public List escortFlyOverDefs; // 多个伴飞定义(随机选择)
+
+ // 生成配置
+ public float spawnIntervalTicks = 600f; // 生成间隔(tick)
+ public int maxEscorts = 3; // 最大伴飞数量
+ public int spawnCount = 1; // 每次生成的伴飞数量
+
+ // 位置配置
+ public float spawnDistance = 10f; // 生成距离(从主FlyOver)
+ public float lateralOffset = 5f; // 横向偏移量
+ public float verticalOffset = 2f; // 垂直偏移量(高度差)
+ public bool useRandomOffset = true; // 是否使用随机偏移
+
+ // 飞行配置
+ public float escortSpeedMultiplier = 1f; // 速度乘数(相对于主FlyOver)
+ public float escortAltitudeOffset = 0f; // 高度偏移
+ public bool mirrorMovement = false; // 是否镜像移动(相反方向)
+
+ // 行为配置
+ public bool spawnOnStart = true; // 开始时立即生成
+ public bool continuousSpawning = true; // 是否持续生成
+ public bool destroyWithParent = true; // 是否随父级销毁
+
+ // 外观配置
+ public float escortScale = 1f; // 缩放比例
+ public bool useParentRotation = true; // 使用父级旋转
+
+ public CompProperties_FlyOverEscort()
+ {
+ compClass = typeof(CompFlyOverEscort);
+ }
+ }
+}
diff --git a/Source/ArachnaeSwarm/Flyover/ARA_ShipArtillery/CompProperties_ShipArtillery.cs b/Source/ArachnaeSwarm/Flyover/ARA_ShipArtillery/CompProperties_ShipArtillery.cs
index 8d02f30..6371b88 100644
--- a/Source/ArachnaeSwarm/Flyover/ARA_ShipArtillery/CompProperties_ShipArtillery.cs
+++ b/Source/ArachnaeSwarm/Flyover/ARA_ShipArtillery/CompProperties_ShipArtillery.cs
@@ -26,7 +26,6 @@ namespace ArachnaeSwarm
public ThingDef skyfallerDef; // 使用的 Skyfaller 定义
public List skyfallerDefs; // 多个 Skyfaller 定义(随机选择)
public int shellsPerVolley = 1; // 每轮齐射的炮弹数量
- public float scatterRadius = 3f; // 散布半径
public bool useDifferentShells = false; // 是否使用不同类型的炮弹
// 音效配置
diff --git a/Source/ArachnaeSwarm/Flyover/ARA_ShipArtillery/CompShipArtillery.cs b/Source/ArachnaeSwarm/Flyover/ARA_ShipArtillery/CompShipArtillery.cs
index ef3354e..8d1fe22 100644
--- a/Source/ArachnaeSwarm/Flyover/ARA_ShipArtillery/CompShipArtillery.cs
+++ b/Source/ArachnaeSwarm/Flyover/ARA_ShipArtillery/CompShipArtillery.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using Verse;
+using Verse.Sound;
namespace ArachnaeSwarm
{
@@ -29,7 +30,7 @@ namespace ArachnaeSwarm
ticksUntilNextAttack = Props.ticksBetweenAttacks;
- Log.Message($"Ship Artillery initialized: {Props.ticksBetweenAttacks} ticks between attacks, {Props.attackRadius} radius, Scatter: {Props.scatterRadius}");
+ Log.Message($"Ship Artillery initialized: {Props.ticksBetweenAttacks} ticks between attacks, {Props.attackRadius} radius");
}
public override void CompTick()
@@ -132,7 +133,7 @@ namespace ArachnaeSwarm
attackEffecter = Props.attackEffect.Spawn();
}
- Log.Message($"Ship Artillery started firing at area {currentTarget} (scatter radius: {Props.scatterRadius})");
+ Log.Message($"Ship Artillery started firing at area {currentTarget}");
// 发送攻击通知
if (Props.sendAttackLetter)
@@ -194,17 +195,20 @@ namespace ArachnaeSwarm
return;
}
- // 计算散布位置 - 使用新的散射逻辑
- IntVec3 shellTarget = GetScatteredTarget(flyOver);
+ // 直接选择随机目标
+ IntVec3 shellTarget = SelectRandomTarget(flyOver);
- // 创建 Skyfaller
- Skyfaller shell = (Skyfaller)ThingMaker.MakeThing(shellDef);
-
- // 生成炮弹
- GenSpawn.Spawn(shell, GetLaunchPosition(flyOver), flyOver.Map);
+ // 关键修复:使用 SkyfallerMaker 创建并立即生成 Skyfaller
+ SkyfallerMaker.SpawnSkyfaller(shellDef, shellTarget, flyOver.Map);
float distanceFromCenter = shellTarget.DistanceTo(currentTarget);
- Log.Message($"Ship Artillery fired shell at {shellTarget} (distance from center: {distanceFromCenter:F1}, scatter radius: {Props.scatterRadius})");
+ Log.Message($"Ship Artillery fired shell at {shellTarget} (distance from center: {distanceFromCenter:F1})");
+
+ // 播放音效
+ if (Props.attackSound != null)
+ {
+ Props.attackSound.PlayOneShot(new TargetInfo(shellTarget, flyOver.Map));
+ }
}
catch (System.Exception ex)
{
@@ -243,45 +247,11 @@ namespace ArachnaeSwarm
return launchPos;
}
- // 新的散射逻辑 - 基于 FlyOverDropPods 的实现方式
- private IntVec3 GetScatteredTarget(FlyOver flyOver)
+ // 简化的目标选择 - 每次直接随机选择目标
+ private IntVec3 SelectRandomTarget(FlyOver flyOver)
{
- // 基础目标(攻击区域中心)
- IntVec3 baseTarget = currentTarget;
-
- // 如果散射半径为0,直接返回基础目标
- if (Props.scatterRadius <= 0)
- return baseTarget;
-
- // 使用类似 FlyOverDropPods 的散射逻辑
- for (int i = 0; i < 20; i++) // 尝试20次找到有效位置
- {
- // 在散射半径内随机选择位置
- IntVec3 scatteredPos = baseTarget;
-
- // 使用圆形散射而不是方形
- float angle = Rand.Range(0f, 360f);
- float distance = Rand.Range(0f, Props.scatterRadius);
-
- scatteredPos.x += Mathf.RoundToInt(Mathf.Cos(angle * Mathf.Deg2Rad) * distance);
- scatteredPos.z += Mathf.RoundToInt(Mathf.Sin(angle * Mathf.Deg2Rad) * distance);
-
- // 检查是否无视保护机制
- bool ignoreProtectionThisShot = Rand.Value < Props.ignoreProtectionChance;
-
- if (scatteredPos.InBounds(flyOver.Map) && IsValidTarget(scatteredPos, flyOver.Map, ignoreProtectionThisShot))
- {
- if (ignoreProtectionThisShot)
- {
- Log.Warning($"Ship Artillery: Protection ignored! Shell may hit player assets at {scatteredPos}");
- }
- return scatteredPos;
- }
- }
-
- // 如果找不到有效位置,使用基础目标
- Log.Warning($"Ship Artillery: Could not find valid scattered target after 20 attempts, using base target");
- return baseTarget;
+ IntVec3 center = GetFlyOverPosition(flyOver) + Props.targetOffset;
+ return FindRandomTargetInRadius(center, flyOver.Map, Props.attackRadius);
}
private IntVec3 SelectTarget(FlyOver flyOver)
@@ -316,7 +286,7 @@ namespace ArachnaeSwarm
return result;
}
- // 新的目标查找逻辑 - 基于攻击半径
+ // 目标查找逻辑 - 基于攻击半径
private IntVec3 FindRandomTargetInRadius(IntVec3 center, Map map, float radius)
{
Log.Message($"Finding target around {center} with radius {radius}");
diff --git a/Source/ArachnaeSwarm/Flyover/ARA_SpawnFlyOver/CompAbilityEffect_SpawnFlyOver.cs b/Source/ArachnaeSwarm/Flyover/ARA_SpawnFlyOver/CompAbilityEffect_SpawnFlyOver.cs
index 0dc2af5..54a0e21 100644
--- a/Source/ArachnaeSwarm/Flyover/ARA_SpawnFlyOver/CompAbilityEffect_SpawnFlyOver.cs
+++ b/Source/ArachnaeSwarm/Flyover/ARA_SpawnFlyOver/CompAbilityEffect_SpawnFlyOver.cs
@@ -22,8 +22,18 @@ namespace ArachnaeSwarm
Log.Message($"Target cell: {target.Cell}, Dest: {dest.Cell}");
// 计算起始和结束位置
- IntVec3 startPos = CalculateStartPosition(target);
- IntVec3 endPos = CalculateEndPosition(target, startPos);
+ IntVec3 startPos, endPos;
+
+ // 根据进场类型选择不同的计算方法
+ if (Props.approachType == ApproachType.Perpendicular)
+ {
+ CalculatePerpendicularPath(target, out startPos, out endPos);
+ }
+ else
+ {
+ startPos = CalculateStartPosition(target);
+ endPos = CalculateEndPosition(target, startPos);
+ }
Log.Message($"Final positions - Start: {startPos}, End: {endPos}");
@@ -40,6 +50,18 @@ namespace ArachnaeSwarm
endPos = parent.pawn.Map.Center;
}
+ // 确保起点和终点不同
+ 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(parent.pawn.Map))
+ {
+ endPos = parent.pawn.Map.Center;
+ }
+ }
+
// 根据类型创建不同的飞越物体
switch (Props.flyOverType)
{
@@ -58,6 +80,78 @@ namespace ArachnaeSwarm
}
}
+ // 新增:计算垂直线进场路径
+ private void CalculatePerpendicularPath(LocalTargetInfo target, out IntVec3 startPos, out IntVec3 endPos)
+ {
+ Map map = parent.pawn.Map;
+ IntVec3 casterPos = parent.pawn.Position;
+ IntVec3 targetPos = target.Cell;
+
+ Log.Message($"Calculating perpendicular path: Caster={casterPos}, Target={targetPos}");
+
+ // 计算施法者到目标的方向向量
+ Vector3 directionToTarget = (targetPos.ToVector3() - casterPos.ToVector3()).normalized;
+
+ // 如果方向为零向量,使用随机方向
+ if (directionToTarget == Vector3.zero)
+ {
+ directionToTarget = new Vector3(Rand.Range(-1f, 1f), 0, Rand.Range(-1f, 1f)).normalized;
+ Log.Message($"Using random direction: {directionToTarget}");
+ }
+
+ // 计算垂直于施法者-目标连线的方向(旋转90度)
+ Vector3 perpendicularDirection = new Vector3(-directionToTarget.z, 0, directionToTarget.x).normalized;
+
+ Log.Message($"Perpendicular direction: {perpendicularDirection}");
+
+ // 从目标点出发,向垂直方向的两侧延伸找到地图边缘
+ IntVec3 edge1 = FindMapEdgeInDirection(map, targetPos, perpendicularDirection);
+ IntVec3 edge2 = FindMapEdgeInDirection(map, targetPos, -perpendicularDirection);
+
+ // 随机选择起点和终点(确保目标点在路径上)
+ if (Rand.Value < 0.5f)
+ {
+ startPos = edge1;
+ endPos = edge2;
+ }
+ else
+ {
+ startPos = edge2;
+ endPos = edge1;
+ }
+
+ Log.Message($"Perpendicular path: {startPos} -> {targetPos} -> {endPos}");
+ }
+
+ // 新增:在指定方向上找到地图边缘
+ private IntVec3 FindMapEdgeInDirection(Map map, IntVec3 fromPos, Vector3 direction)
+ {
+ // 计算最大搜索距离(地图对角线的一半)
+ float maxDistance = Mathf.Sqrt(map.Size.x * map.Size.x + map.Size.z * map.Size.z) * 0.6f;
+
+ // 沿着方向逐步搜索,直到找到地图边界
+ for (float distance = 10f; distance <= maxDistance; distance += 5f)
+ {
+ IntVec3 testPos = fromPos + new IntVec3(
+ Mathf.RoundToInt(direction.x * distance),
+ 0,
+ Mathf.RoundToInt(direction.z * distance));
+
+ if (!testPos.InBounds(map))
+ {
+ // 找到边界,返回最近的有效位置
+ IntVec3 edgePos = FindClosestValidPosition(testPos, map);
+ Log.Message($"Found map edge at {edgePos} (direction: {direction}, distance: {distance})");
+ return edgePos;
+ }
+ }
+
+ // 如果没找到边界,使用随机边缘位置
+ Log.Warning($"Could not find map edge in direction {direction}, using random edge");
+ return GetRandomMapEdgePosition(map);
+ }
+
+ // 原有的位置计算方法
private IntVec3 CalculateStartPosition(LocalTargetInfo target)
{
Map map = parent.pawn.Map;
@@ -114,60 +208,33 @@ namespace ArachnaeSwarm
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(
@@ -177,14 +244,12 @@ namespace ArachnaeSwarm
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);
}
@@ -200,7 +265,6 @@ namespace ArachnaeSwarm
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(
@@ -210,21 +274,18 @@ namespace ArachnaeSwarm
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))
@@ -240,26 +301,8 @@ namespace ArachnaeSwarm
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;
@@ -305,7 +348,6 @@ namespace ArachnaeSwarm
{
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;
@@ -317,7 +359,6 @@ namespace ArachnaeSwarm
private void CreateStandardFlyOver(IntVec3 startPos, IntVec3 endPos)
{
- // 确保有默认的飞越定义
ThingDef flyOverDef = Props.flyOverDef ?? DefDatabase.GetNamedSilentFail("ARA_HiveShip");
if (flyOverDef == null)
{
@@ -334,14 +375,12 @@ namespace ArachnaeSwarm
Props.altitude
);
- // 设置属性
flyOver.spawnContentsOnImpact = Props.dropContentsOnImpact;
flyOver.playFlyOverSound = Props.playFlyOverSound;
- // 应用自定义音效
if (Props.customSound != null)
{
- // 这里需要更复杂的逻辑来替换音效
+ // 自定义音效逻辑
}
Log.Message($"Standard FlyOver created: {flyOver} from {startPos} to {endPos}");
diff --git a/Source/ArachnaeSwarm/Flyover/ARA_SpawnFlyOver/CompProperties_AbilitySpawnFlyOver.cs b/Source/ArachnaeSwarm/Flyover/ARA_SpawnFlyOver/CompProperties_AbilitySpawnFlyOver.cs
index ae5be4d..f975d7f 100644
--- a/Source/ArachnaeSwarm/Flyover/ARA_SpawnFlyOver/CompProperties_AbilitySpawnFlyOver.cs
+++ b/Source/ArachnaeSwarm/Flyover/ARA_SpawnFlyOver/CompProperties_AbilitySpawnFlyOver.cs
@@ -8,6 +8,7 @@ namespace ArachnaeSwarm
{
public ThingDef flyOverDef; // 飞越物体的 ThingDef
public FlyOverType flyOverType = FlyOverType.Standard; // 飞越类型
+ public ApproachType approachType = ApproachType.Standard; // 进场类型
public float flightSpeed = 1f; // 飞行速度
public float altitude = 15f; // 飞行高度
public bool spawnContents = false; // 是否生成内容物
@@ -16,11 +17,11 @@ namespace ArachnaeSwarm
public SoundDef customSound; // 自定义音效
public bool playFlyOverSound = true; // 是否播放飞越音效
- // 起始位置选项
+ // 起始位置选项(当approachType为Standard时使用)
public StartPosition startPosition = StartPosition.Caster;
public IntVec3 customStartOffset = IntVec3.Zero;
- // 终点位置选项
+ // 终点位置选项(当approachType为Standard时使用)
public EndPosition endPosition = EndPosition.TargetCell;
public IntVec3 customEndOffset = IntVec3.Zero;
public int flyOverDistance = 30; // 飞越距离(当终点为自定义时)
@@ -41,6 +42,13 @@ namespace ArachnaeSwarm
Reconnaissance // 侦察飞越
}
+ // 新增:进场类型枚举
+ public enum ApproachType
+ {
+ Standard, // 标准进场(使用原有的位置计算)
+ Perpendicular // 垂直线进场(垂直于施法者-目标连线)
+ }
+
// 起始位置枚举
public enum StartPosition
{
@@ -56,7 +64,7 @@ namespace ArachnaeSwarm
TargetCell, // 目标单元格
OppositeMapEdge, // 对面地图边缘
CustomOffset, // 自定义偏移
- FixedDistance, // 固定距离
+ FixedDistance, // 固定距离
RandomMapEdge
}
}
diff --git a/Source/ArachnaeSwarm/Flyover/ThingclassFlyOver.cs b/Source/ArachnaeSwarm/Flyover/ThingclassFlyOver.cs
index bd3c11f..3407018 100644
--- a/Source/ArachnaeSwarm/Flyover/ThingclassFlyOver.cs
+++ b/Source/ArachnaeSwarm/Flyover/ThingclassFlyOver.cs
@@ -22,6 +22,14 @@ namespace ArachnaeSwarm
public float currentFadeInTime = 0f; // 当前淡入时间
public bool fadeInCompleted = false; // 淡入是否完成
+ // 淡出效果相关
+ public float fadeOutDuration = 0f; // 动态计算的淡出持续时间
+ public float currentFadeOutTime = 0f; // 当前淡出时间
+ public bool fadeOutStarted = false; // 淡出是否开始
+ public bool fadeOutCompleted = false; // 淡出是否完成
+ public float fadeOutStartProgress = 0.7f; // 开始淡出的进度阈值(0-1)
+ public float defaultFadeOutDuration = 1.5f; // 默认淡出持续时间(仅用于销毁)
+
// 状态标志
public bool hasStarted = false;
public bool hasCompleted = false;
@@ -110,6 +118,55 @@ namespace ArachnaeSwarm
}
}
+ // 淡出透明度(0-1)
+ public float FadeOutAlpha
+ {
+ get
+ {
+ if (!fadeOutStarted) return 1f;
+ if (fadeOutCompleted) return 0f;
+ return Mathf.Clamp01(1f - (currentFadeOutTime / fadeOutDuration));
+ }
+ }
+
+ // 总体透明度(淡入 * 淡出)
+ public float OverallAlpha
+ {
+ get
+ {
+ return FadeInAlpha * FadeOutAlpha;
+ }
+ }
+
+ // 新增:计算剩余飞行时间(秒)
+ public float RemainingFlightTime
+ {
+ get
+ {
+ float remainingProgress = 1f - currentProgress;
+ return remainingProgress / (flightSpeed * 0.001f) * (1f / 60f);
+ }
+ }
+
+ // 新增:计算基于剩余距离的淡出持续时间
+ private float CalculateDynamicFadeOutDuration()
+ {
+ // 获取 ModExtension 配置
+ var shadowExtension = def.GetModExtension();
+ float minFadeOutDuration = shadowExtension?.minFadeOutDuration ?? 0.5f;
+ float maxFadeOutDuration = shadowExtension?.maxFadeOutDuration ?? 3f;
+ float fadeOutDistanceFactor = shadowExtension?.fadeOutDistanceFactor ?? 0.3f;
+
+ // 计算剩余飞行时间
+ float remainingTime = RemainingFlightTime;
+
+ // 使用剩余时间的一部分作为淡出持续时间
+ float dynamicDuration = remainingTime * fadeOutDistanceFactor;
+
+ // 限制在最小和最大范围内
+ return Mathf.Clamp(dynamicDuration, minFadeOutDuration, maxFadeOutDuration);
+ }
+
public FlyOver()
{
innerContainer = new ThingOwner(this);
@@ -130,6 +187,14 @@ namespace ArachnaeSwarm
Scribe_Values.Look(ref fadeInDuration, "fadeInDuration", 1.5f);
Scribe_Values.Look(ref currentFadeInTime, "currentFadeInTime", 0f);
Scribe_Values.Look(ref fadeInCompleted, "fadeInCompleted", false);
+
+ // 淡出效果数据保存
+ Scribe_Values.Look(ref fadeOutDuration, "fadeOutDuration", 0f);
+ Scribe_Values.Look(ref currentFadeOutTime, "currentFadeOutTime", 0f);
+ Scribe_Values.Look(ref fadeOutStarted, "fadeOutStarted", false);
+ Scribe_Values.Look(ref fadeOutCompleted, "fadeOutCompleted", false);
+ Scribe_Values.Look(ref fadeOutStartProgress, "fadeOutStartProgress", 0.7f);
+ Scribe_Values.Look(ref defaultFadeOutDuration, "defaultFadeOutDuration", 1.5f);
}
public override void SpawnSetup(Map map, bool respawningAfterLoad)
@@ -148,6 +213,12 @@ namespace ArachnaeSwarm
// 重置淡入状态
currentFadeInTime = 0f;
fadeInCompleted = false;
+
+ // 重置淡出状态
+ currentFadeOutTime = 0f;
+ fadeOutStarted = false;
+ fadeOutCompleted = false;
+ fadeOutDuration = 0f;
// 开始飞行音效
if (playFlyOverSound && def.skyfaller?.floatingSound != null)
@@ -169,7 +240,7 @@ namespace ArachnaeSwarm
// 更新淡入效果
if (!fadeInCompleted)
{
- currentFadeInTime += 1f / 60f; // 假设 60 FPS
+ currentFadeInTime += 1f / 60f;
if (currentFadeInTime >= fadeInDuration)
{
fadeInCompleted = true;
@@ -180,11 +251,29 @@ namespace ArachnaeSwarm
// 更新飞行进度
currentProgress += flightSpeed * 0.001f;
- // 更新当前位置(用于碰撞检测等)
+ // 检查是否应该开始淡出(基于剩余距离动态计算)
+ if (!fadeOutStarted && currentProgress >= fadeOutStartProgress)
+ {
+ StartFadeOut();
+ }
+
+ // 更新淡出效果
+ if (fadeOutStarted && !fadeOutCompleted)
+ {
+ currentFadeOutTime += 1f / 60f;
+ if (currentFadeOutTime >= fadeOutDuration)
+ {
+ fadeOutCompleted = true;
+ currentFadeOutTime = fadeOutDuration;
+ Log.Message("FlyOver fade out completed");
+ }
+ }
+
+ // 更新当前位置
UpdatePosition();
- // 维持飞行音效
- flightSoundPlaying?.Maintain();
+ // 维持飞行音效(在淡出时逐渐降低音量)
+ UpdateFlightSound();
// 检查是否到达终点
if (currentProgress >= 1f)
@@ -192,16 +281,23 @@ namespace ArachnaeSwarm
CompleteFlyOver();
}
- // 可选:生成飞行轨迹特效
- if (Rand.MTBEventOccurs(0.5f, 1f, 1f) && def.skyfaller?.motesPerCell > 0)
- {
- CreateFlightEffects();
- }
+ // 生成飞行轨迹特效(在淡出时减少特效)
+ CreateFlightEffects();
+ }
+
+ // 新增:开始淡出效果
+ private void StartFadeOut()
+ {
+ fadeOutStarted = true;
+
+ // 基于剩余距离动态计算淡出持续时间
+ fadeOutDuration = CalculateDynamicFadeOutDuration();
+
+ Log.Message($"FlyOver started fade out at progress {currentProgress:F2}, duration: {fadeOutDuration:F2}s, remaining time: {RemainingFlightTime:F2}s");
}
private void UpdatePosition()
{
- // 更新物体的网格位置(用于碰撞检测等)
Vector3 currentWorldPos = Vector3.Lerp(startPosition.ToVector3(), endPosition.ToVector3(), currentProgress);
IntVec3 newPos = currentWorldPos.ToIntVec3();
@@ -211,6 +307,19 @@ namespace ArachnaeSwarm
}
}
+ private void UpdateFlightSound()
+ {
+ if (flightSoundPlaying != null)
+ {
+ if (fadeOutStarted)
+ {
+ // 淡出时逐渐降低音效音量
+ flightSoundPlaying.externalParams["VolumeFactor"] = FadeOutAlpha;
+ }
+ flightSoundPlaying?.Maintain();
+ }
+ }
+
private void CompleteFlyOver()
{
hasCompleted = true;
@@ -235,6 +344,21 @@ namespace ArachnaeSwarm
Destroy();
}
+ // 新增:紧急销毁方法(使用默认淡出时间)
+ public void EmergencyDestroy()
+ {
+ if (!fadeOutStarted)
+ {
+ // 如果还没有开始淡出,使用默认淡出时间
+ fadeOutStarted = true;
+ fadeOutDuration = defaultFadeOutDuration;
+ Log.Message($"FlyOver emergency destroy with default fade out: {defaultFadeOutDuration}s");
+ }
+
+ // 设置标记,下一帧会处理淡出
+ hasCompleted = true;
+ }
+
private void SpawnContents()
{
foreach (Thing thing in innerContainer)
@@ -250,33 +374,36 @@ namespace ArachnaeSwarm
private void CreateFlightEffects()
{
// 在飞行轨迹上生成粒子效果
- Vector3 effectPos = DrawPos;
- effectPos.y = AltitudeLayer.MoteOverhead.AltitudeFor();
- FleckMaker.ThrowSmoke(effectPos, base.Map, 1f);
-
- // 可选:根据速度生成更多效果
- if (flightSpeed > 2f)
+ if (Rand.MTBEventOccurs(0.5f, 1f, 1f) && def.skyfaller?.motesPerCell > 0 && !fadeOutCompleted)
{
- FleckMaker.ThrowAirPuffUp(effectPos, base.Map);
+ Vector3 effectPos = DrawPos;
+ effectPos.y = AltitudeLayer.MoteOverhead.AltitudeFor();
+
+ // 淡出时减少粒子效果强度
+ float effectIntensity = fadeOutStarted ? FadeOutAlpha : 1f;
+ FleckMaker.ThrowSmoke(effectPos, base.Map, 1f * effectIntensity);
+
+ // 可选:根据速度生成更多效果
+ if (flightSpeed > 2f && !fadeOutStarted)
+ {
+ FleckMaker.ThrowAirPuffUp(effectPos, base.Map);
+ }
}
}
- // 关键修改:添加淡入效果的绘制方法
+ // 绘制方法保持不变,使用OverallAlpha
protected override void DrawAt(Vector3 drawLoc, bool flip = false)
{
Vector3 finalDrawPos = drawLoc;
- // 绘制阴影
if (createShadow)
{
DrawFlightShadow();
}
- // 绘制主体 - 使用带淡入效果的绘制方法
DrawFlyOverWithFade(finalDrawPos);
}
- // 带淡入效果的主体绘制方法
protected virtual void DrawFlyOverWithFade(Vector3 drawPos)
{
Thing thingForGraphic = GetThingForGraphic();
@@ -285,41 +412,41 @@ namespace ArachnaeSwarm
if (graphic == null)
return;
- // 获取原始材质
Material material = graphic.MatSingle;
if (material == null)
return;
- // 计算淡入透明度
- float alpha = FadeInAlpha;
+ float alpha = OverallAlpha;
- // 如果已经完全淡入,使用原版绘制方法以获得最佳性能
- if (alpha >= 0.999f)
+ if (alpha <= 0.001f)
+ return;
+
+ if (fadeInCompleted && !fadeOutStarted && alpha >= 0.999f)
{
- graphic.Draw(drawPos, Rot4.North, thingForGraphic, ExactRotation.eulerAngles.y);
+ Vector3 highAltitudePos = drawPos;
+ highAltitudePos.y = AltitudeLayer.MetaOverlays.AltitudeFor();
+ graphic.Draw(highAltitudePos, Rot4.North, thingForGraphic, ExactRotation.eulerAngles.y);
return;
}
- // 创建带透明度的材质属性块
- fadePropertyBlock.SetColor(ShaderPropertyIDs.Color,
+ 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);
+ Vector3 highPos = drawPos;
+ highPos.y = AltitudeLayer.MetaOverlays.AltitudeFor();
+
+ Matrix4x4 matrix = Matrix4x4.TRS(highPos, ExactRotation, scale);
Graphics.DrawMesh(MeshPool.plane10, matrix, material, 0, null, 0, fadePropertyBlock);
}
- // 简化的阴影绘制方法 - 完全移除旋转(模仿原版)
protected virtual void DrawFlightShadow()
{
- // 检查是否有 ModExtension
var shadowExtension = def.GetModExtension();
Material shadowMaterial;
@@ -338,7 +465,6 @@ namespace ArachnaeSwarm
Vector3 shadowPos = DrawPos;
shadowPos.y = AltitudeLayer.Shadows.AltitudeFor();
- // 使用 ModExtension 参数或默认值
float shadowIntensity = shadowExtension?.shadowIntensity ?? 1f;
float minAlpha = shadowExtension?.minShadowAlpha ?? 0.3f;
float maxAlpha = shadowExtension?.maxShadowAlpha ?? 1f;
@@ -348,10 +474,11 @@ namespace ArachnaeSwarm
float shadowAlpha = Mathf.Lerp(minAlpha, maxAlpha, currentProgress) * shadowIntensity;
float shadowScale = Mathf.Lerp(minScale, maxScale, currentProgress);
- // 阴影也应用淡入效果
- shadowAlpha *= FadeInAlpha;
+ shadowAlpha *= OverallAlpha;
+
+ if (shadowAlpha <= 0.001f)
+ return;
- // 完全移除阴影旋转 - 始终使用默认朝向(模仿原版 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);
@@ -359,7 +486,7 @@ namespace ArachnaeSwarm
Graphics.DrawMesh(MeshPool.plane10, matrix, shadowMaterial, 0);
}
- // IThingHolder 接口实现
+ // IThingHolder 接口实现和其他方法保持不变
public ThingOwner GetDirectlyHeldThings()
{
return innerContainer;
@@ -381,7 +508,8 @@ namespace ArachnaeSwarm
// 工具方法:创建飞越物体
public static FlyOver MakeFlyOver(ThingDef flyOverDef, IntVec3 start, IntVec3 end, Map map,
- float speed = 1f, float height = 10f, ThingOwner contents = null, float fadeInDuration = 1.5f)
+ float speed = 1f, float height = 10f, ThingOwner contents = null,
+ float fadeInDuration = 1.5f, float defaultFadeOutDuration = 1.5f)
{
FlyOver flyOver = (FlyOver)ThingMaker.MakeThing(flyOverDef);
flyOver.startPosition = start;
@@ -389,6 +517,7 @@ namespace ArachnaeSwarm
flyOver.flightSpeed = speed;
flyOver.altitude = height;
flyOver.fadeInDuration = fadeInDuration;
+ flyOver.defaultFadeOutDuration = defaultFadeOutDuration;
if (contents != null)
{
@@ -397,51 +526,32 @@ namespace ArachnaeSwarm
GenSpawn.Spawn(flyOver, start, map);
- Log.Message($"FlyOver created: {flyOver} from {start} to {end} at altitude {height} with {fadeInDuration}s fade-in");
+ Log.Message($"FlyOver created: {flyOver} from {start} to {end} at altitude {height}");
return flyOver;
}
- // 高空版本的工具方法
- public static FlyOver MakeHighAltitudeFlyOver(ThingDef flyOverDef, IntVec3 start, IntVec3 end, Map map,
- float speed = 2f, float height = 50f, ThingOwner contents = null, float fadeInDuration = 1.5f)
- {
- FlyOver flyOver = MakeFlyOver(flyOverDef, start, end, map, speed, height, contents, fadeInDuration);
-
- // 高空特有的设置
- flyOver.playFlyOverSound = false;
- flyOver.createShadow = true;
- flyOver.spawnContentsOnImpact = false;
-
- 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,添加淡入配置
+ // 更新的 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; // 实际高度
+ 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 defaultFadeOutDuration = 1.5f; // 默认淡出持续时间(用于紧急销毁)
+ public float fadeOutStartProgress = 0.98f;
+
+ // 新增:动态淡出配置
+ public float minFadeOutDuration = 0.5f; // 最小淡出持续时间
+ public float maxFadeOutDuration = 3f; // 最大淡出持续时间
+ public float fadeOutDistanceFactor = 0.3f; // 淡出距离因子(剩余时间的百分比)
+
+ public float ActuallyHeight = 150f;
}
}