feat(Teleporter): implement connected teleporter grouping and shape-based teleportation
This commit is contained in:
Binary file not shown.
@@ -18,6 +18,100 @@ namespace WulaFallenEmpire
|
|||||||
private int warmupTicksLeft = 0;
|
private int warmupTicksLeft = 0;
|
||||||
private GlobalTargetInfo target;
|
private GlobalTargetInfo target;
|
||||||
private WULA_TeleportLandingMarker activeMarker;
|
private WULA_TeleportLandingMarker activeMarker;
|
||||||
|
|
||||||
|
// Group caching
|
||||||
|
private List<CompMapTeleporter> cachedGroupMembers;
|
||||||
|
private int lastGroupCheckTick = -1;
|
||||||
|
|
||||||
|
// Cells caching
|
||||||
|
private List<IntVec3> cachedGroupCells;
|
||||||
|
private int lastGroupCellsCheckTick = -1;
|
||||||
|
|
||||||
|
public CellRect TeleportRect => CellRect.CenteredOn(parent.Position, Props.areaSize.x, Props.areaSize.z);
|
||||||
|
|
||||||
|
private List<CompMapTeleporter> GroupMembers
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (lastGroupCheckTick == Find.TickManager.TicksGame && cachedGroupMembers != null)
|
||||||
|
{
|
||||||
|
return cachedGroupMembers;
|
||||||
|
}
|
||||||
|
|
||||||
|
lastGroupCheckTick = Find.TickManager.TicksGame;
|
||||||
|
cachedGroupMembers = new List<CompMapTeleporter>();
|
||||||
|
var openSet = new Queue<CompMapTeleporter>();
|
||||||
|
var closedSet = new HashSet<CompMapTeleporter>();
|
||||||
|
|
||||||
|
openSet.Enqueue(this);
|
||||||
|
closedSet.Add(this);
|
||||||
|
|
||||||
|
while (openSet.Count > 0)
|
||||||
|
{
|
||||||
|
var currentComp = openSet.Dequeue();
|
||||||
|
cachedGroupMembers.Add(currentComp);
|
||||||
|
|
||||||
|
var potentialNeighbors = parent.Map.listerThings.ThingsOfDef(parent.def);
|
||||||
|
foreach (var potentialNeighbor in potentialNeighbors)
|
||||||
|
{
|
||||||
|
var neighborComp = potentialNeighbor.TryGetComp<CompMapTeleporter>();
|
||||||
|
if (neighborComp == null || closedSet.Contains(neighborComp)) continue;
|
||||||
|
|
||||||
|
if (currentComp.TeleportRect.ExpandedBy(1).Overlaps(neighborComp.TeleportRect))
|
||||||
|
{
|
||||||
|
closedSet.Add(neighborComp);
|
||||||
|
openSet.Enqueue(neighborComp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Sort by ID to ensure consistent leader
|
||||||
|
cachedGroupMembers.SortBy(c => c.parent.thingIDNumber);
|
||||||
|
return cachedGroupMembers;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<IntVec3> GroupCells
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (lastGroupCellsCheckTick == Find.TickManager.TicksGame && cachedGroupCells != null)
|
||||||
|
{
|
||||||
|
return cachedGroupCells;
|
||||||
|
}
|
||||||
|
|
||||||
|
lastGroupCellsCheckTick = Find.TickManager.TicksGame;
|
||||||
|
HashSet<IntVec3> cells = new HashSet<IntVec3>();
|
||||||
|
foreach (var member in GroupMembers)
|
||||||
|
{
|
||||||
|
foreach (var cell in member.TeleportRect)
|
||||||
|
{
|
||||||
|
if (cell.InBounds(parent.Map))
|
||||||
|
{
|
||||||
|
cells.Add(cell);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cachedGroupCells = cells.ToList();
|
||||||
|
return cachedGroupCells;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<IntVec3> GetRelativeGroupCells()
|
||||||
|
{
|
||||||
|
var cells = GroupCells;
|
||||||
|
var center = parent.Position;
|
||||||
|
return cells.Select(c => c - center).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private CompMapTeleporter Leader
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var members = GroupMembers;
|
||||||
|
if (members.Count == 0) return this;
|
||||||
|
return members[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public override void PostExposeData()
|
public override void PostExposeData()
|
||||||
{
|
{
|
||||||
@@ -31,19 +125,43 @@ namespace WulaFallenEmpire
|
|||||||
public override void PostDrawExtraSelectionOverlays()
|
public override void PostDrawExtraSelectionOverlays()
|
||||||
{
|
{
|
||||||
base.PostDrawExtraSelectionOverlays();
|
base.PostDrawExtraSelectionOverlays();
|
||||||
GenDraw.DrawFieldEdges(CellRect.CenteredOn(parent.Position, Props.areaSize.x, Props.areaSize.z).Cells.ToList());
|
|
||||||
|
// Draw the combined field edges
|
||||||
|
GenDraw.DrawFieldEdges(GroupCells, Color.cyan);
|
||||||
|
|
||||||
|
var leader = Leader;
|
||||||
|
if (leader != null)
|
||||||
|
{
|
||||||
|
// Mark the leader clearly
|
||||||
|
Vector3 center = leader.parent.TrueCenter();
|
||||||
|
GenDraw.DrawLineBetween(center + new Vector3(-1f, 0, -1f), center + new Vector3(1f, 0, 1f), SimpleColor.Yellow);
|
||||||
|
GenDraw.DrawLineBetween(center + new Vector3(-1f, 0, 1f), center + new Vector3(1f, 0, -1f), SimpleColor.Yellow);
|
||||||
|
GenDraw.DrawCircleOutline(center, 1.5f, SimpleColor.Yellow);
|
||||||
|
|
||||||
|
// Draw lines from members to leader
|
||||||
|
foreach (var member in GroupMembers)
|
||||||
|
{
|
||||||
|
if (member != leader)
|
||||||
|
{
|
||||||
|
GenDraw.DrawLineBetween(leader.parent.TrueCenter(), member.parent.TrueCenter(), SimpleColor.Cyan);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void CompTick()
|
public override void CompTick()
|
||||||
{
|
{
|
||||||
base.CompTick();
|
base.CompTick();
|
||||||
if (isWarmingUp)
|
if (Leader == this && isWarmingUp)
|
||||||
{
|
{
|
||||||
warmupTicksLeft--;
|
warmupTicksLeft--;
|
||||||
if (warmupTicksLeft % 60 == 0)
|
if (warmupTicksLeft % 60 == 0)
|
||||||
{
|
{
|
||||||
Log.Message($"[WULA] Teleport warmup: {warmupTicksLeft} ticks left.");
|
Log.Message($"[WULA] Teleport warmup: {warmupTicksLeft} ticks left.");
|
||||||
Props.warmupEffecter?.Spawn(parent, parent.Map).Cleanup();
|
foreach (var member in GroupMembers)
|
||||||
|
{
|
||||||
|
Props.warmupEffecter?.Spawn(member.parent, member.parent.Map).Cleanup();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (warmupTicksLeft <= 0)
|
if (warmupTicksLeft <= 0)
|
||||||
@@ -65,6 +183,10 @@ namespace WulaFallenEmpire
|
|||||||
if (parent.Faction != Faction.OfPlayer)
|
if (parent.Faction != Faction.OfPlayer)
|
||||||
yield break;
|
yield break;
|
||||||
|
|
||||||
|
// Only the leader provides the gizmos
|
||||||
|
if (Leader != this)
|
||||||
|
yield break;
|
||||||
|
|
||||||
if (isWarmingUp)
|
if (isWarmingUp)
|
||||||
{
|
{
|
||||||
yield return new Command_Action
|
yield return new Command_Action
|
||||||
@@ -274,17 +396,17 @@ namespace WulaFallenEmpire
|
|||||||
private void TeleportContents(Map targetMap, IntVec3 targetCenter)
|
private void TeleportContents(Map targetMap, IntVec3 targetCenter)
|
||||||
{
|
{
|
||||||
Map sourceMap = parent.Map;
|
Map sourceMap = parent.Map;
|
||||||
CellRect rect = CellRect.CenteredOn(parent.Position, Props.areaSize.x, Props.areaSize.z);
|
List<IntVec3> cells = GroupCells;
|
||||||
IntVec3 center = parent.Position;
|
IntVec3 center = parent.Position;
|
||||||
|
|
||||||
List<ThingToTeleport> thingsToTeleport = new List<ThingToTeleport>();
|
List<ThingToTeleport> thingsToTeleport = new List<ThingToTeleport>();
|
||||||
List<Pair<IntVec3, TerrainDef>> terrainToTeleport = new List<Pair<IntVec3, TerrainDef>>();
|
List<Pair<IntVec3, TerrainDef>> terrainToTeleport = new List<Pair<IntVec3, TerrainDef>>();
|
||||||
|
|
||||||
Log.Message($"[WULA] Collecting data from {rect.Area} cells around {center} with size {Props.areaSize}");
|
Log.Message($"[WULA] Collecting data from {cells.Count} cells in group");
|
||||||
|
|
||||||
// 1. 收集数据
|
// 1. 收集数据
|
||||||
HashSet<Thing> collectedThings = new HashSet<Thing>();
|
HashSet<Thing> collectedThings = new HashSet<Thing>();
|
||||||
foreach (IntVec3 cell in rect)
|
foreach (IntVec3 cell in cells)
|
||||||
{
|
{
|
||||||
if (!cell.InBounds(sourceMap)) continue;
|
if (!cell.InBounds(sourceMap)) continue;
|
||||||
|
|
||||||
@@ -294,7 +416,7 @@ namespace WulaFallenEmpire
|
|||||||
for (int i = thingList.Count - 1; i >= 0; i--)
|
for (int i = thingList.Count - 1; i >= 0; i--)
|
||||||
{
|
{
|
||||||
Thing t = thingList[i];
|
Thing t = thingList[i];
|
||||||
if (t != parent && !collectedThings.Contains(t) &&
|
if (!collectedThings.Contains(t) &&
|
||||||
(t.def.category == ThingCategory.Item ||
|
(t.def.category == ThingCategory.Item ||
|
||||||
t.def.category == ThingCategory.Pawn ||
|
t.def.category == ThingCategory.Pawn ||
|
||||||
t.def.category == ThingCategory.Building))
|
t.def.category == ThingCategory.Building))
|
||||||
@@ -309,14 +431,12 @@ namespace WulaFallenEmpire
|
|||||||
|
|
||||||
// 2. 准备传送 (PreSwapMap)
|
// 2. 准备传送 (PreSwapMap)
|
||||||
foreach (var data in thingsToTeleport) data.thing.PreSwapMap();
|
foreach (var data in thingsToTeleport) data.thing.PreSwapMap();
|
||||||
parent.PreSwapMap();
|
|
||||||
|
|
||||||
// 3. 从源地图移除 (DeSpawn)
|
// 3. 从源地图移除 (DeSpawn)
|
||||||
foreach (var data in thingsToTeleport)
|
foreach (var data in thingsToTeleport)
|
||||||
{
|
{
|
||||||
if (data.thing.Spawned) data.thing.DeSpawn(DestroyMode.WillReplace);
|
if (data.thing.Spawned) data.thing.DeSpawn(DestroyMode.WillReplace);
|
||||||
}
|
}
|
||||||
if (parent.Spawned) parent.DeSpawn(DestroyMode.WillReplace);
|
|
||||||
|
|
||||||
// 4. 修改地形
|
// 4. 修改地形
|
||||||
foreach (var pair in terrainToTeleport)
|
foreach (var pair in terrainToTeleport)
|
||||||
@@ -345,14 +465,12 @@ namespace WulaFallenEmpire
|
|||||||
newPos = newPos.ClampInsideMap(targetMap);
|
newPos = newPos.ClampInsideMap(targetMap);
|
||||||
GenSpawn.Spawn(data.thing, newPos, targetMap, data.thing.Rotation);
|
GenSpawn.Spawn(data.thing, newPos, targetMap, data.thing.Rotation);
|
||||||
}
|
}
|
||||||
GenSpawn.Spawn(parent, targetCenter, targetMap, parent.Rotation);
|
|
||||||
|
|
||||||
// 6. 传送后处理 (PostSwapMap)
|
// 6. 传送后处理 (PostSwapMap)
|
||||||
foreach (var data in thingsToTeleport)
|
foreach (var data in thingsToTeleport)
|
||||||
{
|
{
|
||||||
if (!data.thing.Destroyed) data.thing.PostSwapMap();
|
if (!data.thing.Destroyed) data.thing.PostSwapMap();
|
||||||
}
|
}
|
||||||
parent.PostSwapMap();
|
|
||||||
|
|
||||||
// 7. 完成
|
// 7. 完成
|
||||||
CameraJumper.TryJump(targetCenter, targetMap);
|
CameraJumper.TryJump(targetCenter, targetMap);
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ namespace WulaFallenEmpire
|
|||||||
private WULA_TeleportLandingMarker marker;
|
private WULA_TeleportLandingMarker marker;
|
||||||
private List<Thing> thingsToTeleport = new List<Thing>();
|
private List<Thing> thingsToTeleport = new List<Thing>();
|
||||||
private IntVec3 sourceCenter;
|
private IntVec3 sourceCenter;
|
||||||
|
private List<IntVec3> relativeCells;
|
||||||
|
|
||||||
public override string Label => "WULA_SelectArrivalPoint".Translate();
|
public override string Label => "WULA_SelectArrivalPoint".Translate();
|
||||||
public override string Desc => "WULA_SelectArrivalPointDesc".Translate();
|
public override string Desc => "WULA_SelectArrivalPointDesc".Translate();
|
||||||
@@ -26,31 +27,35 @@ namespace WulaFallenEmpire
|
|||||||
this.soundDragSustain = SoundDefOf.Designate_DragStandard;
|
this.soundDragSustain = SoundDefOf.Designate_DragStandard;
|
||||||
this.soundDragChanged = SoundDefOf.Designate_DragStandard_Changed;
|
this.soundDragChanged = SoundDefOf.Designate_DragStandard_Changed;
|
||||||
this.soundSucceeded = SoundDefOf.Designate_PlaceBuilding;
|
this.soundSucceeded = SoundDefOf.Designate_PlaceBuilding;
|
||||||
|
|
||||||
|
// Cache relative cells from the group
|
||||||
|
this.relativeCells = teleporter.GetRelativeGroupCells();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override AcceptanceReport CanDesignateCell(IntVec3 loc)
|
public override AcceptanceReport CanDesignateCell(IntVec3 loc)
|
||||||
{
|
{
|
||||||
if (!loc.InBounds(targetMap)) return false;
|
if (!loc.InBounds(targetMap)) return false;
|
||||||
|
|
||||||
// 检查区域是否有效
|
// Check all cells in the group shape
|
||||||
CellRect rect = CellRect.CenteredOn(loc, teleporter.Props.areaSize.x, teleporter.Props.areaSize.z);
|
foreach (IntVec3 offset in relativeCells)
|
||||||
foreach (IntVec3 cell in rect)
|
|
||||||
{
|
{
|
||||||
|
IntVec3 cell = loc + offset;
|
||||||
|
|
||||||
if (!cell.InBounds(targetMap)) return "WULA_OutOfBounds".Translate();
|
if (!cell.InBounds(targetMap)) return "WULA_OutOfBounds".Translate();
|
||||||
|
|
||||||
// 检查地图边缘
|
// Check map edge
|
||||||
if (cell.InNoBuildEdgeArea(targetMap))
|
if (cell.InNoBuildEdgeArea(targetMap))
|
||||||
{
|
{
|
||||||
return "WULA_InNoBuildArea".Translate();
|
return "WULA_InNoBuildArea".Translate();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查迷雾
|
// Check fog
|
||||||
if (cell.Fogged(targetMap))
|
if (cell.Fogged(targetMap))
|
||||||
{
|
{
|
||||||
return "WULA_BlockedByFog".Translate();
|
return "WULA_BlockedByFog".Translate();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查是否有不可覆盖的建筑
|
// Check for indestructible buildings
|
||||||
List<Thing> things = targetMap.thingGrid.ThingsListAt(cell);
|
List<Thing> things = targetMap.thingGrid.ThingsListAt(cell);
|
||||||
foreach (Thing t in things)
|
foreach (Thing t in things)
|
||||||
{
|
{
|
||||||
@@ -63,7 +68,7 @@ namespace WulaFallenEmpire
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查地形是否支持建造
|
// Check terrain passability
|
||||||
TerrainDef terrain = cell.GetTerrain(targetMap);
|
TerrainDef terrain = cell.GetTerrain(targetMap);
|
||||||
if (terrain.passability == Traversability.Impassable && !terrain.IsWater)
|
if (terrain.passability == Traversability.Impassable && !terrain.IsWater)
|
||||||
{
|
{
|
||||||
@@ -110,7 +115,13 @@ namespace WulaFallenEmpire
|
|||||||
|
|
||||||
private void DrawRect()
|
private void DrawRect()
|
||||||
{
|
{
|
||||||
GenDraw.DrawFieldEdges(CellRect.CenteredOn(UI.MouseCell(), teleporter.Props.areaSize.x, teleporter.Props.areaSize.z).Cells.ToList());
|
IntVec3 center = UI.MouseCell();
|
||||||
|
List<IntVec3> drawCells = new List<IntVec3>();
|
||||||
|
foreach (var offset in relativeCells)
|
||||||
|
{
|
||||||
|
drawCells.Add(center + offset);
|
||||||
|
}
|
||||||
|
GenDraw.DrawFieldEdges(drawCells);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CacheThings()
|
private void CacheThings()
|
||||||
@@ -120,9 +131,11 @@ namespace WulaFallenEmpire
|
|||||||
|
|
||||||
sourceCenter = teleporter.parent.Position;
|
sourceCenter = teleporter.parent.Position;
|
||||||
Map sourceMap = teleporter.parent.Map;
|
Map sourceMap = teleporter.parent.Map;
|
||||||
CellRect rect = CellRect.CenteredOn(sourceCenter, teleporter.Props.areaSize.x, teleporter.Props.areaSize.z);
|
|
||||||
|
|
||||||
foreach (IntVec3 cell in rect)
|
// Use the group cells directly from the teleporter
|
||||||
|
List<IntVec3> groupCells = teleporter.GroupCells;
|
||||||
|
|
||||||
|
foreach (IntVec3 cell in groupCells)
|
||||||
{
|
{
|
||||||
if (!cell.InBounds(sourceMap)) continue;
|
if (!cell.InBounds(sourceMap)) continue;
|
||||||
foreach (Thing t in sourceMap.thingGrid.ThingsListAt(cell))
|
foreach (Thing t in sourceMap.thingGrid.ThingsListAt(cell))
|
||||||
@@ -133,7 +146,7 @@ namespace WulaFallenEmpire
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 添加自身
|
// Add self (leader)
|
||||||
thingsToTeleport.Add(teleporter.parent);
|
thingsToTeleport.Add(teleporter.parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,7 +171,7 @@ namespace WulaFallenEmpire
|
|||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
// 忽略绘制错误,防止UI崩溃
|
// Ignore drawing errors to prevent UI crash
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user