传送器

This commit is contained in:
2025-11-25 17:31:19 +08:00
parent 79a02f99e0
commit e0b156f687
11 changed files with 794 additions and 18 deletions

View File

@@ -0,0 +1,47 @@
using RimWorld;
using Verse;
using System.Collections.Generic;
namespace WulaFallenEmpire
{
public class CompProperties_AreaTeleporter : CompProperties
{
public float teleportRadius = 15f;
// 种族筛选
public List<ThingDef> allowedRaces;
public List<ThingDef> excludedRaces;
public bool requireHumanlike = false;
public bool requireAnimal = false;
public bool requireMechanoid = false;
// 派系控制
public bool onlyPawnsInSameFaction = true;
public bool affectAllies = true;
public bool affectEnemies = true;
public bool affectNeutrals = true;
public bool affectPrisoners = false;
public bool affectSlaves = false;
// 传送设置 - 移除冷却时间
public int checkIntervalTicks = 30; // 检查间隔
public int stunTicks = 30; // 传送后眩晕时间
public int maxPositionAdjustRadius = 5; // 最大位置调整半径
// 效果设置
public EffecterDef entryEffecter;
public EffecterDef exitEffecter;
public EffecterDef enterRangeEffecter;
public EffecterDef leaveRangeEffecter;
public SoundDef teleportSound;
// 到达时的喧嚣效果
public ClamorDef destClamorType;
public float destClamorRadius = 2f;
public CompProperties_AreaTeleporter()
{
compClass = typeof(ThingComp_AreaTeleporter);
}
}
}

View File

@@ -0,0 +1,66 @@
using HarmonyLib;
using RimWorld;
using Verse;
using Verse.AI;
namespace WulaFallenEmpire
{
[HarmonyPatch(typeof(Pawn_JobTracker), "StartJob")]
public static class Patch_Pawn_JobTracker_StartJob
{
[HarmonyPostfix]
public static void Postfix(Pawn_JobTracker __instance, Pawn ___pawn)
{
// 检查是否是移动相关的任务
if (___pawn?.Map == null || ___pawn.Dead || ___pawn.Downed)
return;
if (__instance.curJob == null)
return;
// 只处理移动任务
if (__instance.curJob.def == JobDefOf.Goto ||
__instance.curJob.def == JobDefOf.GotoWander ||
__instance.curJob.def == JobDefOf.Follow)
{
// 通知区域传送器检查这个Pawn
NotifyAreaTeleporters(___pawn);
}
}
private static void NotifyAreaTeleporters(Pawn pawn)
{
if (pawn.Map == null)
return;
// 查找范围内的所有区域传送器
foreach (var thing in pawn.Map.listerThings.ThingsOfDef(ThingDef.Named("WULA_Support_AreaTeleporter")))
{
var comp = thing.TryGetComp<ThingComp_AreaTeleporter>();
if (comp != null && pawn.Position.DistanceTo(thing.Position) <= comp.Props.teleportRadius)
{
// 强制立即检查这个Pawn
comp.ForceCheckPawn(pawn);
}
}
}
}
// 为ThingComp_AreaTeleporter添加强制检查方法
public partial class ThingComp_AreaTeleporter
{
public void ForceCheckPawn(Pawn pawn)
{
if (parent == null || !parent.Spawned || parent.Map == null)
return;
if (pawn.Position.DistanceTo(parent.Position) > Props.teleportRadius)
return;
if (!ShouldAffectPawn(pawn))
return;
TryTeleportPawn(pawn);
}
}
}

View File

@@ -0,0 +1,548 @@
using RimWorld;
using Verse;
using System.Collections.Generic;
using UnityEngine;
using HarmonyLib;
using Verse.AI;
using Verse.Sound;
namespace WulaFallenEmpire
{
public partial class ThingComp_AreaTeleporter : ThingComp
{
public CompProperties_AreaTeleporter Props => (CompProperties_AreaTeleporter)props;
// 跟踪范围内的Pawn
private HashSet<Pawn> pawnsInRange = new HashSet<Pawn>();
// 效果器缓存
private Dictionary<Pawn, Effecter> effecters = new Dictionary<Pawn, Effecter>();
// 网络相关:存储同一地图上的所有传送器
private static Dictionary<Map, HashSet<ThingComp_AreaTeleporter>> teleporterNetworks =
new Dictionary<Map, HashSet<ThingComp_AreaTeleporter>>();
// 硬编码的工作排除表
private static readonly HashSet<JobDef> ExcludedJobs = new HashSet<JobDef>
{
JobDefOf.GotoWander // 排除闲逛工作
};
public override void Initialize(CompProperties props)
{
base.Initialize(props);
RegisterToNetwork();
}
public override void PostSpawnSetup(bool respawningAfterLoad)
{
base.PostSpawnSetup(respawningAfterLoad);
RegisterToNetwork();
}
public override void PostDeSpawn(Map map, DestroyMode mode = DestroyMode.Vanish)
{
base.PostDeSpawn(map);
UnregisterFromNetwork();
}
public override void PostDestroy(DestroyMode mode, Map previousMap)
{
base.PostDestroy(mode, previousMap);
UnregisterFromNetwork();
}
/// <summary>
/// 注册到网络
/// </summary>
private void RegisterToNetwork()
{
if (parent?.Map == null) return;
var map = parent.Map;
if (!teleporterNetworks.ContainsKey(map))
{
teleporterNetworks[map] = new HashSet<ThingComp_AreaTeleporter>();
}
teleporterNetworks[map].Add(this);
}
/// <summary>
/// 从网络注销
/// </summary>
private void UnregisterFromNetwork()
{
if (parent?.Map == null) return;
var map = parent.Map;
if (teleporterNetworks.ContainsKey(map))
{
teleporterNetworks[map].Remove(this);
if (teleporterNetworks[map].Count == 0)
{
teleporterNetworks.Remove(map);
}
}
}
/// <summary>
/// 获取同一地图上的所有传送器
/// </summary>
private HashSet<ThingComp_AreaTeleporter> GetNetworkTeleporters()
{
if (parent?.Map == null) return new HashSet<ThingComp_AreaTeleporter>();
if (teleporterNetworks.TryGetValue(parent.Map, out var network))
{
return network;
}
return new HashSet<ThingComp_AreaTeleporter>();
}
/// <summary>
/// 检查位置是否在传送网络的范围内
/// </summary>
private bool IsPositionInNetworkRange(IntVec3 position)
{
if (parent?.Map == null) return false;
foreach (var teleporter in GetNetworkTeleporters())
{
if (teleporter.parent?.Spawned == true &&
position.DistanceTo(teleporter.parent.Position) <= teleporter.Props.teleportRadius)
{
return true;
}
}
return false;
}
/// <summary>
/// 在传送网络范围内寻找安全的传送位置
/// </summary>
private IntVec3 FindSafePositionInNetwork(IntVec3 preferredPosition, Pawn pawn)
{
if (parent?.Map == null) return IntVec3.Invalid;
var map = parent.Map;
// 首先检查首选位置
if (preferredPosition.InBounds(map) && CanTeleportTo(preferredPosition, map))
return preferredPosition;
// 在首选位置周围搜索
for (int radius = 1; radius <= Props.maxPositionAdjustRadius; radius++)
{
foreach (var cell in GenRadial.RadialCellsAround(preferredPosition, radius, true))
{
if (cell.InBounds(map) && CanTeleportTo(cell, map) && IsPositionInNetworkRange(cell))
{
return cell;
}
}
}
// 在整个网络范围内搜索安全位置
foreach (var teleporter in GetNetworkTeleporters())
{
if (teleporter.parent?.Spawned != true) continue;
var teleporterPos = teleporter.parent.Position;
var searchRadius = teleporter.Props.teleportRadius;
// 在传送器周围搜索
for (int radius = 0; radius <= searchRadius; radius++)
{
foreach (var cell in GenRadial.RadialCellsAround(teleporterPos, radius, true))
{
if (cell.InBounds(map) && CanTeleportTo(cell, map))
{
return cell;
}
}
}
}
return IntVec3.Invalid;
}
public override void CompTick()
{
base.CompTick();
if (parent == null || !parent.Spawned || parent.Map == null)
return;
// 使用间隔检查优化性能
if (Find.TickManager.TicksGame % Props.checkIntervalTicks != 0)
return;
UpdatePawnsInRange();
ProcessPawnMovements();
}
private void UpdatePawnsInRange()
{
var newPawnsInRange = new HashSet<Pawn>();
// 获取范围内的所有pawn
foreach (var thing in parent.Map.listerThings.ThingsInGroup(ThingRequestGroup.Pawn))
{
if (thing is Pawn pawn &&
pawn.Position.DistanceTo(parent.Position) <= Props.teleportRadius &&
pawn.Spawned && !pawn.Dead && !pawn.Downed)
{
newPawnsInRange.Add(pawn);
}
}
// 处理新进入范围的pawn
foreach (var pawn in newPawnsInRange)
{
if (!pawnsInRange.Contains(pawn) && ShouldAffectPawn(pawn))
{
OnPawnEnterRange(pawn);
}
}
// 处理离开范围的pawn
var pawnsToRemove = new List<Pawn>();
foreach (var pawn in pawnsInRange)
{
if (!newPawnsInRange.Contains(pawn))
{
pawnsToRemove.Add(pawn);
}
}
foreach (var pawn in pawnsToRemove)
{
OnPawnLeaveRange(pawn);
}
pawnsInRange = newPawnsInRange;
}
private void ProcessPawnMovements()
{
foreach (var pawn in pawnsInRange)
{
if (!ShouldAffectPawn(pawn))
continue;
if (IsPawnMoving(pawn))
{
TryTeleportPawn(pawn);
}
}
}
/// <summary>
/// 检查pawn是否应该受到传送效果影响
/// </summary>
private bool ShouldAffectPawn(Pawn pawn)
{
if (pawn == null || pawn.Dead || pawn.Destroyed)
return false;
// 种族类型检查 - 只允许人类和机械体
if (!pawn.RaceProps.Humanlike && !pawn.RaceProps.IsMechanoid)
return false;
// 特定种族检查
if (Props.allowedRaces != null && Props.allowedRaces.Count > 0)
{
if (!Props.allowedRaces.Contains(pawn.def))
return false;
}
// 排除种族检查
if (Props.excludedRaces != null && Props.excludedRaces.Count > 0)
{
if (Props.excludedRaces.Contains(pawn.def))
return false;
}
// 派系关系检查 - 简化版,只检查是否同派系
if (Props.onlyPawnsInSameFaction && parent.Faction != null && pawn.Faction != null)
{
if (parent.Faction != pawn.Faction)
return false;
}
// 囚犯和奴隶检查
if (!Props.affectPrisoners && pawn.IsPrisoner)
return false;
if (!Props.affectSlaves && pawn.IsSlave)
return false;
return true;
}
/// <summary>
/// 检查Pawn是否正在移动
/// </summary>
private bool IsPawnMoving(Pawn pawn)
{
if (pawn == null || pawn.jobs == null || pawn.jobs.curJob == null)
return false;
// 检查工作是否在排除表中
if (IsJobExcluded(pawn.jobs.curJob.def))
return false;
// 检查是否正在执行移动任务
if (pawn.jobs.curJob.def == JobDefOf.Goto ||
pawn.jobs.curJob.def == JobDefOf.GotoWander ||
pawn.jobs.curJob.def == JobDefOf.Follow)
{
return pawn.pather.Moving;
}
return false;
}
/// <summary>
/// 检查工作是否在排除表中
/// </summary>
private bool IsJobExcluded(JobDef jobDef)
{
return ExcludedJobs.Contains(jobDef);
}
/// <summary>
/// 尝试传送Pawn
/// </summary>
private void TryTeleportPawn(Pawn pawn)
{
try
{
if (pawn == null || pawn.Map == null || pawn.Destroyed)
return;
// 检查当前工作是否在排除表中
if (pawn.jobs?.curJob != null && IsJobExcluded(pawn.jobs.curJob.def))
return;
// 获取Pawn的目标位置
LocalTargetInfo target = GetPawnMoveTarget(pawn);
if (!target.IsValid)
return;
// 检查目标位置是否在传送网络范围内
if (!IsPositionInNetworkRange(target.Cell))
return;
// 在网络范围内寻找安全的传送位置
IntVec3 safeTarget = FindSafePositionInNetwork(target.Cell, pawn);
if (!safeTarget.IsValid)
return;
// 执行传送
PerformTeleport(pawn, safeTarget);
// 记录日志
Log.Message($"[AreaTeleporter] 传送 {pawn.LabelShort} 从 {pawn.Position} 到 {safeTarget} (网络传送)");
}
catch (System.Exception ex)
{
Log.Warning($"传送Pawn时出错: {ex.Message}");
}
}
/// <summary>
/// 获取Pawn的移动目标
/// </summary>
private LocalTargetInfo GetPawnMoveTarget(Pawn pawn)
{
if (pawn.jobs?.curJob == null)
return LocalTargetInfo.Invalid;
// 检查工作是否在排除表中
if (IsJobExcluded(pawn.jobs.curJob.def))
return LocalTargetInfo.Invalid;
// 尝试从当前工作中获取目标位置
var job = pawn.jobs.curJob;
// 对于Goto任务目标通常是targetA
if (job.targetA.IsValid)
return job.targetA;
// 对于其他移动任务,可能需要不同的逻辑
if (job.def == JobDefOf.Follow && job.targetB.IsValid)
return job.targetB;
return LocalTargetInfo.Invalid;
}
/// <summary>
/// 检查是否可以传送到指定位置
/// </summary>
private bool CanTeleportTo(IntVec3 cell, Map map)
{
if (!cell.InBounds(map))
return false;
// 检查是否可站立
if (!cell.Standable(map))
return false;
// 检查是否有建筑阻挡
Building edifice = cell.GetEdifice(map);
if (edifice != null && edifice.def.surfaceType != SurfaceType.Item &&
edifice.def.surfaceType != SurfaceType.Eat && !(edifice is Building_Door { Open: not false }))
{
return false;
}
// 检查是否有物品阻挡
List<Thing> thingList = cell.GetThingList(map);
for (int i = 0; i < thingList.Count; i++)
{
if (thingList[i].def.category == ThingCategory.Item)
{
return false;
}
}
return true;
}
/// <summary>
/// 执行传送
/// </summary>
private void PerformTeleport(Pawn pawn, IntVec3 targetCell)
{
Map map = pawn.Map;
// 创建入口特效
if (Props.entryEffecter != null)
{
Effecter entryEffect = Props.entryEffecter.Spawn();
entryEffect.Trigger(new TargetInfo(pawn.Position, map), new TargetInfo(pawn.Position, map));
entryEffect.Cleanup();
}
// 创建出口特效
if (Props.exitEffecter != null)
{
Effecter exitEffect = Props.exitEffecter.Spawn();
exitEffect.Trigger(new TargetInfo(targetCell, map), new TargetInfo(targetCell, map));
exitEffect.Cleanup();
}
// 播放音效
if (Props.teleportSound != null)
{
Props.teleportSound.PlayOneShot(new TargetInfo(targetCell, map));
}
// 执行传送
pawn.Position = targetCell;
pawn.Notify_Teleported();
pawn.jobs.StopAll();
// 如果是玩家阵营,解除战争迷雾
if ((pawn.Faction == Faction.OfPlayer || pawn.IsPlayerControlled) && pawn.Position.Fogged(map))
{
FloodFillerFog.FloodUnfog(pawn.Position, map);
}
// 传送后眩晕
if (Props.stunTicks > 0)
{
pawn.stances.stunner.StunFor(Props.stunTicks, pawn, addBattleLog: false, showMote: false);
}
// 播放到达时的喧嚣效果
if (Props.destClamorType != null)
{
GenClamor.DoClamor(pawn, targetCell, Props.destClamorRadius, Props.destClamorType);
}
}
private void OnPawnEnterRange(Pawn pawn)
{
// Pawn进入范围时的处理
if (Props.enterRangeEffecter != null)
{
Effecter effect = Props.enterRangeEffecter.Spawn();
effect.Trigger(new TargetInfo(pawn.Position, pawn.Map), new TargetInfo(pawn.Position, pawn.Map));
effect.Cleanup();
}
}
private void OnPawnLeaveRange(Pawn pawn)
{
// Pawn离开范围时的处理
if (Props.leaveRangeEffecter != null)
{
Effecter effect = Props.leaveRangeEffecter.Spawn();
effect.Trigger(new TargetInfo(pawn.Position, pawn.Map), new TargetInfo(pawn.Position, pawn.Map));
effect.Cleanup();
}
}
/// <summary>
/// 清理所有效果
/// </summary>
private void CleanupAllEffects()
{
pawnsInRange.Clear();
foreach (var effecter in effecters.Values)
{
effecter.Cleanup();
}
effecters.Clear();
}
// 调试方法:显示传送范围
public override void PostDraw()
{
base.PostDraw();
if (Find.Selector.IsSelected(parent))
{
try
{
// 绘制当前传送器范围
GenDraw.DrawRadiusRing(parent.Position, Props.teleportRadius, Color.blue);
// 绘制网络范围(所有传送器的范围)
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));
}
}
}
catch (System.Exception ex)
{
Log.Warning($"绘制传送范围环时出错: {ex.Message}");
}
}
}
// 获取当前范围内的pawn数量用于显示
public int GetPawnsInRangeCount()
{
return pawnsInRange.Count;
}
// 获取范围内的pawn列表用于调试
public IEnumerable<Pawn> GetPawnsInRange()
{
return pawnsInRange;
}
// 获取网络中的传送器数量
public int GetNetworkTeleporterCount()
{
return GetNetworkTeleporters().Count;
}
}
}

View File

@@ -197,13 +197,14 @@ namespace WulaFallenEmpire
Hediff newHediff = HediffMaker.MakeHediff(Props.hediff, pawn);
newHediff.Severity = Props.initialSeverity;
// 设置持续时间(如果有)
// 正确设置持续时间
if (Props.hediffDurationTicks > 0)
{
var comp = newHediff.TryGetComp<HediffComp_Disappears>();
if (comp != null)
var disappearComp = newHediff.TryGetComp<HediffComp_Disappears>();
if (disappearComp != null)
{
comp.ticksToDisappear = Props.hediffDurationTicks;
// 使用 SetDuration 方法正确设置持续时间
disappearComp.SetDuration(Props.hediffDurationTicks);
}
}
@@ -250,11 +251,11 @@ namespace WulaFallenEmpire
{
if (Props.hediffDurationTicks > 0)
{
var comp = hediff.TryGetComp<HediffComp_Disappears>();
if (comp != null)
var disappearComp = hediff.TryGetComp<HediffComp_Disappears>();
if (disappearComp != null)
{
// 重置持续时间,让效果持续
comp.ticksToDisappear = Props.hediffDurationTicks;
// 使用 SetDuration 方法重置持续时间,让效果持续
disappearComp.SetDuration(Props.hediffDurationTicks);
}
}
}