1
This commit is contained in:
@@ -2,6 +2,7 @@ using RimWorld;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using Verse;
|
||||
using System.Text;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
@@ -14,6 +15,11 @@ namespace WulaFallenEmpire
|
||||
private ThingDef restoreBuildingDef;
|
||||
private int restoreMechanoidCount;
|
||||
|
||||
// 缓存校验结果
|
||||
private bool? lastValidationResult = null;
|
||||
private string lastValidationReason = null;
|
||||
private int lastValidationTick = 0;
|
||||
|
||||
public override void Initialize(CompProperties props)
|
||||
{
|
||||
base.Initialize(props);
|
||||
@@ -46,9 +52,12 @@ namespace WulaFallenEmpire
|
||||
};
|
||||
|
||||
// 检查是否可以转换
|
||||
if (!CanTransformNow())
|
||||
string failReason;
|
||||
bool canTransform = CanTransformNow(out failReason);
|
||||
|
||||
if (!canTransform)
|
||||
{
|
||||
command.Disable("无法在当前位置转换");
|
||||
command.Disable(failReason);
|
||||
}
|
||||
|
||||
yield return command;
|
||||
@@ -57,12 +66,38 @@ namespace WulaFallenEmpire
|
||||
|
||||
private string GetGizmoDescription()
|
||||
{
|
||||
string desc = Props.gizmoDesc;
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.Append(Props.gizmoDesc);
|
||||
|
||||
if (restoreBuildingDef != null)
|
||||
{
|
||||
desc += $"\n\n将恢复为: {restoreBuildingDef.LabelCap}";
|
||||
sb.AppendLine();
|
||||
sb.AppendLine();
|
||||
sb.Append($"将恢复为: {restoreBuildingDef.LabelCap}");
|
||||
|
||||
if (restoreMechanoidCount > 0)
|
||||
{
|
||||
sb.AppendLine();
|
||||
sb.Append($"恢复机械族储存: {restoreMechanoidCount}");
|
||||
}
|
||||
}
|
||||
return desc;
|
||||
|
||||
// 添加空间校验信息
|
||||
string failReason;
|
||||
bool isValid = CanTransformNow(out failReason);
|
||||
|
||||
sb.AppendLine();
|
||||
sb.AppendLine();
|
||||
if (isValid)
|
||||
{
|
||||
sb.Append("<color=green>✓ 当前位置可以放置建筑</color>");
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append($"<color=red>✗ {failReason}</color>");
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private Texture2D GetGizmoIcon()
|
||||
@@ -74,25 +109,84 @@ namespace WulaFallenEmpire
|
||||
return TexCommand.Install;
|
||||
}
|
||||
|
||||
private bool CanTransformNow()
|
||||
/// <summary>
|
||||
/// 检查是否可以转换(带详细失败原因)
|
||||
/// </summary>
|
||||
private bool CanTransformNow(out string failReason)
|
||||
{
|
||||
if (parent == null || !parent.Spawned)
|
||||
return false;
|
||||
failReason = null;
|
||||
|
||||
// 检查空间是否足够
|
||||
if (parent == null || !parent.Spawned)
|
||||
{
|
||||
failReason = "单位未生成或已销毁";
|
||||
return false;
|
||||
}
|
||||
|
||||
// 确定要生成的建筑类型
|
||||
ThingDef buildingDef = restoreBuildingDef ?? Props.targetBuildingDef;
|
||||
if (buildingDef == null)
|
||||
return false;
|
||||
|
||||
foreach (IntVec3 cell in GenAdj.CellsOccupiedBy(Pawn.Position, Rot4.North, buildingDef.Size))
|
||||
{
|
||||
if (!cell.InBounds(Pawn.Map) || !cell.Walkable(Pawn.Map) || cell.GetEdifice(Pawn.Map) != null)
|
||||
failReason = "无法确定目标建筑类型";
|
||||
return false;
|
||||
}
|
||||
|
||||
// 使用缓存优化性能(每60 tick检查一次)
|
||||
if (lastValidationResult.HasValue && Find.TickManager.TicksGame - lastValidationTick < 60)
|
||||
{
|
||||
failReason = lastValidationReason;
|
||||
return lastValidationResult.Value;
|
||||
}
|
||||
|
||||
// 执行完整的空间校验(排除被转换的Pawn本身)
|
||||
bool isValid = TransformValidationUtility.CanPlaceBuildingAt(
|
||||
buildingDef,
|
||||
Pawn.Position,
|
||||
Pawn.Map,
|
||||
Pawn.Faction,
|
||||
Pawn, // 排除被转换的Pawn本身
|
||||
out failReason
|
||||
);
|
||||
|
||||
// 更新缓存
|
||||
lastValidationResult = isValid;
|
||||
lastValidationReason = failReason;
|
||||
lastValidationTick = Find.TickManager.TicksGame;
|
||||
|
||||
return isValid;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找最近的可用位置
|
||||
/// </summary>
|
||||
private bool TryFindNearbyValidPosition(out IntVec3 validPosition, out string failReason)
|
||||
{
|
||||
validPosition = IntVec3.Invalid;
|
||||
failReason = null;
|
||||
|
||||
ThingDef buildingDef = restoreBuildingDef ?? Props.targetBuildingDef;
|
||||
if (buildingDef == null)
|
||||
{
|
||||
failReason = "无法确定目标建筑类型";
|
||||
return false;
|
||||
}
|
||||
|
||||
// 在周围搜索可用位置
|
||||
for (int radius = 1; radius <= 5; radius++)
|
||||
{
|
||||
foreach (IntVec3 cell in GenRadial.RadialPatternInRadius(radius))
|
||||
{
|
||||
return false;
|
||||
IntVec3 checkPos = Pawn.Position + cell;
|
||||
|
||||
if (TransformValidationUtility.CanPlaceBuildingAt(buildingDef, checkPos, Pawn.Map, Pawn.Faction, Pawn, out failReason))
|
||||
{
|
||||
validPosition = checkPos;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
failReason = "周围没有找到合适的放置位置";
|
||||
return false;
|
||||
}
|
||||
|
||||
public void TransformToBuilding()
|
||||
@@ -101,7 +195,7 @@ namespace WulaFallenEmpire
|
||||
return;
|
||||
|
||||
Map map = Pawn.Map;
|
||||
IntVec3 position = Pawn.Position;
|
||||
IntVec3 desiredPosition = Pawn.Position;
|
||||
Faction faction = Pawn.Faction;
|
||||
|
||||
// 确定要生成的建筑类型
|
||||
@@ -112,11 +206,29 @@ namespace WulaFallenEmpire
|
||||
return;
|
||||
}
|
||||
|
||||
// 最终校验(排除被转换的Pawn本身)
|
||||
string failReason;
|
||||
if (!TransformValidationUtility.CanPlaceBuildingAt(buildingDef, desiredPosition, map, faction, Pawn, out failReason))
|
||||
{
|
||||
// 尝试寻找附近的位置
|
||||
IntVec3 alternativePosition;
|
||||
if (TryFindNearbyValidPosition(out alternativePosition, out failReason))
|
||||
{
|
||||
desiredPosition = alternativePosition;
|
||||
Messages.Message($"将在附近位置 {desiredPosition} 部署建筑", MessageTypeDefOf.NeutralEvent);
|
||||
}
|
||||
else
|
||||
{
|
||||
Messages.Message($"无法部署建筑: {failReason}", MessageTypeDefOf.RejectInput);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 移除Pawn
|
||||
Pawn.DeSpawn(DestroyMode.Vanish);
|
||||
|
||||
// 生成建筑
|
||||
Building newBuilding = (Building)GenSpawn.Spawn(buildingDef, position, map, WipeMode.Vanish);
|
||||
Building newBuilding = (Building)GenSpawn.Spawn(buildingDef, desiredPosition, map, WipeMode.Vanish);
|
||||
newBuilding.SetFaction(faction);
|
||||
|
||||
// 恢复机械族计数
|
||||
@@ -151,20 +263,33 @@ namespace WulaFallenEmpire
|
||||
Messages.Message($"{Pawn.LabelCap} 已部署为 {newBuilding.Label}", MessageTypeDefOf.PositiveEvent);
|
||||
|
||||
// 播放转换效果
|
||||
PlayTransformEffects(position, map);
|
||||
PlayTransformEffects(desiredPosition, map);
|
||||
|
||||
// 清除缓存
|
||||
lastValidationResult = null;
|
||||
lastValidationReason = null;
|
||||
}
|
||||
|
||||
private void PlayTransformEffects(IntVec3 position, Map map)
|
||||
{
|
||||
//// 播放转换视觉效果
|
||||
// 播放转换视觉效果
|
||||
//for (int i = 0; i < 3; i++)
|
||||
//{
|
||||
// MoteMaker.ThrowSmoke(position.ToVector3Shifted() + new Vector3(0, 0, 0.5f), map, 1.5f);
|
||||
// MoteMaker.ThrowMicroSparks(position.ToVector3Shifted(), map);
|
||||
//}
|
||||
}
|
||||
|
||||
// 每tick更新校验状态(用于实时反馈)
|
||||
public override void CompTick()
|
||||
{
|
||||
base.CompTick();
|
||||
|
||||
//// 播放音效
|
||||
//SoundDefOf.MechClusterDefeated.PlayOneShot(new TargetInfo(position, map));
|
||||
// 每60 tick清除一次缓存,确保校验结果实时更新
|
||||
if (Find.TickManager.TicksGame % 60 == 0)
|
||||
{
|
||||
lastValidationResult = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,218 @@
|
||||
using RimWorld;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using Verse;
|
||||
using System.Linq;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
public static class TransformValidationUtility
|
||||
{
|
||||
/// <summary>
|
||||
/// 检查位置是否可以放置目标建筑(通用版本)
|
||||
/// </summary>
|
||||
public static bool CanPlaceBuildingAt(ThingDef buildingDef, IntVec3 center, Map map, Faction faction, out string failReason)
|
||||
{
|
||||
return CanPlaceBuildingAt(buildingDef, center, map, faction, null, out failReason);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查位置是否可以放置目标建筑(带忽略物体版本)
|
||||
/// </summary>
|
||||
public static bool CanPlaceBuildingAt(ThingDef buildingDef, IntVec3 center, Map map, Faction faction, Thing thingToIgnore, out string failReason)
|
||||
{
|
||||
failReason = null;
|
||||
|
||||
// 1. 检查建筑定义
|
||||
if (buildingDef == null)
|
||||
{
|
||||
failReason = "目标建筑定义为空";
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. 检查建筑尺寸是否为奇数(根据您的需求)
|
||||
if (!IsOddSizedBuilding(buildingDef))
|
||||
{
|
||||
failReason = $"建筑 {buildingDef.LabelCap} 的尺寸不是奇数,不符合放置要求";
|
||||
return false;
|
||||
}
|
||||
|
||||
// 3. 检查地图边界
|
||||
if (!IsWithinMapBounds(buildingDef, center, map, out failReason))
|
||||
return false;
|
||||
|
||||
// 4. 检查地形affordance
|
||||
if (!HasValidTerrainAffordance(buildingDef, center, map, out failReason))
|
||||
return false;
|
||||
|
||||
// 5. 检查其他建筑挤占(排除要忽略的物体)
|
||||
if (!IsAreaClearOfBlockingBuildings(buildingDef, center, map, thingToIgnore, out failReason))
|
||||
return false;
|
||||
|
||||
// 6. 检查特殊放置条件(如不可放置在水上等)
|
||||
if (!MeetsSpecialPlacementConditions(buildingDef, center, map, out failReason))
|
||||
return false;
|
||||
|
||||
// 7. 使用RimWorld原生的放置检查(最终验证)
|
||||
if (!GenConstruct.CanPlaceBlueprintAt(buildingDef, center, Rot4.North, map, false, null, null, null))
|
||||
{
|
||||
failReason = "该位置不符合建筑放置要求";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查建筑尺寸是否为奇数
|
||||
/// </summary>
|
||||
public static bool IsOddSizedBuilding(ThingDef buildingDef)
|
||||
{
|
||||
return buildingDef.Size.x % 2 == 1 && buildingDef.Size.z % 2 == 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查地图边界
|
||||
/// </summary>
|
||||
private static bool IsWithinMapBounds(ThingDef buildingDef, IntVec3 center, Map map, out string failReason)
|
||||
{
|
||||
CellRect occupiedRect = GenAdj.OccupiedRect(center, Rot4.North, buildingDef.Size);
|
||||
|
||||
if (!occupiedRect.InBounds(map))
|
||||
{
|
||||
failReason = "建筑超出地图边界";
|
||||
return false;
|
||||
}
|
||||
|
||||
failReason = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查地形affordance
|
||||
/// </summary>
|
||||
private static bool HasValidTerrainAffordance(ThingDef buildingDef, IntVec3 center, Map map, out string failReason)
|
||||
{
|
||||
TerrainAffordanceDef requiredAffordance = buildingDef.terrainAffordanceNeeded;
|
||||
if (requiredAffordance == null)
|
||||
{
|
||||
// 如果没有指定affordance要求,则跳过检查
|
||||
failReason = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
CellRect occupiedRect = GenAdj.OccupiedRect(center, Rot4.North, buildingDef.Size);
|
||||
|
||||
foreach (IntVec3 cell in occupiedRect)
|
||||
{
|
||||
if (!cell.InBounds(map))
|
||||
continue;
|
||||
|
||||
TerrainDef terrain = map.terrainGrid.TerrainAt(cell);
|
||||
if (terrain == null || !terrain.affordances.Contains(requiredAffordance))
|
||||
{
|
||||
failReason = $"地形 {terrain?.LabelCap ?? "未知"} 不支持放置 {buildingDef.LabelCap}";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
failReason = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查区域是否被其他建筑阻挡(排除指定物体)
|
||||
/// </summary>
|
||||
private static bool IsAreaClearOfBlockingBuildings(ThingDef buildingDef, IntVec3 center, Map map, Thing thingToIgnore, out string failReason)
|
||||
{
|
||||
CellRect occupiedRect = GenAdj.OccupiedRect(center, Rot4.North, buildingDef.Size);
|
||||
List<Thing> blockingThings = new List<Thing>();
|
||||
|
||||
foreach (IntVec3 cell in occupiedRect)
|
||||
{
|
||||
if (!cell.InBounds(map))
|
||||
continue;
|
||||
|
||||
// 检查该单元格上的所有建筑
|
||||
List<Thing> thingList = map.thingGrid.ThingsListAt(cell);
|
||||
foreach (Thing thing in thingList)
|
||||
{
|
||||
// 跳过要忽略的物体(如被转换的Pawn本身)
|
||||
if (thing == thingToIgnore)
|
||||
continue;
|
||||
|
||||
if (thing.def.category == ThingCategory.Building || thing.def.category == ThingCategory.Pawn)
|
||||
{
|
||||
// 忽略蓝图和框架
|
||||
if (thing.def.IsBlueprint || thing.def.IsFrame)
|
||||
continue;
|
||||
|
||||
// 忽略被转换的Pawn本身
|
||||
if (thing == thingToIgnore)
|
||||
continue;
|
||||
|
||||
blockingThings.Add(thing);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (blockingThings.Count > 0)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.AppendLine("以下物体阻挡了建筑放置:");
|
||||
|
||||
foreach (Thing thing in blockingThings)
|
||||
{
|
||||
sb.AppendLine($" - {thing.LabelCap}");
|
||||
}
|
||||
|
||||
failReason = sb.ToString().TrimEnd();
|
||||
return false;
|
||||
}
|
||||
|
||||
failReason = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查特殊放置条件
|
||||
/// </summary>
|
||||
private static bool MeetsSpecialPlacementConditions(ThingDef buildingDef, IntVec3 center, Map map, out string failReason)
|
||||
{
|
||||
CellRect occupiedRect = GenAdj.OccupiedRect(center, Rot4.North, buildingDef.Size);
|
||||
|
||||
// 检查是否在水体上
|
||||
foreach (IntVec3 cell in occupiedRect)
|
||||
{
|
||||
if (!cell.InBounds(map))
|
||||
continue;
|
||||
|
||||
TerrainDef terrain = map.terrainGrid.TerrainAt(cell);
|
||||
if (terrain != null && (terrain.IsWater || terrain.defName.Contains("Water")))
|
||||
{
|
||||
failReason = "无法在水体上放置建筑";
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查是否在不可建造的地形上
|
||||
if (terrain != null && !terrain.BuildableByPlayer)
|
||||
{
|
||||
failReason = $"地形 {terrain.LabelCap} 不可建造";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
failReason = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取建筑占用的所有单元格
|
||||
/// </summary>
|
||||
public static List<IntVec3> GetOccupiedCells(ThingDef buildingDef, IntVec3 center)
|
||||
{
|
||||
return GenAdj.OccupiedRect(center, Rot4.North, buildingDef.Size).Cells.ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user