This commit is contained in:
2025-12-19 17:27:32 +08:00
parent dc719fbf28
commit 2825ef4e97
35 changed files with 828 additions and 1137 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -15,7 +15,7 @@ namespace ArachnaeSwarm
{
if (DebugEnabled)
{
Log.Message(message);
ArachnaeLog.Debug(message);
}
}
}

View File

@@ -1,4 +1,6 @@
// File: CompDelayedTerrainSpawn.cs
using RimWorld;
using System.Collections.Generic;
using Verse;
namespace ArachnaeSwarm
@@ -44,27 +46,80 @@ namespace ArachnaeSwarm
// The core logic: iterate through nearby cells and change their terrain.
foreach (IntVec3 current in GenRadial.RadialCellsAround(parent.Position, Props.spawnRadius, true))
{
if (current.InBounds(parent.Map) && current.Walkable(parent.Map))
if (!current.InBounds(parent.Map) || !current.Walkable(parent.Map))
continue;
TerrainDef currentTerrain = parent.Map.terrainGrid.TerrainAt(current);
if (currentTerrain == null)
continue;
// === 新增:使用黑名单检查 ===
if (ShouldSkipTerrain(currentTerrain, current))
{
// 添加:检查当前地形是否有 ARA_Creep 标签
TerrainDef currentTerrain = parent.Map.terrainGrid.TerrainAt(current);
if (currentTerrain != null && HasCreepTag(currentTerrain))
{
continue; // 跳过有 ARA_Creep 标签的地面
}
parent.Map.terrainGrid.SetTerrain(current, Props.terrainToSpawn);
continue; // 跳过有排除tag的地面
}
parent.Map.terrainGrid.SetTerrain(current, Props.terrainToSpawn);
}
}
/// <summary>
/// 检查地形是否具有 ARA_Creep 标签
/// === 新增:检查是否应该跳过此地形 ===
/// 基于tag黑名单、允许性和其他条件
/// </summary>
/// <returns>如果地形有 ARA_Creep 标签则返回 true</returns>
private bool HasCreepTag(TerrainDef terrain)
private bool ShouldSkipTerrain(TerrainDef terrain, IntVec3 cell)
{
return terrain.tags != null && terrain.tags.Contains("ARA_Creep") || terrain.tags.Contains("ARA_Incubator_Nutrient_Solution");
if (terrain == null)
return false;
// 1. 检查tag黑名单
if (Props.IsTerrainExcluded(terrain))
{
return true;
}
// 2. 检查允许性(如果启用)
if (Props.checkAffordances && terrain.affordances != null && Props.excludeAffordances != null)
{
foreach (var affordance in terrain.affordances)
{
if (affordance != null && Props.excludeAffordances.Contains(affordance.defName))
{
return true;
}
}
}
// 3. 检查优先地形(如果启用智能覆盖)
if (Props.smartOverlay && Props.preferredTerrains != null)
{
if (Props.preferredTerrains.Contains(terrain))
{
return true;
}
}
// 4. 检查路径(如果启用路径保护)
if (Props.preservePaths)
{
if (IsPathCell(cell, parent.Map))
{
return true;
}
}
return false;
}
/// <summary>
/// === 新增:检查是否是路径单元格 ===
/// </summary>
private bool IsPathCell(IntVec3 cell, Map map)
{
// 检查是否有路径标记
// 这里可以根据需要扩展,检查设计者路径标记等
// 暂时返回false因为需要更多信息
return false;
}
// NOTICE: There is NO CompTick() method here. This component does not perform any updates after it has spawned.

View File

@@ -1,3 +1,5 @@
// File: CompProperties_DelayedTerrainSpawn.cs
using System.Collections.Generic;
using Verse;
namespace ArachnaeSwarm
@@ -10,11 +12,75 @@ namespace ArachnaeSwarm
{
public TerrainDef terrainToSpawn;
public float spawnRadius = 0f;
// === 新增tag黑名单 ===
public List<string> excludeTerrainTags = null; // 要排除的tag列表
public bool useDefaultExclusions = true; // 是否使用默认排除项ARA_Creep和ARA_Incubator_Nutrient_Solution
// === 新增:扩展检查选项 ===
public bool checkAffordances = false; // 是否检查地形允许性affordances
public List<string> excludeAffordances = null; // 要排除的允许性列表
// === 新增:智能覆盖选项 ===
public bool smartOverlay = false; // 是否智能覆盖(仅覆盖"较差"的地形)
public List<TerrainDef> preferredTerrains = null; // 优先地形列表,这些不会被覆盖
public bool preservePaths = false; // 是否保留路径
public CompProperties_DelayedTerrainSpawn()
{
// The component logic now runs instantly, but the class name is kept for compatibility.
compClass = typeof(CompDelayedTerrainSpawn);
}
/// <summary>
/// 获取要排除的tag列表包括默认值
/// </summary>
public List<string> GetExcludedTags()
{
var tags = new List<string>();
// 如果使用默认排除项添加默认tag
if (useDefaultExclusions)
{
if (!tags.Contains("ARA_Creep"))
tags.Add("ARA_Creep");
if (!tags.Contains("ARA_Incubator_Nutrient_Solution"))
tags.Add("ARA_Incubator_Nutrient_Solution");
}
// 添加自定义排除项
if (excludeTerrainTags != null)
{
foreach (var tag in excludeTerrainTags)
{
if (!tags.Contains(tag))
tags.Add(tag);
}
}
return tags;
}
/// <summary>
/// 检查地形是否被排除
/// </summary>
public bool IsTerrainExcluded(TerrainDef terrain)
{
if (terrain == null || terrain.tags == null)
return false;
var excludedTags = GetExcludedTags();
if (excludedTags.Count == 0)
return false;
// 检查地形是否有排除tag
foreach (var tag in terrain.tags)
{
if (excludedTags.Contains(tag))
return true;
}
return false;
}
}
}
}

View File

@@ -492,7 +492,7 @@ namespace ArachnaeSwarm
// 显示消息(仅开发模式)
if (Prefs.DevMode)
{
Log.Message($"[CorpseConverter] Converted {corpseName} at {corpsePosition} to {convertedThing.LabelCap}");
ArachnaeLog.Debug($"[CorpseConverter] Converted {corpseName} at {corpsePosition} to {convertedThing.LabelCap}");
}
}
else
@@ -542,7 +542,7 @@ namespace ArachnaeSwarm
// 显示消息(仅开发模式)
if (Prefs.DevMode)
{
Log.Message($"[CorpseConverter] Marked building at {markingTargetBuilding.Position} ({markingTargetBuilding.LabelCap}) for deconstruction");
ArachnaeLog.Debug($"[CorpseConverter] Marked building at {markingTargetBuilding.Position} ({markingTargetBuilding.LabelCap}) for deconstruction");
}
ResetMarkingState();

View File

@@ -463,7 +463,7 @@ namespace ArachnaeSwarm
// 显示消息(可选)
if (Prefs.DevMode)
{
Log.Message($"[TerrainChanger] Changed terrain at {targetCell} from {previousTerrain?.defName ?? "null"} to {Props.targetTerrain.defName}");
ArachnaeLog.Debug($"[TerrainChanger] Changed terrain at {targetCell} from {previousTerrain?.defName ?? "null"} to {Props.targetTerrain.defName}");
}
ResetWorkState();
@@ -510,7 +510,7 @@ namespace ArachnaeSwarm
// 显示消息(可选)
if (Prefs.DevMode)
{
Log.Message($"[TerrainChanger] Marked terrain at {markingTargetCell} ({currentTerrain.defName}) for removal");
ArachnaeLog.Debug($"[TerrainChanger] Marked terrain at {markingTargetCell} ({currentTerrain.defName}) for removal");
}
ResetMarkingState();

View File

@@ -96,12 +96,12 @@ namespace ArachnaeSwarm
if (costStat != null)
{
totalNutrientCost = Mathf.RoundToInt(incubatingThingDef.GetStatValueAbstract(costStat, null));
Log.Message($"[ARA] 孵化 {incubatingThingDef.defName} 建议有 {totalNutrientCost} 个营养液地块以获得最佳速度");
ArachnaeLog.Debug($"[ARA] 孵化 {incubatingThingDef.defName} 建议有 {totalNutrientCost} 个营养液地块以获得最佳速度");
}
else
{
totalNutrientCost = 0;
Log.Message($"[ARA] 孵化 {incubatingThingDef.defName} 不需要营养液加成");
ArachnaeLog.Debug($"[ARA] 孵化 {incubatingThingDef.defName} 不需要营养液加成");
}
// 立即更新一次营养液计数
@@ -237,24 +237,6 @@ namespace ArachnaeSwarm
builder.AppendLine("ARA_EquipmentIncubator.CurrentNutrientCount".Translate(currentNutrientCount));
builder.AppendLine("ARA_EquipmentIncubator.NutrientSpeedBonus".Translate(NutrientSpeedBonus.ToStringPercent()));
// 显示建议的营养液数量
if (totalNutrientCost > 0 && incubatingThingDef != null)
{
builder.AppendLine();
builder.AppendLine("ARA_EquipmentIncubator.RecommendedNutrients".Translate(incubatingThingDef.LabelCap, totalNutrientCost));
// 显示加成效果
if (currentNutrientCount >= totalNutrientCost)
{
builder.AppendLine("ARA_EquipmentIncubator.MaximumBonusActive".Translate());
}
else
{
int needed = totalNutrientCost - currentNutrientCount;
builder.AppendLine("ARA_EquipmentIncubator.AddMoreNutrients".Translate(needed));
}
}
return builder.ToString().TrimEndNewlines();
}
@@ -398,7 +380,7 @@ namespace ArachnaeSwarm
{
if (!isIncubating) return;
Log.Message($"[ARA] 取消孵化: {incubatingThingDef?.defName}");
ArachnaeLog.Debug($"[ARA] 取消孵化: {incubatingThingDef?.defName}");
isIncubating = false;
incubationProgress = 0f;
@@ -420,7 +402,7 @@ namespace ArachnaeSwarm
{
if (incubatingThingDef == null) return;
Log.Message($"[ARA] 完成孵化: {incubatingThingDef.defName}");
ArachnaeLog.Debug($"[ARA] 完成孵化: {incubatingThingDef.defName}");
float finalQualityPercent = QualityPercent;

View File

@@ -293,7 +293,7 @@ namespace ArachnaeSwarm
// 按物品名称排序
cachedConfigs.Sort((a, b) => string.Compare(a.thingDef?.label ?? "", b.thingDef?.label ?? ""));
Log.Message($"Built {cachedConfigs.Count} equipment incubation configs for {parent.def.defName}");
ArachnaeLog.Debug($"Built {cachedConfigs.Count} equipment incubation configs for {parent.def.defName}");
}
// 切换到特定索引

View File

@@ -123,7 +123,7 @@ namespace ArachnaeSwarm
selfDestructInitiated = true;
pawnMissingTickCount = 0;
Log.Message($"RefuelingVat at {Position}: Pawn missing from container. Self-destruct initiated. Will destroy in {MissingTicksBeforeDestruction} ticks.");
ArachnaeLog.Debug($"RefuelingVat at {Position}: Pawn missing from container. Self-destruct initiated. Will destroy in {MissingTicksBeforeDestruction} ticks.");
// 发送警告消息
Messages.Message("RefuelingVat_ContainmentBreach".Translate(), this, MessageTypeDefOf.NegativeEvent);
@@ -132,7 +132,7 @@ namespace ArachnaeSwarm
// === 新增方法:执行建筑销毁 ===
private void ExecuteSelfDestruct()
{
Log.Message($"RefuelingVat at {Position}: Self-destruct sequence complete. Destroying building.");
ArachnaeLog.Debug($"RefuelingVat at {Position}: Self-destruct sequence complete. Destroying building.");
// 显示爆炸效果
if (!Destroyed)
@@ -172,7 +172,7 @@ namespace ArachnaeSwarm
}
else if (pawnMissingTickCount % 30 == 0) // 每0.5秒报告一次
{
Log.Message($"RefuelingVat at {Position}: Pawn still missing. Self-destruct in {MissingTicksBeforeDestruction - pawnMissingTickCount} ticks.");
ArachnaeLog.Debug($"RefuelingVat at {Position}: Pawn still missing. Self-destruct in {MissingTicksBeforeDestruction - pawnMissingTickCount} ticks.");
}
}
}
@@ -181,7 +181,7 @@ namespace ArachnaeSwarm
// Pawn在容器内重置自毁系统
if (selfDestructInitiated)
{
Log.Message($"RefuelingVat at {Position}: Pawn returned to container. Self-destruct cancelled.");
ArachnaeLog.Debug($"RefuelingVat at {Position}: Pawn returned to container. Self-destruct cancelled.");
selfDestructInitiated = false;
pawnMissingTickCount = 0;
Messages.Message("RefuelingVat_ContainmentRestored".Translate(), this, MessageTypeDefOf.PositiveEvent);
@@ -249,7 +249,7 @@ namespace ArachnaeSwarm
{
if (selectedPawn != null && innerContainer.Contains(selectedPawn))
{
Log.Message($"RefuelingVat despawned with pawn inside, forcing ejection.");
ArachnaeLog.Debug($"RefuelingVat despawned with pawn inside, forcing ejection.");
Finish(); // 使用修改后的Finish方法
}
}
@@ -308,7 +308,7 @@ namespace ArachnaeSwarm
// 检查是否是被建筑杀死的
if (pawnsKilledByVat.Contains(pawn))
{
Log.Message($"Pawn {pawn.Label} killed by RefuelingVat.");
ArachnaeLog.Debug($"Pawn {pawn.Label} killed by RefuelingVat.");
// 注意现在不再需要从容器中移除Pawn或销毁尸体
// 因为建筑会被销毁Pawn会自然弹出
@@ -375,7 +375,7 @@ namespace ArachnaeSwarm
else
{
// 其他原因的死亡 - 启动自毁系统
Log.Message($"Pawn {selectedPawn.Label} died unexpectedly. Starting self-destruct.");
ArachnaeLog.Debug($"Pawn {selectedPawn.Label} died unexpectedly. Starting self-destruct.");
InitiateSelfDestruct();
}
return;
@@ -465,7 +465,7 @@ namespace ArachnaeSwarm
selfDestructInitiated = false;
pawnMissingTickCount = 0;
Log.Message($"Pawn {pawn.Label} inserted into RefuelingVat at {Position}. Self-destruct monitoring active.");
ArachnaeLog.Debug($"Pawn {pawn.Label} inserted into RefuelingVat at {Position}. Self-destruct monitoring active.");
}
if (deselected)
{
@@ -482,7 +482,7 @@ namespace ArachnaeSwarm
// 检查pawn是否还活着如果已经死亡且是被建筑杀死的则启动自毁
if (selectedPawn.Dead && pawnsKilledByVat.Contains(selectedPawn))
{
Log.Message($"Pawn {selectedPawn.Label} killed by vat. Starting self-destruct.");
ArachnaeLog.Debug($"Pawn {selectedPawn.Label} killed by vat. Starting self-destruct.");
InitiateSelfDestruct();
return;
}
@@ -514,7 +514,7 @@ namespace ArachnaeSwarm
// 方法3强制移除仅对活着的pawn
if (!ejected && innerContainer.Contains(selectedPawn) && !selectedPawn.Dead)
{
Log.Message($"Forcing removal of pawn {selectedPawn} from RefuelingVat");
ArachnaeLog.Debug($"Forcing removal of pawn {selectedPawn} from RefuelingVat");
innerContainer.Remove(selectedPawn);
GenPlace.TryPlaceThing(selectedPawn, this.Position, base.Map, ThingPlaceMode.Near);
ejected = true;
@@ -523,11 +523,11 @@ namespace ArachnaeSwarm
if (ejected)
{
Log.Message($"Successfully ejected {selectedPawn} using method: {ejectionMethod}");
ArachnaeLog.Debug($"Successfully ejected {selectedPawn} using method: {ejectionMethod}");
}
else if (!selectedPawn.Dead) // 只有活着的pawn弹出失败才报错
{
Log.Message($"Failed to eject {selectedPawn} from RefuelingVat");
ArachnaeLog.Debug($"Failed to eject {selectedPawn} from RefuelingVat");
}
}
catch (Exception ex)
@@ -551,7 +551,7 @@ namespace ArachnaeSwarm
// 确保pawn不在容器中除非是被建筑杀死的
if (innerContainer.Contains(selectedPawn) && !(selectedPawn.Dead && pawnsKilledByVat.Contains(selectedPawn)))
{
Log.Message($"Pawn {selectedPawn} still in container during OnStop, forcing removal.");
ArachnaeLog.Debug($"Pawn {selectedPawn} still in container during OnStop, forcing removal.");
innerContainer.Remove(selectedPawn);
}
}

View File

@@ -96,7 +96,7 @@ namespace ArachnaeSwarm
manager.RegisterResearch(this, storedResearch);
}
Log.Message($"[ResearchBlueprintReader] Building spawned with ID: {ThingID}, UniqueId: {UniqueId}");
ArachnaeLog.Debug($"[ResearchBlueprintReader] Building spawned with ID: {ThingID}, UniqueId: {UniqueId}");
}
catch (Exception ex)
{
@@ -177,7 +177,7 @@ namespace ArachnaeSwarm
Messages.Message("ResearchBlueprintReader_ResearchCompleted".Translate(storedResearch.LabelCap),
MessageTypeDefOf.PositiveEvent);
Log.Message($"[ResearchBlueprintReader] Research completed: {storedResearch.defName}");
ArachnaeLog.Debug($"[ResearchBlueprintReader] Research completed: {storedResearch.defName}");
}
else
{
@@ -449,21 +449,6 @@ namespace ArachnaeSwarm
else
{
builder.AppendLine("<color=#ff9900>" + "ResearchBlueprintReader_StatusResearching".Translate(storedResearch.LabelCap) + "</color>");
builder.AppendLine("ResearchBlueprintReader_BuildingProgress".Translate(
progress, storedResearch.baseCost, (progress / storedResearch.baseCost * 100)));
builder.Append("ResearchBlueprintReader_GlobalProgress".Translate(
Find.ResearchManager.GetProgress(storedResearch), storedResearch.baseCost));
// 显示研究速度
builder.AppendLine();
builder.Append("ResearchBlueprintReader_ResearchSpeed".Translate(ResearchSpeed));
if (researchStartTime > 0)
{
int days = (Find.TickManager.TicksGame - researchStartTime) / 60000;
builder.AppendLine();
builder.Append("ResearchBlueprintReader_ResearchTime".Translate(days));
}
}
}
else

View File

@@ -27,11 +27,16 @@ namespace ArachnaeSwarm
private List<ResearchProjectDef> serializedProjects;
private List<List<Building_ResearchBlueprintReader>> serializedBuildings;
// === 新增:科技丢失检查计时器 ===
private int lostResearchCheckTimer;
private const int LostResearchCheckInterval = 5000; // 每5秒检查一次
public ResearchBlueprintReaderManager(Game game) : base()
{
instance = this;
allReaders = new List<Building_ResearchBlueprintReader>();
researchBuildings = new Dictionary<ResearchProjectDef, List<Building_ResearchBlueprintReader>>();
lostResearchCheckTimer = 0;
}
public static ResearchBlueprintReaderManager Instance => instance;
@@ -107,13 +112,16 @@ namespace ArachnaeSwarm
}
}
Log.Message($"[ResearchManager] Loaded {allReaders.Count} buildings, {researchBuildings.Count} research projects");
ArachnaeLog.Debug($"[ResearchManager] Loaded {allReaders.Count} buildings, {researchBuildings.Count} research projects");
}
else if (Scribe.mode == LoadSaveMode.PostLoadInit)
{
// 后加载初始化:清理所有无效数据
CleanupInvalidData();
}
// 保存和加载计时器
Scribe_Values.Look(ref lostResearchCheckTimer, "lostResearchCheckTimer", 0);
}
public override void GameComponentTick()
@@ -126,6 +134,92 @@ namespace ArachnaeSwarm
CleanupInvalidData();
cleanupTimer = 0;
}
// === 新增:定期检查是否有科技因建筑损失而丢失 ===
lostResearchCheckTimer++;
if (lostResearchCheckTimer >= LostResearchCheckInterval)
{
CheckForLostResearch();
lostResearchCheckTimer = 0;
}
}
/// <summary>
/// === 新增:检查因建筑损失而丢失的科技 ===
/// </summary>
private void CheckForLostResearch()
{
if (researchBuildings == null || researchBuildings.Count == 0)
return;
ArachnaeLog.Debug($"[ResearchManager] Checking for lost research projects...");
// 获取需要检查的项目列表(复制以避免修改时遍历)
var projectsToCheck = new List<ResearchProjectDef>(researchBuildings.Keys);
foreach (var project in projectsToCheck)
{
if (project == null)
continue;
if (!researchBuildings.ContainsKey(project))
continue;
var buildings = researchBuildings[project];
if (buildings == null)
{
researchBuildings.Remove(project);
continue;
}
// 清理无效建筑
buildings.RemoveAll(b =>
b == null || b.Destroyed || !b.Spawned || b.Map == null);
// 如果已经没有建筑了
if (buildings.Count == 0)
{
researchBuildings.Remove(project);
// 调用Patch创建的移除方法来丢失科技
OnResearchLostDueToBuildingLoss(project);
}
}
}
/// <summary>
/// === 新增:科技因建筑损失而丢失的处理 ===
/// </summary>
private void OnResearchLostDueToBuildingLoss(ResearchProjectDef project)
{
if (project == null)
return;
ArachnaeLog.Debug($"[ResearchManager] Research project lost due to building loss: {project.defName}");
try
{
// 使用ResearchRemover类来移除科技
if (ResearchRemover.RemoveResearchProject(project, removeDependencies: false))
{
// 发送游戏内消息
Messages.Message(
"ResearchManager_ResearchLost".Translate(project.LabelCap),
MessageTypeDefOf.NegativeEvent
);
// 发送日志
ArachnaeLog.Debug($"[ResearchManager] Research project '{project.defName}' has been removed due to loss of all research buildings.");
}
else
{
Log.Warning($"[ResearchManager] Failed to remove research project: {project.defName}");
}
}
catch (Exception ex)
{
Log.Error($"[ResearchManager] Error removing research project {project.defName}: {ex}");
}
}
/// <summary>
@@ -141,7 +235,10 @@ namespace ArachnaeSwarm
removedCount += allReaders.RemoveAll(b =>
b == null || b.Destroyed || !b.Spawned || b.Map == null);
Log.Message($"[ResearchManager] Cleaned up {removedCount} invalid buildings from allReaders");
if (removedCount > 0)
{
ArachnaeLog.Debug($"[ResearchManager] Cleaned up {removedCount} invalid buildings from allReaders");
}
}
else
{
@@ -175,9 +272,18 @@ namespace ArachnaeSwarm
foreach (var project in projectsToRemove)
{
researchBuildings.Remove(project);
// 如果项目存在且有效,触发丢失逻辑
if (project != null)
{
OnResearchLostDueToBuildingLoss(project);
}
}
Log.Message($"[ResearchManager] Cleaned up {projectsToRemove.Count} empty research projects");
if (projectsToRemove.Count > 0)
{
ArachnaeLog.Debug($"[ResearchManager] Cleaned up {projectsToRemove.Count} empty research projects");
}
}
else
{
@@ -202,7 +308,7 @@ namespace ArachnaeSwarm
if (!allReaders.Contains(reader))
{
allReaders.Add(reader);
Log.Message($"[ResearchManager] Registered reader: {reader.ThingID} at position {reader.Position}");
ArachnaeLog.Debug($"[ResearchManager] Registered reader: {reader.ThingID} at position {reader.Position}");
}
}
@@ -225,7 +331,7 @@ namespace ArachnaeSwarm
if (!researchBuildings[project].Contains(reader))
{
researchBuildings[project].Add(reader);
Log.Message($"[ResearchManager] Registered research: {project.defName} at building {reader.Position}");
ArachnaeLog.Debug($"[ResearchManager] Registered research: {project.defName} at building {reader.Position}");
}
}
@@ -236,23 +342,26 @@ namespace ArachnaeSwarm
{
if (project == null)
{
Log.Message("[ResearchManager] Project is null, cannot process building destruction");
ArachnaeLog.Debug("[ResearchManager] Project is null, cannot process building destruction");
return;
}
Log.Message($"[ResearchManager] Processing building destruction for project: {project.defName}");
ArachnaeLog.Debug($"[ResearchManager] Processing building destruction for project: {project.defName}");
// 从列表中移除
if (researchBuildings != null && researchBuildings.ContainsKey(project))
{
researchBuildings[project].Remove(building);
Log.Message($"[ResearchManager] Removed building from project list. Remaining buildings: {researchBuildings[project].Count}");
ArachnaeLog.Debug($"[ResearchManager] Removed building from project list. Remaining buildings: {researchBuildings[project].Count}");
// 检查是否还有建筑
if (researchBuildings[project].Count == 0)
{
researchBuildings.Remove(project);
Log.Message($"[ResearchManager] No buildings left for project: {project.defName}");
ArachnaeLog.Debug($"[ResearchManager] No buildings left for project: {project.defName}");
// 触发科技丢失检查
OnResearchLostDueToBuildingLoss(project);
}
}
@@ -263,7 +372,50 @@ namespace ArachnaeSwarm
}
}
// ... 其余方法保持不变 ...
/// <summary>
/// === 新增:获取指定科技的建筑数量 ===
/// </summary>
public int GetBuildingCountForResearch(ResearchProjectDef project)
{
if (project == null || !researchBuildings.ContainsKey(project))
return 0;
return researchBuildings[project]?.Count(b => b != null && !b.Destroyed && b.Spawned) ?? 0;
}
/// <summary>
/// === 新增:手动触发科技丢失检查(用于调试) ===
/// </summary>
public void DebugTriggerLostResearchCheck()
{
ArachnaeLog.Debug("[ResearchManager] Manual trigger of lost research check");
CheckForLostResearch();
}
/// <summary>
/// === 新增:强制移除某个科技(用于调试) ===
/// </summary>
public void DebugForceRemoveResearch(ResearchProjectDef project)
{
if (project == null)
return;
ArachnaeLog.Debug($"[ResearchManager] Debug force remove research: {project.defName}");
// 从字典中移除
if (researchBuildings.ContainsKey(project))
{
researchBuildings.Remove(project);
}
// 使用ResearchRemover移除科技
ResearchRemover.RemoveResearchProject(project, removeDependencies: false);
Messages.Message(
$"Debug: Research '{project.LabelCap}' has been forcibly removed.",
MessageTypeDefOf.NeutralEvent
);
}
/// <summary>
/// 调试命令
@@ -272,13 +424,13 @@ namespace ArachnaeSwarm
{
if (Instance == null)
{
Log.Message("[ResearchManager] No instance found");
ArachnaeLog.Debug("[ResearchManager] No instance found");
return;
}
Log.Message("=== Research Manager Status ===");
Log.Message($"Total buildings: {Instance.allReaders?.Count ?? 0}");
Log.Message($"Active research projects: {Instance.researchBuildings?.Count ?? 0}");
ArachnaeLog.Debug("=== Research Manager Status ===");
ArachnaeLog.Debug($"Total buildings: {Instance.allReaders?.Count ?? 0}");
ArachnaeLog.Debug($"Active research projects: {Instance.researchBuildings?.Count ?? 0}");
if (Instance.researchBuildings != null)
{
@@ -287,7 +439,7 @@ namespace ArachnaeSwarm
if (kvp.Key == null) continue;
int activeBuildings = kvp.Value?.Count(b => b != null && !b.Destroyed && b.Spawned) ?? 0;
Log.Message($" - {kvp.Key.defName}: {activeBuildings} active buildings");
ArachnaeLog.Debug($" - {kvp.Key.defName}: {activeBuildings} active buildings");
}
}
}

View File

@@ -5,7 +5,7 @@ using System.Linq;
using Verse;
using UnityEngine;
namespace ArachnaeSwarm.Comps
namespace ArachnaeSwarm
{
public class CompDestroyRemovesResearch : ThingComp
{
@@ -70,7 +70,7 @@ namespace ArachnaeSwarm.Comps
int removedCount = 0;
foreach (var project in projectsToRemove)
{
if (Utilities.ResearchRemover.RemoveResearchProject(project, false))
if (ResearchRemover.RemoveResearchProject(project, false))
{
removedCount++;
}
@@ -124,7 +124,7 @@ namespace ArachnaeSwarm.Comps
Messages.Message(message, MessageTypeDefOf.NegativeEvent);
// 同时在日志中记录
Log.Message($"[ResearchRemover] Building {parent.LabelCap} destroyed, removed {removedCount} research projects: " +
ArachnaeLog.Debug($"[ResearchRemover] Building {parent.LabelCap} destroyed, removed {removedCount} research projects: " +
string.Join(", ", projectsRemoved.Select(p => p.defName)));
}
@@ -210,7 +210,7 @@ namespace ArachnaeSwarm.Comps
int removedCount = 0;
foreach (var project in projectsToRemove)
{
if (Utilities.ResearchRemover.RemoveResearchProject(project, false))
if (ResearchRemover.RemoveResearchProject(project, false))
{
removedCount++;
}

View File

@@ -3,7 +3,7 @@ using System.Collections.Generic;
using RimWorld;
using Verse;
namespace ArachnaeSwarm.Comps
namespace ArachnaeSwarm
{
public class CompProperties_DestroyRemovesResearch : CompProperties
{

View File

@@ -6,7 +6,7 @@ using System.Linq;
using System.Reflection;
using Verse;
namespace ArachnaeSwarm.Utilities
namespace ArachnaeSwarm
{
public static class ResearchRemover
{
@@ -61,7 +61,7 @@ namespace ArachnaeSwarm.Utilities
return false;
}
Log.Message($"[ResearchRemover] Removing research project: {projectDef.defName}");
ArachnaeLog.Debug($"[ResearchRemover] Removing research project: {projectDef.defName}");
// 获取字段值
var progress = (Dictionary<ResearchProjectDef, float>)progressField.GetValue(manager);
@@ -81,21 +81,21 @@ namespace ArachnaeSwarm.Utilities
if (progress != null && progress.ContainsKey(projectDef))
{
progress.Remove(projectDef);
Log.Message($" Removed from progress dictionary");
ArachnaeLog.Debug($" Removed from progress dictionary");
}
// 2. 从科技碎片字典中移除
if (techprints != null && techprints.ContainsKey(projectDef))
{
techprints.Remove(projectDef);
Log.Message($" Removed from techprints dictionary");
ArachnaeLog.Debug($" Removed from techprints dictionary");
}
// 3. 从异常知识字典中移除
if (anomalyKnowledge != null && anomalyKnowledge.ContainsKey(projectDef))
{
anomalyKnowledge.Remove(projectDef);
Log.Message($" Removed from anomalyKnowledge dictionary");
ArachnaeLog.Debug($" Removed from anomalyKnowledge dictionary");
}
// 4. 如果这是当前项目,停止它
@@ -103,7 +103,7 @@ namespace ArachnaeSwarm.Utilities
{
manager.StopProject(projectDef);
currentProjField.SetValue(manager, null);
Log.Message($" Stopped current project");
ArachnaeLog.Debug($" Stopped current project");
}
// 5. 从异常知识项目中移除
@@ -119,7 +119,7 @@ namespace ArachnaeSwarm.Utilities
removed = true;
}
}
if (removed) Log.Message($" Removed from anomaly knowledge projects");
if (removed) ArachnaeLog.Debug($" Removed from anomaly knowledge projects");
}
// 6. 如果设置了移除依赖项,递归移除依赖于此科技的项目
@@ -132,7 +132,7 @@ namespace ArachnaeSwarm.Utilities
// 7. 重新应用所有mod取消该科技的效果
manager.ReapplyAllMods();
Log.Message($"[ResearchRemover] Successfully removed research project: {projectDef.defName}");
ArachnaeLog.Debug($"[ResearchRemover] Successfully removed research project: {projectDef.defName}");
return true;
}
catch (Exception ex)
@@ -184,7 +184,7 @@ namespace ArachnaeSwarm.Utilities
foreach (var dependent in dependentProjects)
{
Log.Message($" Removing dependent project: {dependent.defName}");
ArachnaeLog.Debug($" Removing dependent project: {dependent.defName}");
// 递归移除依赖项
RemoveDependentProjects(dependent, progress, techprints, anomalyKnowledge,
@@ -246,7 +246,7 @@ namespace ArachnaeSwarm.Utilities
.Where(p => p.IsFinished)
.ToList();
Log.Message($"[ResearchRemover] Removing all {allFinishedProjects.Count} finished research projects");
ArachnaeLog.Debug($"[ResearchRemover] Removing all {allFinishedProjects.Count} finished research projects");
// 批量移除所有科技
RemoveMultipleProjects(allFinishedProjects, false);

View File

@@ -1,5 +1,8 @@
using RimWorld;
// File: CompHediffTerrainSpawn.cs
using RimWorld;
using System.Collections.Generic;
using Verse;
using Verse.Sound;
namespace ArachnaeSwarm
{
@@ -13,6 +16,11 @@ namespace ArachnaeSwarm
private int ticksUntilNextSpawn;
private bool initialized = false;
// === 新增:缓存数据 ===
private int lastCellCount = 0;
private float lastSpawnTime = 0f;
private List<IntVec3> affectedCells = new List<IntVec3>();
public override void CompPostTick(ref float severityAdjustment)
{
@@ -55,6 +63,14 @@ namespace ArachnaeSwarm
if (Props.onlyWhenMoving && (parent.pawn.pather == null || !parent.pawn.pather.Moving))
return false;
// === 新增检查是否在Creep上 ===
if (Props.onlyWhenOnCreep)
{
var terrain = parent.pawn.Map.terrainGrid.TerrainAt(parent.pawn.Position);
if (terrain == null || terrain.tags == null || !terrain.tags.Contains("ARA_Creep"))
return false;
}
// 确保pawn在地图内
if (!parent.pawn.Position.InBounds(parent.pawn.Map))
@@ -72,20 +88,59 @@ namespace ArachnaeSwarm
{
Map map = parent.pawn.Map;
IntVec3 center = parent.pawn.Position;
// 清空受影响单元格列表
affectedCells.Clear();
int spawnedCount = 0;
foreach (IntVec3 current in GenRadial.RadialCellsAround(center, Props.spawnRadius, true))
{
if (current.InBounds(map) && current.Walkable(map))
if (!current.InBounds(map) || !current.Walkable(map))
continue;
// === 新增:检查是否被占用 ===
if (Props.ignoreOccupiedCells && IsCellOccupied(current, map))
continue;
TerrainDef currentTerrain = map.terrainGrid.TerrainAt(current);
if (currentTerrain == null)
continue;
// === 新增:使用黑名单检查 ===
if (ShouldSkipTerrain(currentTerrain, current, map))
{
// 检查当前地形是否有 ARA_Creep 标签
TerrainDef currentTerrain = map.terrainGrid.TerrainAt(current);
if (currentTerrain != null && HasCreepTag(currentTerrain))
{
continue; // 跳过有 ARA_Creep 标签的地面
}
map.terrainGrid.SetTerrain(current, Props.terrainToSpawn);
continue; // 跳过有排除tag的地面
}
// === 新增检查是否只影响自己的Creep ===
if (Props.affectOwnCreepOnly && currentTerrain.tags != null &&
currentTerrain.tags.Contains("ARA_Creep"))
{
// 这里可以添加派系检查逻辑
// 例如只覆盖自己派系的Creep
}
// 应用地形变化
map.terrainGrid.SetTerrain(current, Props.terrainToSpawn);
affectedCells.Add(current);
spawnedCount++;
// === 新增:播放效果 ===
TryPlayEffects(current, map, spawnedCount);
}
lastCellCount = spawnedCount;
lastSpawnTime = Find.TickManager.TicksGame;
// 播放声音
if (Props.spawnSound != null && Rand.Chance(Props.soundChance))
{
Props.spawnSound.PlayOneShot(new TargetInfo(center, map));
}
// 调试日志
if (spawnedCount > 0)
{
ArachnaeLog.Debug($"[HediffTerrainSpawn] Spawned {spawnedCount} terrain cells for {parent.pawn.LabelShort}");
}
}
catch (System.Exception ex)
@@ -93,13 +148,135 @@ namespace ArachnaeSwarm
ArachnaeLog.Debug($"Error in CompHediffTerrainSpawn.DoTerrainSpawn: {ex}");
}
}
/// <summary>
/// === 新增:检查单元格是否被占用 ===
/// </summary>
private bool IsCellOccupied(IntVec3 cell, Map map)
{
var thingList = cell.GetThingList(map);
foreach (var thing in thingList)
{
// 忽略建筑和植物
if (thing is Building || thing is Plant)
{
return true;
}
// 忽略有生命的生物
if (thing is Pawn pawn && !pawn.Dead)
{
return true;
}
}
return false;
}
/// <summary>
/// 检查地形是否具有 ARA_Creep 标签
/// === 新增:检查是否应该跳过此地形 ===
/// 基于tag黑名单、允许性和其他条件
/// </summary>
private bool HasCreepTag(TerrainDef terrain)
private bool ShouldSkipTerrain(TerrainDef terrain, IntVec3 cell, Map map)
{
return terrain.tags != null && terrain.tags.Contains("ARA_Creep");
if (terrain == null)
return false;
// 1. 检查tag黑名单
if (Props.IsTerrainExcluded(terrain))
{
return true;
}
// 2. 检查允许性(如果启用)
if (Props.checkAffordances && terrain.affordances != null && Props.excludeAffordances != null)
{
foreach (var affordance in terrain.affordances)
{
if (affordance != null && Props.excludeAffordances.Contains(affordance.defName))
{
return true;
}
}
}
// 3. 检查优先地形(如果启用智能覆盖)
if (Props.smartOverlay && Props.IsPreferredTerrain(terrain))
{
return true;
}
// 4. 检查路径(如果启用路径保护)
if (Props.preservePaths)
{
if (IsPathCell(cell, map))
{
return true;
}
}
// 5. 检查是否是水或其他特殊地形
if (terrain.IsWater || terrain.defName.Contains("Water") || terrain.defName.Contains("Marsh"))
{
return true;
}
return false;
}
/// <summary>
/// === 新增:检查是否应该覆盖此地形(智能覆盖模式) ===
/// </summary>
private bool ShouldOverlayTerrain(TerrainDef terrain)
{
if (terrain == null)
return false;
// 默认逻辑:仅覆盖"较差"的地形
// 如果地形已经有我们要生成的类型,跳过
if (terrain == Props.terrainToSpawn)
{
return false;
}
// 检查是否是肥沃土壤
if (terrain.fertility > 0.5f)
{
return false;
}
return true;
}
/// <summary>
/// === 新增:检查是否是路径单元格 ===
/// </summary>
private bool IsPathCell(IntVec3 cell, Map map)
{
// 检查是否有路径标记
// 这里可以根据需要扩展,检查设计者路径标记等
// 暂时返回false因为需要更多信息
return false;
}
/// <summary>
/// === 新增:尝试播放效果 ===
/// </summary>
private void TryPlayEffects(IntVec3 cell, Map map, int spawnedCount)
{
if (Props.spawnEffecter == null || !Rand.Chance(Props.effectChance))
return;
try
{
var effecter = Props.spawnEffecter.Spawn();
effecter.Trigger(new TargetInfo(cell, map), new TargetInfo(cell, map));
effecter.Cleanup();
}
catch (System.Exception ex)
{
ArachnaeLog.Debug($"Error playing effect at {cell}: {ex.Message}");
}
}
public override void CompExposeData()
@@ -107,6 +284,8 @@ namespace ArachnaeSwarm
base.CompExposeData();
Scribe_Values.Look(ref ticksUntilNextSpawn, "ticksUntilNextSpawn", Props.intervalTicks);
Scribe_Values.Look(ref initialized, "initialized", false);
Scribe_Values.Look(ref lastCellCount, "lastCellCount", 0);
Scribe_Values.Look(ref lastSpawnTime, "lastSpawnTime", 0f);
}
/// <summary>
@@ -120,7 +299,28 @@ namespace ArachnaeSwarm
return $"Next spawn in: {ticksUntilNextSpawn} ticks\n" +
$"Interval: {Props.intervalTicks} ticks\n" +
$"Radius: {Props.spawnRadius}\n" +
$"Terrain: {Props.terrainToSpawn?.defName ?? "None"}";
$"Terrain: {Props.terrainToSpawn?.defName ?? "None"}\n" +
$"Last spawned: {lastCellCount} cells at tick {lastSpawnTime}";
}
/// <summary>
/// === 新增:获取受影响的单元格(用于调试) ===
/// </summary>
public List<IntVec3> GetAffectedCells()
{
return new List<IntVec3>(affectedCells);
}
/// <summary>
/// === 新增:获取黑名单信息 ===
/// </summary>
public string GetBlacklistInfo()
{
var excludedTags = Props.GetExcludedTags();
if (excludedTags.Count == 0)
return "No tags excluded";
return $"Excluded tags: {string.Join(", ", excludedTags)}";
}
}
}

View File

@@ -1,3 +1,5 @@
// File: CompProperties_HediffTerrainSpawn.cs
using System.Collections.Generic;
using Verse;
namespace ArachnaeSwarm
@@ -16,9 +18,95 @@ namespace ArachnaeSwarm
public bool onlyWhenDowned = false;
public bool onlyWhenMoving = false;
// === 新增tag黑名单 ===
public List<string> excludedTerrainTags = null; // 要排除的tag列表
public bool useDefaultExclusions = true; // 是否使用默认排除项ARA_Creep和ARA_Incubator_Nutrient_Solution
// === 新增:扩展检查选项 ===
public bool checkAffordances = false; // 是否检查地形允许性affordances
public List<string> excludeAffordances = null; // 要排除的允许性列表
// === 新增:智能覆盖选项 ===
public bool smartOverlay = false; // 是否智能覆盖(仅覆盖"较差"的地形)
public List<TerrainDef> preferredTerrains = null; // 优先地形列表,这些不会被覆盖
public bool preservePaths = false; // 是否保留路径
// === 新增:行为选项 ===
public bool onlyWhenOnCreep = false; // 是否只在Creep上生效
public bool ignoreOccupiedCells = true; // 是否忽略被占用的单元格
public bool affectOwnCreepOnly = false; // 是否只影响自己的Creep如果有派系
// === 新增:视觉效果选项 ===
public EffecterDef spawnEffecter = null; // 生成地形时的效果
public float effectChance = 0.3f; // 效果播放几率
public SoundDef spawnSound = null; // 生成地形时的声音
public float soundChance = 0.1f; // 声音播放几率
public CompProperties_HediffTerrainSpawn()
{
compClass = typeof(CompHediffTerrainSpawn);
}
/// <summary>
/// 获取要排除的tag列表包括默认值
/// </summary>
public List<string> GetExcludedTags()
{
var tags = new List<string>();
// 如果使用默认排除项添加默认tag
if (useDefaultExclusions)
{
if (!tags.Contains("ARA_Creep"))
tags.Add("ARA_Creep");
if (!tags.Contains("ARA_Incubator_Nutrient_Solution"))
tags.Add("ARA_Incubator_Nutrient_Solution");
}
// 添加自定义排除项
if (excludedTerrainTags != null)
{
foreach (var tag in excludedTerrainTags)
{
if (!tags.Contains(tag))
tags.Add(tag);
}
}
return tags;
}
/// <summary>
/// 检查地形是否被排除
/// </summary>
public bool IsTerrainExcluded(TerrainDef terrain)
{
if (terrain == null || terrain.tags == null)
return false;
var excludedTags = GetExcludedTags();
if (excludedTags.Count == 0)
return false;
// 检查地形是否有排除tag
foreach (var tag in terrain.tags)
{
if (excludedTags.Contains(tag))
return true;
}
return false;
}
/// <summary>
/// 检查地形是否在优先列表中(智能覆盖)
/// </summary>
public bool IsPreferredTerrain(TerrainDef terrain)
{
if (terrain == null || preferredTerrains == null)
return false;
return preferredTerrains.Contains(terrain);
}
}
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using Verse;
using RimWorld;
@@ -44,10 +44,53 @@ namespace ArachnaeSwarm
if (!Props.allowDuplicates && pawn.health.hediffSet.HasHediff(hediffDef))
continue;
// === 新增:获取应应用的部位 ===
BodyPartDef bodyPartDef = Props.GetBodyPartForHediff(hediffDef);
BodyPartRecord bodyPartRecord = null;
if (bodyPartDef != null)
{
bodyPartRecord = GetFirstMatchingBodyPart(pawn, bodyPartDef);
}
// 添加hediff
pawn.health.AddHediff(hediffDef);
if (bodyPartRecord != null)
{
pawn.health.AddHediff(hediffDef, bodyPartRecord);
}
else
{
pawn.health.AddHediff(hediffDef);
}
}
}
/// <summary>
/// 获取第一个匹配的身体部位记录
/// </summary>
private BodyPartRecord GetFirstMatchingBodyPart(Pawn pawn, BodyPartDef bodyPartDef)
{
if (pawn == null || bodyPartDef == null || pawn.RaceProps?.body == null)
return null;
try
{
// 获取所有匹配的身体部位
List<BodyPartRecord> matchingParts = pawn.RaceProps.body.GetPartsWithDef(bodyPartDef);
if (matchingParts != null && matchingParts.Count > 0)
{
// 返回第一个可用的部位
return matchingParts[0];
}
}
catch (Exception ex)
{
ArachnaeLog.Debug($"Error getting body part for {bodyPartDef.defName}: {ex.Message}");
}
return null;
}
// 新增序列化hediffsApplied标记
public override void PostExposeData()
@@ -66,5 +109,33 @@ namespace ArachnaeSwarm
ArachnaeLog.Debug($"Debug: Applied hediffs to {pawn.Label}");
}
}
/// <summary>
/// === 新增获取已应用的hediff信息用于调试 ===
/// </summary>
public string GetAppliedHediffInfo()
{
if (!hediffsApplied || !(this.parent is Pawn pawn))
return "No hediffs applied";
var result = new System.Text.StringBuilder();
result.AppendLine("Applied hediffs:");
foreach (var hediffDef in Props.hediffs)
{
var hediff = pawn.health.hediffSet.GetFirstHediffOfDef(hediffDef);
if (hediff != null)
{
string partInfo = hediff.Part?.def?.defName ?? "No specific part";
result.AppendLine($"- {hediffDef.defName} on {partInfo}");
}
else
{
result.AppendLine($"- {hediffDef.defName} (not applied)");
}
}
return result.ToString();
}
}
}

View File

@@ -1,4 +1,3 @@
using System;
using System.Collections.Generic;
using Verse;
@@ -14,10 +13,39 @@ namespace ArachnaeSwarm
// 是否允许重复添加相同的hediff
public bool allowDuplicates = false;
// === 新增:优先应用部位设置 ===
public bool useDefaultInstallPart = true; // 是否使用HediffDef的defaultInstallPart
// === 新增:自定义部位映射 ===
public Dictionary<HediffDef, BodyPartDef> customBodyPartMapping = null;
public CompProperties_HediffGiver()
{
this.compClass = typeof(CompHediffGiver);
}
/// <summary>
/// 获取Hediff应该应用的部位
/// </summary>
public BodyPartDef GetBodyPartForHediff(HediffDef hediffDef)
{
if (hediffDef == null)
return null;
// 首先检查自定义映射
if (customBodyPartMapping != null && customBodyPartMapping.ContainsKey(hediffDef))
{
return customBodyPartMapping[hediffDef];
}
// 然后检查是否使用默认安装部位
if (useDefaultInstallPart && hediffDef.defaultInstallPart != null)
{
return hediffDef.defaultInstallPart;
}
return null; // 没有指定部位
}
}
}

View File

@@ -75,7 +75,7 @@ namespace ArachnaeSwarm
}
// 智能溅射:次要目标的敌对状态必须与主目标一致
if (secondaryTargetPawn.HostileTo(casterPawn))
if (secondaryTargetPawn.Faction == casterPawn.Faction)
{
continue;
}