This commit is contained in:
Tourswen
2025-12-01 00:26:52 +08:00
parent 6a5c5bd9aa
commit 4c7fd3c60f
25 changed files with 2702 additions and 110 deletions

View File

@@ -359,8 +359,6 @@ namespace WulaFallenEmpire
return Mathf.Max(0, remainingTicks / 2500f);
}
// 开发模式方法:立即结束冷却
[DebugAction("机械族回收器", "立即结束冷却", actionType = DebugActionType.Action, allowedGameStates = AllowedGameStates.Playing)]
public static void DevEndCooldown()
{
Building_MechanoidRecycler selectedRecycler = Find.Selector.SingleSelectedThing as Building_MechanoidRecycler;

View File

@@ -2,6 +2,7 @@ using RimWorld;
using System.Collections.Generic;
using System.Linq;
using Verse;
using UnityEngine;
namespace WulaFallenEmpire
{
@@ -75,31 +76,96 @@ namespace WulaFallenEmpire
ConsumeMaterials();
Thing thing = ThingMaker.MakeThing(Props.skyfallerDef);
if (thing is Skyfaller_PrefabSpawner skyfaller)
{
skyfaller.prefabDefName = PropsPrefab.prefabDefName;
GenSpawn.Spawn(skyfaller, parent.Position, parent.Map);
}
else
{
Log.Error($"[PrefabSkyfallerCaller] Failed to create Skyfaller_PrefabSpawner. Created thing is of type {thing.GetType().FullName}. Def: {Props.skyfallerDef.defName}, ThingClass: {Props.skyfallerDef.thingClass.FullName}");
// Fallback: spawn as normal skyfaller if possible, or just abort
if (thing is Skyfaller normalSkyfaller)
{
GenSpawn.Spawn(normalSkyfaller, parent.Position, parent.Map);
}
}
if (PropsPrefab.destroyBuilding && !parent.Destroyed)
{
parent.Destroy(DestroyMode.Vanish);
}
SpawnPrefabSkyfaller();
}
else
{
base.ExecuteSkyfallerCall();
}
}
// 新增:重写自动呼叫方法
protected override void ExecuteAutoSkyfallerCall()
{
if (!string.IsNullOrEmpty(PropsPrefab.prefabDefName))
{
Log.Message($"[PrefabSkyfallerCaller] Executing auto skyfaller call for prefab at {parent.Position}");
// 非玩家派系自动呼叫不需要资源检查
HandleRoofDestruction();
SpawnPrefabSkyfaller();
ResetCall();
autoCallScheduled = false;
}
else
{
base.ExecuteAutoSkyfallerCall();
}
}
// 新增:统一的 Prefab Skyfaller 生成方法
private void SpawnPrefabSkyfaller()
{
Thing thing = ThingMaker.MakeThing(Props.skyfallerDef);
if (thing is Skyfaller_PrefabSpawner skyfaller)
{
skyfaller.prefabDefName = PropsPrefab.prefabDefName;
Log.Message($"[PrefabSkyfallerCaller] Setting prefabDefName to: {PropsPrefab.prefabDefName}");
GenSpawn.Spawn(skyfaller, parent.Position, parent.Map);
}
else
{
Log.Error($"[PrefabSkyfallerCaller] Failed to create Skyfaller_PrefabSpawner. Created thing is of type {thing.GetType().FullName}. Def: {Props.skyfallerDef.defName}, ThingClass: {Props.skyfallerDef.thingClass.FullName}");
// Fallback: spawn as normal skyfaller if possible, or just abort
if (thing is Skyfaller normalSkyfaller)
{
GenSpawn.Spawn(normalSkyfaller, parent.Position, parent.Map);
}
}
if (PropsPrefab.destroyBuilding && !parent.Destroyed)
{
parent.Destroy(DestroyMode.Vanish);
}
}
// 新增:重写屋顶处理方法以确保日志输出
private void HandleRoofDestruction()
{
if (parent?.Map == null) return;
IntVec3 targetPos = parent.Position;
RoofDef roof = targetPos.GetRoof(parent.Map);
if (roof != null && !roof.isThickRoof && Props.allowThinRoof)
{
Log.Message($"[PrefabSkyfallerCaller] Destroying thin roof at {targetPos}");
parent.Map.roofGrid.SetRoof(targetPos, null);
// 生成屋顶破坏效果
FleckMaker.ThrowDustPuffThick(targetPos.ToVector3Shifted(), parent.Map, 2f, new Color(1f, 1f, 1f, 2f));
}
}
// 新增:调试信息
public override string CompInspectStringExtra()
{
var baseString = base.CompInspectStringExtra();
if (!string.IsNullOrEmpty(PropsPrefab.prefabDefName))
{
var sb = new System.Text.StringBuilder();
if (!string.IsNullOrEmpty(baseString))
{
sb.AppendLine(baseString);
}
sb.Append($"Prefab: {PropsPrefab.prefabDefName}");
return sb.ToString();
}
return baseString;
}
}
}
}

View File

@@ -10,7 +10,7 @@ namespace WulaFallenEmpire
public int delayTicks = 0;
public bool canAutoCall = true; // 默认启用自动召唤
public int autoCallDelayTicks = 600; // 默认10秒
public int autoCallDelayTicks = 1; // 默认10秒
// 新增:是否需要 FlyOver 作为前提条件
public bool requireFlyOver = false; // 默认不需要 FlyOver

View File

@@ -41,10 +41,17 @@ namespace WulaFallenEmpire
private bool used = false;
private int callTick = -1;
private bool calling = false;
private bool usedGlobalStorage = false; // 新增:标记是否使用了全局储存器
private bool usedGlobalStorage = false;
public bool autoCallScheduled = false; // 新增:标记是否已安排自动呼叫
public bool CanCall => !used && !calling;
// 新增:检查建筑是否属于非玩家派系
public bool IsNonPlayerFaction => parent.Faction != null && parent.Faction != Faction.OfPlayer;
// 新增:检查是否应该自动呼叫
private bool ShouldAutoCall => IsNonPlayerFaction && Props.canAutoCall && !autoCallScheduled && !used;
// 固定的显示标签
public string RequiredFlyOverLabel => "建筑空投飞行器";
@@ -127,6 +134,22 @@ namespace WulaFallenEmpire
public bool CanCallSkyfaller => CanCall && HasRequiredFlyOver && CheckRoofConditions;
// 新增:在建筑生成时检查是否需要自动呼叫
public override void PostSpawnSetup(bool respawningAfterLoad)
{
base.PostSpawnSetup(respawningAfterLoad);
if (!respawningAfterLoad && ShouldAutoCall)
{
// 安排自动呼叫
autoCallScheduled = true;
callTick = Find.TickManager.TicksGame + Props.autoCallDelayTicks;
calling = true;
Log.Message($"[SkyfallerCaller] Scheduled auto-call for non-player building {parent.Label} at tick {callTick}");
}
}
public override void PostExposeData()
{
base.PostExposeData();
@@ -134,23 +157,95 @@ namespace WulaFallenEmpire
Scribe_Values.Look(ref callTick, "callTick", -1);
Scribe_Values.Look(ref calling, "calling", false);
Scribe_Values.Look(ref usedGlobalStorage, "usedGlobalStorage", false);
Scribe_Values.Look(ref autoCallScheduled, "autoCallScheduled", false);
}
public override void CompTick()
{
base.CompTick();
// 处理自动呼叫
if (calling && callTick >= 0 && Find.TickManager.TicksGame >= callTick)
{
ExecuteSkyfallerCall();
if (autoCallScheduled)
{
// 执行自动呼叫
ExecuteAutoSkyfallerCall();
}
else
{
// 执行手动呼叫
ExecuteSkyfallerCall();
}
}
}
// 新增:自动呼叫方法(非玩家派系专用)
protected virtual void ExecuteAutoSkyfallerCall()
{
try
{
Log.Message($"[SkyfallerCaller] Executing auto skyfaller call for non-player building at {parent.Position}");
if (Props.skyfallerDef == null)
{
Log.Error("[SkyfallerCaller] Skyfaller def is null!");
ResetCall();
return;
}
// 非玩家派系自动呼叫不需要资源检查
// 直接处理屋顶
HandleRoofDestruction();
// 创建Skyfaller
Skyfaller skyfaller = SkyfallerMaker.MakeSkyfaller(Props.skyfallerDef);
if (skyfaller == null)
{
Log.Error("[SkyfallerCaller] Failed to create skyfaller!");
ResetCall();
return;
}
IntVec3 spawnPos = parent.Position;
Log.Message($"[SkyfallerCaller] Spawning auto skyfaller at {spawnPos}");
GenSpawn.Spawn(skyfaller, spawnPos, parent.Map);
if (Props.destroyBuilding)
{
Log.Message($"[SkyfallerCaller] Destroying non-player building {parent.Label}");
parent.Destroy(DestroyMode.Vanish);
}
// 重置状态
ResetCall();
autoCallScheduled = false;
// 显示自动呼叫消息
Messages.Message("WULA_AutoSkyfallerCalled".Translate(parent.Faction.Name),
MessageTypeDefOf.NeutralEvent);
}
catch (System.Exception ex)
{
Log.Error($"[SkyfallerCaller] Error in ExecuteAutoSkyfallerCall: {ex}");
ResetCall();
}
}
public void CallSkyfaller(bool isAutoCall = false)
{
// 新增:非玩家派系不能手动呼叫
if (IsNonPlayerFaction && !isAutoCall)
{
Messages.Message("WULA_NonPlayerCannotCall".Translate(), parent, MessageTypeDefOf.RejectInput);
return;
}
if (!CanCallSkyfaller)
{
// 显示相应的错误消息
if (!HasRequiredFlyOver && Props.requireFlyOver) // 只在需要 FlyOver 时才显示此消息
if (!HasRequiredFlyOver && Props.requireFlyOver)
{
Messages.Message("WULA_NoBuildingDropperFlyOver".Translate(), parent, MessageTypeDefOf.RejectInput);
}
@@ -195,6 +290,7 @@ namespace WulaFallenEmpire
used = false;
callTick = -1;
usedGlobalStorage = false;
autoCallScheduled = false;
}
protected virtual void ExecuteSkyfallerCall()
@@ -599,6 +695,7 @@ namespace WulaFallenEmpire
used = false;
callTick = -1;
usedGlobalStorage = false;
autoCallScheduled = false;
Messages.Message("WULA_SkyfallerCallCancelled".Translate(), parent, MessageTypeDefOf.NeutralEvent);
}
@@ -607,6 +704,10 @@ namespace WulaFallenEmpire
foreach (var gizmo in base.CompGetGizmosExtra())
yield return gizmo;
// 新增:非玩家派系不显示呼叫按钮
if (IsNonPlayerFaction)
yield break;
if (calling)
{
Command_Action cancelCommand = new Command_Action
@@ -679,6 +780,12 @@ namespace WulaFallenEmpire
private string GetDisabledReason()
{
// 新增:非玩家派系不能手动呼叫
if (IsNonPlayerFaction)
{
return "WULA_NonPlayerCannotCall".Translate();
}
// 只在需要 FlyOver 时检查并显示相关原因
if (Props.requireFlyOver && !HasRequiredFlyOver)
{
@@ -719,7 +826,12 @@ namespace WulaFallenEmpire
var sb = new System.Text.StringBuilder();
if (calling)
// 新增:显示自动呼叫状态
if (autoCallScheduled && calling)
{
int ticksLeft = callTick - Find.TickManager.TicksGame;
}
else if (calling)
{
int ticksLeft = callTick - Find.TickManager.TicksGame;
if (ticksLeft > 0)
@@ -732,7 +844,15 @@ namespace WulaFallenEmpire
}
else if (!used)
{
sb.Append("WULA_ReadyToCallSkyfaller".Translate());
// 新增:显示非玩家派系自动呼叫信息
if (IsNonPlayerFaction && Props.canAutoCall)
{
sb.Append("WULA_AutoSkyfallerReady".Translate());
}
else
{
sb.Append("WULA_ReadyToCallSkyfaller".Translate());
}
if (Props.requireFlyOver && !HasRequiredFlyOver)
{

View File

@@ -0,0 +1,428 @@
using System.Collections.Generic;
using System.Linq;
using RimWorld;
using UnityEngine;
using Verse;
using LudeonTK;
namespace WulaFallenEmpire
{
public class DebugActions_PrefabSkyfallerCaller
{
[DebugAction("Wula Fallen Empire", "Spawn Prefab Skyfaller Caller (Single)", actionType = DebugActionType.Action, allowedGameStates = AllowedGameStates.Playing)]
public static void SpawnPrefabSkyfallerCallerSingle()
{
var eligibleDefs = DefDatabase<ThingDef>.AllDefs
.Where(def => def.comps != null && def.comps.Any(comp => comp is CompProperties_PrefabSkyfallerCaller))
.ToList();
if (!eligibleDefs.Any())
{
Log.Warning("[Debug] No ThingDefs found with CompProperties_PrefabSkyfallerCaller");
return;
}
var options = new List<DebugMenuOption>();
foreach (var thingDef in eligibleDefs)
{
options.Add(new DebugMenuOption(thingDef.defName, DebugMenuOptionMode.Tool, () =>
{
ShowFactionSelectionMenu(thingDef, false);
}));
}
Find.WindowStack.Add(new Dialog_DebugOptionListLister(options));
}
[DebugAction("Wula Fallen Empire", "Spawn Prefab Skyfaller Caller (x10)", actionType = DebugActionType.Action, allowedGameStates = AllowedGameStates.Playing)]
public static void SpawnPrefabSkyfallerCallerMultiple()
{
var eligibleDefs = DefDatabase<ThingDef>.AllDefs
.Where(def => def.comps != null && def.comps.Any(comp => comp is CompProperties_PrefabSkyfallerCaller))
.ToList();
if (!eligibleDefs.Any())
{
Log.Warning("[Debug] No ThingDefs found with CompProperties_PrefabSkyfallerCaller");
return;
}
var options = new List<DebugMenuOption>();
foreach (var thingDef in eligibleDefs)
{
options.Add(new DebugMenuOption(thingDef.defName, DebugMenuOptionMode.Tool, () =>
{
ShowFactionSelectionMenu(thingDef, true);
}));
}
Find.WindowStack.Add(new Dialog_DebugOptionListLister(options));
}
private static void ShowFactionSelectionMenu(ThingDef thingDef, bool spawnMultiple)
{
var allFactions = Find.FactionManager.AllFactions.ToList();
var options = new List<DebugMenuOption>();
options.Add(new DebugMenuOption("No Faction", DebugMenuOptionMode.Tool, () =>
{
SpawnThingAtValidLocation(thingDef, null, spawnMultiple);
}));
foreach (var faction in allFactions)
{
options.Add(new DebugMenuOption(faction.Name, DebugMenuOptionMode.Tool, () =>
{
SpawnThingAtValidLocation(thingDef, faction, spawnMultiple);
}));
}
Find.WindowStack.Add(new Dialog_DebugOptionListLister(options));
}
private static void SpawnThingAtValidLocation(ThingDef thingDef, Faction faction, bool spawnMultiple)
{
var currentMap = Find.CurrentMap;
if (currentMap == null)
{
Log.Warning("[Debug] No current map found");
return;
}
int spawnCount = spawnMultiple ? 10 : 1;
int successCount = 0;
int attempts = 0;
const int maxAttempts = 50;
var compProps = thingDef.comps.OfType<CompProperties_PrefabSkyfallerCaller>().FirstOrDefault();
if (compProps == null)
{
Log.Warning($"[Debug] Could not find CompProperties_PrefabSkyfallerCaller for {thingDef.defName}");
return;
}
Log.Message($"[Debug] Looking for spawn positions for {thingDef.defName} (Size: {thingDef.Size})");
for (int i = 0; i < spawnCount && attempts < maxAttempts; i++)
{
attempts++;
IntVec3 spawnPos = FindSpawnPositionForSkyfaller(currentMap, thingDef, compProps);
if (spawnPos.IsValid)
{
Thing thing = ThingMaker.MakeThing(thingDef);
if (faction != null)
{
thing.SetFaction(faction);
}
GenSpawn.Spawn(thing, spawnPos, currentMap);
successCount++;
Log.Message($"[Debug] Successfully spawned {thingDef.defName} at {spawnPos} for faction {faction?.Name ?? "None"}");
}
else
{
Log.Warning($"[Debug] Failed to find valid spawn position for {thingDef.defName} (attempt {attempts})");
}
}
if (successCount > 0)
{
Messages.Message($"[Debug] Successfully spawned {successCount} {thingDef.defName}", MessageTypeDefOf.PositiveEvent);
}
else
{
Messages.Message($"[Debug] Failed to spawn any {thingDef.defName} after {attempts} attempts", MessageTypeDefOf.NegativeEvent);
}
}
private static IntVec3 FindSpawnPositionForSkyfaller(Map map, ThingDef thingDef, CompProperties_SkyfallerCaller compProps)
{
// 基于Skyfaller实际行为的查找逻辑
var potentialCells = new List<IntVec3>();
// 策略1首先尝试玩家基地附近的开放区域
Log.Message($"[Debug] Searching near base area...");
var baseCells = GetOpenAreaCellsNearBase(map, thingDef.Size);
foreach (var cell in baseCells)
{
if (IsValidForSkyfallerDrop(map, cell, thingDef, compProps))
{
potentialCells.Add(cell);
}
if (potentialCells.Count > 20) break; // 找到足够位置就停止
}
if (potentialCells.Count > 0)
{
Log.Message($"[Debug] Found {potentialCells.Count} positions near base");
return potentialCells.RandomElement();
}
// 策略2搜索整个地图的开阔区域
Log.Message($"[Debug] Searching open areas...");
var openAreas = FindOpenAreas(map, thingDef.Size, 1000);
foreach (var cell in openAreas)
{
if (IsValidForSkyfallerDrop(map, cell, thingDef, compProps))
{
potentialCells.Add(cell);
}
if (potentialCells.Count > 10) break;
}
if (potentialCells.Count > 0)
{
Log.Message($"[Debug] Found {potentialCells.Count} positions in open areas");
return potentialCells.RandomElement();
}
// 策略3使用随机采样
Log.Message($"[Debug] Trying random sampling...");
for (int i = 0; i < 500; i++)
{
IntVec3 randomCell = new IntVec3(
Rand.Range(thingDef.Size.x, map.Size.x - thingDef.Size.x),
0,
Rand.Range(thingDef.Size.z, map.Size.z - thingDef.Size.z)
);
if (randomCell.InBounds(map) && IsValidForSkyfallerDrop(map, randomCell, thingDef, compProps))
{
potentialCells.Add(randomCell);
}
if (potentialCells.Count > 5) break;
}
if (potentialCells.Count > 0)
{
Log.Message($"[Debug] Found {potentialCells.Count} positions via random sampling");
return potentialCells.RandomElement();
}
Log.Warning($"[Debug] No valid positions found for {thingDef.defName}");
return IntVec3.Invalid;
}
/// <summary>
/// 基于Skyfaller实际行为的有效性检查
/// </summary>
private static bool IsValidForSkyfallerDrop(Map map, IntVec3 cell, ThingDef thingDef, CompProperties_SkyfallerCaller compProps)
{
// 1. 检查边界
if (!cell.InBounds(map))
return false;
// 2. 检查整个建筑区域
CellRect occupiedRect = GenAdj.OccupiedRect(cell, Rot4.North, thingDef.Size);
foreach (IntVec3 occupiedCell in occupiedRect)
{
if (!occupiedCell.InBounds(map))
return false;
// 3. 检查厚岩顶 - 绝对不允许
RoofDef roof = occupiedCell.GetRoof(map);
if (roof != null && roof.isThickRoof)
{
if (!compProps.allowThickRoof)
return false;
}
// 4. 检查水体 - 不允许
TerrainDef terrain = occupiedCell.GetTerrain(map);
if (terrain != null && terrain.IsWater)
return false;
// 5. 检查建筑 - 不允许(但自然物体如树、石头是允许的)
var things = map.thingGrid.ThingsListAtFast(occupiedCell);
foreach (var thing in things)
{
// 允许自然物体(树、石头等),它们会被空投清除
if (thing.def.category == ThingCategory.Plant ||
thing.def.category == ThingCategory.Item ||
thing.def.category == ThingCategory.Filth)
{
continue; // 这些是可以被清除的
}
// 不允许建筑、蓝图、框架等
if (thing.def.category == ThingCategory.Building ||
thing.def.IsBlueprint ||
thing.def.IsFrame)
{
return false;
}
// 不允许其他不可清除的物体
if (thing.def.passability == Traversability.Impassable &&
thing.def.category != ThingCategory.Plant) // 植物是可清除的
{
return false;
}
}
// 6. 检查薄岩顶和普通屋顶的条件
if (roof != null && !roof.isThickRoof)
{
if (!compProps.allowThinRoof)
return false;
}
}
return true;
}
/// <summary>
/// 获取基地附近的开阔区域
/// </summary>
private static IEnumerable<IntVec3> GetOpenAreaCellsNearBase(Map map, IntVec2 size)
{
var homeArea = map.areaManager.Home;
IntVec3 searchCenter;
if (homeArea != null && homeArea.ActiveCells.Any())
{
searchCenter = homeArea.ActiveCells.First();
}
else
{
searchCenter = new IntVec3(map.Size.x / 2, 0, map.Size.z / 2);
}
// 在基地周围搜索开阔区域
int searchRadius = 50;
var searchArea = CellRect.CenteredOn(searchCenter, searchRadius);
// 返回该区域内所有可能的中心点
foreach (var cell in searchArea.Cells)
{
if (!cell.InBounds(map)) continue;
// 检查这个位置周围是否有足够的开阔空间
if (IsAreaMostlyOpen(map, cell, size, 0.8f)) // 80%的区域需要是开阔的
{
yield return cell;
}
}
}
/// <summary>
/// 查找地图上的开阔区域
/// </summary>
private static IEnumerable<IntVec3> FindOpenAreas(Map map, IntVec2 size, int maxCellsToCheck)
{
int cellsChecked = 0;
// 优先检查地图上已知的开阔区域
var allCells = map.AllCells.Where(c => c.InBounds(map)).ToList();
foreach (var cell in allCells)
{
if (cellsChecked >= maxCellsToCheck) yield break;
cellsChecked++;
// 快速检查:如果这个单元格本身就不适合,跳过
if (!cell.InBounds(map) || cell.GetTerrain(map).IsWater)
continue;
// 检查整个区域是否开阔
if (IsAreaMostlyOpen(map, cell, size, 0.7f)) // 70%的区域需要是开阔的
{
yield return cell;
}
}
}
/// <summary>
/// 检查区域是否大部分是开阔的(没有建筑,允许自然物体)
/// </summary>
private static bool IsAreaMostlyOpen(Map map, IntVec3 center, IntVec2 size, float openThreshold)
{
CellRect area = GenAdj.OccupiedRect(center, Rot4.North, size);
int totalCells = area.Area;
int openCells = 0;
foreach (IntVec3 cell in area)
{
if (!cell.InBounds(map))
{
continue; // 边界外的单元格不计入
}
// 检查是否有不可清除的建筑
bool hasBlockingBuilding = false;
var things = map.thingGrid.ThingsListAtFast(cell);
foreach (var thing in things)
{
if (thing.def.category == ThingCategory.Building ||
thing.def.IsBlueprint ||
thing.def.IsFrame ||
(thing.def.passability == Traversability.Impassable &&
thing.def.category != ThingCategory.Plant))
{
hasBlockingBuilding = true;
break;
}
}
// 检查水体
bool isWater = cell.GetTerrain(map).IsWater;
if (!hasBlockingBuilding && !isWater)
{
openCells++;
}
}
float openRatio = (float)openCells / totalCells;
return openRatio >= openThreshold;
}
/// <summary>
/// 强制清除区域(作为最后手段)
/// </summary>
private static bool TryForceClearAreaForSkyfaller(Map map, IntVec3 center, IntVec2 size)
{
try
{
CellRect clearRect = GenAdj.OccupiedRect(center, Rot4.North, size);
int clearedCount = 0;
foreach (IntVec3 cell in clearRect)
{
if (!cell.InBounds(map)) continue;
// 清除植物和物品
var thingsToRemove = map.thingGrid.ThingsAt(cell)
.Where(thing => thing.def.category == ThingCategory.Plant ||
thing.def.category == ThingCategory.Item ||
thing.def.category == ThingCategory.Filth)
.ToList();
foreach (var thing in thingsToRemove)
{
thing.Destroy();
clearedCount++;
}
// 确保不是水体
if (cell.GetTerrain(map).IsWater)
{
map.terrainGrid.SetTerrain(cell, TerrainDefOf.Soil);
}
}
Log.Message($"[Debug] Force cleared {clearedCount} objects for skyfaller drop");
return clearedCount > 0;
}
catch (System.Exception ex)
{
Log.Error($"[Debug] Error force clearing area: {ex}");
return false;
}
}
}
}

View File

@@ -0,0 +1,382 @@
using System.Collections.Generic;
using System.Linq;
using RimWorld;
using RimWorld.Planet;
using UnityEngine;
using Verse;
using RimWorld.QuestGen;
namespace WulaFallenEmpire
{
public class QuestNode_SpawnPrefabSkyfallerCaller : QuestNode
{
[NoTranslate]
public SlateRef<string> inSignal;
public SlateRef<ThingDef> thingDef;
public SlateRef<Faction> faction;
public SlateRef<int> spawnCount = 1;
public SlateRef<Map> map;
public SlateRef<bool> sendMessageOnSuccess = true;
public SlateRef<bool> sendMessageOnFailure = true;
protected override bool TestRunInt(Slate slate)
{
// 在测试运行中只检查基本条件
if (thingDef.GetValue(slate) == null)
{
Log.Warning("[QuestNode] ThingDef is null in TestRun");
return false;
}
var mapValue = map.GetValue(slate) ?? Find.CurrentMap;
if (mapValue == null)
{
Log.Warning("[QuestNode] Map is null in TestRun");
return false;
}
var compProps = thingDef.GetValue(slate).comps?.OfType<CompProperties_PrefabSkyfallerCaller>().FirstOrDefault();
if (compProps == null)
{
Log.Warning($"[QuestNode] ThingDef {thingDef.GetValue(slate).defName} does not have CompProperties_PrefabSkyfallerCaller");
return false;
}
return true;
}
protected override void RunInt()
{
Slate slate = QuestGen.slate;
// 获取参数值
ThingDef targetThingDef = thingDef.GetValue(slate);
Faction targetFaction = faction.GetValue(slate);
int targetSpawnCount = spawnCount.GetValue(slate);
Map targetMap = map.GetValue(slate) ?? Find.CurrentMap;
bool doSendMessageOnSuccess = sendMessageOnSuccess.GetValue(slate);
bool doSendMessageOnFailure = sendMessageOnFailure.GetValue(slate);
if (targetThingDef == null)
{
Log.Error("[QuestNode] ThingDef is null in RunInt");
return;
}
if (targetMap == null)
{
Log.Error("[QuestNode] Map is null in RunInt");
return;
}
// 获取组件属性
var compProps = targetThingDef.comps?.OfType<CompProperties_PrefabSkyfallerCaller>().FirstOrDefault();
if (compProps == null)
{
Log.Error($"[QuestNode] ThingDef {targetThingDef.defName} does not have CompProperties_PrefabSkyfallerCaller");
return;
}
Log.Message($"[QuestNode] Attempting to spawn {targetSpawnCount} {targetThingDef.defName} on map {targetMap}");
// 执行生成
int successCount = SpawnThingsAtValidLocations(targetThingDef, targetFaction, targetSpawnCount, targetMap);
// 发送结果消息
if (successCount > 0)
{
if (doSendMessageOnSuccess)
{
Messages.Message($"[Quest] Successfully spawned {successCount} {targetThingDef.label}", MessageTypeDefOf.PositiveEvent);
}
Log.Message($"[QuestNode] Successfully spawned {successCount}/{targetSpawnCount} {targetThingDef.defName}");
}
else
{
if (doSendMessageOnFailure)
{
Messages.Message($"[Quest] Failed to spawn any {targetThingDef.label}", MessageTypeDefOf.NegativeEvent);
}
Log.Warning($"[QuestNode] Failed to spawn any {targetThingDef.defName}");
}
// 将结果存储到Slate中供后续节点使用
QuestGen.slate.Set("prefabSpawnSuccessCount", successCount);
QuestGen.slate.Set("prefabSpawnRequestedCount", targetSpawnCount);
}
/// <summary>
/// 在有效位置生成多个建筑
/// </summary>
private int SpawnThingsAtValidLocations(ThingDef thingDef, Faction faction, int spawnCount, Map targetMap)
{
int successCount = 0;
int attempts = 0;
const int maxAttempts = 100; // 最大尝试次数
var compProps = thingDef.comps.OfType<CompProperties_PrefabSkyfallerCaller>().FirstOrDefault();
if (compProps == null)
{
Log.Error($"[QuestNode] Could not find CompProperties_PrefabSkyfallerCaller for {thingDef.defName}");
return 0;
}
for (int i = 0; i < spawnCount && attempts < maxAttempts; i++)
{
attempts++;
IntVec3 spawnPos = FindSpawnPositionForSkyfaller(targetMap, thingDef, compProps);
if (spawnPos.IsValid)
{
Thing thing = ThingMaker.MakeThing(thingDef);
if (faction != null)
{
thing.SetFaction(faction);
}
GenSpawn.Spawn(thing, spawnPos, targetMap);
successCount++;
Log.Message($"[QuestNode] Successfully spawned {thingDef.defName} at {spawnPos} for faction {faction?.Name ?? "None"}");
}
else
{
Log.Warning($"[QuestNode] Failed to find valid spawn position for {thingDef.defName} (attempt {attempts})");
}
}
return successCount;
}
/// <summary>
/// 查找适合Skyfaller空投的位置
/// </summary>
private IntVec3 FindSpawnPositionForSkyfaller(Map map, ThingDef thingDef, CompProperties_SkyfallerCaller compProps)
{
var potentialCells = new List<IntVec3>();
// 策略1首先尝试玩家基地附近的开放区域
var baseCells = GetOpenAreaCellsNearBase(map, thingDef.Size);
foreach (var cell in baseCells)
{
if (IsValidForSkyfallerDrop(map, cell, thingDef, compProps))
{
potentialCells.Add(cell);
}
if (potentialCells.Count > 20) break;
}
if (potentialCells.Count > 0)
{
return potentialCells.RandomElement();
}
// 策略2搜索整个地图的开阔区域
var openAreas = FindOpenAreas(map, thingDef.Size, 500);
foreach (var cell in openAreas)
{
if (IsValidForSkyfallerDrop(map, cell, thingDef, compProps))
{
potentialCells.Add(cell);
}
if (potentialCells.Count > 10) break;
}
if (potentialCells.Count > 0)
{
return potentialCells.RandomElement();
}
// 策略3使用随机采样
for (int i = 0; i < 300; i++)
{
IntVec3 randomCell = new IntVec3(
Rand.Range(thingDef.Size.x, map.Size.x - thingDef.Size.x),
0,
Rand.Range(thingDef.Size.z, map.Size.z - thingDef.Size.z)
);
if (randomCell.InBounds(map) && IsValidForSkyfallerDrop(map, randomCell, thingDef, compProps))
{
potentialCells.Add(randomCell);
}
if (potentialCells.Count > 5) break;
}
if (potentialCells.Count > 0)
{
return potentialCells.RandomElement();
}
Log.Warning($"[QuestNode] No valid positions found for {thingDef.defName} after exhaustive search");
return IntVec3.Invalid;
}
/// <summary>
/// 基于Skyfaller实际行为的有效性检查
/// </summary>
private bool IsValidForSkyfallerDrop(Map map, IntVec3 cell, ThingDef thingDef, CompProperties_SkyfallerCaller compProps)
{
// 1. 检查边界
if (!cell.InBounds(map))
return false;
// 2. 检查整个建筑区域
CellRect occupiedRect = GenAdj.OccupiedRect(cell, Rot4.North, thingDef.Size);
foreach (IntVec3 occupiedCell in occupiedRect)
{
if (!occupiedCell.InBounds(map))
return false;
// 3. 检查厚岩顶 - 绝对不允许
RoofDef roof = occupiedCell.GetRoof(map);
if (roof != null && roof.isThickRoof)
{
if (!compProps.allowThickRoof)
return false;
}
// 4. 检查水体 - 不允许
TerrainDef terrain = occupiedCell.GetTerrain(map);
if (terrain != null && terrain.IsWater)
return false;
// 5. 检查建筑 - 不允许(但自然物体如树、石头是允许的)
var things = map.thingGrid.ThingsListAtFast(occupiedCell);
foreach (var thing in things)
{
// 允许自然物体(树、石头等),它们会被空投清除
if (thing.def.category == ThingCategory.Plant ||
thing.def.category == ThingCategory.Item ||
thing.def.category == ThingCategory.Filth)
{
continue;
}
// 不允许建筑、蓝图、框架等
if (thing.def.category == ThingCategory.Building ||
thing.def.IsBlueprint ||
thing.def.IsFrame)
{
return false;
}
// 不允许其他不可清除的物体
if (thing.def.passability == Traversability.Impassable &&
thing.def.category != ThingCategory.Plant)
{
return false;
}
}
// 6. 检查薄岩顶和普通屋顶的条件
if (roof != null && !roof.isThickRoof)
{
if (!compProps.allowThinRoof)
return false;
}
}
return true;
}
/// <summary>
/// 获取基地附近的开阔区域
/// </summary>
private IEnumerable<IntVec3> GetOpenAreaCellsNearBase(Map map, IntVec2 size)
{
var homeArea = map.areaManager.Home;
IntVec3 searchCenter;
if (homeArea != null && homeArea.ActiveCells.Any())
{
searchCenter = homeArea.ActiveCells.First();
}
else
{
searchCenter = new IntVec3(map.Size.x / 2, 0, map.Size.z / 2);
}
int searchRadius = 50;
var searchArea = CellRect.CenteredOn(searchCenter, searchRadius);
foreach (var cell in searchArea.Cells)
{
if (!cell.InBounds(map)) continue;
if (IsAreaMostlyOpen(map, cell, size, 0.8f))
{
yield return cell;
}
}
}
/// <summary>
/// 查找地图上的开阔区域
/// </summary>
private IEnumerable<IntVec3> FindOpenAreas(Map map, IntVec2 size, int maxCellsToCheck)
{
int cellsChecked = 0;
var allCells = map.AllCells.Where(c => c.InBounds(map)).ToList();
foreach (var cell in allCells)
{
if (cellsChecked >= maxCellsToCheck) yield break;
cellsChecked++;
if (!cell.InBounds(map) || cell.GetTerrain(map).IsWater)
continue;
if (IsAreaMostlyOpen(map, cell, size, 0.7f))
{
yield return cell;
}
}
}
/// <summary>
/// 检查区域是否大部分是开阔的
/// </summary>
private bool IsAreaMostlyOpen(Map map, IntVec3 center, IntVec2 size, float openThreshold)
{
CellRect area = GenAdj.OccupiedRect(center, Rot4.North, size);
int totalCells = area.Area;
int openCells = 0;
foreach (IntVec3 cell in area)
{
if (!cell.InBounds(map))
{
continue;
}
bool hasBlockingBuilding = false;
var things = map.thingGrid.ThingsListAtFast(cell);
foreach (var thing in things)
{
if (thing.def.category == ThingCategory.Building ||
thing.def.IsBlueprint ||
thing.def.IsFrame ||
(thing.def.passability == Traversability.Impassable &&
thing.def.category != ThingCategory.Plant))
{
hasBlockingBuilding = true;
break;
}
}
bool isWater = cell.GetTerrain(map).IsWater;
if (!hasBlockingBuilding && !isWater)
{
openCells++;
}
}
float openRatio = (float)openCells / totalCells;
return openRatio >= openThreshold;
}
}
}

View File

@@ -18,10 +18,13 @@ namespace WulaFallenEmpire
public QuestScriptDef questDef; // 关联的任务定义(可选)
public bool preventDuplicateQuests = true; // 防止重复任务
// 派系关系校验 - 新增字段
// 派系关系校验
public FactionDef requiredFaction; // 必须存在的派系
public bool requireNonHostileRelation = true; // 是否要求非敌对关系默认true
public bool requireFactionExists = true; // 是否要求派系必须存在默认true
// 敌对情况下的替代事件 - 新增字段
public IncidentDef incidentIfHostile; // 当派系敌对且requireNonHostileRelation为false时触发的事件
// 调试配置
public bool debugLogging = false; // 启用调试日志

View File

@@ -12,7 +12,6 @@ namespace WulaFallenEmpire
private StorytellerCompProperties_SimpleTechnologyTrigger SimpleProps =>
(StorytellerCompProperties_SimpleTechnologyTrigger)props;
// 重新实现基类的私有属性
private static int IntervalsPassed => Find.TickManager.TicksGame / 1000;
public override IEnumerable<FiringIncident> MakeIntervalIncidents(IIncidentTarget target)
@@ -25,8 +24,13 @@ namespace WulaFallenEmpire
if (!PassesIntervalCheck())
yield break;
// 检查派系关系条件 - 新增检查
if (!PassesRequiredFactionCheck())
// 检查派系关系条件
var factionCheckResult = PassesRequiredFactionCheck();
// 根据派系关系结果决定触发哪个事件
IncidentDef incidentToTrigger = GetIncidentBasedOnFactionRelation(factionCheckResult);
if (incidentToTrigger == null)
yield break;
// 检查派系过滤条件
@@ -38,30 +42,59 @@ namespace WulaFallenEmpire
yield break;
// 检查是否防止重复任务
if (SimpleProps.preventDuplicateQuests && HasActiveQuest())
if (SimpleProps.preventDuplicateQuests && HasActiveQuest(incidentToTrigger))
yield break;
// 触发事件
IncidentDef techIncident = SimpleProps.incident;
if (techIncident.TargetAllowed(target))
if (incidentToTrigger.TargetAllowed(target))
{
if (SimpleProps.debugLogging)
{
Log.Message($"[SimpleTechnologyTrigger] Triggering incident {techIncident.defName} for technology {SimpleProps.technology.defName}");
Log.Message($"[SimpleTechnologyTrigger] Triggering incident {incidentToTrigger.defName} for technology {SimpleProps.technology.defName}");
Log.Message($"[SimpleTechnologyTrigger] Faction relation status: {factionCheckResult}");
}
yield return new FiringIncident(techIncident, this, GenerateParms(techIncident.category, target));
yield return new FiringIncident(incidentToTrigger, this, GenerateParms(incidentToTrigger.category, target));
}
}
/// <summary>
/// 检查必需派系关系条件 - 新增方法
/// 根据派系关系状态决定触发哪个事件
/// </summary>
private bool PassesRequiredFactionCheck()
private IncidentDef GetIncidentBasedOnFactionRelation(FactionRelationResult factionCheckResult)
{
switch (factionCheckResult.Status)
{
case FactionRelationStatus.Valid:
return SimpleProps.incident;
case FactionRelationStatus.HostileButAllowed:
// 如果配置了敌对情况下的替代事件,则使用替代事件
if (SimpleProps.incidentIfHostile != null)
{
if (SimpleProps.debugLogging)
{
Log.Message($"[SimpleTechnologyTrigger] Using hostile alternative incident: {SimpleProps.incidentIfHostile.defName}");
}
return SimpleProps.incidentIfHostile;
}
// 如果没有配置替代事件,则使用原事件
return SimpleProps.incident;
case FactionRelationStatus.Invalid:
default:
return null;
}
}
/// <summary>
/// 检查必需派系关系条件
/// </summary>
private FactionRelationResult PassesRequiredFactionCheck()
{
// 如果没有配置必需派系,直接通过
if (SimpleProps.requiredFaction == null)
return true;
return FactionRelationResult.Valid();
// 查找派系
Faction requiredFactionInstance = Find.FactionManager.FirstFactionOfDef(SimpleProps.requiredFaction);
@@ -75,21 +108,35 @@ namespace WulaFallenEmpire
{
Log.Message($"[SimpleTechnologyTrigger] Required faction {SimpleProps.requiredFaction.defName} does not exist or is defeated");
}
return false;
return FactionRelationResult.Invalid("Faction does not exist or is defeated");
}
}
// 检查派系关系
if (SimpleProps.requireNonHostileRelation && requiredFactionInstance != null)
if (requiredFactionInstance != null)
{
Faction playerFaction = Faction.OfPlayer;
if (requiredFactionInstance.HostileTo(playerFaction))
bool isHostile = requiredFactionInstance.HostileTo(playerFaction);
if (isHostile)
{
if (SimpleProps.debugLogging)
if (SimpleProps.requireNonHostileRelation)
{
Log.Message($"[SimpleTechnologyTrigger] Required faction {SimpleProps.requiredFaction.defName} is hostile to player");
if (SimpleProps.debugLogging)
{
Log.Message($"[SimpleTechnologyTrigger] Required faction {SimpleProps.requiredFaction.defName} is hostile to player and requireNonHostileRelation is true");
}
return FactionRelationResult.Invalid("Faction is hostile and requireNonHostileRelation is true");
}
else
{
// 不要求非敌对关系,但派系是敌对的 - 这是一个特殊状态
if (SimpleProps.debugLogging)
{
Log.Message($"[SimpleTechnologyTrigger] Required faction {SimpleProps.requiredFaction.defName} is hostile but requireNonHostileRelation is false - allowing with possible alternative");
}
return FactionRelationResult.HostileButAllowed();
}
return false;
}
}
@@ -98,7 +145,7 @@ namespace WulaFallenEmpire
Log.Message($"[SimpleTechnologyTrigger] Required faction {SimpleProps.requiredFaction.defName} check passed");
}
return true;
return FactionRelationResult.Valid();
}
/// <summary>
@@ -106,11 +153,9 @@ namespace WulaFallenEmpire
/// </summary>
private bool PassesIntervalCheck()
{
// 简单的周期检查:每 X 天检查一次
int currentInterval = IntervalsPassed;
int checkInterval = (int)(SimpleProps.checkIntervalDays * 60);
// 如果检查间隔为0则每个间隔都检查
if (checkInterval <= 0)
return true;
@@ -128,7 +173,6 @@ namespace WulaFallenEmpire
return false;
}
// 简单检查科技是否已研究完成
bool hasTechnology = SimpleProps.technology.IsFinished;
if (SimpleProps.debugLogging)
@@ -144,16 +188,13 @@ namespace WulaFallenEmpire
/// </summary>
private bool PassesFactionFilter(IIncidentTarget target)
{
// 如果不启用派系过滤,直接通过
if (!SimpleProps.useFactionFilter)
return true;
// 获取目标的派系
Faction faction = GetTargetFaction(target);
if (faction == null)
return false;
// 检查黑名单
if (SimpleProps.factionTypeBlacklist != null &&
SimpleProps.factionTypeBlacklist.Contains(faction.def))
{
@@ -164,7 +205,6 @@ namespace WulaFallenEmpire
return false;
}
// 检查白名单
if (SimpleProps.factionTypeWhitelist != null &&
SimpleProps.factionTypeWhitelist.Count > 0)
{
@@ -173,14 +213,8 @@ namespace WulaFallenEmpire
switch (SimpleProps.defaultBehavior)
{
case FactionFilterDefaultBehavior.Allow:
// 白名单模式:在白名单中或默认允许
if (SimpleProps.debugLogging && !inWhitelist)
{
Log.Message($"[SimpleTechnologyTrigger] Faction {faction.def.defName} not in whitelist, but default behavior is Allow");
}
return true;
case FactionFilterDefaultBehavior.Deny:
// 白名单模式:只有在白名单中才允许
if (inWhitelist)
{
if (SimpleProps.debugLogging)
@@ -200,7 +234,6 @@ namespace WulaFallenEmpire
}
}
// 如果没有设置白名单,根据默认行为决定
switch (SimpleProps.defaultBehavior)
{
case FactionFilterDefaultBehavior.Allow:
@@ -244,20 +277,23 @@ namespace WulaFallenEmpire
/// <summary>
/// 检查是否存在活跃的相同任务
/// </summary>
private bool HasActiveQuest()
private bool HasActiveQuest(IncidentDef incident)
{
if (SimpleProps.questDef == null)
return false;
bool hasActiveQuest = Find.QuestManager.QuestsListForReading.Any((Quest q) =>
q.root == SimpleProps.questDef && !q.Historical);
if (SimpleProps.debugLogging && hasActiveQuest)
// 如果配置了防止重复,检查是否有相同根源的任务
if (SimpleProps.preventDuplicateQuests && SimpleProps.questDef != null)
{
Log.Message($"[SimpleTechnologyTrigger] Active quest {SimpleProps.questDef.defName} found, skipping trigger");
bool hasActiveQuest = Find.QuestManager.QuestsListForReading.Any((Quest q) =>
q.root == SimpleProps.questDef && !q.Historical);
if (SimpleProps.debugLogging && hasActiveQuest)
{
Log.Message($"[SimpleTechnologyTrigger] Active quest {SimpleProps.questDef.defName} found, skipping trigger");
}
return hasActiveQuest;
}
return hasActiveQuest;
return false;
}
/// <summary>
@@ -267,13 +303,23 @@ namespace WulaFallenEmpire
{
StringBuilder status = new StringBuilder();
status.AppendLine($"Simple Technology Trigger: {SimpleProps.technology?.defName ?? "NULL"}");
var factionCheck = PassesRequiredFactionCheck();
status.AppendLine($"Faction Relation Status: {factionCheck.Status}");
if (!factionCheck.IsValid)
{
status.AppendLine($"Faction Relation Reason: {factionCheck.Reason}");
}
status.AppendLine($"Research Status: {(PassesTechnologyCheck() ? " COMPLETED" : " NOT COMPLETED")}");
status.AppendLine($"Required Faction: {SimpleProps.requiredFaction?.defName ?? "NONE"}");
status.AppendLine($"Faction Relation: {(PassesRequiredFactionCheck() ? " VALID" : " INVALID")}");
status.AppendLine($"Hostile Alternative: {SimpleProps.incidentIfHostile?.defName ?? "NONE"}");
status.AppendLine($"Check Interval: {SimpleProps.checkIntervalDays} days");
status.AppendLine($"Current Interval: {IntervalsPassed}");
status.AppendLine($"Next Check: {GetNextCheckInterval()} intervals");
status.AppendLine($"Can Trigger Now: {PassesIntervalCheck() && PassesTechnologyCheck() && PassesRequiredFactionCheck()}");
bool canTrigger = PassesIntervalCheck() && PassesTechnologyCheck() && factionCheck.IsValid;
status.AppendLine($"Can Trigger Now: {canTrigger}");
// 详细派系信息
if (SimpleProps.requiredFaction != null)
@@ -305,4 +351,39 @@ namespace WulaFallenEmpire
return ((currentInterval / checkInterval) + 1) * checkInterval;
}
}
/// <summary>
/// 派系关系检查结果
/// </summary>
public struct FactionRelationResult
{
public FactionRelationStatus Status { get; }
public string Reason { get; }
public bool IsValid => Status == FactionRelationStatus.Valid || Status == FactionRelationStatus.HostileButAllowed;
public FactionRelationResult(FactionRelationStatus status, string reason = "")
{
Status = status;
Reason = reason;
}
public static FactionRelationResult Valid() => new FactionRelationResult(FactionRelationStatus.Valid);
public static FactionRelationResult HostileButAllowed() => new FactionRelationResult(FactionRelationStatus.HostileButAllowed);
public static FactionRelationResult Invalid(string reason) => new FactionRelationResult(FactionRelationStatus.Invalid, reason);
public override string ToString()
{
return $"{Status}{(string.IsNullOrEmpty(Reason) ? "" : $" ({Reason})")}";
}
}
/// <summary>
/// 派系关系状态枚举
/// </summary>
public enum FactionRelationStatus
{
Valid, // 关系有效,可以触发原事件
HostileButAllowed, // 关系敌对但允许,可以触发替代事件
Invalid // 关系无效,不触发任何事件
}
}

View File

@@ -0,0 +1,106 @@
using RimWorld;
using UnityEngine;
using Verse;
namespace WulaFallenEmpire
{
public class CompProperties_DelayedDamageIfNotPlayer : CompProperties
{
public DamageDef damageDef;
public int damageAmount = 10;
public float armorPenetration = 0f;
public BodyPartDef hitPart;
public bool destroyIfKilled = true;
public CompProperties_DelayedDamageIfNotPlayer()
{
compClass = typeof(CompDelayedDamageIfNotPlayer);
}
}
public class CompDelayedDamageIfNotPlayer : ThingComp
{
private bool damageApplied = false;
private bool scheduledForNextFrame = false;
public CompProperties_DelayedDamageIfNotPlayer Props => (CompProperties_DelayedDamageIfNotPlayer)props;
public override void PostSpawnSetup(bool respawningAfterLoad)
{
base.PostSpawnSetup(respawningAfterLoad);
// 只在初次生成时检查,重新加载时不重复
if (!respawningAfterLoad)
{
CheckAndScheduleDamage();
}
}
private void CheckAndScheduleDamage()
{
// 检查派系,如果不是玩家派系则安排伤害
if (parent.Faction != Faction.OfPlayer && !damageApplied && !scheduledForNextFrame)
{
scheduledForNextFrame = true;
// 使用LongEventHandler来在下一帧执行
LongEventHandler.ExecuteWhenFinished(ApplyDelayedDamage);
}
}
private void ApplyDelayedDamage()
{
if (scheduledForNextFrame && !damageApplied)
{
// 再次确认对象仍然存在且未被销毁
if (parent != null && parent.Spawned && !parent.Destroyed)
{
ApplyDamage();
}
scheduledForNextFrame = false;
}
}
private void ApplyDamage()
{
try
{
if (parent == null || parent.Destroyed || damageApplied)
return;
// 创建伤害信息
DamageInfo damageInfo = new DamageInfo(
Props.damageDef,
Props.damageAmount,
armorPenetration: Props.armorPenetration,
instigator: parent
);
// 施加伤害
parent.TakeDamage(damageInfo);
damageApplied = true;
// 记录日志以便调试
Log.Message($"[CompDelayedDamage] Applied {Props.damageAmount} {Props.damageDef.defName} damage to {parent.Label} (Faction: {parent.Faction?.Name ?? "None"})");
// 检查是否被杀死
if (Props.destroyIfKilled && (parent.Destroyed || (parent is Pawn pawn && pawn.Dead)))
{
Log.Message($"[CompDelayedDamage] {parent.Label} was destroyed by delayed damage");
}
}
catch (System.Exception ex)
{
Log.Error($"[CompDelayedDamage] Error applying delayed damage: {ex}");
}
}
public override void PostExposeData()
{
base.PostExposeData();
Scribe_Values.Look(ref damageApplied, "damageApplied", false);
Scribe_Values.Look(ref scheduledForNextFrame, "scheduledForNextFrame", false);
}
}
}

View File

@@ -122,6 +122,7 @@
<Compile Include="BuildingComp\WULA_SkyfallerCaller\CompSkyfallerCaller.cs" />
<Compile Include="BuildingComp\WULA_SkyfallerCaller\CompPrefabSkyfallerCaller.cs" />
<Compile Include="BuildingComp\WULA_SkyfallerCaller\CompProperties_PrefabSkyfallerCaller.cs" />
<Compile Include="BuildingComp\WULA_SkyfallerCaller\DebugActions_PrefabSkyfallerCaller.cs" />
<Compile Include="BuildingComp\WULA_SkyfallerCaller\Skyfaller_PrefabSpawner.cs" />
<Compile Include="BuildingComp\WULA_SkyfallerCaller\WulaSkyfallerWorldComponent.cs" />
<Compile Include="BuildingComp\WULA_SkyfallerCaller\WULA_SkyfallerFactioncs\CompProperties_SkyfallerFaction.cs" />
@@ -259,6 +260,7 @@
<Compile Include="QuestNodes\QuestNode_CheckGlobalResource.cs" />
<Compile Include="QuestNodes\QuestNode_GeneratePawnWithCustomization.cs" />
<Compile Include="QuestNodes\QuestNode_Hyperlinks.cs" />
<Compile Include="QuestNodes\QuestNode_SpawnPrefabSkyfallerCaller.cs" />
<Compile Include="QuestNodes\QuestPart_GlobalResourceCheck.cs" />
<Compile Include="Stat\StatWorker_Energy.cs" />
<Compile Include="Stat\StatWorker_Maintenance.cs" />
@@ -269,6 +271,7 @@
<Compile Include="Storyteller\WULA_SimpleTechnologyTrigger\StorytellerComp_SimpleTechnologyTrigger.cs" />
<Compile Include="ThingComp\CompAndPatch_GiveHediffOnShot.cs" />
<Compile Include="ThingComp\CompApparelInterceptor.cs" />
<Compile Include="ThingComp\CompProperties_DelayedDamageIfNotPlayer.cs" />
<Compile Include="ThingComp\CompPsychicScaling.cs" />
<Compile Include="ThingComp\CompUseEffect_AddDamageShieldCharges.cs" />
<Compile Include="ThingComp\CompUseEffect_OpenCustomUI.cs" />