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