572 lines
19 KiB
C#
572 lines
19 KiB
C#
using RimWorld;
|
||
using System;
|
||
using System.Collections.Generic;
|
||
using System.Linq;
|
||
using UnityEngine;
|
||
using Verse;
|
||
|
||
namespace ArachnaeSwarm
|
||
{
|
||
/// <summary>
|
||
/// 唯一Pawn管理器
|
||
/// 管理所有具有唯一标识的Pawn,确保同一标识只有一个存活
|
||
/// 注意:数据完全绑定到当前Game,不会跨存档生效
|
||
/// </summary>
|
||
public class UniquePawnManager : GameComponent
|
||
{
|
||
#region 数据定义
|
||
/// <summary>
|
||
/// Pawn注册信息
|
||
/// </summary>
|
||
public class UniquePawnInfo : IExposable
|
||
{
|
||
public string globalVariable;
|
||
public int thingID;
|
||
public int spawnTick;
|
||
public Map map;
|
||
public string pawnName;
|
||
public bool isValid = true;
|
||
public int lastVerificationTick = -1;
|
||
public string gameId; // 添加游戏ID,确保只对当前游戏有效
|
||
|
||
public void ExposeData()
|
||
{
|
||
Scribe_Values.Look(ref globalVariable, "globalVariable");
|
||
Scribe_Values.Look(ref thingID, "thingID");
|
||
Scribe_Values.Look(ref spawnTick, "spawnTick");
|
||
Scribe_References.Look(ref map, "map");
|
||
Scribe_Values.Look(ref pawnName, "pawnName");
|
||
Scribe_Values.Look(ref isValid, "isValid", true);
|
||
Scribe_Values.Look(ref lastVerificationTick, "lastVerificationTick", -1);
|
||
Scribe_Values.Look(ref gameId, "gameId");
|
||
}
|
||
}
|
||
#endregion
|
||
|
||
#region 字段
|
||
private List<UniquePawnInfo> registeredPawns = new List<UniquePawnInfo>();
|
||
private int lastVerificationTick = -1;
|
||
private const int VERIFICATION_INTERVAL = 600; // 每10秒验证一次
|
||
private const int VERIFICATION_TIMEOUT = 1200; // 20秒无响应视为失效
|
||
private string currentGameId;
|
||
private bool initialized = false;
|
||
#endregion
|
||
|
||
#region 构造函数
|
||
public UniquePawnManager(Game game) : base()
|
||
{
|
||
if (game == null)
|
||
return;
|
||
|
||
Initialize();
|
||
}
|
||
|
||
private void Initialize()
|
||
{
|
||
if (initialized)
|
||
return;
|
||
|
||
try
|
||
{
|
||
// 生成当前游戏的唯一ID(基于游戏开始时间和随机数)
|
||
GenerateGameId();
|
||
|
||
// 清理不属于当前游戏的注册信息
|
||
CleanupForeignRegistrations();
|
||
|
||
initialized = true;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Log.Error($"[唯一Pawn系统] 初始化失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// 生成当前游戏的唯一ID
|
||
/// </summary>
|
||
private void GenerateGameId()
|
||
{
|
||
if (Current.Game == null)
|
||
{
|
||
currentGameId = "NoGame";
|
||
return;
|
||
}
|
||
|
||
try
|
||
{
|
||
// 获取游戏开始时间(使用tickManager的gameStartAbsTick)
|
||
int gameStartTick = Find.TickManager.gameStartAbsTick;
|
||
int randomSeed = UnityEngine.Random.Range(1000, 9999);
|
||
|
||
// 获取游戏信息
|
||
string gameName = GetGameName();
|
||
|
||
// 创建唯一ID
|
||
currentGameId = $"Game_{gameStartTick}_{randomSeed}_{gameName.GetHashCode():X8}";
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Log.Error($"[唯一Pawn系统] 生成游戏ID失败: {ex.Message}");
|
||
// 使用备用ID
|
||
currentGameId = $"Game_{Find.TickManager.TicksGame}_{UnityEngine.Random.Range(1000, 9999)}";
|
||
}
|
||
}
|
||
// <summary>
|
||
/// 获取游戏名称
|
||
/// </summary>
|
||
private string GetGameName()
|
||
{
|
||
try
|
||
{
|
||
// 方法1:尝试从存档文件获取
|
||
if (Current.Game.Info != null)
|
||
{
|
||
// GameInfo中没有name属性,但可能有其他标识
|
||
if (!string.IsNullOrEmpty(Current.Game.Info.permadeathModeUniqueName))
|
||
{
|
||
return Current.Game.Info.permadeathModeUniqueName;
|
||
}
|
||
}
|
||
|
||
// 方法2:使用世界种子
|
||
if (Current.Game.World != null && Current.Game.World.info != null)
|
||
{
|
||
return Current.Game.World.info.seedString ?? "UnknownSeed";
|
||
}
|
||
|
||
// 方法3:使用当前时间
|
||
return DateTime.Now.ToString("yyyyMMdd_HHmmss");
|
||
}
|
||
catch (Exception)
|
||
{
|
||
return "UnknownGame";
|
||
}
|
||
}
|
||
#endregion
|
||
|
||
#region 公共接口
|
||
/// <summary>
|
||
/// 注册唯一Pawn
|
||
/// </summary>
|
||
public bool RegisterPawn(Pawn pawn, string globalVariable)
|
||
{
|
||
if (pawn == null || string.IsNullOrEmpty(globalVariable))
|
||
{
|
||
Log.Warning("[唯一Pawn系统] 尝试注册空Pawn或空全局变量");
|
||
return false;
|
||
}
|
||
|
||
if (!initialized)
|
||
Initialize();
|
||
|
||
// 清理无效注册
|
||
CleanupInvalidRegistrations();
|
||
|
||
// 获取当前tick
|
||
int currentTick = Find.TickManager.TicksGame;
|
||
|
||
// 检查是否已存在相同全局变量的Pawn(仅限当前游戏)
|
||
var existingInfos = GetInfosForVariable(globalVariable, true);
|
||
var validExistingInfos = existingInfos.Where(info => info.isValid).ToList();
|
||
|
||
// 如果有有效的已注册Pawn
|
||
if (validExistingInfos.Count > 0)
|
||
{
|
||
// 按生成时间排序,保留最早的
|
||
var earliestInfo = validExistingInfos.OrderBy(info => info.spawnTick).First();
|
||
|
||
// 如果当前Pawn不是最早的,则杀死
|
||
if (earliestInfo.thingID != pawn.thingIDNumber)
|
||
{
|
||
// 杀死当前Pawn
|
||
KillDuplicatePawn(pawn, globalVariable, earliestInfo.pawnName);
|
||
return false;
|
||
}
|
||
else
|
||
{
|
||
// 当前Pawn就是最早的,更新验证时间
|
||
var info = registeredPawns.FirstOrDefault(i =>
|
||
i.globalVariable == globalVariable &&
|
||
i.thingID == pawn.thingIDNumber &&
|
||
i.gameId == currentGameId);
|
||
|
||
if (info != null)
|
||
{
|
||
info.lastVerificationTick = currentTick;
|
||
info.isValid = true;
|
||
info.map = pawn.Map;
|
||
}
|
||
return true;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
// 创建新注册信息(绑定到当前游戏)
|
||
var newInfo = new UniquePawnInfo
|
||
{
|
||
globalVariable = globalVariable,
|
||
thingID = pawn.thingIDNumber,
|
||
spawnTick = currentTick,
|
||
map = pawn.Map,
|
||
pawnName = pawn.Label,
|
||
isValid = true,
|
||
lastVerificationTick = currentTick,
|
||
gameId = currentGameId
|
||
};
|
||
|
||
registeredPawns.Add(newInfo);
|
||
|
||
return true;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 注销Pawn
|
||
/// </summary>
|
||
public void UnregisterPawn(Pawn pawn, string globalVariable)
|
||
{
|
||
if (pawn == null || string.IsNullOrEmpty(globalVariable))
|
||
return;
|
||
|
||
if (!initialized)
|
||
Initialize();
|
||
|
||
var info = registeredPawns.FirstOrDefault(i =>
|
||
i.globalVariable == globalVariable &&
|
||
i.thingID == pawn.thingIDNumber &&
|
||
i.gameId == currentGameId);
|
||
|
||
if (info != null)
|
||
{
|
||
info.isValid = false;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 发送验证信号
|
||
/// </summary>
|
||
public void SendVerificationSignal(Pawn pawn, string globalVariable)
|
||
{
|
||
if (pawn == null || string.IsNullOrEmpty(globalVariable) || pawn.Destroyed || !pawn.Spawned)
|
||
return;
|
||
|
||
if (!initialized)
|
||
Initialize();
|
||
|
||
var info = registeredPawns.FirstOrDefault(i =>
|
||
i.globalVariable == globalVariable &&
|
||
i.thingID == pawn.thingIDNumber &&
|
||
i.gameId == currentGameId);
|
||
|
||
if (info != null)
|
||
{
|
||
info.lastVerificationTick = Find.TickManager.TicksGame;
|
||
info.isValid = true;
|
||
info.map = pawn.Map;
|
||
}
|
||
else
|
||
{
|
||
// Pawn未注册,尝试重新注册(只注册到当前游戏)
|
||
RegisterPawn(pawn, globalVariable);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 检查Pawn是否是最早的(仅限当前游戏)
|
||
/// </summary>
|
||
public bool IsEarliestPawn(Pawn pawn, string globalVariable)
|
||
{
|
||
if (pawn == null || string.IsNullOrEmpty(globalVariable))
|
||
return false;
|
||
|
||
if (!initialized)
|
||
Initialize();
|
||
|
||
var existingInfos = GetInfosForVariable(globalVariable, true);
|
||
var validExistingInfos = existingInfos.Where(info => info.isValid).ToList();
|
||
|
||
if (validExistingInfos.Count == 0)
|
||
return true;
|
||
|
||
var earliestInfo = validExistingInfos.OrderBy(info => info.spawnTick).First();
|
||
return earliestInfo.thingID == pawn.thingIDNumber;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取指定变量的所有Pawn信息
|
||
/// </summary>
|
||
public List<UniquePawnInfo> GetInfosForVariable(string globalVariable, bool onlyCurrentGame = true)
|
||
{
|
||
if (!initialized)
|
||
Initialize();
|
||
|
||
if (onlyCurrentGame)
|
||
{
|
||
return registeredPawns.Where(info =>
|
||
info.globalVariable == globalVariable &&
|
||
info.gameId == currentGameId).ToList();
|
||
}
|
||
else
|
||
{
|
||
return registeredPawns.Where(info => info.globalVariable == globalVariable).ToList();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取调试信息
|
||
/// </summary>
|
||
public string GetDebugInfo()
|
||
{
|
||
System.Text.StringBuilder sb = new System.Text.StringBuilder();
|
||
sb.AppendLine("=== 唯一Pawn管理器调试信息 ===");
|
||
sb.AppendLine($"当前游戏ID: {currentGameId}");
|
||
sb.AppendLine($"已注册Pawn总数: {registeredPawns.Count}");
|
||
sb.AppendLine($"当前游戏Pawn数: {registeredPawns.Count(info => info.gameId == currentGameId)}");
|
||
|
||
var grouped = registeredPawns
|
||
.Where(info => info.gameId == currentGameId) // 只显示当前游戏的
|
||
.GroupBy(info => info.globalVariable)
|
||
.OrderBy(g => g.Key);
|
||
|
||
foreach (var group in grouped)
|
||
{
|
||
sb.AppendLine($"\n变量: {group.Key}");
|
||
var ordered = group.OrderBy(info => info.spawnTick).ToList();
|
||
|
||
for (int i = 0; i < ordered.Count; i++)
|
||
{
|
||
var info = ordered[i];
|
||
string status = i == 0 ? "[最早]" : "[重复]";
|
||
int ageTicks = Find.TickManager.TicksGame - info.spawnTick;
|
||
int lastVerifyAgo = Find.TickManager.TicksGame - info.lastVerificationTick;
|
||
|
||
sb.AppendLine($" {status} {info.pawnName} (ID: {info.thingID})");
|
||
sb.AppendLine($" 生成时间: {info.spawnTick} ({ageTicks} ticks前)");
|
||
sb.AppendLine($" 最后验证: {info.lastVerificationTick} ({lastVerifyAgo} ticks前)");
|
||
sb.AppendLine($" 地图: {info.map?.Index ?? -1}");
|
||
sb.AppendLine($" 有效: {info.isValid}");
|
||
}
|
||
}
|
||
|
||
return sb.ToString();
|
||
}
|
||
#endregion
|
||
|
||
#region 内部方法
|
||
/// <summary>
|
||
/// 清理无效注册
|
||
/// </summary>
|
||
private void CleanupInvalidRegistrations()
|
||
{
|
||
int currentTick = Find.TickManager.TicksGame;
|
||
int removedCount = 0;
|
||
|
||
for (int i = registeredPawns.Count - 1; i >= 0; i--)
|
||
{
|
||
var info = registeredPawns[i];
|
||
|
||
// 移除无效的
|
||
if (!info.isValid)
|
||
{
|
||
registeredPawns.RemoveAt(i);
|
||
removedCount++;
|
||
continue;
|
||
}
|
||
|
||
// 检查超时(只检查当前游戏的)
|
||
if (info.gameId == currentGameId &&
|
||
info.lastVerificationTick > 0 &&
|
||
currentTick - info.lastVerificationTick > VERIFICATION_TIMEOUT)
|
||
{
|
||
// 尝试查找Pawn
|
||
Pawn pawn = FindPawnByID(info.thingID, info.map);
|
||
if (pawn == null || pawn.Destroyed || !pawn.Spawned)
|
||
{
|
||
info.isValid = false;
|
||
registeredPawns.RemoveAt(i);
|
||
removedCount++;
|
||
}
|
||
else
|
||
{
|
||
// Pawn仍然存在,更新验证时间
|
||
info.lastVerificationTick = currentTick;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 清理不属于当前游戏的注册信息
|
||
/// </summary>
|
||
private void CleanupForeignRegistrations()
|
||
{
|
||
if (string.IsNullOrEmpty(currentGameId))
|
||
return;
|
||
|
||
int foreignCount = 0;
|
||
for (int i = registeredPawns.Count - 1; i >= 0; i--)
|
||
{
|
||
var info = registeredPawns[i];
|
||
if (info.gameId != currentGameId)
|
||
{
|
||
registeredPawns.RemoveAt(i);
|
||
foreignCount++;
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 通过ID查找Pawn
|
||
/// </summary>
|
||
private Pawn FindPawnByID(int thingID, Map map)
|
||
{
|
||
if (map == null || thingID <= 0)
|
||
return null;
|
||
|
||
try
|
||
{
|
||
return map.listerThings.ThingsInGroup(ThingRequestGroup.Pawn)
|
||
.OfType<Pawn>()
|
||
.FirstOrDefault(p => p.thingIDNumber == thingID);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Log.Error($"[唯一Pawn系统] 查找Pawn失败: {ex.Message}");
|
||
return null;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 杀死重复的Pawn
|
||
/// </summary>
|
||
private void KillDuplicatePawn(Pawn pawn, string globalVariable, string earliestPawnName)
|
||
{
|
||
try
|
||
{
|
||
if (pawn == null || pawn.Destroyed || !pawn.Spawned)
|
||
return;
|
||
|
||
// 显示死亡消息
|
||
string deathMessage = $"{pawn.Label} 被移除,因为 {earliestPawnName} 是更早生成的唯一Pawn ({globalVariable})";
|
||
Messages.Message(deathMessage, pawn, MessageTypeDefOf.NegativeEvent);
|
||
|
||
// 使用安全的杀死方法
|
||
pawn.Kill(null);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Log.Error($"[唯一Pawn系统] 杀死重复Pawn失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 定期验证所有Pawn(只验证当前游戏的)
|
||
/// </summary>
|
||
private void VerifyAllPawns()
|
||
{
|
||
int currentTick = Find.TickManager.TicksGame;
|
||
int verifiedCount = 0;
|
||
int timeoutCount = 0;
|
||
|
||
foreach (var info in registeredPawns.Where(i => i.isValid && i.gameId == currentGameId))
|
||
{
|
||
// 查找Pawn
|
||
Pawn pawn = FindPawnByID(info.thingID, info.map);
|
||
|
||
if (pawn == null || pawn.Destroyed || !pawn.Spawned)
|
||
{
|
||
// Pawn已不存在
|
||
info.isValid = false;
|
||
timeoutCount++;
|
||
}
|
||
else if (currentTick - info.lastVerificationTick > VERIFICATION_INTERVAL)
|
||
{
|
||
// 需要发送验证请求
|
||
SendVerificationRequest(pawn, info.globalVariable);
|
||
}
|
||
else
|
||
{
|
||
verifiedCount++;
|
||
}
|
||
}
|
||
|
||
// 清理无效的
|
||
CleanupInvalidRegistrations();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 发送验证请求给Pawn
|
||
/// </summary>
|
||
private void SendVerificationRequest(Pawn pawn, string globalVariable)
|
||
{
|
||
// 发送验证信号
|
||
var comp = pawn.TryGetComp<CompUniquePawn>();
|
||
if (comp != null)
|
||
{
|
||
comp.SendVerificationSignal();
|
||
}
|
||
}
|
||
#endregion
|
||
|
||
#region GameComponent实现
|
||
public override void GameComponentTick()
|
||
{
|
||
base.GameComponentTick();
|
||
|
||
if (!initialized)
|
||
Initialize();
|
||
|
||
if (Find.TickManager.TicksGame - lastVerificationTick > VERIFICATION_INTERVAL)
|
||
{
|
||
VerifyAllPawns();
|
||
lastVerificationTick = Find.TickManager.TicksGame;
|
||
}
|
||
}
|
||
|
||
public override void ExposeData()
|
||
{
|
||
base.ExposeData();
|
||
|
||
Scribe_Collections.Look(ref registeredPawns, "registeredPawns", LookMode.Deep);
|
||
Scribe_Values.Look(ref lastVerificationTick, "lastVerificationTick", -1);
|
||
Scribe_Values.Look(ref currentGameId, "currentGameId");
|
||
Scribe_Values.Look(ref initialized, "initialized", false);
|
||
|
||
if (Scribe.mode == LoadSaveMode.LoadingVars)
|
||
{
|
||
// 加载后重新初始化
|
||
initialized = false;
|
||
}
|
||
else if (Scribe.mode == LoadSaveMode.PostLoadInit)
|
||
{
|
||
// 加载后初始化
|
||
Initialize();
|
||
|
||
// 清理无效数据
|
||
CleanupInvalidRegistrations();
|
||
}
|
||
}
|
||
|
||
public override void StartedNewGame()
|
||
{
|
||
base.StartedNewGame();
|
||
|
||
// 开始新游戏时重新初始化
|
||
initialized = false;
|
||
Initialize();
|
||
}
|
||
|
||
public override void LoadedGame()
|
||
{
|
||
base.LoadedGame();
|
||
|
||
// 加载游戏时确保初始化
|
||
if (!initialized)
|
||
Initialize();
|
||
}
|
||
#endregion
|
||
}
|
||
}
|