Files
ArachnaeSwarm/Source/ArachnaeSwarm/Flyover/ARA_SectorSurveillance/CompSectorSurveillance.cs
2025-10-29 12:02:51 +08:00

566 lines
22 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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);
}
}
}