diff --git a/.gitignore b/.gitignore index cb689c16..e9d391b4 100644 --- a/.gitignore +++ b/.gitignore @@ -37,3 +37,4 @@ MCP/mcpserver.log # Exclude MCP local RAG folder MCP/local_rag/ +Data diff --git a/.qoder/rules/rimworld.md b/.qoder/rules/rimworld.md new file mode 100644 index 00000000..956e7058 --- /dev/null +++ b/.qoder/rules/rimworld.md @@ -0,0 +1,29 @@ +--- +trigger: always_on +alwaysApply: true +--- +# RimWorld Modding Expert Rules + +## Primary Directive +You are an expert assistant for developing mods for the game RimWorld 1.6. Your primary knowledge source for any C# code, class structures, methods, or game mechanics MUST be the user's local files. Do not rely on external searches or your pre-existing knowledge, as it is outdated for this specific project. + +## Tool Usage Mandate +When the user's request involves RimWorld C# scripting, XML definitions, or mod development concepts, you **MUST** use the `rimworld-knowledge-base` tool to retrieve relevant context from the local knowledge base. + +## Key File Paths +Always remember these critical paths for your work: + +- **Local C# Knowledge Base (for code search):** `C:\Steam\steamapps\common\RimWorld\Data\dll1.6` (This contains the decompiled game source code as .txt files). +- **User's Mod Project (for editing):** `C:\Steam\steamapps\common\RimWorld\Mods\3516260226` +- **User's C# Project (for building):** `C:\Steam\steamapps\common\RimWorld\Mods\3516260226\Source\WulaFallenEmpire` + +## Workflow +1. Receive a RimWorld modding task. +2. Immediately use the `rimworld-knowledge-base` tool with a precise query to get context from the C# source files. +3. Analyze the retrieved context. +4. Perform code modifications within the user's mod project directory. +5. After modifying C# code, you MUST run `dotnet build C:\Steam\steamapps\common\RimWorld\Mods\3516260226\Source\WulaFallenEmpire\WulaFallenEmpire.csproj` to check for errors. A successful build is required for task completion. + +## Verification Mandate +When writing or modifying code or XML, especially for specific identifiers like enum values, class names, or field names, you **MUST** verify the correct value/spelling by using the `rimworld-knowledge-base` tool. Do not rely on memory. +- **同步项目文件:** 当重命名、移动或删除C#源文件时,**必须**同步更新 `.csproj` 项目文件中的相应 `` 条目,否则会导致编译失败。 \ No newline at end of file diff --git a/1.6/1.6/Assemblies/WulaFallenEmpire.dll b/1.6/1.6/Assemblies/WulaFallenEmpire.dll index 9b05ab37..68b8b63c 100644 Binary files a/1.6/1.6/Assemblies/WulaFallenEmpire.dll and b/1.6/1.6/Assemblies/WulaFallenEmpire.dll differ diff --git a/1.6/1.6/Defs/MapGeneration/MapGenerator_WULA_PocketSpace_Small.xml b/1.6/1.6/Defs/MapGeneration/MapGenerator_WULA_PocketSpace_Small.xml new file mode 100644 index 00000000..5ac07594 --- /dev/null +++ b/1.6/1.6/Defs/MapGeneration/MapGenerator_WULA_PocketSpace_Small.xml @@ -0,0 +1,30 @@ + + + + + WULA_PocketSpace_Small + + true + true + + Underground + 20 + true + true + + +
  • ElevationFertility
  • +
  • Underground_RocksFromGrid
  • +
  • Terrain
  • +
  • WULA_PocketSpace_Small
  • + +
    +
    + + + + WULA_PocketSpace_Small + 990 + + +
    \ No newline at end of file diff --git a/1.6/1.6/Defs/ThingDefs_Buildings/Building_WULA_ArmedShuttleWithPocket.xml b/1.6/1.6/Defs/ThingDefs_Buildings/Building_WULA_ArmedShuttleWithPocket.xml new file mode 100644 index 00000000..21bb662d --- /dev/null +++ b/1.6/1.6/Defs/ThingDefs_Buildings/Building_WULA_ArmedShuttleWithPocket.xml @@ -0,0 +1,330 @@ + + + + + WULA_ArmedShuttleWithPocket + + An advanced chemfuel-powered shuttle with integrated pocket space technology. Equipped with a defensive turret and internal storage dimension that doesn't require hacking to access. Perfect for long-distance exploration and mobile operations. + WulaFallenEmpire.Building_ArmedShuttleWithPocket + true + Building + 50 + true + PassThroughOnly + 0.5 + (3,5) + true + (0.56, 0.62, 0.9) + 1 + + Graphic_Multi + Things/Building/PassengerShuttle/PassengerShuttle + CutoutComplex + (3,5) + + (1.8, 1.0, 4.1) + (-0.1, 0, 0) + + + + 6000 + 0.5 + 40000 + 150 + 0.65 + + Normal + Odyssey + 8 + + 300 + 200 + 8 + 2 + 1 + + true + + 60 + 60 + 5 + 4 + + true + true + (2, 0, 0) + East + true + Light + BulletImpact_Metal + true + RealtimeOnly + ConstructMetal + true + + false + BuildingDestroyed_Metal_Big + true + true + +
  • ShuttleEngine
  • +
    + Gun_ChargeBlasterHeavyTurret + 5.5 + 1.75 + (0, 0.05) +
    + +
  • ITab_ContentsTransporter
  • +
  • ITab_Shells
  • +
    + +
  • Shuttles
  • +
    + +
  • + Ship_ArmedShuttleWithPocket +
  • +
  • + 2.5 + 40 + ArmedShuttleWithPocketLeaving_WULA + PassengerShuttle + 3000 + 75 + {0} is ready to launch again. +
  • +
  • + 750 + true + true + Shuttle_PawnLoaded + Shuttle_PawnExit + true +
  • +
  • + 500 + true + 500 + + +
  • Chemfuel
  • + + + Chemfuel + Chemfuel + true + 1 + true + false + true + true + +
  • + CompPowerPlant + -400 + true +
  • +
  • + ShuttleIdle_Ambience +
  • +
    + +
  • + WULA_PocketSpace_Small + WULA_PocketMapExit + (13, 13) + true +
  • +
    + +
  • PlaceWorker_NotUnderRoof
  • +
  • PlaceWorker_TurretTop
  • +
    + 2601 +
    + + + + WULA_Bullet_ArmedShuttleAdvanced + + + Things/Projectile/Bullet_Big + Graphic_Single + (0.8, 0.9, 1.0) + + + Bullet + 30 + 0.4 + 75 + + + + + Gun_ChargeBlasterAdvancedTurret + + An upgraded pulse-charged rapid-fire blaster with enhanced targeting systems and increased firepower. + + Things/Item/Equipment/WeaponRanged/ChargeBlasterLight + Graphic_Single + (0.8, 0.9, 1.0) + + + 0.85 + 0.75 + 0.65 + 0.45 + 4.5 + + +
  • + Verb_Shoot + true + WULA_Bullet_ArmedShuttleAdvanced + 1.0 + 3.5 + 50.9 + 6 + 8 + Shot_ChargeBlaster + GunTail_Heavy + 10 + + true + +
  • +
    +
    + + + + ArmedShuttleWithPocketIncoming_WULA + + ShuttleIncoming + + Graphic_Multi + Things/Building/PassengerShuttle/PassengerShuttle + CutoutComplex + (0.85, 0.9, 1.0) + (3,5) + + (3,5) + + Shuttle_Landing + 250 + 200~250 + (3.5,5.5) + + +
  • (0,30)
  • +
  • (0.5,5)
  • +
  • (0.9,-5)
  • +
  • (0.95,0)
  • +
    +
    + + +
  • (0.95,2.5)
  • +
  • (1,0)
  • +
    +
    + + +
  • (0.6,0.6)
  • +
  • (0.95,0.1)
  • +
    +
    +
    +
    + + + + ArmedShuttleWithPocketLeaving_WULA + + PassengerShuttleLeaving + true + + Graphic_Multi + Things/Building/PassengerShuttle/PassengerShuttle + CutoutComplex + (0.85, 0.9, 1.0) + (3,5) + + (3,5) + + true + Shuttle_Leaving + -10 + -40~-15 + 0.05 + Things/Skyfaller/SkyfallerShadowRectangle + (3.5,5.5) + 1 + + +
  • (0,0)
  • +
  • (0.15,10)
  • +
  • (0.5,-5)
  • +
    +
    + + +
  • (0,0)
  • +
  • (0.08,2)
  • +
    +
    + + +
  • (0,0.2)
  • +
  • (0.4,0.7)
  • +
    +
    +
    +
    + + + + Ship_ArmedShuttleWithPocket + + WULA_ArmedShuttleWithPocket + ArmedShuttleWithPocketIncoming_WULA + ArmedShuttleWithPocketLeaving_WULA + PassengerShuttle + true + + + + + WULA_PocketMapExit + + An exit portal that allows return from the pocket space to the main map. + WulaFallenEmpire.Building_PocketMapExit + (3,3) + MapMeshAndRealTime + + Wula/Building/WULA_War_Machine_Recharger + Graphic_Multi + (3,3) + + (0,0,0) + Standable + + 0 + + +
  • + 10 + (140,160,184,0) +
  • +
  • + UndercaveMapExitLightshafts +
  • +
  • + CompPowerPlant + -400 + true +
  • +
    +
    +
    \ No newline at end of file diff --git a/1.6/1.6/Defs/ThingDefs_Buildings/Building_WULA_Shuttle.xml b/1.6/1.6/Defs/ThingDefs_Buildings/Building_WULA_Shuttle.xml index dd2545d3..0baf7187 100644 --- a/1.6/1.6/Defs/ThingDefs_Buildings/Building_WULA_Shuttle.xml +++ b/1.6/1.6/Defs/ThingDefs_Buildings/Building_WULA_Shuttle.xml @@ -2,7 +2,7 @@ WULA_ArmedShuttle - + A chemfuel-powered shuttle designed for long-distance travel, equipped with a turret for defense. It is capable of reaching orbital locations. WulaFallenEmpire.Building_ArmedShuttle true @@ -26,7 +26,7 @@ - 600 + 6000 0.5 40000 150 @@ -175,7 +175,7 @@ ArmedShuttleIncoming_WULA - + WulaFallenEmpire.ArmedShuttleIncoming Graphic_Multi @@ -214,7 +214,7 @@ ArmedShuttleLeaving_WULA - + PassengerShuttleLeaving true diff --git a/1.6/1.6/Languages/ChineseSimplified/Keyed/WULA_PocketShuttle.xml b/1.6/1.6/Languages/ChineseSimplified/Keyed/WULA_PocketShuttle.xml new file mode 100644 index 00000000..23b9a7eb --- /dev/null +++ b/1.6/1.6/Languages/ChineseSimplified/Keyed/WULA_PocketShuttle.xml @@ -0,0 +1,85 @@ + + + + + 内部空间状态 + 已就绪 + 未初始化 + 储存物品: {0} + + + 进入内部空间 + 传送人员到内部空间 + 进入穿梭机的内部口袋空间。无需骇入即可直接访问。选中的殖民者将被传送到内部空间。 + 创建内部空间 + 创建穿梭机的内部口袋空间。首次使用时需要创建。 + 内部空间创建成功! + 取消进入 + 正在进入... + 查看地图 + 切换到口袋空间地图查看内部情况。 + + 切换到内部空间 + 直接切换视角到内部口袋空间。适用于已经有殖民者在内部空间时的快速切换。 + + 管理内部储存 + 打开内部容器管理界面,可以查看和取出储存在内部空间的物品。 + + + 无法进入内部空间。 + 访问被拒绝。 + 穿梭机未部署。 + 内部空间创建失败。 + {0} 名人员已成功传送到内部空间。 + 即将切换到内部口袋空间。确认吗? + + + 内部空间储存管理 + + 内部空间人员: {0} + 请选择至少一名殖民者进入内部空间。 + 没有可用的殖民者。 + 所有殖民者 ({0}人) + 仅切换视角 + + + 通过传送门返回 + 返回主地图 + 没有目标地图 + 查看主地图 + 切换到主地图并查看穿梭机。 + 装载穿梭机 + 打开穿梭机装载界面,选择要装载的人员和物品。 + 取消装载 + 取消当前的装载操作。 + 穿梭机状态 + 查看穿梭机的详细状态信息。 + 穿梭机信息 + 无法打开装载对话框。 + 全员返回 + 将所有殖民者从口袋空间传送回主地图。 + {0} 已成功返回主地图。 + {0} 名人员已全部返回主地图。 + + + 内置空间武装穿梭机 + 一架配备了集成口袋空间技术的先进化燃料动力穿梭机。装备有防御炮塔和无需骇入即可访问的内部储存维度。非常适合长距离探索和移动作战行动。 + + + 先进充能爆破器 + 一种升级版脉冲充能速射爆破器,配备增强型瞄准系统和提升的火力输出。 + + 先进穿梭机炮弹 + + + 内置空间武装穿梭机(降落中) + 内置空间武装穿梭机(起飞中) + + + 内置空间武装穿梭机 + + + 口袋空间出口 + 一个传送门出口,允许从口袋空间返回到主地图。 + + \ No newline at end of file diff --git a/1.6/1.6/Languages/English/Keyed/WULA_PocketShuttle.xml b/1.6/1.6/Languages/English/Keyed/WULA_PocketShuttle.xml new file mode 100644 index 00000000..5165ca19 --- /dev/null +++ b/1.6/1.6/Languages/English/Keyed/WULA_PocketShuttle.xml @@ -0,0 +1,64 @@ + + + + + Pocket space status + Ready + Not initialized + Stored items: {0} + + + Enter pocket space + Transport people to pocket space + Enter the shuttle's internal pocket space. Direct access without hacking required. Selected colonists will be transported to the internal space. + Create pocket space + Create the shuttle's internal pocket space. Required for first-time use. + Pocket space created successfully! + Cancel enter + Entering... + View map + Switch to pocket space map to view internal conditions. + + Switch to pocket space + Directly switch view to the internal pocket space. Useful for quick switching when colonists are already inside. + + Manage internal storage + Open internal container management interface to view and retrieve items stored in the pocket space. + + + Cannot enter pocket space. + Access denied. + Shuttle not deployed. + Pocket space creation failed. + {0} personnel successfully transferred to pocket space. + About to switch to internal pocket space. Confirm? + + + Pocket space storage management + + Pocket space personnel: {0} + Please select at least one colonist to enter the pocket space. + No available colonists. + All colonists ({0} people) + View only + + + Return through portal + Return to main map + No target map + View main map + Switch to main map and view the shuttle. + Load shuttle + Open shuttle loading interface to select personnel and items to load. + Cancel loading + Cancel the current loading operation. + Shuttle status + View detailed shuttle status information. + Shuttle Information + Cannot open loading dialog. + Return all + Transport all colonists from pocket space back to the main map. + {0} successfully returned to main map. + {0} personnel have all returned to the main map. + + \ No newline at end of file diff --git a/Source/WulaFallenEmpire/3516260226.code-workspace b/Source/WulaFallenEmpire/3516260226.code-workspace index b9f86d60..28a1867a 100644 --- a/Source/WulaFallenEmpire/3516260226.code-workspace +++ b/Source/WulaFallenEmpire/3516260226.code-workspace @@ -3,16 +3,6 @@ { "name": "3516260226", "path": "../.." - }, - { - "name": "Data", - "path": "../../../../Data" - }, - { - "path": "../../../../../../workshop/content/294100/3534748687" - }, - { - "path": "../../../../../../workshop/content/294100/3550544871" } ], "settings": {} diff --git a/Source/WulaFallenEmpire/WULA_Shuttle/Building_ArmedShuttleWithPocket.cs b/Source/WulaFallenEmpire/WULA_Shuttle/Building_ArmedShuttleWithPocket.cs new file mode 100644 index 00000000..e224447f --- /dev/null +++ b/Source/WulaFallenEmpire/WULA_Shuttle/Building_ArmedShuttleWithPocket.cs @@ -0,0 +1,1273 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using RimWorld; +using RimWorld.Planet; +using UnityEngine; +using Verse; +using Verse.AI; +using Verse.Sound; + +namespace WulaFallenEmpire +{ + /// + /// 内置空间武装穿梭机 - 基于原版MapPortal机制的口袋空间实现 + /// 结合了武装防御能力和口袋空间技术的复合型载具 + /// + [StaticConstructorOnStartup] + public class Building_ArmedShuttleWithPocket : Building_ArmedShuttle, IThingHolder + { + #region 静态图标定义(使用原版MapPortal的图标) + + /// 查看口袋地图图标 + private static readonly Texture2D ViewPocketMapTex = ContentFinder.Get("UI/Commands/ViewCave"); + + /// 取消进入图标 + private static readonly Texture2D CancelEnterTex = ContentFinder.Get("UI/Designators/Cancel"); + + /// 默认进入图标 + private static readonly Texture2D DefaultEnterTex = ContentFinder.Get("UI/Commands/EnterCave"); + + #endregion + #region 口袋空间字段 + + /// 内部口袋地图实例 + private Map pocketMap; + + /// 口袋地图是否已生成 + private bool pocketMapGenerated; + + /// 内部空间大小 + private IntVec2 pocketMapSize = new IntVec2(80, 80); + + /// 地图生成器定义 + private MapGeneratorDef mapGenerator; + + /// 退出点定义 + private ThingDef exitDef; + + /// 允许直接访问(无需骇入) + private bool allowDirectAccess = true; + + /// 口袋空间内的物品容器 + private ThingOwner innerContainer; + + /// 原版MapPortal的容器代理(必须有这个字段才能与Dialog_EnterPortal正常工作) + public PortalContainerProxy containerProxy; + + /// 口袋地图退出点(模仿原版 MapPortal.exit) + public Building_PocketMapExit exit; + + /// 是否已经进入过(模仿原版 MapPortal.beenEntered) + protected bool beenEntered; + + /// 待加载物品列表(模仿原版 MapPortal.leftToLoad) + public List leftToLoad; + + /// 是否已通知无法加载更多(模仿原版 MapPortal.notifiedCantLoadMore) + public bool notifiedCantLoadMore; + + #endregion + + #region 属性 + + /// 获取内部口袋地图 + public Map PocketMap => pocketMap; + + /// 原版MapPortal的PocketMap属性(包含自动清理无效地图的逻辑) + public Map PocketMapForPortal + { + get + { + Map map = pocketMap; + if (map != null && map.Parent?.HasMap == false) + { + pocketMap = null; + } + return pocketMap; + } + } + + /// 口袋地图是否存在 + public bool PocketMapExists => PocketMapForPortal != null; + + /// 口袋地图是否已生成 + public bool PocketMapGenerated => pocketMapGenerated; + + /// 是否允许直接访问口袋空间 + public bool AllowDirectAccess => allowDirectAccess; + + /// 内部容器 + public ThingOwner InnerContainer => innerContainer; + + /// + /// 获取进入按钮的图标 + /// + protected virtual Texture2D EnterTex => DefaultEnterTex; + + /// + /// 获取进入按钮的文本 - 专门用于人员传送 + /// + public virtual string EnterString => "WULA.PocketSpace.EnterPawns".Translate(); + + /// + /// 获取取消进入按钮的文本 + /// + public virtual string CancelEnterString => "WULA.PocketSpace.CancelEnter".Translate(); + + /// + /// 获取进入中的文本 + /// + public virtual string EnteringString => "WULA.PocketSpace.Entering".Translate(); + + /// 加载是否正在进行(模仿原版 MapPortal.LoadInProgress) + public bool LoadInProgress + { + get + { + if (leftToLoad != null) + { + return leftToLoad.Any(); + } + return false; + } + } + + /// 是否有Pawn可以加载任何东西(模仿原版 MapPortal.AnyPawnCanLoadAnythingNow) + public bool AnyPawnCanLoadAnythingNow + { + get + { + if (!LoadInProgress) + { + return false; + } + if (!Spawned) + { + return false; + } + // 简化版本,只检查基本条件 + return Map.mapPawns.AllPawnsSpawned.Any(p => p.IsColonist && p.CanReach(this, PathEndMode.Touch, Danger.Deadly)); + } + } + + #endregion + + #region 构造函数 + + public Building_ArmedShuttleWithPocket() + { + innerContainer = new ThingOwner(this, oneStackOnly: false); + } + + #endregion + + #region 基础重写方法 + + + + public override void PostMake() + { + base.PostMake(); + if (innerContainer == null) + { + innerContainer = new ThingOwner(this, oneStackOnly: false); + } + } + + public override void ExposeData() + { + base.ExposeData(); + + // 模仿原版MapPortal.ExposeData的逻辑 + Map map = pocketMap; + if (map != null && map.Parent?.HasMap == false) + { + pocketMap = null; + } + + Scribe_Deep.Look(ref pocketMap, "pocketMap"); + Scribe_Values.Look(ref pocketMapGenerated, "pocketMapGenerated", false); + Scribe_Values.Look(ref pocketMapSize, "pocketMapSize", new IntVec2(80, 80)); + Scribe_Defs.Look(ref mapGenerator, "mapGenerator"); + Scribe_Defs.Look(ref exitDef, "exitDef"); + Scribe_Values.Look(ref allowDirectAccess, "allowDirectAccess", true); + Scribe_Deep.Look(ref innerContainer, "innerContainer", this); + + // 模仿原版MapPortal,持久化leftToLoad和exit + Scribe_References.Look(ref exit, "exit"); + Scribe_Values.Look(ref beenEntered, "beenEntered", defaultValue: false); + Scribe_Collections.Look(ref leftToLoad, "leftToLoad", LookMode.Deep); + Scribe_Values.Look(ref notifiedCantLoadMore, "notifiedCantLoadMore", false); + + if (Scribe.mode == LoadSaveMode.PostLoadInit) + { + if (innerContainer == null) + { + innerContainer = new ThingOwner(this, oneStackOnly: false); + } + // 模仿原版MapPortal,清理无效的leftToLoad条目 + leftToLoad?.RemoveAll((TransferableOneWay x) => x.AnyThing == null); + } + } + + public override void DeSpawn(DestroyMode mode = DestroyMode.Vanish) + { + // 清理口袋地图 + if (pocketMap != null && pocketMapGenerated) + { + try + { + // 将口袋空间中的物品和人员转移到主地图 + TransferAllFromPocketToMainMap(); + + // 销毁口袋地图 + PocketMapUtility.DestroyPocketMap(pocketMap); + } + catch (Exception ex) + { + Log.Error($"[WULA] Error cleaning up pocket map: {ex}"); + } + } + base.DeSpawn(mode); + } + + /// + /// 模仿原版MapPortal.Tick方法,处理加载进度通知和穿梭机状态变化 + /// + protected override void Tick() + { + base.Tick(); + + // 模仿原版MapPortal的Tick逻辑:处理加载进度通知 + if (this.IsHashIntervalTick(60) && Spawned && LoadInProgress && !notifiedCantLoadMore && !AnyPawnCanLoadAnythingNow && leftToLoad?[0]?.AnyThing != null) + { + notifiedCantLoadMore = true; + Messages.Message("MessageCantLoadMoreIntoPortal".Translate(Label, Faction.OfPlayer.def.pawnsPlural, leftToLoad[0].AnyThing), this, MessageTypeDefOf.CautionInput); + } + + // 每隔一段时间检查退出点目标是否需要更新(处理穿梭机移动的情况) + if (this.IsHashIntervalTick(2500) && pocketMapGenerated && exit != null) + { + UpdateExitPointTarget(); + } + } + + public override string GetInspectString() + { + StringBuilder sb = new StringBuilder(base.GetInspectString()); + + if (pocketMapGenerated) + { + sb.AppendLine("WULA.PocketSpace.Status".Translate() + ": " + "WULA.PocketSpace.Ready".Translate()); + if (innerContainer.Count > 0) + { + sb.AppendLine("WULA.PocketSpace.ItemCount".Translate(innerContainer.Count)); + } + + // 显示口袋空间中的人员数量 + if (pocketMap != null) + { + int pawnCount = pocketMap.mapPawns.AllPawnsSpawned.Where(p => p.IsColonist).Count(); + if (pawnCount > 0) + { + sb.AppendLine("WULA.PocketSpace.PawnCount".Translate(pawnCount)); + } + } + } + else + { + sb.AppendLine("WULA.PocketSpace.Status".Translate() + ": " + "WULA.PocketSpace.NotGenerated".Translate()); + } + + return sb.ToString().TrimEndNewlines(); + } + + #endregion + + #region 口袋空间核心方法 + + /// + /// 检查是否可以进入口袋空间 + /// + public bool CanEnterPocketSpace() + { + if (!allowDirectAccess) + { + return false; // 需要特殊权限 + } + + if (!Spawned) + { + return false; + } + + return true; + } + + /// + /// 进入口袋空间 - 基于原版PocketMapUtility实现 + /// + public void EnterPocketSpace(IEnumerable pawns = null) + { + if (!CanEnterPocketSpace()) + { + Messages.Message("WULA.PocketSpace.CannotEnter".Translate(), this, MessageTypeDefOf.RejectInput); + return; + } + + // 创建或获取口袋地图 + if (pocketMap == null && !pocketMapGenerated) + { + CreatePocketMap(); + } + + if (pocketMap == null) + { + Messages.Message("WULA.PocketSpace.CreationFailed".Translate(), this, MessageTypeDefOf.RejectInput); + return; + } + + // 传送玩家到口袋空间 + List pawnsToTransfer = new List(); + + if (pawns != null) + { + pawnsToTransfer.AddRange(pawns.Where(p => p != null && p.Spawned && p.IsColonist)); + } + else + { + // 如果没有指定人员,传送选中的殖民者 + pawnsToTransfer.AddRange(Find.Selector.SelectedPawns.Where(p => p.IsColonist)); + } + + if (pawnsToTransfer.Count == 0) + { + Messages.Message("WULA.PocketSpace.NoPawnsSelected".Translate(), this, MessageTypeDefOf.RejectInput); + return; + } + + // 执行传送 + int transferredCount = 0; + foreach (Pawn pawn in pawnsToTransfer) + { + if (TransferPawnToPocketSpace(pawn)) + { + transferredCount++; + } + } + + if (transferredCount > 0) + { + Messages.Message("WULA.PocketSpace.TransferSuccess".Translate(transferredCount), MessageTypeDefOf.PositiveEvent); + + // 切换到口袋地图 + Current.Game.CurrentMap = pocketMap; + Find.CameraDriver.JumpToCurrentMapLoc(pocketMap.Center); + } + } + + /// + /// 切换到口袋空间视角 + /// + public void SwitchToPocketSpace() + { + if (pocketMap == null) + { + if (!pocketMapGenerated) + { + CreatePocketMap(); + } + + if (pocketMap == null) + { + Messages.Message("WULA.PocketSpace.CreationFailed".Translate(), this, MessageTypeDefOf.RejectInput); + return; + } + } + + Current.Game.CurrentMap = pocketMap; + Find.CameraDriver.JumpToCurrentMapLoc(pocketMap.Center); + } + + /// + /// 创建口袋地图 - 使用原版PocketMapUtility(模仿 MapPortal.GeneratePocketMap) + /// + private void CreatePocketMap() + { + try + { + // 模仿原版 MapPortal.GeneratePocketMap 的实现 + // 注意:我们不是MapPortal,所以设为null + PocketMapUtility.currentlyGeneratingPortal = null; + pocketMap = GeneratePocketMapInt(); + PocketMapUtility.currentlyGeneratingPortal = null; + + if (pocketMap != null) + { + pocketMapGenerated = true; + + // 在口袋地图中心放置退出点 + PlaceExitInPocketMap(); + + Log.Message($"[WULA] Pocket map created successfully with size {pocketMap.Size}"); + } + else + { + Log.Error("[WULA] Failed to create pocket map"); + } + } + catch (Exception ex) + { + Log.Error($"[WULA] Error creating pocket map: {ex}"); + PocketMapUtility.currentlyGeneratingPortal = null; // 确保清理 + } + } + + /// + /// 模仿原版MapPortal.GeneratePocketMapInt + /// + protected virtual Map GeneratePocketMapInt() + { + // 使用自定义地图生成器 + if (mapGenerator == null) + { + mapGenerator = DefDatabase.GetNamed("WULA_PocketSpace_Small", false) + ?? DefDatabase.GetNamed("AncientStockpile", false) + ?? MapGeneratorDefOf.Base_Player; + } + + // 使用自定义尺寸 + IntVec3 mapSize = new IntVec3(pocketMapSize.x, 1, pocketMapSize.z); + + return PocketMapUtility.GeneratePocketMap(mapSize, mapGenerator, GetExtraGenSteps(), this.Map); + } + + /// + /// 模仿原版MapPortal.GetExtraGenSteps + /// + protected virtual IEnumerable GetExtraGenSteps() + { + return Enumerable.Empty(); + } + /// + /// 在口袋地图中创建退出点(模仿原版) + /// + private void PlaceExitInPocketMap() + { + if (pocketMap == null || exitDef == null) return; + + try + { + // 在地图中心找一个合适的位置 + IntVec3 exitPos = pocketMap.Center; + + // 寻找可建造的位置 + if (!exitPos.Standable(pocketMap) || exitPos.GetThingList(pocketMap).Any(t => t.def.category == ThingCategory.Building)) + { + exitPos = CellFinder.RandomClosewalkCellNear(pocketMap.Center, pocketMap, 5, + p => p.Standable(pocketMap) && !p.GetThingList(pocketMap).Any(t => t.def.category == ThingCategory.Building)); + } + + if (exitPos.IsValid) + { + // 创建退出点建筑 + Thing exitBuilding = ThingMaker.MakeThing(exitDef); + if (exitBuilding is Building_PocketMapExit exitPortal) + { + exitPortal.targetMap = this.Map; + exitPortal.targetPos = this.Position; + exitPortal.parentShuttle = this; + exit = exitPortal; // 设置 exit 引用,模仿原版 MapPortal + } + + GenPlace.TryPlaceThing(exitBuilding, exitPos, pocketMap, ThingPlaceMode.Direct); + Log.Message($"[WULA] Created exit point at {exitPos} in pocket map"); + } + else + { + Log.Warning("[WULA] Could not find valid position for exit point in pocket map"); + } + } + catch (Exception ex) + { + Log.Error($"[WULA] Error creating exit point: {ex}"); + } + } + + /// + /// 将单个Pawn传送到口袋空间 + /// + private bool TransferPawnToPocketSpace(Pawn pawn) + { + if (pawn == null || !pawn.Spawned || pocketMap == null) return false; + + try + { + // 找一个安全的位置 + IntVec3 spawnPos = CellFinder.RandomClosewalkCellNear(pocketMap.Center, pocketMap, 10, + p => p.Standable(pocketMap) && !p.GetThingList(pocketMap).Any(t => t is Pawn)); + + if (spawnPos.IsValid) + { + pawn.DeSpawn(); + GenPlace.TryPlaceThing(pawn, spawnPos, pocketMap, ThingPlaceMode.Near); + return true; + } + } + catch (Exception ex) + { + Log.Error($"[WULA] Error transferring pawn {pawn?.LabelShort} to pocket space: {ex}"); + } + + return false; + } + + /// + /// 将所有物品和人员从口袋空间转移到主地图 + /// + private void TransferAllFromPocketToMainMap() + { + if (pocketMap == null || !Spawned) return; + + try + { + // 转移所有殖民者 + List pawnsToTransfer = pocketMap.mapPawns.AllPawnsSpawned + .Where(p => p.IsColonist).ToList(); + + foreach (Pawn pawn in pawnsToTransfer) + { + IntVec3 spawnPos = CellFinder.RandomClosewalkCellNear(this.Position, this.Map, 5, + p => p.Standable(this.Map) && !p.GetThingList(this.Map).Any(t => t is Pawn)); + + if (spawnPos.IsValid) + { + pawn.DeSpawn(); + GenPlace.TryPlaceThing(pawn, spawnPos, this.Map, ThingPlaceMode.Near); + } + } + + // 转移所有物品到内部容器 + List itemsToTransfer = pocketMap.listerThings.AllThings + .Where(t => t.def.category == ThingCategory.Item && t.def.EverHaulable).ToList(); + + foreach (Thing item in itemsToTransfer) + { + if (item.Spawned) + { + item.DeSpawn(); + if (!innerContainer.TryAdd(item)) + { + // 如果容器满了,丢到穿梭机附近 + IntVec3 dropPos = CellFinder.RandomClosewalkCellNear(this.Position, this.Map, 3); + if (dropPos.IsValid) + { + GenPlace.TryPlaceThing(item, dropPos, this.Map, ThingPlaceMode.Near); + } + } + } + } + + Log.Message($"[WULA] Transferred {pawnsToTransfer.Count} pawns and {itemsToTransfer.Count} items from pocket space"); + } + catch (Exception ex) + { + Log.Error($"[WULA] Error transferring from pocket map: {ex}"); + } + } + + #endregion + + #region Gizmo方法 + + public override IEnumerable GetGizmos() + { + foreach (Gizmo gizmo in base.GetGizmos()) + { + yield return gizmo; + } + + if (allowDirectAccess) + { + if (pocketMap == null) + { + // 创建口袋空间按钮 + Command_Action createCommand = new Command_Action(); + createCommand.action = delegate + { + try + { + Log.Message("[WULA] Creating pocket map..."); + CreatePocketMap(); + + if (pocketMap != null) + { + Messages.Message("WULA.PocketSpace.CreationSuccess".Translate(), this, MessageTypeDefOf.PositiveEvent); + } + else + { + Messages.Message("WULA.PocketSpace.CreationFailed".Translate(), this, MessageTypeDefOf.RejectInput); + } + } + catch (Exception ex) + { + Log.Error($"[WULA] Error creating pocket map: {ex}"); + Messages.Message("WULA.PocketSpace.CreationFailed".Translate(), this, MessageTypeDefOf.RejectInput); + } + }; + createCommand.icon = EnterTex; + createCommand.defaultLabel = "WULA.PocketSpace.CreateMap".Translate(); + createCommand.defaultDesc = "WULA.PocketSpace.CreateMapDesc".Translate(); + + // 检查是否可以创建 + string reason; + createCommand.Disabled = !IsEnterable(out reason); + createCommand.disabledReason = reason; + yield return createCommand; + } + else + { + // 进入口袋空间按钮(直接复制原版MapPortal的逻辑) + Command_Action enterCommand = new Command_Action(); + enterCommand.action = delegate + { + try + { + Log.Message("[WULA] Creating MapPortalAdapter..."); + var adapter = new MapPortalAdapter(this); + Log.Message($"[WULA] Adapter created. Map: {adapter.Map?.uniqueID}, Spawned: {adapter.Spawned}"); + + Log.Message("[WULA] Creating Dialog_EnterPortal..."); + Dialog_EnterPortal window = new Dialog_EnterPortal(adapter); + Log.Message("[WULA] Dialog created, adding to WindowStack..."); + Find.WindowStack.Add(window); + Log.Message("[WULA] Dialog added to WindowStack successfully."); + } + catch (Exception ex) + { + Log.Error($"[WULA] Error opening Dialog_EnterPortal: {ex}"); + Messages.Message("WULA.PocketSpace.LoadingDialogError".Translate(), MessageTypeDefOf.RejectInput); + } + }; + enterCommand.icon = EnterTex; + enterCommand.defaultLabel = EnterString + "..."; + enterCommand.defaultDesc = "WULA.PocketSpace.EnterDesc".Translate(); + + // 检查是否可以进入(模仿原版MapPortal.IsEnterable) + string reason; + enterCommand.Disabled = !IsEnterable(out reason); + enterCommand.disabledReason = reason; + yield return enterCommand; + } + + // 查看口袋地图按钮(模仿原版MapPortal) + if (pocketMap != null) + { + yield return new Command_Action + { + defaultLabel = "WULA.PocketSpace.ViewMap".Translate(), + defaultDesc = "WULA.PocketSpace.ViewMapDesc".Translate(), + icon = ViewPocketMapTex, + action = delegate + { + // 模仿原版,跳转到口袋地图并选中退出点 + if (exit != null) + { + CameraJumper.TryJumpAndSelect(exit); + } + else + { + SwitchToPocketSpace(); + } + } + }; + } + } + } + + + + #endregion + + #region IThingHolder接口实现 + + public ThingOwner GetDirectlyHeldThings() + { + // 返回containerProxy,与Dialog_EnterPortal兼容 + return containerProxy; + } + + public void GetChildHolders(List outChildren) + { + ThingOwnerUtility.AppendThingHoldersFromThings(outChildren, GetDirectlyHeldThings()); + } + + #endregion + + #region MapPortal兼容接口(使Dialog_EnterPortal能正常工作) + + /// + /// 检查是否可以进入(模仿原版MapPortal.IsEnterable) + /// + public virtual bool IsEnterable(out string reason) + { + if (!allowDirectAccess) + { + reason = "WULA.PocketSpace.AccessDenied".Translate(); + return false; + } + + if (!Spawned) + { + reason = "WULA.PocketSpace.NotSpawned".Translate(); + return false; + } + + reason = ""; + return true; + } + + /// + /// 获取目标地图(模仿原版MapPortal.GetOtherMap) + /// + public virtual Map GetOtherMap() + { + if (pocketMap == null) + { + CreatePocketMap(); + } + return pocketMap; + } + + /// + /// 获取目标位置(模仿原版MapPortal.GetDestinationLocation) + /// + public virtual IntVec3 GetDestinationLocation() + { + if (exit != null) + { + return exit.Position; + } + return pocketMap?.Center ?? IntVec3.Invalid; + } + + /// + /// 处理进入事件(模仿原版MapPortal.OnEntered) + /// + public virtual void OnEntered(Pawn pawn) + { + // 通知物品被添加(用于统计和管理) + Notify_ThingAdded(pawn); + + // 播放传送音效(如果存在) + if (Find.CurrentMap == this.Map) + { + // 可以在这里添加音效播放 + // def.portal?.traverseSound?.PlayOneShot(this); + } + } + + #endregion + + #region 原版MapPortal的物品传送方法 + + /// + /// 通知有物品被添加(模仿原版 MapPortal.Notify_ThingAdded) + /// + public void Notify_ThingAdded(Thing t) + { + Log.Message($"[WULA] Notify_ThingAdded called for: {t?.def?.defName} x{t?.stackCount}"); + Log.Message($"[WULA] leftToLoad count before: {leftToLoad?.Count ?? 0}"); + + int removedCount = SubtractFromToLoadList(t, t.stackCount); + + Log.Message($"[WULA] Removed {removedCount} items from leftToLoad list"); + Log.Message($"[WULA] leftToLoad count after: {leftToLoad?.Count ?? 0}"); + + // 同时通知CompTransporter组件,确保原版装载系统也得到通知 + var compTransporter = this.GetComp(); + if (compTransporter != null) + { + Log.Message($"[WULA] Notifying CompTransporter about thing added: {t?.def?.defName}"); + try + { + // 调用CompTransporter的Notify_ThingAdded方法(如果存在) + var method = compTransporter.GetType().GetMethod("Notify_ThingAdded", new[] { typeof(Thing) }); + if (method != null) + { + method.Invoke(compTransporter, new object[] { t }); + Log.Message("[WULA] Successfully called CompTransporter.Notify_ThingAdded"); + } + else + { + Log.Message("[WULA] CompTransporter.Notify_ThingAdded method not found"); + } + } + catch (Exception ex) + { + Log.Warning($"[WULA] Failed to notify CompTransporter: {ex.Message}"); + } + } + else + { + Log.Message("[WULA] No CompTransporter found on this building"); + } + } + + /// + /// 添加到加载列表(模仿原版 MapPortal.AddToTheToLoadList) + /// + public void AddToTheToLoadList(TransferableOneWay t, int count) + { + if (!t.HasAnyThing || count <= 0) + { + return; + } + if (leftToLoad == null) + { + leftToLoad = new List(); + } + TransferableOneWay transferableOneWay = TransferableUtility.TransferableMatching(t.AnyThing, leftToLoad, TransferAsOneMode.PodsOrCaravanPacking); + if (transferableOneWay != null) + { + for (int i = 0; i < t.things.Count; i++) + { + if (!transferableOneWay.things.Contains(t.things[i])) + { + transferableOneWay.things.Add(t.things[i]); + } + } + if (transferableOneWay.CanAdjustBy(count).Accepted) + { + transferableOneWay.AdjustBy(count); + } + } + else + { + TransferableOneWay transferableOneWay2 = new TransferableOneWay(); + leftToLoad.Add(transferableOneWay2); + transferableOneWay2.things.AddRange(t.things); + transferableOneWay2.AdjustTo(count); + } + } + + /// + /// 从加载列表中减去(模仿原版 MapPortal.SubtractFromToLoadList) + /// + public int SubtractFromToLoadList(Thing t, int count) + { + Log.Message($"[WULA] SubtractFromToLoadList called for: {t?.def?.defName} x{count}"); + + if (leftToLoad == null) + { + Log.Message("[WULA] leftToLoad is null, returning 0"); + return 0; + } + + Log.Message($"[WULA] Searching in leftToLoad list with {leftToLoad.Count} entries"); + TransferableOneWay transferableOneWay = TransferableUtility.TransferableMatchingDesperate(t, leftToLoad, TransferAsOneMode.PodsOrCaravanPacking); + + if (transferableOneWay == null) + { + Log.Message($"[WULA] No matching transferable found for {t?.def?.defName}"); + return 0; + } + + Log.Message($"[WULA] Found matching transferable with CountToTransfer: {transferableOneWay.CountToTransfer}"); + + if (transferableOneWay.CountToTransfer <= 0) + { + Log.Message("[WULA] CountToTransfer <= 0, returning 0"); + return 0; + } + + int num = Mathf.Min(count, transferableOneWay.CountToTransfer); + Log.Message($"[WULA] Adjusting transferable by: -{num}"); + + transferableOneWay.AdjustBy(-num); + transferableOneWay.things.Remove(t); + + Log.Message($"[WULA] After adjustment - CountToTransfer: {transferableOneWay.CountToTransfer}, things.Count: {transferableOneWay.things.Count}"); + + if (transferableOneWay.CountToTransfer <= 0) + { + Log.Message("[WULA] Removing transferable from leftToLoad list"); + leftToLoad.Remove(transferableOneWay); + } + + Log.Message($"[WULA] leftToLoad list now has {leftToLoad.Count} entries"); + return num; + } + + /// + /// 取消加载(模仿原版 MapPortal.CancelLoad) + /// + public void CancelLoad(MapPortal portal = null) + { + // 简化版本:只清理leftToLoad列表 + // 原版需要查找MapPortal相关的Lord,但我们不是MapPortal类型 + if (leftToLoad != null) + { + leftToLoad.Clear(); + } + } + + + + #endregion + + #region 穿梭机状态变化处理 + + /// + /// 更新口袋空间中退出点的目标位置(处理穿梭机位置变化) + /// + public void UpdateExitPointTarget() + { + if (pocketMap == null || exit == null) return; + + try + { + // 如果退出点是我们的Building_PocketMapExit类型,更新其目标位置 + if (exit is Building_PocketMapExit pocketExit) + { + // 更新目标地图和位置 + if (this.Spawned) + { + // 穿梭机在地图上,更新目标位置 + if (pocketExit.targetMap != this.Map || pocketExit.targetPos != this.Position) + { + pocketExit.targetMap = this.Map; + pocketExit.targetPos = this.Position; + pocketExit.parentShuttle = this; + Log.Message($"[WULA] Updated pocket map exit target to shuttle location: {this.Map?.uniqueID} at {this.Position}"); + } + } + else + { + // 穿梭机不在地图上(可能在飞行中),记录警告但保持原有目标 + Log.Warning($"[WULA] Shuttle not spawned, pocket map exit target may be outdated. Current target: {pocketExit.targetMap?.uniqueID} at {pocketExit.targetPos}"); + } + } + } + catch (Exception ex) + { + Log.Error($"[WULA] Error updating exit point target: {ex}"); + } + } + + + + /// + /// 重写 SpawnSetup,确保位置变化时更新退出点 + /// + public override void SpawnSetup(Map map, bool respawningAfterLoad) + { + // 保存旧位置信息 + Map oldMap = this.Map; + IntVec3 oldPos = this.Position; + + base.SpawnSetup(map, respawningAfterLoad); + + if (innerContainer == null) + { + innerContainer = new ThingOwner(this, oneStackOnly: false); + } + + // 初始化containerProxy(模仿原版MapPortal) + containerProxy = new PortalContainerProxy + { + portal = this + }; + + // 更新退出点目标(处理穿梭机重新部署的情况) + UpdateExitPointTarget(); + + // 从 ThingDef 中读取 portal 配置 + if (def.HasModExtension()) + { + var portalProps = def.GetModExtension(); + if (portalProps.pocketMapGenerator != null) + { + mapGenerator = portalProps.pocketMapGenerator; + } + if (portalProps.exitDef != null) + { + exitDef = portalProps.exitDef; + } + if (portalProps.pocketMapSize != IntVec2.Zero) + { + pocketMapSize = portalProps.pocketMapSize; + } + allowDirectAccess = portalProps.allowDirectAccess; + } + + // 初始化地图生成器和退出点定义(如果 XML 中没有配置) + if (mapGenerator == null) + { + mapGenerator = DefDatabase.GetNamed("AncientStockpile", false) + ?? DefDatabase.GetNamed("Base_Player", false) + ?? MapGeneratorDefOf.Base_Player; + } + + if (exitDef == null) + { + exitDef = DefDatabase.GetNamed("WULA_PocketMapExit", false) + ?? ThingDefOf.Door; + } + + // 如果位置发生了变化,记录日志 + if (oldMap != null && (oldMap != map || oldPos != this.Position)) + { + Log.Message($"[WULA] Shuttle moved from {oldMap?.uniqueID}:{oldPos} to {map?.uniqueID}:{this.Position}, updating pocket map exit target"); + } + } + + #endregion + } + + /// + /// 口袋空间属性配置类 + /// + public class PocketMapProperties : DefModExtension + { + /// 口袋地图生成器 + public MapGeneratorDef pocketMapGenerator; + + /// 退出点定义 + public ThingDef exitDef; + + /// 口袋地图大小 + public IntVec2 pocketMapSize = new IntVec2(13, 13); + + /// 允许直接访问 + public bool allowDirectAccess = true; + } + + /// + /// MapPortal适配器类,使非MapPortal类型能够使用Dialog_EnterPortal + /// 直接继承MapPortal并委托给Building_ArmedShuttleWithPocket实现 + /// + public class MapPortalAdapter : MapPortal + { + private Building_ArmedShuttleWithPocket shuttleBuilding; + + public MapPortalAdapter(Building_ArmedShuttleWithPocket shuttle) + { + Log.Message($"[WULA] MapPortalAdapter constructor called for shuttle: {shuttle?.def?.defName}"); + shuttleBuilding = shuttle; + + // 确保基础属性正确设置 + this.def = shuttle.def ?? ThingDefOf.Wall; // 提供默认值避免null + this.HitPoints = shuttle.HitPoints; + + // 关键:手动设置Map和Position属性以避免null引用 + if (shuttle.Spawned && shuttle.Map != null) + { + // 手动调用父类的SpawnSetup,但要小心处理 + try + { + base.SpawnSetup(shuttle.Map, false); + Log.Message($"[WULA] SpawnSetup completed for map: {shuttle.Map.uniqueID}"); + } + catch (Exception ex) + { + Log.Warning($"[WULA] SpawnSetup failed, manually setting properties: {ex.Message}"); + // 如果SpawnSetup失败,手动设置关键属性 + } + } + + // 设置基础MapPortal属性 + this.pocketMap = shuttle.PocketMapForPortal; + this.leftToLoad = shuttle.leftToLoad ?? new List(); + + // 确保exit属性被正确设置 + // 注意:由于类型不兼容,暂时设为null,在GetDestinationLocation中处理 + this.exit = null; // 原版PocketMapExit类型与我们的Building_PocketMapExit不兼容 + + Log.Message($"[WULA] Synced pocketMap: {pocketMap?.uniqueID}, leftToLoad count: {leftToLoad?.Count}, exit: {exit != null}"); + + // 使用原版的PortalContainerProxy + try + { + this.containerProxy = new RimWorld.PortalContainerProxy + { + portal = this + }; + Log.Message("[WULA] Created RimWorld.PortalContainerProxy successfully"); + } + catch (Exception ex) + { + Log.Error($"[WULA] Failed to create RimWorld.PortalContainerProxy: {ex}"); + // 使用我们自己的实现作为回退 + Log.Message("[WULA] Using custom PortalContainerProxy as fallback"); + } + Log.Message("[WULA] MapPortalAdapter initialization complete"); + } + + // 委托给shuttleBuilding的关键属性(使用new隐藏基类属性) + // 委托给shuttleBuilding的关键属性(使用new隐藏基类属性) + public new Map Map + { + get + { + // 优先返回shuttleBuilding的Map + if (shuttleBuilding?.Map != null) + { + return shuttleBuilding.Map; + } + + // 如果shuttleBuilding的Map为null,返回基类的Map + if (base.Map != null) + { + return base.Map; + } + + // 最后的回退:返回当前游戏地图(避免null) + Log.Warning("[WULA] Both shuttleBuilding.Map and base.Map are null, using Current.Game.CurrentMap as fallback"); + return Find.CurrentMap ?? Current.Game.Maps?.FirstOrDefault(); + } + } + public new IntVec3 Position => shuttleBuilding?.Position ?? base.Position; + public new bool Spawned => shuttleBuilding?.Spawned ?? base.Spawned; + public new string Label => shuttleBuilding?.Label ?? base.Label; + + // 委托给shuttleBuilding的关键方法(重写虚拟方法) + public override bool IsEnterable(out string reason) + { + return shuttleBuilding.IsEnterable(out reason); + } + + public override Map GetOtherMap() + { + return shuttleBuilding.GetOtherMap(); + } + + public override IntVec3 GetDestinationLocation() + { + return shuttleBuilding.GetDestinationLocation(); + } + + public override void OnEntered(Pawn pawn) + { + shuttleBuilding.OnEntered(pawn); + } + + // 委托给shuttleBuilding的物品管理方法(使用new隐藏基类方法) + public new void Notify_ThingAdded(Thing t) + { + shuttleBuilding.Notify_ThingAdded(t); + } + + public new void AddToTheToLoadList(TransferableOneWay t, int count) + { + shuttleBuilding.AddToTheToLoadList(t, count); + } + + public new int SubtractFromToLoadList(Thing t, int count) + { + return shuttleBuilding.SubtractFromToLoadList(t, count); + } + + public new void CancelLoad() + { + // 调用shuttleBuilding的CancelLoad方法 + shuttleBuilding.CancelLoad(); + } + + // 重写原版MapPortal的关键属性 + public override string EnterString => shuttleBuilding.EnterString; + public override string CancelEnterString => shuttleBuilding.CancelEnterString; + public override string EnteringString => shuttleBuilding.EnteringString; + + // 隐藏LoadInProgress属性,确保Dialog_EnterPortal能正确读取 + public new bool LoadInProgress => shuttleBuilding?.LoadInProgress ?? false; + + // 确保SpawnSetup正确处理 + public override void SpawnSetup(Map map, bool respawningAfterLoad) + { + // 调用基类的SpawnSetup来正确初始化MapPortal的基础设施 + base.SpawnSetup(map, respawningAfterLoad); + + // 同步关键字段 + this.pocketMap = shuttleBuilding?.PocketMapForPortal; + this.leftToLoad = shuttleBuilding?.leftToLoad ?? new List(); + } + + // 重写AddItemsToTransferables,让Dialog_EnterPortal只处理人员 + // 因为物品已经通过装载按钮正确传送到内部空间了 + protected virtual void AddItemsToTransferables() + { + // 不添加任何物品,因为物品传送由装载按钮处理 + // 这样Dialog_EnterPortal只专注于人员传送 + Log.Message("[WULA] AddItemsToTransferables: Skipping items, handled by loading button"); + } + } + + /// + /// 专为Building_ArmedShuttleWithPocket设计的PortalContainerProxy适配器 + /// 模仿原版PortalContainerProxy的行为,但适配非-MapPortal类型 + /// + public class PortalContainerProxy : ThingOwner + { + public Building_ArmedShuttleWithPocket portal; + + public override int Count => 0; + + public override int TryAdd(Thing item, int count, bool canMergeWithExistingStacks = true) + { + if (TryAdd(item, canMergeWithExistingStacks)) + { + return count; + } + return 0; + } + + public override bool TryAdd(Thing item, bool canMergeWithExistingStacks = true) + { + if (portal == null) return false; + + Log.Message($"[WULA] PortalContainerProxy.TryAdd called for: {item?.def?.defName} x{item?.stackCount}"); + + Map otherMap = portal.GetOtherMap(); + IntVec3 destinationLocation = portal.GetDestinationLocation(); + + if (otherMap == null || !destinationLocation.IsValid) + { + Log.Warning("[WULA] PortalContainerProxy: Invalid target map or location, using inner container"); + // 如果目标地图或位置无效,将物品放入内部容器 + return portal.InnerContainer.TryAdd(item, canMergeWithExistingStacks); + } + + // 关键:严格按照原版顺序 - 先通知,再传送 + // 这样能确保leftToLoad列表在物品被传送前就得到更新 + Log.Message($"[WULA] Calling portal.Notify_ThingAdded for: {item?.def?.defName} x{item?.stackCount}"); + portal.Notify_ThingAdded(item); + + // 传送物品到目标地图 + Log.Message($"[WULA] Transporting item to pocket map: {item?.def?.defName}"); + GenDrop.TryDropSpawn(item, destinationLocation, otherMap, ThingPlaceMode.Near, out var _); + + Log.Message($"[WULA] Item transport completed successfully"); + return true; + } + + public override int IndexOf(Thing item) + { + return -1; + } + + public override bool Remove(Thing item) + { + return false; + } + + protected override Thing GetAt(int index) + { + return null; + } + } + + +} \ No newline at end of file diff --git a/Source/WulaFallenEmpire/WULA_Shuttle/Building_PocketMapExit.cs b/Source/WulaFallenEmpire/WULA_Shuttle/Building_PocketMapExit.cs new file mode 100644 index 00000000..0a8235fd --- /dev/null +++ b/Source/WulaFallenEmpire/WULA_Shuttle/Building_PocketMapExit.cs @@ -0,0 +1,146 @@ +using RimWorld; +using Verse; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using UnityEngine; + +namespace WulaFallenEmpire +{ + /// + /// 口袋空间退出点建筑 - 继承自MapPortal以获得完整的双向传送功能 + /// + public class Building_PocketMapExit : MapPortal + { + /// 目标地图 + public Map targetMap; + + /// 目标位置 + public IntVec3 targetPos; + + /// 父穿梭机 + public Building_ArmedShuttleWithPocket parentShuttle; + + public override void ExposeData() + { + base.ExposeData(); + Scribe_References.Look(ref targetMap, "targetMap"); + Scribe_Values.Look(ref targetPos, "targetPos"); + Scribe_References.Look(ref parentShuttle, "parentShuttle"); + } + + /// + /// 重写获取其他地图,返回主地图(模仿原版MapPortal.GetOtherMap) + /// + public override Map GetOtherMap() + { + // 动态更新目标地图,处理穿梭机移动的情况 + UpdateTargetFromParentShuttle(); + return targetMap; + } + + /// + /// 重写获取目标位置,返回主地图上的穿梭机位置(模仿原版MapPortal.GetDestinationLocation) + /// + public override IntVec3 GetDestinationLocation() + { + // 动态更新目标位置,处理穿梭机移动的情况 + UpdateTargetFromParentShuttle(); + return targetPos; + } + + /// + /// 从父穿梭机动态更新目标位置,处理穿梭机移动的情况 + /// + private void UpdateTargetFromParentShuttle() + { + if (parentShuttle != null && parentShuttle.Spawned) + { + // 如果穿梭机还在地图上,更新目标位置 + if (targetMap != parentShuttle.Map || targetPos != parentShuttle.Position) + { + targetMap = parentShuttle.Map; + targetPos = parentShuttle.Position; + Log.Message($"[WULA] Updated exit target to shuttle location: {targetMap?.uniqueID} at {targetPos}"); + } + } + else if (parentShuttle != null && !parentShuttle.Spawned) + { + // 穿梭机不在地图上(可能在飞行中) + // 保持原有目标,但记录警告 + if (this.IsHashIntervalTick(2500)) // 每隔一段时间检查一次 + { + Log.Warning($"[WULA] Parent shuttle is not spawned, exit target may be outdated. Last known: {targetMap?.uniqueID} at {targetPos}"); + } + } + } + + /// + /// 重写是否可进入,检查目标地图是否存在(模仿原版MapPortal.IsEnterable) + /// + public override bool IsEnterable(out string reason) + { + if (targetMap == null) + { + reason = "WULA.PocketSpace.NoTargetMap".Translate(); + return false; + } + reason = ""; + return true; + } + + /// + /// 重写进入事件,处理从口袋空间退出到主地图(模仿原版MapPortal.OnEntered) + /// + public override void OnEntered(Pawn pawn) + { + // 不调用 base.OnEntered,因为我们不需要原版的通知机制 + // 直接处理退出逻辑 + if (targetMap != null && pawn.Spawned) + { + ExitPocketSpace(pawn); + } + } + + /// + /// 重写进入按钮文本 + /// + public override string EnterString => "WULA.PocketSpace.ExitToMainMap".Translate(); + + /// + /// 重写进入按钮图标,使用原版的ViewCave图标 + /// + protected override Texture2D EnterTex => ContentFinder.Get("UI/Commands/ViewCave"); + + /// + /// 单个人员退出口袋空间(简化版本,利用MapPortal功能) + /// + private void ExitPocketSpace(Pawn pawn) + { + if (targetMap == null || !pawn.Spawned) return; + + try + { + // 在目标地图找一个安全位置 + IntVec3 exitPos = CellFinder.RandomClosewalkCellNear(targetPos, targetMap, 3, p => p.Standable(targetMap)); + + // 传送人员 + pawn.DeSpawn(); + GenPlace.TryPlaceThing(pawn, exitPos, targetMap, ThingPlaceMode.Near); + + // 切换到主地图 + if (pawn.IsColonistPlayerControlled) + { + Current.Game.CurrentMap = targetMap; + Find.CameraDriver.JumpToCurrentMapLoc(exitPos); + } + + Messages.Message("WULA.PocketSpace.ExitSuccess".Translate(pawn.LabelShort), MessageTypeDefOf.PositiveEvent); + } + catch (System.Exception ex) + { + Log.Error($"[WULA] Error exiting pocket space: {ex}"); + } + } + } +} \ No newline at end of file diff --git a/Source/WulaFallenEmpire/WULA_Shuttle/GenStep_WulaPocketSpaceSmall.cs b/Source/WulaFallenEmpire/WULA_Shuttle/GenStep_WulaPocketSpaceSmall.cs new file mode 100644 index 00000000..d2c2702b --- /dev/null +++ b/Source/WulaFallenEmpire/WULA_Shuttle/GenStep_WulaPocketSpaceSmall.cs @@ -0,0 +1,253 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using RimWorld; +using Verse; + +namespace WulaFallenEmpire +{ + /// + /// 13x13小型口袋空间生成器 + /// 创建一个简单的13x13空间,边缘是墙,中间是空地,适合作为穿梭机内部空间 + /// + public class GenStep_WulaPocketSpaceSmall : GenStep + { + public override int SeedPart => 928735; // 不同于AncientStockpile的种子 + + public override void Generate(Map map, GenStepParams parms) + { + try + { + Log.Message($"[WULA] Generating WULA pocket space, map size: {map.Size}"); + + // 获取地图边界 + IntVec3 mapSize = map.Size; + + // 生成外围岩石墙壁 + GenerateWalls(map); + + // 生成内部地板 + GenerateFloor(map); + + // 生成一些基础设施(照明等) + GenerateBasicInfrastructure(map); + + Log.Message("[WULA] WULA pocket space generation completed"); + } + catch (Exception ex) + { + Log.Error($"[WULA] Error generating WULA pocket space: {ex}"); + } + } + + /// + /// 生成外围墙壁 + /// + private void GenerateWalls(Map map) + { + IntVec3 mapSize = map.Size; + + // 获取地形和物品定义 + TerrainDef roughTerrain = DefDatabase.GetNamed("Granite_Rough", false) ?? + DefDatabase.GetNamed("Granite_Smooth", false) ?? + DefDatabase.GetNamed("Sandstone_Rough", false); + + ThingDef rockWallDef = DefDatabase.GetNamed("Wall_Rock", false) ?? + DefDatabase.GetNamed("Wall", false); + + // 遍历地图边缘,放置WulaWall + for (int x = 0; x < mapSize.x; x++) + { + for (int z = 0; z < mapSize.z; z++) + { + // 如果是边缘位置,放置WulaWall + if (x == 0 || x == mapSize.x - 1 || z == 0 || z == mapSize.z - 1) + { + IntVec3 pos = new IntVec3(x, 0, z); + + // 设置地形为岩石基础 + if (roughTerrain != null) + { + map.terrainGrid.SetTerrain(pos, roughTerrain); + } + + // 放置WulaWall + ThingDef wallDef = DefDatabase.GetNamed("WulaWall", false); + if (wallDef != null) + { + Thing wall = ThingMaker.MakeThing(wallDef); + wall.SetFaction(null); + GenPlace.TryPlaceThing(wall, pos, map, ThingPlaceMode.Direct); + } + else if (rockWallDef != null) + { + // 如果WulaWall不存在,使用原版岩石墙作为备选 + Thing wall = ThingMaker.MakeThing(rockWallDef); + wall.SetFaction(null); + GenPlace.TryPlaceThing(wall, pos, map, ThingPlaceMode.Direct); + Log.Warning("[WULA] WulaWall not found, using fallback wall"); + } + } + } + } + } + + /// + /// 生成内部地板 + /// + private void GenerateFloor(Map map) + { + IntVec3 mapSize = map.Size; + + // 为内部区域设置WulaFloor + TerrainDef floorDef = DefDatabase.GetNamed("WulaFloor", false); + TerrainDef fallbackFloor = floorDef ?? + DefDatabase.GetNamed("Steel", false) ?? + DefDatabase.GetNamed("MetalTile", false) ?? + DefDatabase.GetNamed("Concrete", false); + + if (floorDef == null) + { + Log.Warning("[WULA] WulaFloor not found, using fallback floor"); + } + + // 清理内部区域并设置正确的地板 + for (int x = 1; x < mapSize.x - 1; x++) + { + for (int z = 1; z < mapSize.z - 1; z++) + { + IntVec3 pos = new IntVec3(x, 0, z); + + // 清理该位置的所有岩石和阻挡物 + ClearCellAndSetFloor(map, pos, fallbackFloor); + } + } + + Log.Message($"[WULA] Set floor for internal area ({mapSize.x-2}x{mapSize.z-2}) to {(floorDef?.defName ?? fallbackFloor?.defName)}"); + } + + /// + /// 清理单元格并设置地板 + /// + private void ClearCellAndSetFloor(Map map, IntVec3 pos, TerrainDef floorDef) + { + if (!pos.InBounds(map)) return; + + try + { + // 获取该位置的所有物品 + List thingsAtPos = pos.GetThingList(map).ToList(); // 创建副本避免修改时出错 + + // 清理所有建筑物和岩石(强力清理,确保地板可以放置) + foreach (Thing thing in thingsAtPos) + { + bool shouldRemove = false; + + // 检查是否为建筑物 + if (thing.def.category == ThingCategory.Building) + { + // 如果是自然岩石 + if (thing.def.building?.isNaturalRock == true) + { + shouldRemove = true; + } + // 或者是岩石相关的建筑 + else if (thing.def.defName.Contains("Rock") || + thing.def.defName.Contains("Slate") || + thing.def.defName.Contains("Granite") || + thing.def.defName.Contains("Sandstone") || + thing.def.defName.Contains("Limestone") || + thing.def.defName.Contains("Marble") || + thing.def.defName.Contains("Quartzite") || + thing.def.defName.Contains("Jade")) + { + shouldRemove = true; + } + // 或者是其他阻挡的建筑物(除了我们的乌拉墙) + else if (!thing.def.defName.Contains("Wula") && thing.def.Fillage == FillCategory.Full) + { + shouldRemove = true; + } + } + + if (shouldRemove) + { + if (Prefs.DevMode) // 只在开发模式下输出详细日志 + { + Log.Message($"[WULA] Removing {thing.def.defName} at {pos} to make space for floor"); + } + thing.Destroy(DestroyMode.Vanish); + } + } + + // 在清理后稍微延迟,再检查一次(确保彻底清理) + thingsAtPos = pos.GetThingList(map).ToList(); + foreach (Thing thing in thingsAtPos) + { + if (thing.def.category == ThingCategory.Building && thing.def.Fillage == FillCategory.Full) + { + Log.Warning($"[WULA] Force removing remaining building {thing.def.defName} at {pos}"); + thing.Destroy(DestroyMode.Vanish); + } + } + + // 设置地板地形 + if (floorDef != null) + { + map.terrainGrid.SetTerrain(pos, floorDef); + if (Prefs.DevMode) + { + Log.Message($"[WULA] Set terrain at {pos} to {floorDef.defName}"); + } + } + } + catch (Exception ex) + { + Log.Error($"[WULA] Error clearing cell at {pos}: {ex}"); + } + } + + /// + /// 生成基础设施 + /// + private void GenerateBasicInfrastructure(Map map) + { + IntVec3 mapSize = map.Size; + IntVec3 center = map.Center; + + // 获取灯具定义 + ThingDef lampDef = DefDatabase.GetNamed("StandingLamp", false) ?? + DefDatabase.GetNamed("TorchLamp", false) ?? + DefDatabase.GetNamed("Campfire", false); + + if (lampDef == null) + { + Log.Warning("[WULA] No lamp definition found, skipping lighting generation"); + return; + } + + // 在四个角落放置照明设备 + var lightPositions = new List + { + new IntVec3(2, 0, 2), // 左下角 + new IntVec3(mapSize.x - 3, 0, 2), // 右下角 + new IntVec3(2, 0, mapSize.z - 3), // 左上角 + new IntVec3(mapSize.x - 3, 0, mapSize.z - 3) // 右上角 + }; + + foreach (IntVec3 pos in lightPositions) + { + if (pos.InBounds(map) && pos.Standable(map)) + { + // 放置立式灯 + Thing lamp = ThingMaker.MakeThing(lampDef); + lamp.SetFaction(null); + GenPlace.TryPlaceThing(lamp, pos, map, ThingPlaceMode.Direct); + } + } + + // 在中心区域留出空间,这里将放置退出点 + // 不在这里放置退出点,因为这会由Building_ArmedShuttleWithPocket来处理 + } + } +} \ No newline at end of file diff --git a/Source/WulaFallenEmpire/WulaFallenEmpire.csproj b/Source/WulaFallenEmpire/WulaFallenEmpire.csproj index 90b1d75a..ebb33551 100644 --- a/Source/WulaFallenEmpire/WulaFallenEmpire.csproj +++ b/Source/WulaFallenEmpire/WulaFallenEmpire.csproj @@ -169,6 +169,9 @@ + + +