This commit is contained in:
2025-11-27 17:14:53 +08:00
parent 58249fb306
commit 0cca8b0516
30 changed files with 924 additions and 682 deletions

View File

@@ -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;
}
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -10,12 +10,16 @@ namespace WulaFallenEmpire
[StaticConstructorOnStartup]
public class ThingComp_AreaShield : ThingComp
{
// 现有的字段保持不变...
private int lastInterceptTicks = -999999;
public int ticksToReset = 0;
public int currentHitPoints;
private bool wasNotAtFullHp = false;
private bool wasActiveLastCheck = false;
// 新增:绘制控制字段
private bool drawShield = true;
// 视觉效果变量
private float lastInterceptAngle;
private bool drawInterceptCone;
@@ -43,30 +47,51 @@ namespace WulaFallenEmpire
private const float TextureActualRingSizeFactor = 1.1601562f;
private static readonly Color InactiveColor = new Color(0.2f, 0.2f, 0.2f);
// 护盾绘制方法 - 参考原版实现
// 新增:检查是否应该显示绘制控制按钮
public bool ShouldShowDrawToggleGizmo
{
get
{
// 条件1装备且穿戴在己方pawn身上
if (IsEquipment && Wearer != null && Wearer.Faction == Faction.OfPlayer)
return true;
// 条件2固定物品且属于己方派系
if (IsStandalone && parent.Faction == Faction.OfPlayer)
return true;
return false;
}
}
// 护盾绘制方法 - 修改:添加绘制控制检查
public override void CompDrawWornExtras()
{
base.CompDrawWornExtras();
if (!IsEquipment) return; // 只有装备使用这个方法
if (!IsEquipment || !drawShield) return; // 新增绘制控制检查
DrawShield();
}
public override void PostDraw()
{
base.PostDraw();
if (IsEquipment) return; // 装备使用 CompDrawWornExtras
if (IsEquipment || !drawShield) return; // 新增绘制控制检查
DrawShield();
}
/// <summary>
/// 统一的护盾绘制方法 - 参考原版实现
/// 统一的护盾绘制方法 - 修改:添加绘制控制检查
/// </summary>
private void DrawShield()
{
if (!Active || Holder?.Map == null || Holder.Destroyed)
if (!drawShield || !Active || Holder?.Map == null || Holder.Destroyed) // 新增绘制控制检查
return;
Vector3 drawPos = GetHolderDrawPos();
drawPos.y = AltitudeLayer.MoteOverhead.AltitudeFor();
float currentAlpha = GetCurrentAlpha();
@@ -96,9 +121,8 @@ namespace WulaFallenEmpire
Graphics.DrawMesh(MeshPool.plane10, matrix, ForceFieldConeMat, 0, null, 0, MatPropertyBlock);
}
}
/// <summary>
/// 获取当前透明度 - 参考原版的多状态叠加
/// </summary>
// 现有的其他方法保持不变...
private float GetCurrentAlpha()
{
// 多个透明度来源叠加,取最大值
@@ -113,9 +137,7 @@ namespace WulaFallenEmpire
0.1f // 最小透明度
);
}
/// <summary>
/// 空闲状态透明度
/// </summary>
private float GetCurrentAlpha_Idle()
{
if (!Active) return 0f;
@@ -144,9 +166,7 @@ namespace WulaFallenEmpire
return 0f;
}
/// <summary>
/// 被选中状态透明度 - 参考原版实现
/// </summary>
private float GetCurrentAlpha_Selected()
{
// 如果被选中,显示更高的透明度
@@ -158,17 +178,13 @@ namespace WulaFallenEmpire
return 0f;
}
/// <summary>
/// 最近拦截状态透明度
/// </summary>
private float GetCurrentAlpha_RecentlyIntercepted()
{
int ticksSinceIntercept = Find.TickManager.TicksGame - lastInterceptTicks;
return Mathf.Clamp01(1f - (float)ticksSinceIntercept / 40f) * 0.3f;
}
/// <summary>
/// 拦截锥形透明度
/// </summary>
private float GetCurrentConeAlpha()
{
if (!drawInterceptCone) return 0f;
@@ -176,9 +192,7 @@ namespace WulaFallenEmpire
int ticksSinceIntercept = Find.TickManager.TicksGame - lastInterceptTicks;
return Mathf.Clamp01(1f - (float)ticksSinceIntercept / 40f) * 0.82f;
}
/// <summary>
/// 获取持有者绘制位置(回退机制)
/// </summary>
private Vector3 GetHolderDrawPos()
{
if (Holder is Pawn pawn)
@@ -224,6 +238,7 @@ namespace WulaFallenEmpire
{
base.PostPostMake();
currentHitPoints = HitPointsMax;
drawShield = true; // 默认启用绘制
}
public override void PostExposeData()
@@ -232,6 +247,7 @@ namespace WulaFallenEmpire
Scribe_Values.Look(ref lastInterceptTicks, "lastInterceptTicks", -999999);
Scribe_Values.Look(ref ticksToReset, "ticksToReset", 0);
Scribe_Values.Look(ref currentHitPoints, "currentHitPoints", 0);
Scribe_Values.Look(ref drawShield, "drawShield", true); // 新增:保存绘制状态
}
public override void CompTick()
@@ -273,6 +289,56 @@ namespace WulaFallenEmpire
}
}
// 新增绘制控制Gizmo
private Gizmo CreateDrawToggleGizmo()
{
Command_Toggle toggle = new Command_Toggle
{
defaultLabel = drawShield ? "WULA_HideAreaShieldLabel".Translate() : "WULA_ShowAreaShieldLabel".Translate(),
defaultDesc = drawShield ? "WULA_HideAreaShieldDesc".Translate() : "WULA_ShowAreaShieldDesc".Translate(),
icon = ContentFinder<Texture2D>.Get("Wula/UI/Commands/WULA_HideAreaShield"),
isActive = () => drawShield,
toggleAction = () => drawShield = !drawShield
};
return toggle;
}
public override IEnumerable<Gizmo> CompGetWornGizmosExtra()
{
EnsureInitialized();
// 原有的状态显示Gizmo
if (IsEquipment && Wearer != null && Find.Selector.SingleSelectedThing == Wearer)
{
yield return new Gizmo_AreaShieldStatus { shield = this };
// 新增:绘制控制按钮(只在符合条件的装备上显示)
if (ShouldShowDrawToggleGizmo)
{
yield return CreateDrawToggleGizmo();
}
}
}
public override IEnumerable<Gizmo> CompGetGizmosExtra()
{
EnsureInitialized();
// 原有的状态显示Gizmo
if (IsStandalone && Find.Selector.SingleSelectedThing == parent)
{
yield return new Gizmo_AreaShieldStatus { shield = this };
// 新增:绘制控制按钮(只在符合条件的固定物品上显示)
if (ShouldShowDrawToggleGizmo)
{
yield return CreateDrawToggleGizmo();
}
}
}
// 现有的其他方法保持不变...
private void ApplyCosts(int cost = 1)
{
currentHitPoints -= cost;
@@ -566,6 +632,7 @@ namespace WulaFallenEmpire
return false;
}
}
private void EnsureInitialized()
{
if (initialized) return;
@@ -579,27 +646,6 @@ namespace WulaFallenEmpire
initialized = true;
}
public override IEnumerable<Gizmo> CompGetWornGizmosExtra()
{
EnsureInitialized();
if (IsEquipment && Wearer != null && Find.Selector.SingleSelectedThing == Wearer)
{
yield return new Gizmo_AreaShieldStatus { shield = this };
}
}
public override IEnumerable<Gizmo> CompGetGizmosExtra()
{
EnsureInitialized();
// 固定物品也显示护盾状态
if (IsStandalone && Find.Selector.SingleSelectedThing == parent)
{
yield return new Gizmo_AreaShieldStatus { shield = this };
}
}
public override void Notify_Equipped(Pawn pawn)
{
base.Notify_Equipped(pawn);

View File

@@ -509,14 +509,14 @@ namespace WulaFallenEmpire
try
{
// 绘制当前传送器范围
GenDraw.DrawRadiusRing(parent.Position, Props.teleportRadius, Color.blue);
GenDraw.DrawRadiusRing(parent.Position, Props.teleportRadius, new Color(0.3f, 0.7f, 1, 0.3f));
// 绘制网络范围(所有传送器的范围)
foreach (var teleporter in GetNetworkTeleporters())
{
if (teleporter != this && teleporter.parent.Spawned)
{
GenDraw.DrawRadiusRing(teleporter.parent.Position, teleporter.Props.teleportRadius, new Color(0, 0, 1, 0.3f));
GenDraw.DrawRadiusRing(teleporter.parent.Position, teleporter.Props.teleportRadius, new Color(0.3f, 0.7f, 1, 0.3f));
}
}
}

View File

@@ -126,6 +126,7 @@
<Compile Include="BuildingComp\WULA_TransformAtFullCapacity\CompProperties_TransformIntoBuilding.cs" />
<Compile Include="BuildingComp\WULA_TransformAtFullCapacity\CompTransformAtFullCapacity.cs" />
<Compile Include="BuildingComp\WULA_TransformAtFullCapacity\CompTransformIntoBuilding.cs" />
<Compile Include="BuildingComp\WULA_TransformAtFullCapacity\TransformValidationUtility.cs" />
<Compile Include="EventSystem\CompOpenCustomUI.cs" />
<Compile Include="EventSystem\Condition\ConditionBase.cs" />
<Compile Include="EventSystem\Condition\Condition_FlagExists.cs" />