diff --git a/1.6/1.6/Assemblies/WulaFallenEmpire.dll b/1.6/1.6/Assemblies/WulaFallenEmpire.dll index 018b8abd..f50252aa 100644 Binary files a/1.6/1.6/Assemblies/WulaFallenEmpire.dll and b/1.6/1.6/Assemblies/WulaFallenEmpire.dll differ diff --git a/1.6/1.6/Defs/ThingDefs_Buildings/WULA_Misc_Buildings.xml b/1.6/1.6/Defs/ThingDefs_Buildings/WULA_Misc_Buildings.xml index 400634de..203bea16 100644 --- a/1.6/1.6/Defs/ThingDefs_Buildings/WULA_Misc_Buildings.xml +++ b/1.6/1.6/Defs/ThingDefs_Buildings/WULA_Misc_Buildings.xml @@ -66,6 +66,56 @@ Thing + + WULA_Watch_Tower + + + WulaFallenEmpire.Building_MapObserver + true + Building + 50 + true + PassThroughOnly + 1 + (3,3) + true + (0.56, 0.62, 0.9) + + Wula/Building/WULA_Fake_Fighter_Drone_Building + Graphic_Single + TransparentPostLight + (3,3) + (195,195,195,255) + + + 100 + 0.5 + 36000 + 125 + 0.65 + + WULA_Buildings + Normal + true + true + false + East + true + Light + BulletImpact_Metal + true + RealtimeOnly + ConstructMetal + true + + BuildingDestroyed_Metal_Big + true + true + + + + + diff --git a/1.6/1.6/Defs/ThingDefs_Misc/WULA_Flyover_Item.xml b/1.6/1.6/Defs/ThingDefs_Misc/WULA_Flyover_Item.xml index 0d91548c..5bf8aa46 100644 --- a/1.6/1.6/Defs/ThingDefs_Misc/WULA_Flyover_Item.xml +++ b/1.6/1.6/Defs/ThingDefs_Misc/WULA_Flyover_Item.xml @@ -877,12 +877,6 @@ - - WULA_Firepower_Minigun_Strafe_Damage - - 25 - Explosion_Bomb - WULA_EnergyLance_Base diff --git a/Source/WulaFallenEmpire/BuildingComp/Building_MapObserver.cs b/Source/WulaFallenEmpire/BuildingComp/Building_MapObserver.cs new file mode 100644 index 00000000..63d3a62a --- /dev/null +++ b/Source/WulaFallenEmpire/BuildingComp/Building_MapObserver.cs @@ -0,0 +1,230 @@ +using System.Collections.Generic; +using RimWorld; +using RimWorld.Planet; +using UnityEngine; +using Verse; + +namespace WulaFallenEmpire +{ + public class Building_MapObserver : Building + { + public MapParent observedMap; + + private CompPowerTrader compPower; + + // 静态列表跟踪所有活跃的观察者 + public static HashSet activeObservers = new HashSet(); + + public override void SpawnSetup(Map map, bool respawningAfterLoad) + { + base.SpawnSetup(map, respawningAfterLoad); + compPower = this.TryGetComp(); + + // 如果正在观察地图且建筑正常,注册到活跃列表 + if (observedMap != null && (compPower == null || compPower.PowerOn)) + { + activeObservers.Add(this); + } + } + + public override void DeSpawn(DestroyMode mode = DestroyMode.Vanish) + { + // 建筑被销毁时停止监测 + DisposeObservedMapIfEmpty(); + activeObservers.Remove(this); + base.DeSpawn(mode); + } + + public override IEnumerable GetGizmos() + { + foreach (Gizmo gizmo in base.GetGizmos()) + { + yield return gizmo; + } + + // 只有在有电力且属于玩家时才显示控制按钮 + if (Faction == Faction.OfPlayer && (compPower == null || compPower.PowerOn)) + { + // 开始监测按钮 + yield return new Command_Action + { + defaultLabel = "开始监测地图", + defaultDesc = "选择一个世界位置进行监测", + icon = ContentFinder.Get("UI/Commands/ShowMap"), + action = delegate + { + CameraJumper.TryShowWorld(); + Find.WorldTargeter.BeginTargeting(ChooseWorldTarget, canTargetTiles: true); + } + }; + + // 如果正在监测地图,显示停止按钮 + if (observedMap != null) + { + if (observedMap.Destroyed) + { + observedMap = null; + activeObservers.Remove(this); + } + else + { + yield return new Command_Action + { + defaultLabel = "停止监测", + defaultDesc = $"停止监测 {observedMap.Label}", + icon = ContentFinder.Get("UI/Commands/DesirePower"), + action = delegate + { + StopObserving(); + } + }; + } + } + } + } + + private bool ChooseWorldTarget(GlobalTargetInfo target) + { + DisposeObservedMapIfEmpty(); + + if (target.WorldObject != null && target.WorldObject is MapParent mapParent) + { + // 开始监测选中的地图 + observedMap = mapParent; + activeObservers.Add(this); + + // 确保地图被生成并取消迷雾 + LongEventHandler.QueueLongEvent(delegate + { + Map map = GetOrGenerateMapUtility.GetOrGenerateMap(target.Tile, null); + if (map != null) + { + // 取消迷雾获得完整视野 + map.fogGrid.ClearAllFog(); + + // 记录日志以便调试 + Log.Message($"[MapObserver] 开始监测地图: {mapParent.Label} at tile {target.Tile}"); + } + }, "GeneratingMap", doAsynchronously: false, null); + + return true; + } + + // 在空地创建新监测点 + if (target.WorldObject == null && !Find.World.Impassable(target.Tile)) + { + LongEventHandler.QueueLongEvent(delegate + { + // 创建新的玩家定居点用于监测 + SettleUtility.AddNewHome(target.Tile, Faction.OfPlayer); + Map map = GetOrGenerateMapUtility.GetOrGenerateMap(target.Tile, Find.World.info.initialMapSize, null); + observedMap = map.Parent; + activeObservers.Add(this); + + // 取消迷雾获得完整视野 + map.fogGrid.ClearAllFog(); + + // 设置监测点名称 + if (observedMap is Settlement settlement) + { + settlement.Name = $"监测点-{thingIDNumber}"; + Log.Message($"[MapObserver] 创建新监测点: {settlement.Name} at tile {target.Tile}"); + } + else + { + // 如果observedMap不是Settlement,使用Label属性 + Log.Message($"[MapObserver] 创建新监测点: {observedMap.Label} at tile {target.Tile}"); + } + + }, "GeneratingMap", doAsynchronously: false, null); + + return true; + } + + Messages.Message("无法监测该位置", MessageTypeDefOf.RejectInput); + return false; + } + + private void StopObserving() + { + DisposeObservedMapIfEmpty(); + observedMap = null; + activeObservers.Remove(this); + } + + private void DisposeObservedMapIfEmpty() + { + if (observedMap != null && observedMap.Map != null && + !observedMap.Map.mapPawns.AnyColonistSpawned && + !observedMap.Map.listerBuildings.allBuildingsColonist.Any() && + observedMap.Faction == Faction.OfPlayer) + { + // 只有在没有殖民者、没有玩家建筑的情况下才销毁 + Current.Game.DeinitAndRemoveMap(observedMap.Map, notifyPlayer: false); + if (!observedMap.Destroyed) + { + Find.World.worldObjects.Remove(observedMap); + } + Log.Message($"[MapObserver] 清理空置监测地图: {observedMap.Label}"); + } + } + + protected override void ReceiveCompSignal(string signal) + { + base.ReceiveCompSignal(signal); + + // 断电或被关闭时停止监测 + if (observedMap != null && (signal == "PowerTurnedOff" || signal == "FlickedOff")) + { + Log.Message($"[MapObserver] 电力中断,停止监测: {observedMap.Label}"); + StopObserving(); + } + // 恢复电力时重新注册 + else if (observedMap != null && (signal == "PowerTurnedOn" || signal == "FlickedOn")) + { + activeObservers.Add(this); + Log.Message($"[MapObserver] 电力恢复,继续监测: {observedMap.Label}"); + } + } + + public override string GetInspectString() + { + string text = base.GetInspectString(); + + if (observedMap != null) + { + if (!text.NullOrEmpty()) + { + text += "\n"; + } + text += $"正在监测: {observedMap.Label}"; + + // 显示电力状态 + if (compPower != null && !compPower.PowerOn) + { + text += " (电力中断)"; + } + } + + return text; + } + + public override void ExposeData() + { + base.ExposeData(); + Scribe_References.Look(ref observedMap, "observedMap"); + + // 加载后重新注册到活跃列表 + if (Scribe.mode == LoadSaveMode.PostLoadInit && observedMap != null && (compPower == null || compPower.PowerOn)) + { + activeObservers.Add(this); + } + } + + // 检查这个观察者是否正在监测指定的地图 + public bool IsObservingMap(MapParent mapParent) + { + return observedMap == mapParent && (compPower == null || compPower.PowerOn); + } + } +} \ No newline at end of file diff --git a/Source/WulaFallenEmpire/BuildingComp/WULA_FlyOverBeacon/Building_FlyOverBeacon.cs b/Source/WulaFallenEmpire/BuildingComp/WULA_FlyOverBeacon/Building_FlyOverBeacon.cs new file mode 100644 index 00000000..db52a936 --- /dev/null +++ b/Source/WulaFallenEmpire/BuildingComp/WULA_FlyOverBeacon/Building_FlyOverBeacon.cs @@ -0,0 +1,480 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; +using Verse; +using Verse.AI; +using Verse.Sound; +using RimWorld; +using RimWorld.Planet; + +namespace WulaFallenEmpire +{ + [StaticConstructorOnStartup] + public class Building_FlyOverBeacon : Building + { + public GlobalTargetInfo flyOverTarget; + public FlyOverConfig flyOverConfig; + public static readonly Texture2D CallFlyOverTex = ContentFinder.Get("UI/Commands/CallFlyOver", true); + + private CompPowerTrader powerComp; + private CompRefuelable refuelableComp; + private int cooldownTicksLeft; + + public bool IsReady => (powerComp == null || powerComp.PowerOn) && + (refuelableComp == null || refuelableComp.HasFuel) && + cooldownTicksLeft <= 0; + + public override void SpawnSetup(Map map, bool respawningAfterLoad) + { + base.SpawnSetup(map, respawningAfterLoad); + powerComp = this.TryGetComp(); + refuelableComp = this.TryGetComp(); + + if (!respawningAfterLoad) + { + flyOverTarget = GlobalTargetInfo.Invalid; + flyOverConfig = new FlyOverConfig(); + } + } + + public override void ExposeData() + { + base.ExposeData(); + Scribe_TargetInfo.Look(ref flyOverTarget, "flyOverTarget"); + Scribe_Deep.Look(ref flyOverConfig, "flyOverConfig"); + Scribe_Values.Look(ref cooldownTicksLeft, "cooldownTicksLeft", 0); + } + + protected override void Tick() + { + base.Tick(); + + // 冷却计时 + if (cooldownTicksLeft > 0) + cooldownTicksLeft--; + + // 自动执行已设定的飞越任务 + if (flyOverTarget.IsValid && IsReady) + { + ExecuteFlyOverMission(); + } + } + + public override string GetInspectString() + { + StringBuilder sb = new StringBuilder(base.GetInspectString()); + + if (cooldownTicksLeft > 0) + { + if (sb.Length > 0) sb.AppendLine(); + sb.Append("飞越信标冷却中: ".Translate() + cooldownTicksLeft.ToStringTicksToPeriod()); + } + + if (flyOverTarget.IsValid) + { + if (sb.Length > 0) sb.AppendLine(); + sb.Append("已锁定目标: ".Translate() + flyOverTarget.Label); + } + + if (!IsReady) + { + if (sb.Length > 0) sb.AppendLine(); + if (powerComp != null && !powerComp.PowerOn) + sb.Append("需要电力".Translate()); + else if (refuelableComp != null && !refuelableComp.HasFuel) + sb.Append("需要燃料".Translate()); + else if (cooldownTicksLeft > 0) + sb.Append("冷却中".Translate()); + } + + return sb.ToString(); + } + + public override IEnumerable GetGizmos() + { + foreach (var g in base.GetGizmos()) + { + yield return g; + } + + // 召唤飞越命令 + Command_Action callFlyOver = new Command_Action + { + defaultLabel = "召唤跨图飞越", + defaultDesc = "在世界地图上选择目标位置召唤飞越单位", + icon = CallFlyOverTex, + action = StartChoosingFlyOverTarget + }; + + if (!IsReady) + { + callFlyOver.Disable(GetDisabledReason()); + } + yield return callFlyOver; + + // 配置飞越参数命令 + if (flyOverTarget.IsValid) + { + Command_Action configureFlyOver = new Command_Action + { + defaultLabel = "配置飞越参数", + defaultDesc = "调整飞越单位的类型和行为", + icon = ContentFinder.Get("UI/Commands/Configure", true), + action = OpenConfigurationDialog + }; + yield return configureFlyOver; + + // 清除目标命令 + Command_Action clearTarget = new Command_Action + { + defaultLabel = "清除目标", + defaultDesc = "清除已锁定的飞越目标", + icon = ContentFinder.Get("UI/Designators/Cancel", true), + action = () => flyOverTarget = GlobalTargetInfo.Invalid + }; + yield return clearTarget; + } + } + + private string GetDisabledReason() + { + if (powerComp != null && !powerComp.PowerOn) + return "需要电力"; + if (refuelableComp != null && !refuelableComp.HasFuel) + return "需要燃料"; + if (cooldownTicksLeft > 0) + return "冷却中"; + return "无法使用"; + } + + private void StartChoosingFlyOverTarget() + { + CameraJumper.TryJump(CameraJumper.GetWorldTarget(this)); + Find.WorldSelector.ClearSelection(); + + Find.WorldTargeter.BeginTargeting( + ChooseWorldTarget, + canTargetTiles: true, + targeterMouseAttachment: CallFlyOverTex, + extraLabelGetter: GetExtraTargetingLabel + ); + } + + private string GetExtraTargetingLabel(GlobalTargetInfo target) + { + if (target.IsValid) + { + return "召唤飞越至: " + target.Label; + } + return "选择飞越目标位置"; + } + + private bool ChooseWorldTarget(GlobalTargetInfo target) + { + if (!target.IsValid) + { + Messages.Message("无效的目标位置", MessageTypeDefOf.RejectInput); + return false; + } + + if (target.Tile == this.Map.Tile) + { + Messages.Message("无法在同一地图召唤飞越", MessageTypeDefOf.RejectInput); + return false; + } + + // 处理不同类型的目标 + if (target.WorldObject is MapParent mapParent && mapParent.HasMap) + { + // 切换到目标地图选择具体飞越路径 + var originalMap = this.Map; + Action onFinished = () => { + if (Current.Game.CurrentMap != originalMap) + Current.Game.CurrentMap = originalMap; + }; + + Current.Game.CurrentMap = mapParent.Map; + Find.Targeter.BeginTargeting( + new TargetingParameters + { + canTargetLocations = true, + canTargetPawns = false, + canTargetBuildings = false, + mapObjectTargetsMustBeAutoAttackable = false + }, + (LocalTargetInfo localTarget) => + { + // 设置飞越配置 + flyOverTarget = new GlobalTargetInfo(localTarget.Cell, mapParent.Map); + flyOverConfig.targetCell = localTarget.Cell; + flyOverConfig.targetMap = mapParent.Map; + + // 自动计算飞越路径 + CalculateFlyOverPath(mapParent.Map, localTarget.Cell); + }, + null, onFinished, CallFlyOverTex, true); + } + else + { + // 空地目标,生成临时地图 + flyOverTarget = target; + flyOverConfig.targetTile = target.Tile; + CalculateFlyOverPathForTile(target.Tile); + } + + return true; + } + + private void CalculateFlyOverPath(Map targetMap, IntVec3 targetCell) + { + // 计算从地图边缘到目标点的飞越路径 + Vector3 targetPos = targetCell.ToVector3(); + + // 随机选择进入方向 + Vector3[] approachDirections = { + new Vector3(1, 0, 0), // 从右边进入 + new Vector3(-1, 0, 0), // 从左边进入 + new Vector3(0, 0, 1), // 从上边进入 + new Vector3(0, 0, -1) // 从下边进入 + }; + + Vector3 approachDir = approachDirections.RandomElement(); + Vector3 startPos = FindMapEdgePosition(targetMap, approachDir); + Vector3 endPos = FindMapEdgePosition(targetMap, -approachDir); // 从对面飞出 + + flyOverConfig.startPos = startPos.ToIntVec3(); + flyOverConfig.endPos = endPos.ToIntVec3(); + flyOverConfig.approachType = FlyOverApproachType.StraightLine; + + Log.Message($"Calculated flyover path: {flyOverConfig.startPos} -> {targetCell} -> {flyOverConfig.endPos}"); + } + + private void CalculateFlyOverPathForTile(int tile) + { + // 为地图瓦片计算默认飞越路径(穿越地图中心) + Map targetMap = GetOrGenerateMapUtility.GetOrGenerateMap(tile, Find.World.info.initialMapSize, null); + if (targetMap != null) + { + targetMap.fogGrid.ClearAllFog(); // 获得视野 + + IntVec3 center = targetMap.Center; + CalculateFlyOverPath(targetMap, center); + } + } + + private IntVec3 FindMapEdgePosition(Map map, Vector3 direction) + { + // 找到地图边缘位置 + Vector3 center = map.Center.ToVector3(); + Vector3 edgePos = center; + float maxDistance = Mathf.Max(map.Size.x, map.Size.z) * 0.6f; + + for (int i = 1; i <= maxDistance; i++) + { + Vector3 testPos = center + direction.normalized * i; + IntVec3 testCell = new IntVec3((int)testPos.x, 0, (int)testPos.z); + + if (!testCell.InBounds(map)) + { + // 找到最近的边界内单元格 + return FindClosestValidPosition(testCell, map); + } + } + + return map.Center; + } + + private IntVec3 FindClosestValidPosition(IntVec3 invalidPos, Map map) + { + for (int radius = 1; radius <= 10; radius++) + { + foreach (IntVec3 offset in GenRadial.RadialPatternInRadius(radius)) + { + IntVec3 testPos = invalidPos + offset; + if (testPos.InBounds(map)) + return testPos; + } + } + return map.Center; + } + + private void OpenConfigurationDialog() + { + // 打开飞越配置对话框 + List options = new List + { + new FloatMenuOption("标准侦察飞越", () => ConfigureStandardRecon()), + new FloatMenuOption("地面扫射飞越", () => ConfigureGroundStrafing()), + new FloatMenuOption("监视巡逻飞越", () => ConfigureSurveillance()), + new FloatMenuOption("货运投送飞越", () => ConfigureCargoDrop()) + }; + + Find.WindowStack.Add(new FloatMenu(options)); + } + + private void ConfigureStandardRecon() + { + flyOverConfig.flyOverType = FlyOverType.Standard; + flyOverConfig.flyOverDef = DefDatabase.GetNamedSilentFail("ARA_HiveScout"); + flyOverConfig.altitude = 20f; + flyOverConfig.flightSpeed = 1.2f; + Messages.Message("已配置为标准侦察飞越", MessageTypeDefOf.TaskCompletion); + } + + private void ConfigureGroundStrafing() + { + flyOverConfig.flyOverType = FlyOverType.GroundStrafing; + flyOverConfig.flyOverDef = DefDatabase.GetNamedSilentFail("ARA_HiveGunship"); + flyOverConfig.altitude = 10f; + flyOverConfig.flightSpeed = 0.8f; + flyOverConfig.enableStrafing = true; + Messages.Message("已配置为地面扫射飞越", MessageTypeDefOf.TaskCompletion); + } + + private void ConfigureSurveillance() + { + flyOverConfig.flyOverType = FlyOverType.Surveillance; + flyOverConfig.flyOverDef = DefDatabase.GetNamedSilentFail("ARA_HiveObserver"); + flyOverConfig.altitude = 25f; + flyOverConfig.flightSpeed = 0.6f; + flyOverConfig.enableSurveillance = true; + Messages.Message("已配置为监视巡逻飞越", MessageTypeDefOf.TaskCompletion); + } + + private void ConfigureCargoDrop() + { + flyOverConfig.flyOverType = FlyOverType.CargoDrop; + flyOverConfig.flyOverDef = DefDatabase.GetNamedSilentFail("ARA_HiveTransport"); + flyOverConfig.altitude = 15f; + flyOverConfig.flightSpeed = 1.0f; + flyOverConfig.dropContentsOnImpact = true; + Messages.Message("已配置为货运投送飞越", MessageTypeDefOf.TaskCompletion); + } + + public void ExecuteFlyOverMission() + { + if (!IsReady) return; + + try + { + Log.Message($"[FlyOverBeacon] Executing flyover mission to {flyOverTarget.Label}"); + + // 创建世界飞越载体 + WorldObject_FlyOverCarrier carrier = (WorldObject_FlyOverCarrier)WorldObjectMaker.MakeWorldObject( + DefDatabase.GetNamed("FlyOverCarrier") + ); + + carrier.Tile = this.Map.Tile; + carrier.destinationTile = flyOverTarget.Tile; + carrier.flyOverConfig = flyOverConfig.Clone(); + carrier.sourceBeacon = this; + + Find.WorldObjects.Add(carrier); + + // 本地视觉效果 + CreateLocalLaunchEffects(); + + // 资源消耗 + if (refuelableComp != null) + refuelableComp.ConsumeFuel(1); + + // 进入冷却 + cooldownTicksLeft = 6000; // 1分钟冷却 + + Messages.Message($"飞越单位已派遣至 {flyOverTarget.Label}", MessageTypeDefOf.PositiveEvent); + + } + catch (Exception ex) + { + Log.Error($"Error executing flyover mission: {ex}"); + Messages.Message("飞越任务执行失败", MessageTypeDefOf.NegativeEvent); + } + } + + private void CreateLocalLaunchEffects() + { + // 发射视觉效果 + MoteMaker.MakeStaticMote(this.DrawPos, this.Map, ThingDefOf.Mote_ExplosionFlash, 3f); + + for (int i = 0; i < 3; i++) + { + FleckMaker.ThrowSmoke(this.DrawPos, this.Map, 2f); + FleckMaker.ThrowLightningGlow(this.DrawPos, this.Map, 1.5f); + } + + // 发射音效 + SoundDefOf.PsychicPulseGlobal.PlayOneShot(new TargetInfo(this.Position, this.Map)); + } + } + + // 飞越配置类 + public class FlyOverConfig : IExposable + { + public ThingDef flyOverDef; + public FlyOverType flyOverType = FlyOverType.Standard; + public IntVec3 startPos; + public IntVec3 endPos; + public IntVec3 targetCell; + public Map targetMap; + public int targetTile = -1; + public float flightSpeed = 1f; + public float altitude = 15f; + public bool dropContentsOnImpact = false; + public bool enableStrafing = false; + public bool enableSurveillance = false; + public FlyOverApproachType approachType = FlyOverApproachType.StraightLine; + + public void ExposeData() + { + Scribe_Defs.Look(ref flyOverDef, "flyOverDef"); + Scribe_Values.Look(ref flyOverType, "flyOverType", FlyOverType.Standard); + Scribe_Values.Look(ref startPos, "startPos"); + Scribe_Values.Look(ref endPos, "endPos"); + Scribe_Values.Look(ref targetCell, "targetCell"); + Scribe_References.Look(ref targetMap, "targetMap"); + Scribe_Values.Look(ref targetTile, "targetTile", -1); + Scribe_Values.Look(ref flightSpeed, "flightSpeed", 1f); + Scribe_Values.Look(ref altitude, "altitude", 15f); + Scribe_Values.Look(ref dropContentsOnImpact, "dropContentsOnImpact", false); + Scribe_Values.Look(ref enableStrafing, "enableStrafing", false); + Scribe_Values.Look(ref enableSurveillance, "enableSurveillance", false); + Scribe_Values.Look(ref approachType, "approachType", FlyOverApproachType.StraightLine); + } + + public FlyOverConfig Clone() + { + return new FlyOverConfig + { + flyOverDef = this.flyOverDef, + flyOverType = this.flyOverType, + startPos = this.startPos, + endPos = this.endPos, + targetCell = this.targetCell, + targetMap = this.targetMap, + targetTile = this.targetTile, + flightSpeed = this.flightSpeed, + altitude = this.altitude, + dropContentsOnImpact = this.dropContentsOnImpact, + enableStrafing = this.enableStrafing, + enableSurveillance = this.enableSurveillance, + approachType = this.approachType + }; + } + } + + public enum FlyOverType + { + Standard, + GroundStrafing, + Surveillance, + CargoDrop + } + + public enum FlyOverApproachType + { + StraightLine, + CirclePattern, + FigureEight + } +} diff --git a/Source/WulaFallenEmpire/BuildingComp/WULA_FlyOverBeacon/WorldObject_FlyOverCarrier.cs b/Source/WulaFallenEmpire/BuildingComp/WULA_FlyOverBeacon/WorldObject_FlyOverCarrier.cs new file mode 100644 index 00000000..92a533b6 --- /dev/null +++ b/Source/WulaFallenEmpire/BuildingComp/WULA_FlyOverBeacon/WorldObject_FlyOverCarrier.cs @@ -0,0 +1,241 @@ +using RimWorld.Planet; +using UnityEngine; +using Verse; +using RimWorld; +using System.Collections.Generic; + +namespace WulaFallenEmpire +{ + public class WorldObject_FlyOverCarrier : WorldObject + { + public int destinationTile = -1; + public FlyOverConfig flyOverConfig; + public Building_FlyOverBeacon sourceBeacon; + + private int initialTile = -1; + private float traveledPct; + private const float TravelSpeed = 0.0001f; // 比导弹慢,适合侦察 + + public override void ExposeData() + { + base.ExposeData(); + Scribe_Values.Look(ref destinationTile, "destinationTile", -1); + Scribe_Deep.Look(ref flyOverConfig, "flyOverConfig"); + Scribe_References.Look(ref sourceBeacon, "sourceBeacon"); + Scribe_Values.Look(ref initialTile, "initialTile", -1); + Scribe_Values.Look(ref traveledPct, "traveledPct", 0f); + } + + public override void PostAdd() + { + base.PostAdd(); + this.initialTile = this.Tile; + Log.Message($"[FlyOverCarrier] Launched from tile {initialTile} to {destinationTile}"); + } + + private Vector3 StartPos => Find.WorldGrid.GetTileCenter(this.initialTile); + private Vector3 EndPos => Find.WorldGrid.GetTileCenter(this.destinationTile); + + public override Vector3 DrawPos => Vector3.Slerp(StartPos, EndPos, traveledPct); + + protected override void Tick() + { + base.Tick(); + + if (this.destinationTile < 0) + { + Log.Error("FlyOverCarrier has invalid destination tile"); + Find.WorldObjects.Remove(this); + return; + } + + float distance = GenMath.SphericalDistance(StartPos.normalized, EndPos.normalized); + if (distance > 0) + { + traveledPct += TravelSpeed / distance; + } + else + { + traveledPct = 1; + } + + // 更新世界图标位置 + if (Find.WorldRenderer != null) + { + Find.WorldRenderer.Notify_WorldObjectPosChanged(this); + } + + if (traveledPct >= 1f) + { + Arrived(); + } + } + + private void Arrived() + { + Log.Message($"[FlyOverCarrier] Arrived at destination tile {destinationTile}"); + + Map targetMap = GetTargetMap(); + if (targetMap != null) + { + CreateFlyOverInTargetMap(targetMap); + } + else + { + Log.Warning($"[FlyOverCarrier] Could not find or generate map for tile {destinationTile}"); + } + + // 通知源信标任务完成 + if (sourceBeacon != null && !sourceBeacon.Destroyed) + { + Messages.Message($"飞越单位已到达 {flyOverConfig.targetMap?.Parent?.Label ?? "目标区域"}", + sourceBeacon, MessageTypeDefOf.PositiveEvent); + } + + Find.WorldObjects.Remove(this); + } + + private Map GetTargetMap() + { + // 优先使用配置中的目标地图 + if (flyOverConfig.targetMap != null && !flyOverConfig.targetMap.Destroyed) + { + return flyOverConfig.targetMap; + } + + // 生成临时地图 + return GetOrGenerateMapUtility.GetOrGenerateMap(destinationTile, Find.World.info.initialMapSize, null); + } + + private void CreateFlyOverInTargetMap(Map targetMap) + { + if (flyOverConfig.flyOverDef == null) + { + Log.Warning("[FlyOverCarrier] No fly over def specified, using default"); + flyOverConfig.flyOverDef = DefDatabase.GetNamedSilentFail("ARA_HiveScout"); + } + + // 确保目标地图有视野 + targetMap.fogGrid.ClearAllFog(); + + // 验证并调整飞越路径 + IntVec3 startPos = ValidateAndAdjustPosition(flyOverConfig.startPos, targetMap); + IntVec3 endPos = ValidateAndAdjustPosition(flyOverConfig.endPos, targetMap); + + Log.Message($"[FlyOverCarrier] Creating flyover: {startPos} -> {endPos} in {targetMap}"); + + // 创建飞越物体 + FlyOver flyOver = FlyOver.MakeFlyOver( + flyOverConfig.flyOverDef, + startPos, + endPos, + targetMap, + flyOverConfig.flightSpeed, + flyOverConfig.altitude, + casterPawn: null + ); + + // 配置飞越属性 + flyOver.spawnContentsOnImpact = flyOverConfig.dropContentsOnImpact; + flyOver.playFlyOverSound = true; + flyOver.faction = sourceBeacon?.Faction; + + // 配置特殊组件 + ConfigureFlyOverComponents(flyOver); + + // 创建到达视觉效果 + CreateArrivalEffects(targetMap, startPos); + } + + private IntVec3 ValidateAndAdjustPosition(IntVec3 pos, Map map) + { + if (pos.IsValid && pos.InBounds(map)) + return pos; + + // 如果位置无效,使用地图边缘位置 + return CellFinder.RandomEdgeCell(Rand.Range(0, 4), map); + } + + private void ConfigureFlyOverComponents(FlyOver flyOver) + { + // 地面扫射配置 + if (flyOverConfig.enableStrafing) + { + CompGroundStrafing strafingComp = flyOver.GetComp(); + if (strafingComp != null) + { + // 计算扫射区域 + Vector3 flightDirection = (flyOverConfig.endPos.ToVector3() - flyOverConfig.startPos.ToVector3()).normalized; + List impactCells = CalculateStrafingImpactCells(flyOverConfig.targetCell, flightDirection); + List confirmedTargets = PreprocessStrafingTargets(impactCells, 0.7f); + + strafingComp.SetConfirmedTargets(confirmedTargets); + } + } + + // 监视功能配置 + if (flyOverConfig.enableSurveillance) + { + // 可以在这里添加监视组件的配置 + Log.Message("[FlyOverCarrier] Surveillance mode configured"); + } + } + + private List CalculateStrafingImpactCells(IntVec3 targetCell, Vector3 flightDirection) + { + // 简化的扫射区域计算 + List cells = new List(); + Map map = Find.CurrentMap; + + if (map != null) + { + Vector3 perpendicular = new Vector3(-flightDirection.z, 0f, flightDirection.x).normalized; + + for (int i = -2; i <= 2; i++) + { + for (int j = -5; j <= 5; j++) + { + Vector3 offset = perpendicular * i + flightDirection * j; + IntVec3 cell = targetCell + new IntVec3((int)offset.x, 0, (int)offset.z); + + if (cell.InBounds(map)) + { + cells.Add(cell); + } + } + } + } + + return cells; + } + + private List PreprocessStrafingTargets(List potentialTargets, float fireChance) + { + List confirmedTargets = new List(); + + foreach (IntVec3 cell in potentialTargets) + { + if (Rand.Value <= fireChance) + { + confirmedTargets.Add(cell); + } + } + + return confirmedTargets; + } + + private void CreateArrivalEffects(Map targetMap, IntVec3 entryPos) + { + // 进入视觉效果 + MoteMaker.MakeStaticMote(entryPos.ToVector3Shifted(), targetMap, ThingDefOf.Mote_PsycastAreaEffect, 2f); + + for (int i = 0; i < 5; i++) + { + FleckMaker.ThrowAirPuffUp(entryPos.ToVector3Shifted(), targetMap); + } + + // 进入音效 + SoundDefOf.PsychicPulse.PlayOneShot(new TargetInfo(entryPos, targetMap)); + } + } +} diff --git a/Source/WulaFallenEmpire/EventSystem/Effect.cs b/Source/WulaFallenEmpire/EventSystem/Effect/EffectBase.cs similarity index 95% rename from Source/WulaFallenEmpire/EventSystem/Effect.cs rename to Source/WulaFallenEmpire/EventSystem/Effect/EffectBase.cs index 1645269b..02288374 100644 --- a/Source/WulaFallenEmpire/EventSystem/Effect.cs +++ b/Source/WulaFallenEmpire/EventSystem/Effect/EffectBase.cs @@ -7,13 +7,13 @@ using RimWorld; namespace WulaFallenEmpire { - public abstract class Effect + public abstract class EffectBase { public float weight = 1.0f; public abstract void Execute(Window dialog = null); } - public class Effect_OpenCustomUI : Effect + public class Effect_OpenCustomUI : EffectBase { public string defName; public int delayTicks = 0; @@ -88,7 +88,7 @@ namespace WulaFallenEmpire } } - public class Effect_CloseDialog : Effect + public class Effect_CloseDialog : EffectBase { public override void Execute(Window dialog = null) { @@ -96,7 +96,7 @@ namespace WulaFallenEmpire } } - public class Effect_ShowMessage : Effect + public class Effect_ShowMessage : EffectBase { public string message; public MessageTypeDef messageTypeDef; @@ -111,7 +111,7 @@ namespace WulaFallenEmpire } } - public class Effect_FireIncident : Effect + public class Effect_FireIncident : EffectBase { public IncidentDef incident; @@ -136,7 +136,7 @@ namespace WulaFallenEmpire } } - public class Effect_ChangeFactionRelation : Effect + public class Effect_ChangeFactionRelation : EffectBase { public FactionDef faction; public int goodwillChange; @@ -160,7 +160,7 @@ namespace WulaFallenEmpire } } - public class Effect_SetVariable : Effect + public class Effect_SetVariable : EffectBase { public string name; public string value; @@ -195,7 +195,7 @@ namespace WulaFallenEmpire } } - public class Effect_ChangeFactionRelation_FromVariable : Effect + public class Effect_ChangeFactionRelation_FromVariable : EffectBase { public FactionDef faction; public string goodwillVariableName; @@ -220,7 +220,7 @@ namespace WulaFallenEmpire } } - public class Effect_SpawnPawnAndStore : Effect + public class Effect_SpawnPawnAndStore : EffectBase { public PawnKindDef kindDef; public int count = 1; @@ -260,7 +260,7 @@ namespace WulaFallenEmpire } } - public class Effect_GiveThing : Effect + public class Effect_GiveThing : EffectBase { public ThingDef thingDef; public int count = 1; @@ -290,7 +290,7 @@ namespace WulaFallenEmpire } } - public class Effect_SpawnPawn : Effect + public class Effect_SpawnPawn : EffectBase { public PawnKindDef kindDef; public int count = 1; @@ -350,7 +350,7 @@ namespace WulaFallenEmpire Divide } - public class Effect_ModifyVariable : Effect + public class Effect_ModifyVariable : EffectBase { public string name; public string value; @@ -431,7 +431,7 @@ namespace WulaFallenEmpire } } - public class Effect_ClearVariable : Effect + public class Effect_ClearVariable : EffectBase { public string name; @@ -446,7 +446,7 @@ namespace WulaFallenEmpire } } - public class Effect_AddQuest : Effect + public class Effect_AddQuest : EffectBase { public QuestScriptDef quest; @@ -465,7 +465,7 @@ namespace WulaFallenEmpire } } - public class Effect_FinishResearch : Effect + public class Effect_FinishResearch : EffectBase { public ResearchProjectDef research; @@ -480,7 +480,7 @@ namespace WulaFallenEmpire Find.ResearchManager.FinishProject(research); } } - public class Effect_TriggerRaid : Effect + public class Effect_TriggerRaid : EffectBase { public float points; public FactionDef faction; @@ -559,7 +559,7 @@ namespace WulaFallenEmpire } } - public class Effect_CheckFactionGoodwill : Effect + public class Effect_CheckFactionGoodwill : EffectBase { public FactionDef factionDef; public string variableName; @@ -589,7 +589,7 @@ namespace WulaFallenEmpire } } - public class Effect_StoreRealPlayTime : Effect + public class Effect_StoreRealPlayTime : EffectBase { public string variableName; @@ -608,7 +608,7 @@ namespace WulaFallenEmpire } } - public class Effect_StoreDaysPassed : Effect + public class Effect_StoreDaysPassed : EffectBase { public string variableName; @@ -627,7 +627,7 @@ namespace WulaFallenEmpire } } - public class Effect_StoreColonyWealth : Effect + public class Effect_StoreColonyWealth : EffectBase { public string variableName; diff --git a/Source/WulaFallenEmpire/EventSystem/EventDef.cs b/Source/WulaFallenEmpire/EventSystem/EventDef.cs index a9d6a589..d8c25a1d 100644 --- a/Source/WulaFallenEmpire/EventSystem/EventDef.cs +++ b/Source/WulaFallenEmpire/EventSystem/EventDef.cs @@ -92,14 +92,14 @@ namespace WulaFallenEmpire { public int count = 1; public string countVariableName; - public List effects; + public List effects; } public class ConditionalEffects { public List conditions; - public List effects; - public List randomlistEffects; + public List effects; + public List randomlistEffects; public List loopEffects; public void Execute(Window dialog) diff --git a/Source/WulaFallenEmpire/HarmonyPatches/MapParent_ShouldRemoveMapNow_Patch.cs b/Source/WulaFallenEmpire/HarmonyPatches/MapParent_ShouldRemoveMapNow_Patch.cs index 9946c2a0..f6adba9b 100644 --- a/Source/WulaFallenEmpire/HarmonyPatches/MapParent_ShouldRemoveMapNow_Patch.cs +++ b/Source/WulaFallenEmpire/HarmonyPatches/MapParent_ShouldRemoveMapNow_Patch.cs @@ -1,7 +1,8 @@ -using System.Linq; using HarmonyLib; using RimWorld.Planet; +using System.Linq; using Verse; +using WulaFallenEmpire; namespace WulaFallenEmpire { @@ -11,7 +12,7 @@ namespace WulaFallenEmpire [HarmonyPrefix] public static bool Prefix(MapParent __instance) { - // 如果该 MapParent 没有地图,则直接放行,执行原方法(虽然原方法也会检查 HasMap,但这里提前返回更清晰) + // 如果该 MapParent 没有地图,则直接放行 if (!__instance.HasMap) { return true; @@ -19,25 +20,34 @@ namespace WulaFallenEmpire try { - // 在当前地图上查找所有武装穿梭机 + // 检查是否有活跃的观察者正在监测这个地图 + bool isBeingObserved = Building_MapObserver.activeObservers + .Any(observer => observer.IsObservingMap(__instance)); + + if (isBeingObserved) + { + // 如果地图正在被监测,阻止地图被移除 + Log.Message($"[MapObserver] 阻止地图移除: {__instance.Label} 正在被监测"); + return false; + } + + // 原有的穿梭机检查逻辑(保留你的原有功能) foreach (var shuttle in __instance.Map.listerBuildings.AllBuildingsColonistOfClass()) { - // 检查穿梭机是否有已生成的口袋地图,并且该地图里是否有人 if (shuttle != null && shuttle.PocketMapGenerated && shuttle.PocketMap != null && shuttle.PocketMap.mapPawns.AnyPawnBlockingMapRemoval) { - // 如果找到了这样的穿梭机,则阻止原方法 CheckRemoveMapNow 的执行,从而阻止地图被移除。 - // Log.Message($"[WULA] Prevented removal of map '{__instance.Map}' because shuttle '{shuttle.Label}' still contains pawns in its pocket dimension."); - return false; // 返回 false 以跳过原方法的执行 + Log.Message($"[WULA] 阻止地图移除: 穿梭机 '{shuttle.Label}' 的口袋维度中仍有生物"); + return false; } } } catch (System.Exception ex) { - Log.Error($"[WULA] Error in MapParent_CheckRemoveMapNow_Patch Prefix: {ex}"); + Log.Error($"[MapObserver] MapParent_CheckRemoveMapNow_Patch 错误: {ex}"); } - // 如果没有找到需要保护的穿梭机,则允许原方法 CheckRemoveMapNow 继续执行其正常的逻辑 + // 如果没有找到需要保护的情况,允许原方法继续执行 return true; } } -} \ No newline at end of file +} diff --git a/Source/WulaFallenEmpire/Pawn/WULA_AutonomousMech/Patch_FloatMenuOptionProvider_SelectedPawnValid.cs b/Source/WulaFallenEmpire/HarmonyPatches/WULA_AutonomousMech/Patch_FloatMenuOptionProvider_SelectedPawnValid.cs similarity index 100% rename from Source/WulaFallenEmpire/Pawn/WULA_AutonomousMech/Patch_FloatMenuOptionProvider_SelectedPawnValid.cs rename to Source/WulaFallenEmpire/HarmonyPatches/WULA_AutonomousMech/Patch_FloatMenuOptionProvider_SelectedPawnValid.cs diff --git a/Source/WulaFallenEmpire/Pawn/WULA_AutonomousMech/Patch_IsColonyMechPlayerControlled.cs b/Source/WulaFallenEmpire/HarmonyPatches/WULA_AutonomousMech/Patch_IsColonyMechPlayerControlled.cs similarity index 100% rename from Source/WulaFallenEmpire/Pawn/WULA_AutonomousMech/Patch_IsColonyMechPlayerControlled.cs rename to Source/WulaFallenEmpire/HarmonyPatches/WULA_AutonomousMech/Patch_IsColonyMechPlayerControlled.cs diff --git a/Source/WulaFallenEmpire/Pawn/WULA_AutonomousMech/Patch_MechanitorUtility_CanDraftMech.cs b/Source/WulaFallenEmpire/HarmonyPatches/WULA_AutonomousMech/Patch_MechanitorUtility_CanDraftMech.cs similarity index 100% rename from Source/WulaFallenEmpire/Pawn/WULA_AutonomousMech/Patch_MechanitorUtility_CanDraftMech.cs rename to Source/WulaFallenEmpire/HarmonyPatches/WULA_AutonomousMech/Patch_MechanitorUtility_CanDraftMech.cs diff --git a/Source/WulaFallenEmpire/Pawn/WULA_AutonomousMech/Patch_UncontrolledMechDrawPulse.cs b/Source/WulaFallenEmpire/HarmonyPatches/WULA_AutonomousMech/Patch_UncontrolledMechDrawPulse.cs similarity index 100% rename from Source/WulaFallenEmpire/Pawn/WULA_AutonomousMech/Patch_UncontrolledMechDrawPulse.cs rename to Source/WulaFallenEmpire/HarmonyPatches/WULA_AutonomousMech/Patch_UncontrolledMechDrawPulse.cs diff --git a/Source/WulaFallenEmpire/WulaFallenEmpire.csproj b/Source/WulaFallenEmpire/WulaFallenEmpire.csproj index ce6ffd2e..f4a472f1 100644 --- a/Source/WulaFallenEmpire/WulaFallenEmpire.csproj +++ b/Source/WulaFallenEmpire/WulaFallenEmpire.csproj @@ -82,6 +82,7 @@ + @@ -89,6 +90,8 @@ + + @@ -102,7 +105,7 @@ - + @@ -138,6 +141,10 @@ + + + + @@ -146,10 +153,6 @@ - - - -