This commit is contained in:
2025-10-29 12:02:51 +08:00
parent 9118e6ee58
commit e8d96472ee
11 changed files with 1081 additions and 198 deletions

View File

@@ -0,0 +1,565 @@
using System.Collections.Generic;
using RimWorld;
using UnityEngine;
using Verse;
namespace ArachnaeSwarm
{
public class CompSectorSurveillance : ThingComp
{
public CompProperties_SectorSurveillance Props => (CompProperties_SectorSurveillance)props;
// 监视状态
private HashSet<Pawn> attackedPawns = new HashSet<Pawn>();
private Dictionary<Pawn, int> activeTargets = new Dictionary<Pawn, int>();
private Dictionary<Pawn, int> shotCooldowns = new Dictionary<Pawn, int>();
// 性能优化
private int checkInterval = 10;
private int lastCheckTick = 0;
// 调试状态
private int totalFramesProcessed = 0;
private int totalTargetsFound = 0;
private int totalShotsFired = 0;
// 派系缓存
private Faction cachedFaction = null;
private bool factionInitialized = false;
// 新增:射弹数量跟踪
private int remainingProjectiles = -1; // -1 表示无限
private bool ammoExhausted = false;
public override void PostSpawnSetup(bool respawningAfterLoad)
{
base.PostSpawnSetup(respawningAfterLoad);
// 初始化射弹数量
if (!respawningAfterLoad)
{
remainingProjectiles = Props.maxProjectiles;
ammoExhausted = false;
}
Log.Message($"SectorSurveillance: Initialized - Angle: {Props.sectorAngle}°, Range: {Props.sectorRange}, Shots: {Props.shotCount}, Interval: {Props.shotInterval}s");
Log.Message($"SectorSurveillance: ProjectileDef = {Props.projectileDef?.defName ?? "NULL"}");
Log.Message($"SectorSurveillance: Parent = {parent?.def?.defName ?? "NULL"} at {parent?.Position.ToString() ?? "NULL"}");
Log.Message($"SectorSurveillance: Max Projectiles = {Props.maxProjectiles}, Remaining = {remainingProjectiles}");
InitializeFactionCache();
}
private void InitializeFactionCache()
{
Log.Message($"SectorSurveillance: Initializing faction cache...");
if (parent.Faction != null)
{
cachedFaction = parent.Faction;
Log.Message($"SectorSurveillance: Using parent.Faction: {cachedFaction?.Name ?? "NULL"}");
}
else
{
FlyOver flyOver = parent as FlyOver;
if (flyOver?.caster != null && flyOver.caster.Faction != null)
{
cachedFaction = flyOver.caster.Faction;
Log.Message($"SectorSurveillance: Using caster.Faction: {cachedFaction?.Name ?? "NULL"}");
}
else if (flyOver?.faction != null)
{
cachedFaction = flyOver.faction;
Log.Message($"SectorSurveillance: Using flyOver.faction: {cachedFaction?.Name ?? "NULL"}");
}
else
{
Log.Error($"SectorSurveillance: CRITICAL - No faction found!");
}
}
factionInitialized = true;
Log.Message($"SectorSurveillance: Faction cache initialized: {cachedFaction?.Name ?? "NULL"}");
}
private Faction GetEffectiveFaction()
{
if (!factionInitialized)
{
InitializeFactionCache();
}
if (cachedFaction == null)
{
Log.Warning("SectorSurveillance: Cached faction is null, reinitializing...");
InitializeFactionCache();
}
return cachedFaction;
}
public override void CompTick()
{
base.CompTick();
totalFramesProcessed++;
// 每60帧输出一次状态摘要
if (Find.TickManager.TicksGame % 60 == 0)
{
Faction currentFaction = GetEffectiveFaction();
Log.Message($"SectorSurveillance Status: Frames={totalFramesProcessed}, TargetsFound={totalTargetsFound}, ShotsFired={totalShotsFired}, ActiveTargets={activeTargets.Count}, Cooldowns={shotCooldowns.Count}, Faction={currentFaction?.Name ?? "NULL"}, RemainingProjectiles={remainingProjectiles}, AmmoExhausted={ammoExhausted}");
}
UpdateShotCooldowns();
if (Find.TickManager.TicksGame - lastCheckTick >= checkInterval)
{
CheckSectorForTargets();
lastCheckTick = Find.TickManager.TicksGame;
}
ExecuteAttacks();
}
private void UpdateShotCooldowns()
{
List<Pawn> toRemove = new List<Pawn>();
// 关键修复:创建键的副本
List<Pawn> cooldownKeys = new List<Pawn>(shotCooldowns.Keys);
foreach (Pawn pawn in cooldownKeys)
{
// 检查pawn是否仍然有效可能已被爆炸杀死
if (pawn == null || pawn.Destroyed || pawn.Dead || !pawn.Spawned)
{
toRemove.Add(pawn);
continue;
}
// 检查键是否仍在字典中
if (!shotCooldowns.ContainsKey(pawn))
{
continue;
}
shotCooldowns[pawn]--;
if (shotCooldowns[pawn] <= 0)
{
toRemove.Add(pawn);
}
}
foreach (Pawn pawn in toRemove)
{
shotCooldowns.Remove(pawn);
Log.Message($"SectorSurveillance: Cooldown finished for {pawn?.Label ?? "NULL"}");
}
}
private void CheckSectorForTargets()
{
// 如果弹药耗尽,不再检查新目标
if (ammoExhausted)
{
return;
}
List<Pawn> enemiesInSector = GetEnemiesInSector();
Log.Message($"SectorSurveillance: Found {enemiesInSector.Count} enemies in sector");
if (enemiesInSector.Count > 0)
{
Log.Message($"SectorSurveillance: Enemies in sector: {string.Join(", ", enemiesInSector.ConvertAll(p => p.Label))}");
}
foreach (Pawn enemy in enemiesInSector)
{
totalTargetsFound++;
if (!attackedPawns.Contains(enemy) &&
!activeTargets.ContainsKey(enemy) &&
!shotCooldowns.ContainsKey(enemy))
{
activeTargets[enemy] = Props.shotCount;
Log.Message($"SectorSurveillance: Starting attack sequence on {enemy.Label} at {enemy.Position} - {Props.shotCount} shots");
}
}
}
private void ExecuteAttacks()
{
// 如果弹药耗尽,不再执行攻击
if (ammoExhausted)
{
return;
}
List<Pawn> completedTargets = new List<Pawn>();
// 关键修复:在枚举之前创建键的副本
List<Pawn> targetsToProcess = new List<Pawn>(activeTargets.Keys);
foreach (Pawn enemy in targetsToProcess)
{
// 检查目标是否仍然有效(可能已被爆炸杀死)
if (enemy == null || enemy.Destroyed || enemy.Dead || !enemy.Spawned)
{
completedTargets.Add(enemy);
continue;
}
// 检查目标是否仍在字典中
if (!activeTargets.ContainsKey(enemy))
{
continue;
}
int remainingShots = activeTargets[enemy];
if (!IsInSector(enemy.Position))
{
Log.Message($"SectorSurveillance: Target {enemy.Label} left sector, cancelling attack");
completedTargets.Add(enemy);
continue;
}
if (shotCooldowns.ContainsKey(enemy))
{
Log.Message($"SectorSurveillance: Target {enemy.Label} in cooldown, skipping this frame");
continue;
}
// 新增:检查剩余射弹数量
if (remainingProjectiles == 0)
{
Log.Message($"SectorSurveillance: Ammo exhausted, cannot fire at {enemy.Label}");
ammoExhausted = true;
break; // 跳出循环,不再发射任何射弹
}
Log.Message($"SectorSurveillance: Attempting to fire at {enemy.Label}, remaining shots: {remainingShots}, remaining projectiles: {remainingProjectiles}");
if (LaunchProjectileAt(enemy))
{
totalShotsFired++;
remainingShots--;
activeTargets[enemy] = remainingShots;
// 新增:减少剩余射弹数量(如果不是无限)
if (remainingProjectiles > 0)
{
remainingProjectiles--;
Log.Message($"SectorSurveillance: Remaining projectiles: {remainingProjectiles}");
// 检查是否耗尽弹药
if (remainingProjectiles == 0)
{
ammoExhausted = true;
Log.Message($"SectorSurveillance: AMMO EXHAUSTED - No more projectiles available");
}
}
int cooldownTicks = Mathf.RoundToInt(Props.shotInterval * 60f);
shotCooldowns[enemy] = cooldownTicks;
Log.Message($"SectorSurveillance: Successfully fired at {enemy.Label}, {remainingShots} shots remaining, cooldown: {cooldownTicks} ticks");
if (remainingShots <= 0)
{
attackedPawns.Add(enemy);
completedTargets.Add(enemy);
Log.Message($"SectorSurveillance: Completed attack sequence on {enemy.Label}");
}
}
else
{
Log.Error($"SectorSurveillance: Failed to fire projectile at {enemy.Label}");
}
}
// 清理已完成的目标
foreach (Pawn enemy in completedTargets)
{
// 再次检查目标是否有效
if (enemy != null)
{
activeTargets.Remove(enemy);
Log.Message($"SectorSurveillance: Removed {enemy.Label} from active targets");
}
else
{
// 如果目标已不存在,直接从字典中移除对应的键
activeTargets.Remove(enemy);
Log.Message($"SectorSurveillance: Removed null target from active targets");
}
}
}
private List<Pawn> GetEnemiesInSector()
{
List<Pawn> enemies = new List<Pawn>();
Map map = parent.Map;
if (map == null)
{
Log.Error("SectorSurveillance: Map is null!");
return enemies;
}
FlyOver flyOver = parent as FlyOver;
if (flyOver == null)
{
Log.Error("SectorSurveillance: Parent is not a FlyOver!");
return enemies;
}
Vector3 center = parent.DrawPos;
Vector3 flightDirection = flyOver.MovementDirection;
float range = Props.sectorRange;
float halfAngle = Props.sectorAngle * 0.5f;
Log.Message($"SectorSurveillance: Checking sector - Center: {center}, Direction: {flightDirection}, Range: {range}, HalfAngle: {halfAngle}");
int totalEnemiesChecked = 0;
// 关键修复创建pawn列表的副本避免在枚举时集合被修改
List<Pawn> allPawns = new List<Pawn>(map.mapPawns.AllPawnsSpawned);
foreach (Pawn pawn in allPawns)
{
totalEnemiesChecked++;
if (IsValidTarget(pawn))
{
bool inSector = IsInSector(pawn.Position);
if (inSector)
{
enemies.Add(pawn);
Log.Message($"SectorSurveillance: Valid target found - {pawn.Label} at {pawn.Position}, in sector: {inSector}");
}
}
}
Log.Message($"SectorSurveillance: Checked {totalEnemiesChecked} pawns, found {enemies.Count} valid targets in sector");
return enemies;
}
private bool IsValidTarget(Pawn pawn)
{
if (pawn == null)
{
Log.Message("SectorSurveillance: IsValidTarget - pawn is null");
return false;
}
// 关键修复检查pawn是否已被销毁或死亡
if (pawn.Destroyed || pawn.Dead || !pawn.Spawned)
{
Log.Message($"SectorSurveillance: IsValidTarget - {pawn.Label} is destroyed/dead/unspawned");
return false;
}
if (pawn.Downed)
{
Log.Message($"SectorSurveillance: IsValidTarget - {pawn.Label} is downed");
return false;
}
Faction effectiveFaction = GetEffectiveFaction();
if (effectiveFaction == null)
{
Log.Error($"SectorSurveillance: IsValidTarget - No effective faction found for {pawn.Label}");
return false;
}
bool hostile = pawn.HostileTo(effectiveFaction);
Log.Message($"SectorSurveillance: IsValidTarget - {pawn.Label} from {pawn.Faction?.Name ?? "NULL"} is hostile to {effectiveFaction.Name}: {hostile}");
return hostile;
}
private bool IsInSector(IntVec3 targetPos)
{
FlyOver flyOver = parent as FlyOver;
if (flyOver == null)
{
Log.Error("SectorSurveillance: IsInSector - Parent is not a FlyOver!");
return false;
}
Vector3 flyOverPos = parent.DrawPos;
Vector3 targetVector = targetPos.ToVector3() - flyOverPos;
targetVector.y = 0;
float distance = targetVector.magnitude;
if (distance > Props.sectorRange)
{
Log.Message($"SectorSurveillance: IsInSector - Target at {targetPos} is out of range: {distance:F1} > {Props.sectorRange}");
return false;
}
Vector3 flightDirection = flyOver.MovementDirection;
float angle = Vector3.Angle(flightDirection, targetVector);
bool inAngle = angle <= Props.sectorAngle * 0.5f;
Log.Message($"SectorSurveillance: IsInSector - Target at {targetPos}, distance: {distance:F1}, angle: {angle:F1}°, inAngle: {inAngle}");
return inAngle;
}
private bool LaunchProjectileAt(Pawn target)
{
if (Props.projectileDef == null)
{
Log.Error("SectorSurveillance: No projectile defined for sector surveillance");
return false;
}
Log.Message($"SectorSurveillance: LaunchProjectileAt - Starting launch for target {target?.Label ?? "NULL"}");
try
{
Vector3 spawnPos = parent.DrawPos;
IntVec3 spawnCell = spawnPos.ToIntVec3();
Log.Message($"SectorSurveillance: Spawn position - World: {spawnPos}, Cell: {spawnCell}");
if (parent.Map == null)
{
Log.Error("SectorSurveillance: Map is null during projectile launch");
return false;
}
if (!spawnCell.InBounds(parent.Map))
{
Log.Error($"SectorSurveillance: Spawn cell {spawnCell} is out of bounds");
return false;
}
Log.Message($"SectorSurveillance: Attempting to spawn projectile: {Props.projectileDef.defName}");
Projectile projectile = (Projectile)GenSpawn.Spawn(Props.projectileDef, spawnCell, parent.Map);
if (projectile != null)
{
Log.Message($"SectorSurveillance: Projectile spawned successfully: {projectile}");
Thing launcher = GetLauncher();
Vector3 launchPos = parent.DrawPos;
LocalTargetInfo targetInfo = new LocalTargetInfo(target);
Log.Message($"SectorSurveillance: Launching projectile - Launcher: {launcher?.def?.defName ?? "NULL"}, LaunchPos: {launchPos}, Target: {targetInfo.Cell}");
projectile.Launch(
launcher,
launchPos,
targetInfo,
targetInfo,
ProjectileHitFlags.IntendedTarget,
false
);
Log.Message($"SectorSurveillance: Projectile launched successfully");
return true;
}
else
{
Log.Error("SectorSurveillance: Failed to spawn projectile - GenSpawn.Spawn returned null");
return false;
}
}
catch (System.Exception ex)
{
Log.Error($"SectorSurveillance: Exception launching projectile: {ex}");
Log.Error($"SectorSurveillance: Stack trace: {ex.StackTrace}");
return false;
}
}
private Thing GetLauncher()
{
FlyOver flyOver = parent as FlyOver;
if (flyOver != null && flyOver.caster != null)
{
Log.Message($"SectorSurveillance: Using caster as launcher: {flyOver.caster.Label}");
return flyOver.caster;
}
Log.Message($"SectorSurveillance: Using parent as launcher: {parent.Label}");
return parent;
}
// 新增获取剩余射弹数量的方法用于UI显示等
public int GetRemainingProjectiles()
{
return remainingProjectiles;
}
// 新增:检查是否还有弹药
public bool HasAmmo()
{
return !ammoExhausted;
}
public override void PostExposeData()
{
base.PostExposeData();
Scribe_Collections.Look(ref attackedPawns, "attackedPawns", LookMode.Reference);
Scribe_Collections.Look(ref activeTargets, "activeTargets", LookMode.Reference, LookMode.Value);
Scribe_Collections.Look(ref shotCooldowns, "shotCooldowns", LookMode.Reference, LookMode.Value);
Scribe_Values.Look(ref lastCheckTick, "lastCheckTick", 0);
Scribe_Values.Look(ref totalFramesProcessed, "totalFramesProcessed", 0);
Scribe_Values.Look(ref totalTargetsFound, "totalTargetsFound", 0);
Scribe_Values.Look(ref totalShotsFired, "totalShotsFired", 0);
Scribe_References.Look(ref cachedFaction, "cachedFaction");
Scribe_Values.Look(ref factionInitialized, "factionInitialized", false);
// 新增:保存和加载射弹数量状态
Scribe_Values.Look(ref remainingProjectiles, "remainingProjectiles", -1);
Scribe_Values.Look(ref ammoExhausted, "ammoExhausted", false);
}
public override string CompInspectStringExtra()
{
string baseString = base.CompInspectStringExtra();
string ammoString = "";
if (Props.maxProjectiles == -1)
{
ammoString = "Ammo: Unlimited";
}
else
{
ammoString = $"Ammo: {remainingProjectiles}/{Props.maxProjectiles}";
if (ammoExhausted)
{
ammoString += " (EXHAUSTED)";
}
}
if (!string.IsNullOrEmpty(baseString))
{
return baseString + "\n" + ammoString;
}
return ammoString;
}
}
public class CompProperties_SectorSurveillance : CompProperties
{
public ThingDef projectileDef;
public float sectorAngle = 90f;
public float sectorRange = 25f;
public int shotCount = 3;
public float shotInterval = 0.3f;
// 新增:最大射弹数量限制
public int maxProjectiles = -1; // -1 表示无限开火
public CompProperties_SectorSurveillance()
{
compClass = typeof(CompSectorSurveillance);
}
}
}