using System.Collections.Generic; using RimWorld; using UnityEngine; using Verse; using Verse.Sound; namespace WulaFallenEmpire { [StaticConstructorOnStartup] public class FlyOver : ThingWithComps, IThingHolder { // 核心字段 public ThingOwner innerContainer; // 内部物品容器 public IntVec3 startPosition; // 起始位置 public IntVec3 endPosition; // 结束位置 public float flightSpeed = 1f; // 飞行速度 public float currentProgress = 0f; // 当前进度 (0-1) public float altitude = 10f; // 飞行高度 public Faction faction; // 派系引用 // 淡入效果相关 public float fadeInDuration = 1.5f; // 淡入持续时间(秒) 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; // 默认淡出持续时间(仅用于销毁) // 新增:淡入淡出开关 private bool useFadeEffects = true; private bool useFadeIn = true; private bool useFadeOut = true; // 进场动画相关 - 新增 public float approachDuration = 1.0f; // 进场动画持续时间(秒) public float currentApproachTime = 0f; // 当前进场动画时间 public bool approachCompleted = false; // 进场动画是否完成 public float approachOffsetDistance = 3f; // 进场偏移距离(格) public bool useApproachAnimation = true; // 是否使用进场动画 // 伴飞相关 public float escortScale = 1f; // 伴飞缩放比例 public bool isEscort = false; // 是否是伴飞 // 状态标志 public bool hasStarted = false; public bool hasCompleted = false; // 音效系统 private Sustainer flightSoundPlaying; // 视觉效果 private Material cachedShadowMaterial; private static MaterialPropertyBlock shadowPropertyBlock = new MaterialPropertyBlock(); private static MaterialPropertyBlock fadePropertyBlock = new MaterialPropertyBlock(); // 配置字段 public bool spawnContentsOnImpact = false; // 是否在结束时生成内容物 public bool playFlyOverSound = true; // 是否播放飞越音效 public bool createShadow = true; // 是否创建阴影 public Pawn caster; // 施法者引用 // 属性 - 修改后的 DrawPos,包含进场动画 public override Vector3 DrawPos { get { // 线性插值计算基础位置 Vector3 start = startPosition.ToVector3(); Vector3 end = endPosition.ToVector3(); Vector3 basePos = Vector3.Lerp(start, end, currentProgress); // 添加高度偏移 basePos.y = altitude; // 应用进场动画偏移 if (useApproachAnimation && !approachCompleted) { basePos = ApplyApproachAnimation(basePos); } return basePos; } } // 进场动画位置计算 private Vector3 ApplyApproachAnimation(Vector3 basePos) { float approachProgress = currentApproachTime / approachDuration; // 使用缓动函数让移动更自然 float easedProgress = EasingFunction(approachProgress, EasingType.OutCubic); // 计算偏移方向(飞行方向的反方向) Vector3 approachDirection = -MovementDirection.normalized; // 计算偏移量:从最大偏移逐渐减少到0 float currentOffset = approachOffsetDistance * (1f - easedProgress); // 应用偏移 Vector3 offsetPos = basePos + approachDirection * currentOffset; return offsetPos; } // 缓动函数 - 让动画更自然 private float EasingFunction(float t, EasingType type) { switch (type) { case EasingType.OutCubic: return 1f - Mathf.Pow(1f - t, 3f); case EasingType.OutQuad: return 1f - (1f - t) * (1f - t); case EasingType.OutSine: return Mathf.Sin(t * Mathf.PI * 0.5f); default: return t; } } // 缓动类型枚举 private enum EasingType { Linear, OutQuad, OutCubic, OutSine } // 新增:进场动画进度(0-1) public float ApproachProgress { get { if (approachCompleted) return 1f; return Mathf.Clamp01(currentApproachTime / approachDuration); } } public override Graphic Graphic { get { Thing thingForGraphic = GetThingForGraphic(); if (thingForGraphic == this) { return base.Graphic; } return thingForGraphic.Graphic.ExtractInnerGraphicFor(thingForGraphic); } } protected Material ShadowMaterial { get { if (cachedShadowMaterial == null && createShadow) { cachedShadowMaterial = MaterialPool.MatFrom("Things/Skyfaller/SkyfallerShadowCircle", ShaderDatabase.Transparent); } return cachedShadowMaterial; } } // 精确旋转 - 模仿原版 Projectile public virtual Quaternion ExactRotation { get { Vector3 direction = (endPosition.ToVector3() - startPosition.ToVector3()).normalized; return Quaternion.LookRotation(direction.Yto0()); } } // 简化的方向计算方法 public Vector3 MovementDirection { get { return (endPosition.ToVector3() - startPosition.ToVector3()).normalized; } } // 修改后的淡入透明度属性 public float FadeInAlpha { get { if (!useFadeIn || fadeInCompleted) return 1f; return Mathf.Clamp01(currentFadeInTime / fadeInDuration); } } // 新增:计算剩余飞行时间(秒) public float RemainingFlightTime { get { float remainingProgress = 1f - currentProgress; return remainingProgress / (flightSpeed * 0.001f) * (1f / 60f); } } // 修改后的紧急销毁方法 - 急速加速版本 public void EmergencyDestroy() { if (Destroyed || hasCompleted) return; // 计算剩余进度 float remainingProgress = 1f - currentProgress; // 计算需要的速度:确保在1秒内完成剩余进度 float requiredSpeed = remainingProgress / 0.06f; // 设置新的飞行速度,确保至少是当前速度的2倍 flightSpeed = Mathf.Max(requiredSpeed, flightSpeed * 2f); // 标记为紧急销毁状态 hasCompleted = false; // 确保可以继续飞行 } // 修改后的淡出透明度属性 - 紧急销毁时强制启用淡出 public float FadeOutAlpha { get { // 如果已经开始了淡出(包括紧急销毁),就应用淡出效果 if (!fadeOutStarted) return 1f; if (fadeOutCompleted) return 0f; return Mathf.Clamp01(1f - (currentFadeOutTime / fadeOutDuration)); } } // 修改后的总体透明度属性 - 紧急销毁时强制计算淡出 public float OverallAlpha { get { if (!useFadeEffects && !fadeOutStarted) return 1f; return FadeInAlpha * FadeOutAlpha; } } // 修改后的 Tick 方法,优化紧急销毁逻辑 protected override void Tick() { base.Tick(); if (!hasStarted || hasCompleted) return; // 更新进场动画 if (useApproachAnimation && !approachCompleted) { currentApproachTime += 1f / 60f; if (currentApproachTime >= approachDuration) { approachCompleted = true; currentApproachTime = approachDuration; } } // 更新淡入效果(仅在启用时) if (useFadeIn && !fadeInCompleted) { currentFadeInTime += 1f / 60f; if (currentFadeInTime >= fadeInDuration) { fadeInCompleted = true; currentFadeInTime = fadeInDuration; } } // 更新飞行进度 currentProgress += flightSpeed * 0.001f; // 检查是否应该开始淡出(仅在启用时且未紧急销毁) if (useFadeOut && !fadeOutStarted && currentProgress >= fadeOutStartProgress) { StartFadeOut(); } // 更新淡出效果(仅在启用时) if (useFadeOut && fadeOutStarted && !fadeOutCompleted) { currentFadeOutTime += 1f / 60f; if (currentFadeOutTime >= fadeOutDuration) { fadeOutCompleted = true; currentFadeOutTime = fadeOutDuration; } } // 更新当前位置 UpdatePosition(); // 维持飞行音效 UpdateFlightSound(); // 检查是否到达终点 if (currentProgress >= 1f) { CompleteFlyOver(); return; // 立即返回,避免后续处理 } // 生成飞行轨迹特效 CreateFlightEffects(); } // 修改后的 CompleteFlyOver 方法,添加紧急销毁处理 private void CompleteFlyOver() { if (hasCompleted) return; hasCompleted = true; currentProgress = 1f; // 生成内容物(如果需要) if (spawnContentsOnImpact && innerContainer.Any) { SpawnContents(); } // 播放完成音效 if (def.skyfaller?.impactSound != null) { def.skyfaller.impactSound.PlayOneShot( SoundInfo.InMap(new TargetInfo(endPosition, base.Map))); } // 立即销毁 Destroy(); } // 修改后的 UpdatePosition 方法,添加安全检查 private void UpdatePosition() { if (hasCompleted) return; Vector3 currentWorldPos = Vector3.Lerp(startPosition.ToVector3(), endPosition.ToVector3(), currentProgress); IntVec3 newPos = currentWorldPos.ToIntVec3(); if (newPos != base.Position && newPos.InBounds(base.Map)) { base.Position = newPos; } } // 新增:计算基于剩余距离的淡出持续时间 private 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); } public override void ExposeData() { base.ExposeData(); Scribe_Deep.Look(ref innerContainer, "innerContainer", this); Scribe_Values.Look(ref startPosition, "startPosition"); Scribe_Values.Look(ref endPosition, "endPosition"); Scribe_Values.Look(ref flightSpeed, "flightSpeed", 1f); Scribe_Values.Look(ref currentProgress, "currentProgress", 0f); Scribe_Values.Look(ref altitude, "altitude", 10f); Scribe_Values.Look(ref hasStarted, "hasStarted", false); Scribe_Values.Look(ref hasCompleted, "hasCompleted", false); Scribe_Values.Look(ref spawnContentsOnImpact, "spawnContentsOnImpact", false); Scribe_Values.Look(ref fadeInDuration, "fadeInDuration", 1.5f); Scribe_Values.Look(ref currentFadeInTime, "currentFadeInTime", 0f); Scribe_Values.Look(ref fadeInCompleted, "fadeInCompleted", false); // 淡出效果数据保存 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); // 进场动画数据保存 Scribe_Values.Look(ref approachDuration, "approachDuration", 1.0f); Scribe_Values.Look(ref currentApproachTime, "currentApproachTime", 0f); Scribe_Values.Look(ref approachCompleted, "approachCompleted", false); Scribe_Values.Look(ref approachOffsetDistance, "approachOffsetDistance", 3f); Scribe_Values.Look(ref useApproachAnimation, "useApproachAnimation", true); // 新增:淡入淡出开关保存 Scribe_Values.Look(ref useFadeEffects, "useFadeEffects", true); Scribe_Values.Look(ref useFadeIn, "useFadeIn", true); Scribe_Values.Look(ref useFadeOut, "useFadeOut", true); Scribe_References.Look(ref caster, "caster"); Scribe_References.Look(ref faction, "faction"); } public override void SpawnSetup(Map map, bool respawningAfterLoad) { base.SpawnSetup(map, respawningAfterLoad); if (!respawningAfterLoad) { // 设置初始位置 base.Position = startPosition; hasStarted = true; // 从 ModExtension 加载配置 var extension = def.GetModExtension(); if (extension != null) { useApproachAnimation = extension.useApproachAnimation; approachDuration = extension.approachDuration; approachOffsetDistance = extension.approachOffsetDistance; // 加载淡入淡出配置 useFadeEffects = extension.useFadeEffects; useFadeIn = extension.useFadeIn; useFadeOut = extension.useFadeOut; // 设置淡入淡出持续时间 fadeInDuration = extension.defaultFadeInDuration; defaultFadeOutDuration = extension.defaultFadeOutDuration; fadeOutStartProgress = extension.fadeOutStartProgress; } // 重置淡入状态 currentFadeInTime = 0f; fadeInCompleted = !useFadeIn; // 如果不使用淡入,直接标记为完成 // 重置淡出状态 currentFadeOutTime = 0f; fadeOutStarted = false; fadeOutCompleted = false; fadeOutDuration = 0f; // 重置进场动画状态 currentApproachTime = 0f; approachCompleted = !useApproachAnimation; // 如果不使用进场动画,直接标记为完成 // 开始飞行音效 if (playFlyOverSound && def.skyfaller?.floatingSound != null) { flightSoundPlaying = def.skyfaller.floatingSound.TrySpawnSustainer( SoundInfo.InMap(new TargetInfo(startPosition, map), MaintenanceType.PerTick)); } } } // 新增:开始淡出效果 private void StartFadeOut() { fadeOutStarted = true; // 基于剩余距离动态计算淡出持续时间 fadeOutDuration = CalculateDynamicFadeOutDuration(); } // 修改后的 UpdateFlightSound 方法,添加紧急销毁时的音效处理 private void UpdateFlightSound() { if (flightSoundPlaying != null) { // 紧急销毁时提高音效音量或频率 if (flightSoundPlaying != null && flightSoundPlaying.externalParams != null) { // 可以根据需要调整紧急销毁时的音效参数 flightSoundPlaying.externalParams["VolumeFactor"] = 1f; // 保持最大音量 } flightSoundPlaying?.Maintain(); } } private void SpawnContents() { foreach (Thing thing in innerContainer) { if (thing != null && !thing.Destroyed) { GenPlace.TryPlaceThing(thing, endPosition, base.Map, ThingPlaceMode.Near); } } innerContainer.Clear(); } // 修改后的 CreateFlightEffects 方法,添加紧急销毁时的特效增强 private void CreateFlightEffects() { // 在飞行轨迹上生成粒子效果 if (Rand.MTBEventOccurs(0.5f, 1f, 1f) && def.skyfaller?.motesPerCell > 0) { Vector3 effectPos = DrawPos; effectPos.y = AltitudeLayer.MoteOverhead.AltitudeFor(); // 紧急销毁时增强粒子效果 float effectIntensity = 1f; FleckMaker.ThrowSmoke(effectPos, base.Map, 1f * effectIntensity); // 紧急销毁时生成更多效果 if (flightSpeed > 2f) { FleckMaker.ThrowAirPuffUp(effectPos, base.Map); } } } // 关键修复:重写 DrawAt 方法,绕过探索状态检查 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(); Graphic graphic = thingForGraphic.Graphic; if (graphic == null) return; Material material = graphic.MatSingle; if (material == null) return; float alpha = OverallAlpha; if (alpha <= 0.001f) return; if (fadeInCompleted && !fadeOutStarted && alpha >= 0.999f) { Vector3 highAltitudePos = drawPos; highAltitudePos.y = AltitudeLayer.MetaOverlays.AltitudeFor(); // 应用伴飞缩放 Vector3 finalScale = Vector3.one; if (def.graphicData != null) { finalScale = new Vector3(def.graphicData.drawSize.x * escortScale, 1f, def.graphicData.drawSize.y * escortScale); } else { finalScale = new Vector3(escortScale, 1f, escortScale); } Matrix4x4 matrix = Matrix4x4.TRS(highAltitudePos, ExactRotation, finalScale); Graphics.DrawMesh(MeshPool.plane10, matrix, material, 0); 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 * escortScale, 1f, def.graphicData.drawSize.y * escortScale); } else { scale = new Vector3(escortScale, 1f, escortScale); } Vector3 highPos = drawPos; highPos.y = AltitudeLayer.MetaOverlays.AltitudeFor(); Matrix4x4 matrix2 = Matrix4x4.TRS(highPos, ExactRotation, scale); Graphics.DrawMesh(MeshPool.plane10, matrix2, material, 0, null, 0, fadePropertyBlock); } protected virtual void DrawFlightShadow() { var shadowExtension = def.GetModExtension(); 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 shadowIntensity = shadowExtension?.shadowIntensity ?? 1f; float minAlpha = shadowExtension?.minShadowAlpha ?? 0.3f; float maxAlpha = shadowExtension?.maxShadowAlpha ?? 1f; float minScale = shadowExtension?.minShadowScale ?? 0.5f; float maxScale = shadowExtension?.maxShadowScale ?? 1.5f; float shadowAlpha = Mathf.Lerp(minAlpha, maxAlpha, currentProgress) * shadowIntensity; float shadowScale = Mathf.Lerp(minScale, maxScale, currentProgress); shadowAlpha *= OverallAlpha; if (shadowAlpha <= 0.001f) return; 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 接口实现 public ThingOwner GetDirectlyHeldThings() { return innerContainer; } public void GetChildHolders(List outChildren) { ThingOwnerUtility.AppendThingHoldersFromThings(outChildren, GetDirectlyHeldThings()); } private Thing GetThingForGraphic() { if (def.graphicData != null || !innerContainer.Any) { return this; } return innerContainer[0]; } // 修改后的 MakeFlyOver 方法,添加淡入淡出参数 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 defaultFadeOutDuration = 1.5f, Pawn casterPawn = null, bool useApproachAnimation = true, float approachDuration = 1.0f, float approachOffsetDistance = 3f, bool? useFadeEffects = null, bool? useFadeIn = null, bool? useFadeOut = null) // 新增参数 { FlyOver flyOver = (FlyOver)ThingMaker.MakeThing(flyOverDef); flyOver.startPosition = start; flyOver.endPosition = end; flyOver.flightSpeed = speed; flyOver.altitude = height; flyOver.fadeInDuration = fadeInDuration; flyOver.defaultFadeOutDuration = defaultFadeOutDuration; flyOver.caster = casterPawn; // 进场动画参数 flyOver.useApproachAnimation = useApproachAnimation; flyOver.approachDuration = approachDuration; flyOver.approachOffsetDistance = approachOffsetDistance; // 淡入淡出参数 - 新增 if (useFadeEffects.HasValue) flyOver.useFadeEffects = useFadeEffects.Value; if (useFadeIn.HasValue) flyOver.useFadeIn = useFadeIn.Value; if (useFadeOut.HasValue) flyOver.useFadeOut = useFadeOut.Value; // 简化派系设置 if (casterPawn != null && casterPawn.Faction != null) { flyOver.faction = casterPawn.Faction; } if (contents != null) { flyOver.innerContainer.TryAddRangeOrTransfer(contents); } GenSpawn.Spawn(flyOver, start, map); return flyOver; } } // 扩展的 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 defaultFadeOutDuration = 0.5f; public float fadeOutStartProgress = 0.98f; // 动态淡出配置 public float minFadeOutDuration = 0.5f; public float maxFadeOutDuration = 0.5f; public float fadeOutDistanceFactor = 0.01f; public float ActuallyHeight = 150f; // 进场动画配置 public bool useApproachAnimation = true; public float approachDuration = 1.0f; public float approachOffsetDistance = 3f; // 新增:淡入淡出开关 public bool useFadeEffects = true; // 是否启用淡入淡出效果 public bool useFadeIn = true; // 是否启用淡入效果 public bool useFadeOut = true; // 是否启用淡出效果 } }