This commit is contained in:
2025-11-11 12:00:02 +08:00
parent d5f3277fd6
commit 6583df5d5c
14 changed files with 1052 additions and 44 deletions

View File

@@ -66,6 +66,56 @@
<thingClass>Thing</thingClass>
</ThingDef>
<ThingDef ParentName="BuildingBase">
<defName>WULA_Watch_Tower</defName>
<label>乌拉帝国监视塔</label>
<description></description>
<thingClass>WulaFallenEmpire.Building_MapObserver</thingClass>
<preventDroppingThingsOn>true</preventDroppingThingsOn>
<altitudeLayer>Building</altitudeLayer>
<pathCost>50</pathCost>
<blockWind>true</blockWind>
<passability>PassThroughOnly</passability>
<fillPercent>1</fillPercent>
<size>(3,3)</size>
<drawHighlight>true</drawHighlight>
<highlightColor>(0.56, 0.62, 0.9)</highlightColor>
<graphicData>
<texPath>Wula/Building/WULA_Fake_Fighter_Drone_Building</texPath>
<graphicClass>Graphic_Single</graphicClass>
<shaderType>TransparentPostLight</shaderType>
<drawSize>(3,3)</drawSize>
<color>(195,195,195,255)</color>
</graphicData>
<statBases>
<MaxHitPoints>100</MaxHitPoints>
<Flammability>0.5</Flammability>
<WorkToBuild>36000</WorkToBuild>
<Mass>125</Mass>
<Comfort>0.65</Comfort>
</statBases>
<designationCategory>WULA_Buildings</designationCategory>
<tickerType>Normal</tickerType>
<canOverlapZones>true</canOverlapZones>
<rotatable>true</rotatable>
<hasInteractionCell>false</hasInteractionCell>
<defaultPlacingRot>East</defaultPlacingRot>
<selectable>true</selectable>
<terrainAffordanceNeeded>Light</terrainAffordanceNeeded>
<soundImpactDefault>BulletImpact_Metal</soundImpactDefault>
<preventSkyfallersLandingOn>true</preventSkyfallersLandingOn>
<drawerType>RealtimeOnly</drawerType>
<repairEffect>ConstructMetal</repairEffect>
<forceDebugSpawnable>true</forceDebugSpawnable>
<building>
<destroySound>BuildingDestroyed_Metal_Big</destroySound>
<paintable>true</paintable>
<isInert>true</isInert>
</building>
<comps>
</comps>
</ThingDef>
<!-- 战机 -->
<ThingDef ParentName="BuildingBase">

View File

@@ -877,12 +877,6 @@
</li>
</comps>
</ThingDef>
<DamageDef ParentName="Bullet">
<defName>WULA_Firepower_Minigun_Strafe_Damage</defName>
<label>子弹</label>
<defaultDamage>25</defaultDamage>
<soundExplosion>Explosion_Bomb</soundExplosion>
</DamageDef>
<ThingDef ParentName="EtherealThingBase">
<defName>WULA_EnergyLance_Base</defName>
<label>乌拉帝国光矛</label>

View File

@@ -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<Building_MapObserver> activeObservers = new HashSet<Building_MapObserver>();
public override void SpawnSetup(Map map, bool respawningAfterLoad)
{
base.SpawnSetup(map, respawningAfterLoad);
compPower = this.TryGetComp<CompPowerTrader>();
// 如果正在观察地图且建筑正常,注册到活跃列表
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<Gizmo> 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<Texture2D>.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<Texture2D>.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);
}
}
}

View File

@@ -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<Texture2D>.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<CompPowerTrader>();
refuelableComp = this.TryGetComp<CompRefuelable>();
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<Gizmo> 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<Texture2D>.Get("UI/Commands/Configure", true),
action = OpenConfigurationDialog
};
yield return configureFlyOver;
// 清除目标命令
Command_Action clearTarget = new Command_Action
{
defaultLabel = "清除目标",
defaultDesc = "清除已锁定的飞越目标",
icon = ContentFinder<Texture2D>.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<FloatMenuOption> options = new List<FloatMenuOption>
{
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<ThingDef>.GetNamedSilentFail("ARA_HiveScout");
flyOverConfig.altitude = 20f;
flyOverConfig.flightSpeed = 1.2f;
Messages.Message("已配置为标准侦察飞越", MessageTypeDefOf.TaskCompletion);
}
private void ConfigureGroundStrafing()
{
flyOverConfig.flyOverType = FlyOverType.GroundStrafing;
flyOverConfig.flyOverDef = DefDatabase<ThingDef>.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<ThingDef>.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<ThingDef>.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<WorldObjectDef>.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
}
}

View File

@@ -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<ThingDef>.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<CompGroundStrafing>();
if (strafingComp != null)
{
// 计算扫射区域
Vector3 flightDirection = (flyOverConfig.endPos.ToVector3() - flyOverConfig.startPos.ToVector3()).normalized;
List<IntVec3> impactCells = CalculateStrafingImpactCells(flyOverConfig.targetCell, flightDirection);
List<IntVec3> confirmedTargets = PreprocessStrafingTargets(impactCells, 0.7f);
strafingComp.SetConfirmedTargets(confirmedTargets);
}
}
// 监视功能配置
if (flyOverConfig.enableSurveillance)
{
// 可以在这里添加监视组件的配置
Log.Message("[FlyOverCarrier] Surveillance mode configured");
}
}
private List<IntVec3> CalculateStrafingImpactCells(IntVec3 targetCell, Vector3 flightDirection)
{
// 简化的扫射区域计算
List<IntVec3> cells = new List<IntVec3>();
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<IntVec3> PreprocessStrafingTargets(List<IntVec3> potentialTargets, float fireChance)
{
List<IntVec3> confirmedTargets = new List<IntVec3>();
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));
}
}
}

View File

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

View File

@@ -92,14 +92,14 @@ namespace WulaFallenEmpire
{
public int count = 1;
public string countVariableName;
public List<Effect> effects;
public List<EffectBase> effects;
}
public class ConditionalEffects
{
public List<Condition> conditions;
public List<Effect> effects;
public List<Effect> randomlistEffects;
public List<EffectBase> effects;
public List<EffectBase> randomlistEffects;
public List<LoopEffects> loopEffects;
public void Execute(Window dialog)

View File

@@ -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<Building_ArmedShuttleWithPocket>())
{
// 检查穿梭机是否有已生成的口袋地图,并且该地图里是否有人
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;
}
}
}
}

View File

@@ -82,6 +82,7 @@
<Compile Include="Ability\WULA_AbilityEnergyLance\EnergyLance.cs" />
<Compile Include="Ability\WULA_AbilityEnergyLance\EnergyLanceExtension.cs" />
<Compile Include="Ability\WULA_AbilityCallSkyfaller\CompAbilityEffect_CallSkyfaller.cs" />
<Compile Include="BuildingComp\Building_MapObserver.cs" />
<Compile Include="BuildingComp\WULA_BuildingBombardment\CompBuildingBombardment.cs" />
<Compile Include="Ability\WULA_AbilityCallSkyfaller\CompProperties_AbilityCallSkyfaller.cs" />
<Compile Include="BuildingComp\WULA_BuildingBombardment\CompProperties_BuildingBombardment.cs" />
@@ -89,6 +90,8 @@
<Compile Include="BuildingComp\Building_TurretGunHasSpeed.cs" />
<Compile Include="BuildingComp\WULA_EnergyLanceTurret\CompEnergyLanceTurret.cs" />
<Compile Include="BuildingComp\WULA_EnergyLanceTurret\CompProperties_EnergyLanceTurret.cs" />
<Compile Include="BuildingComp\WULA_FlyOverBeacon\Building_FlyOverBeacon.cs" />
<Compile Include="BuildingComp\WULA_FlyOverBeacon\WorldObject_FlyOverCarrier.cs" />
<Compile Include="BuildingComp\WULA_InitialFaction\CompProperties_InitialFaction.cs" />
<Compile Include="BuildingComp\WULA_MechanoidRecycler\Building_MechanoidRecycler.cs" />
<Compile Include="BuildingComp\WULA_MechanoidRecycler\CompProperties_MechanoidRecycler.cs" />
@@ -102,7 +105,7 @@
<Compile Include="EventSystem\DelayedActionManager.cs" />
<Compile Include="EventSystem\Dialog_CustomDisplay.cs" />
<Compile Include="EventSystem\Dialog_ManageEventVariables.cs" />
<Compile Include="EventSystem\Effect.cs" />
<Compile Include="EventSystem\Effect\EffectBase.cs" />
<Compile Include="EventSystem\EventDef.cs" />
<Compile Include="EventSystem\EventUIConfigDef.cs" />
<Compile Include="EventSystem\EventVariableManager.cs" />
@@ -138,6 +141,10 @@
<Compile Include="GlobalWorkTable\ITab_GlobalBills.cs" />
<Compile Include="GlobalWorkTable\CompLaunchable_ToGlobalStorage.cs" />
<Compile Include="GlobalWorkTable\CompProperties_Launchable_ToGlobalStorage.cs" />
<Compile Include="HarmonyPatches\WULA_AutonomousMech\Patch_FloatMenuOptionProvider_SelectedPawnValid.cs" />
<Compile Include="HarmonyPatches\WULA_AutonomousMech\Patch_IsColonyMechPlayerControlled.cs" />
<Compile Include="HarmonyPatches\WULA_AutonomousMech\Patch_MechanitorUtility_CanDraftMech.cs" />
<Compile Include="HarmonyPatches\WULA_AutonomousMech\Patch_UncontrolledMechDrawPulse.cs" />
<Compile Include="HediffComp\HediffCompProperties_NanoRepair.cs" />
<Compile Include="HediffComp\HediffCompProperties_SwitchableHediff.cs" />
<Compile Include="HediffComp\WULA_HediffDamgeShield\DRMDamageShield.cs" />
@@ -146,10 +153,6 @@
<Compile Include="Pawn\WULA_AutoMechCarrier\CompProperties_AutoMechCarrier.cs" />
<Compile Include="Pawn\WULA_AutoMechCarrier\PawnProductionEntry.cs" />
<Compile Include="Pawn\WULA_AutonomousMech\CompAutonomousMech.cs" />
<Compile Include="Pawn\WULA_AutonomousMech\Patch_FloatMenuOptionProvider_SelectedPawnValid.cs" />
<Compile Include="Pawn\WULA_AutonomousMech\Patch_IsColonyMechPlayerControlled.cs" />
<Compile Include="Pawn\WULA_AutonomousMech\Patch_MechanitorUtility_CanDraftMech.cs" />
<Compile Include="Pawn\WULA_AutonomousMech\Patch_UncontrolledMechDrawPulse.cs" />
<Compile Include="Pawn\WULA_AutonomousMech\ThinkNode_ConditionalAutonomousWorkMode.cs" />
<Compile Include="Pawn\WULA_AutonomousMech\ThinkNode_ConditionalNeedRecharge.cs" />
<Compile Include="Pawn\WULA_BrokenPersonality\MentalBreakWorker_BrokenPersonality.cs" />