diff --git a/1.6/1.6/Assemblies/ArachnaeSwarm.dll b/1.6/1.6/Assemblies/ArachnaeSwarm.dll index 7409190..cc15259 100644 Binary files a/1.6/1.6/Assemblies/ArachnaeSwarm.dll and b/1.6/1.6/Assemblies/ArachnaeSwarm.dll differ diff --git a/1.6/1.6/Defs/Thing_building/ARA_WormholeDefs.xml b/1.6/1.6/Defs/Thing_building/ARA_WormholeDefs.xml new file mode 100644 index 0000000..b48046f --- /dev/null +++ b/1.6/1.6/Defs/Thing_building/ARA_WormholeDefs.xml @@ -0,0 +1,98 @@ + + + + + + ARA_WormholePortal_A + + The primary control unit of a wormhole network. It can launch a secondary portal to a distant location, establishing a stable connection. + ArachnaeSwarm.Building_WormholePortal_A + + Things/Building/Misc/LongRangeMineralScanner + Graphic_Multi + (4,4) + + Damage/Corner + Damage/Corner + Damage/Corner + Damage/Corner + + + Building + Impassable + Normal + Building + 50 + 0.5 + + 250 + 8000 + 0.5 + 100 + + (2,2) + + 100 + 6 + + +
  • + CompPowerTrader + 500 +
  • +
  • +
  • + 500.0 + + +
  • ARA_InsectJelly
  • + + + 虫蜜 + true + true + +
  • + 50 + 100 +
  • +
    + Misc + + false + +
    + + + + ARA_WormholePortal_B + + A remotely deployed secondary portal. It is linked to a primary portal (A) and allows for two-way travel. + ArachnaeSwarm.Building_WormholePortal_B + + Things/Building/Misc/LongRangeMineralScanner + Graphic_Multi + (150, 150, 250) + (4,4) + + Building + Impassable + + 250 + 0.5 + + (2,2) + +
  • + CompPowerTrader + 200 +
  • +
  • + + None + + + + + + \ No newline at end of file diff --git a/Source/ArachnaeSwarm/ArachnaeSwarm.csproj b/Source/ArachnaeSwarm/ArachnaeSwarm.csproj index 6e473cc..b488458 100644 --- a/Source/ArachnaeSwarm/ArachnaeSwarm.csproj +++ b/Source/ArachnaeSwarm/ArachnaeSwarm.csproj @@ -205,6 +205,15 @@ + + + + + + + + + diff --git a/Source/ArachnaeSwarm/HarmonyPatches/Patch_Game_DeinitAndRemoveMap.cs b/Source/ArachnaeSwarm/HarmonyPatches/Patch_Game_DeinitAndRemoveMap.cs new file mode 100644 index 0000000..0cbbb94 --- /dev/null +++ b/Source/ArachnaeSwarm/HarmonyPatches/Patch_Game_DeinitAndRemoveMap.cs @@ -0,0 +1,23 @@ +using HarmonyLib; +using Verse; +using System.Linq; + +namespace ArachnaeSwarm +{ + [HarmonyPatch(typeof(Game), "DeinitAndRemoveMap")] + public static class Patch_Game_DeinitAndRemoveMap + { + [HarmonyPrefix] + public static bool Prefix(Map map) + { + // 如果地图上存在B端传送门,则阻止该地图被销毁 + if (map != null && map.listerBuildings.AllBuildingsColonistOfClass().Any()) + { + return false; // 返回 false, 阻止原版方法执行 + } + + // 否则,正常执行原版方法 + return true; + } + } +} \ No newline at end of file diff --git a/Source/ArachnaeSwarm/HarmonyPatches/Patch_SettlementDefeatUtility_CheckDefeated.cs b/Source/ArachnaeSwarm/HarmonyPatches/Patch_SettlementDefeatUtility_CheckDefeated.cs new file mode 100644 index 0000000..ebc7951 --- /dev/null +++ b/Source/ArachnaeSwarm/HarmonyPatches/Patch_SettlementDefeatUtility_CheckDefeated.cs @@ -0,0 +1,25 @@ +using HarmonyLib; +using RimWorld; +using RimWorld.Planet; +using Verse; +using System.Linq; + +namespace ArachnaeSwarm +{ + [HarmonyPatch(typeof(SettlementDefeatUtility), "CheckDefeated")] + public static class Patch_SettlementDefeatUtility_CheckDefeated + { + [HarmonyPrefix] + public static bool Prefix(Settlement factionBase) + { + // 如果目标没有地图,或者地图上存在B端传送门,则跳过原版的失败检查 + if (!factionBase.HasMap || factionBase.Map.listerBuildings.AllBuildingsColonistOfClass().Any()) + { + return false; // 返回 false, 阻止原版方法执行 + } + + // 否则,正常执行原版方法 + return true; + } + } +} \ No newline at end of file diff --git a/Source/ArachnaeSwarm/HarmonyPatches/Patch_Site_ShouldRemoveMapNow.cs b/Source/ArachnaeSwarm/HarmonyPatches/Patch_Site_ShouldRemoveMapNow.cs new file mode 100644 index 0000000..fdf8f28 --- /dev/null +++ b/Source/ArachnaeSwarm/HarmonyPatches/Patch_Site_ShouldRemoveMapNow.cs @@ -0,0 +1,27 @@ +using HarmonyLib; +using RimWorld.Planet; +using Verse; +using System.Linq; + +namespace ArachnaeSwarm +{ + [HarmonyPatch(typeof(Site), "ShouldRemoveMapNow")] + public static class Patch_Site_ShouldRemoveMapNow + { + [HarmonyPostfix] + public static void Postfix(MapParent __instance, ref bool __result) + { + // 如果原方法已经决定不移除,我们就不需要干预 + if (!__result) + { + return; + } + + // 如果地图上存在B端传送门,则推翻原方法的决定,阻止地图被移除 + if (__instance.HasMap && __instance.Map.listerBuildings.AllBuildingsColonistOfClass().Any()) + { + __result = false; + } + } + } +} \ No newline at end of file diff --git a/Source/ArachnaeSwarm/Wormhole/Building_WormholePortal_A.cs b/Source/ArachnaeSwarm/Wormhole/Building_WormholePortal_A.cs new file mode 100644 index 0000000..4a51ba4 --- /dev/null +++ b/Source/ArachnaeSwarm/Wormhole/Building_WormholePortal_A.cs @@ -0,0 +1,108 @@ +using RimWorld; +using RimWorld.Planet; +using System.Collections.Generic; +using Verse; + +namespace ArachnaeSwarm +{ + public enum WormholePortalStatus + { + Idle, + Linked + } + + public class Building_WormholePortal_A : Building + { + private Building_WormholePortal_B linkedPortalB; + public WormholePortalStatus status = WormholePortalStatus.Idle; + + private CompRefuelable refuelableComp; + + public Building_WormholePortal_B LinkedPortal => linkedPortalB; + + public override void ExposeData() + { + base.ExposeData(); + Scribe_References.Look(ref linkedPortalB, "linkedPortalB"); + Scribe_Values.Look(ref status, "status", WormholePortalStatus.Idle); + } + + public override void SpawnSetup(Map map, bool respawningAfterLoad) + { + base.SpawnSetup(map, respawningAfterLoad); + refuelableComp = GetComp(); + } + + public override void DeSpawn(DestroyMode mode = DestroyMode.Vanish) + { + if (linkedPortalB != null && !linkedPortalB.Destroyed) + { + linkedPortalB.Notify_A_Destroyed(); + linkedPortalB.Destroy(DestroyMode.Vanish); + } + base.DeSpawn(mode); + } + + public void SetLinkedPortal(Building_WormholePortal_B portalB) + { + if (portalB == null) + { + Notify_B_Destroyed(); + return; + } + linkedPortalB = portalB; + status = WormholePortalStatus.Linked; + Messages.Message("WormholePortalLinked".Translate(this.Label, portalB.Map.Parent.LabelCap), this, MessageTypeDefOf.PositiveEvent); + } + + public void Notify_B_Destroyed() + { + linkedPortalB = null; + status = WormholePortalStatus.Idle; + Messages.Message("WormholePortalB_Destroyed".Translate(this.Label), this, MessageTypeDefOf.NegativeEvent); + } + + public override IEnumerable GetGizmos() + { + foreach (Gizmo g in base.GetGizmos()) + { + yield return g; + } + + if (status == WormholePortalStatus.Linked) + { + if (linkedPortalB == null || linkedPortalB.Destroyed) + { + // 安全检查,处理幽灵链接 + Notify_B_Destroyed(); + yield break; + } + + Command_Action enterCommand = new Command_Action + { + defaultLabel = "EnterWormhole".Translate(), + defaultDesc = "EnterWormholeDesc".Translate(), + icon = ContentFinder.Get("UI/Commands/EnterCave"), + action = BeginTeleportation + }; + yield return enterCommand; + } + } + + public override string GetInspectString() + { + string text = base.GetInspectString(); + text += "\n" + "Status".Translate() + ": " + status.ToString().Translate(); + if (linkedPortalB != null && !linkedPortalB.Destroyed) + { + text += "\n" + "LinkedTo".Translate() + ": " + linkedPortalB.Map.Parent.LabelCap; + } + return text; + } + + private void BeginTeleportation() + { + Find.WindowStack.Add(new Dialog_WormholeTransfer(this, this.LinkedPortal)); + } + } +} \ No newline at end of file diff --git a/Source/ArachnaeSwarm/Wormhole/Building_WormholePortal_B.cs b/Source/ArachnaeSwarm/Wormhole/Building_WormholePortal_B.cs new file mode 100644 index 0000000..bee7f92 --- /dev/null +++ b/Source/ArachnaeSwarm/Wormhole/Building_WormholePortal_B.cs @@ -0,0 +1,79 @@ +using RimWorld.Planet; +using System.Collections.Generic; +using Verse; + +namespace ArachnaeSwarm +{ + public class Building_WormholePortal_B : Building + { + private Building_WormholePortal_A linkedPortalA; + + public Building_WormholePortal_A LinkedPortal => linkedPortalA; + + public override void ExposeData() + { + base.ExposeData(); + Scribe_References.Look(ref linkedPortalA, "linkedPortalA"); + } + + public override void DeSpawn(DestroyMode mode = DestroyMode.Vanish) + { + // 如果B被摧毁,通知A + if (linkedPortalA != null && !linkedPortalA.Destroyed) + { + linkedPortalA.Notify_B_Destroyed(); + } + base.DeSpawn(mode); + } + + // 这个方法在A端被摧毁时由A端调用,避免B端重复通知 + public void Notify_A_Destroyed() + { + linkedPortalA = null; + } + + public void SetLinkedPortal(Building_WormholePortal_A portalA) + { + linkedPortalA = portalA; + } + + public override IEnumerable GetGizmos() + { + foreach (Gizmo g in base.GetGizmos()) + { + yield return g; + } + + if (linkedPortalA != null && !linkedPortalA.Destroyed) + { + Command_Action enterCommand = new Command_Action + { + defaultLabel = "EnterWormhole".Translate(), + defaultDesc = "EnterWormholeDesc".Translate(), + icon = ContentFinder.Get("UI/Commands/EnterCave"), + action = BeginTeleportation + }; + yield return enterCommand; + } + } + + public override string GetInspectString() + { + string text = base.GetInspectString(); + if (linkedPortalA != null && !linkedPortalA.Destroyed) + { + text += "\n" + "LinkedTo".Translate() + ": " + linkedPortalA.Map.Parent.LabelCap; + } + else + { + text += "\n" + "ConnectionLost".Translate(); + } + return text; + } + + private void BeginTeleportation() + { + Find.WindowStack.Add(new Dialog_WormholeTransfer(this, this.LinkedPortal)); + } + } +} \ No newline at end of file diff --git a/Source/ArachnaeSwarm/Wormhole/CompLaunchableWormhole.cs b/Source/ArachnaeSwarm/Wormhole/CompLaunchableWormhole.cs new file mode 100644 index 0000000..9b25342 --- /dev/null +++ b/Source/ArachnaeSwarm/Wormhole/CompLaunchableWormhole.cs @@ -0,0 +1,115 @@ +using System.Collections.Generic; +using Verse; +using RimWorld; +using RimWorld.Planet; +using UnityEngine; + +namespace ArachnaeSwarm +{ + public class CompLaunchableWormhole : ThingComp + { + private CompRefuelableNutrition refuelableComp; + + public Building_WormholePortal_A PortalA => this.parent as Building_WormholePortal_A; + public CompProperties_LaunchableWormhole Props => (CompProperties_LaunchableWormhole)props; + + public override void Initialize(CompProperties props) + { + base.Initialize(props); + this.refuelableComp = this.parent.GetComp(); + } + + public override IEnumerable CompGetGizmosExtra() + { + if (PortalA?.status == WormholePortalStatus.Linked) + { + yield break; + } + + Command_Action launchCommand = new Command_Action(); + launchCommand.defaultLabel = "CommandDeployWormholePortalB".Translate(); + launchCommand.defaultDesc = "CommandDeployWormholePortalBDesc".Translate(); + launchCommand.icon = ContentFinder.Get("UI/Commands/LaunchShip"); + + if (refuelableComp.Fuel < this.Props.fuelNeededToLaunch) + { + launchCommand.Disable("NotEnoughFuel".Translate()); + } + + launchCommand.action = delegate + { + StartChoosingDestination(); + }; + yield return launchCommand; + } + + public void StartChoosingDestination() + { + CameraJumper.TryJump(CameraJumper.GetWorldTarget(this.parent)); + Find.WorldSelector.ClearSelection(); + int tile = this.parent.Map.Tile; + Find.WorldTargeter.BeginTargeting(ChoseWorldTarget, true, CompLaunchable.TargeterMouseAttachment, true, delegate + { + GenDraw.DrawWorldRadiusRing(tile, this.Props.maxLaunchDistance); + }, (GlobalTargetInfo t) => "Select target tile"); + } + + private bool ChoseWorldTarget(GlobalTargetInfo t) + { + if (!t.IsValid) + { + Messages.Message("MessageTransportPodsDestinationIsInvalid".Translate(), MessageTypeDefOf.RejectInput); + return false; + } + if (Find.World.Impassable(t.Tile)) + { + Messages.Message("MessageTransportPodsDestinationIsImpassable".Translate(), MessageTypeDefOf.RejectInput); + return false; + } + + MapParent mapParent = Find.World.worldObjects.MapParentAt(t.Tile); + if (mapParent?.HasMap ?? false) + { + Deploy(mapParent, t); + } + else + { + LongEventHandler.QueueLongEvent(delegate + { + var newMap = GetOrGenerateMapUtility.GetOrGenerateMap(t.Tile, WorldObjectDefOf.Camp); + Deploy(newMap.Parent, t); + }, "GeneratingMap", doAsynchronously: false, null); + } + + return true; + } + + + + private void Deploy(MapParent mapParent, GlobalTargetInfo t) + { + refuelableComp.ConsumeFuel(this.Props.fuelNeededToLaunch); + EffecterDefOf.Skip_Entry.Spawn(this.parent.Position, this.parent.Map); + + Building_WormholePortal_B portalB = (Building_WormholePortal_B)ThingMaker.MakeThing(ThingDef.Named("ARA_WormholePortal_B")); + IntVec3 cell = DropCellFinder.RandomDropSpot(mapParent.Map); + GenSpawn.Spawn(portalB, cell, mapParent.Map, WipeMode.Vanish); + + EffecterDefOf.Skip_Exit.Spawn(cell, mapParent.Map); + + PortalA.SetLinkedPortal(portalB); + portalB.SetLinkedPortal(PortalA); + } + } + + public class CompProperties_LaunchableWormhole : CompProperties + { + public float fuelNeededToLaunch; + public int maxLaunchDistance; + + public CompProperties_LaunchableWormhole() + { + this.compClass = typeof(CompLaunchableWormhole); + } + } +} \ No newline at end of file diff --git a/Source/ArachnaeSwarm/Wormhole/Dialog_WormholeTransfer.cs b/Source/ArachnaeSwarm/Wormhole/Dialog_WormholeTransfer.cs new file mode 100644 index 0000000..1eeeba8 --- /dev/null +++ b/Source/ArachnaeSwarm/Wormhole/Dialog_WormholeTransfer.cs @@ -0,0 +1,214 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using RimWorld; +using RimWorld.Planet; +using UnityEngine; +using Verse; +using Verse.Sound; + +namespace ArachnaeSwarm +{ + public class Dialog_WormholeTransfer : Window + { + private enum Tab + { + Pawns, + Items + } + + private const float TitleRectHeight = 35f; + private const float BottomAreaHeight = 55f; + private readonly Vector2 BottomButtonSize = new Vector2(160f, 40f); + + private Building sourcePortal; // 通用源传送门 + private Building destinationPortal; // 通用目标传送门 + + private List transferables; + private TransferableOneWayWidget pawnsTransfer; + private TransferableOneWayWidget itemsTransfer; + private Tab tab; + + private static List tabsList = new List(); + + public override Vector2 InitialSize => new Vector2(1024f, UI.screenHeight); + protected override float Margin => 0f; + + public Dialog_WormholeTransfer(Building sourcePortal, Building destinationPortal) + { + this.sourcePortal = sourcePortal; + this.destinationPortal = destinationPortal; + forcePause = true; + absorbInputAroundWindow = true; + } + + public override void PostOpen() + { + base.PostOpen(); + CalculateAndRecacheTransferables(); + } + + public override void DoWindowContents(Rect inRect) + { + Rect rect = new Rect(0f, 0f, inRect.width, TitleRectHeight); + Text.Font = GameFont.Medium; + Text.Anchor = TextAnchor.MiddleCenter; + Widgets.Label(rect, "EnterWormhole".Translate()); + Text.Font = GameFont.Small; + Text.Anchor = TextAnchor.UpperLeft; + + tabsList.Clear(); + tabsList.Add(new TabRecord("PawnsTab".Translate(), () => tab = Tab.Pawns, tab == Tab.Pawns)); + tabsList.Add(new TabRecord("ItemsTab".Translate(), () => tab = Tab.Items, tab == Tab.Items)); + + inRect.yMin += 67f; + Widgets.DrawMenuSection(inRect); + TabDrawer.DrawTabs(inRect, tabsList); + inRect = inRect.ContractedBy(17f); + + Widgets.BeginGroup(inRect); + Rect rect2 = inRect.AtZero(); + DoBottomButtons(rect2); + Rect inRect2 = rect2; + inRect2.yMax -= 76f; + + bool anythingChanged = false; + switch (tab) + { + case Tab.Pawns: + pawnsTransfer.OnGUI(inRect2, out anythingChanged); + break; + case Tab.Items: + itemsTransfer.OnGUI(inRect2, out anythingChanged); + break; + } + if (anythingChanged) + { + // 可以添加一些计数或质量更新的逻辑 + } + Widgets.EndGroup(); + } + + private void DoBottomButtons(Rect rect) + { + float buttonY = rect.height - BottomAreaHeight + 17; + + if (Widgets.ButtonText(new Rect(rect.width / 2f - BottomButtonSize.x / 2f, buttonY, BottomButtonSize.x, BottomButtonSize.y), "ResetButton".Translate())) + { + SoundDefOf.Tick_Low.PlayOneShotOnCamera(); + CalculateAndRecacheTransferables(); + } + if (Widgets.ButtonText(new Rect(0f, buttonY, BottomButtonSize.x, BottomButtonSize.y), "CancelButton".Translate())) + { + Close(); + } + if (Widgets.ButtonText(new Rect(rect.width - BottomButtonSize.x, buttonY, BottomButtonSize.x, BottomButtonSize.y), "AcceptButton".Translate())) + { + if (TryAccept()) + { + SoundDefOf.Tick_High.PlayOneShotOnCamera(); + Close(doCloseSound: false); + } + } + } + + private bool TryAccept() + { + List toTransfer = transferables.Where(x => x.CountToTransfer > 0).ToList(); + if (!toTransfer.Any()) + { + Messages.Message("NothingToTransfer".Translate(), MessageTypeDefOf.RejectInput); + return false; + } + + foreach (var trans in toTransfer) + { + // 传送逻辑 + var things = trans.things.Take(trans.CountToTransfer).ToList(); + foreach (var thing in things) + { + Pawn pawn = thing as Pawn; + if (pawn != null) + { + pawn.DeSpawn(); + GenSpawn.Spawn(pawn, CellFinder.RandomClosewalkCellNear(destinationPortal.Position, destinationPortal.Map, 5), destinationPortal.Map, WipeMode.Vanish); + } + else + { + thing.DeSpawn(); + GenSpawn.Spawn(thing, CellFinder.RandomClosewalkCellNear(destinationPortal.Position, destinationPortal.Map, 5), destinationPortal.Map, WipeMode.Vanish); + } + } + } + + // 切换视角 + if (toTransfer.Any(x => x.ThingDef.category == ThingCategory.Pawn)) + { + var firstPawn = toTransfer.First(x => x.ThingDef.category == ThingCategory.Pawn).AnyThing as Pawn; + CameraJumper.TryJump(new GlobalTargetInfo(destinationPortal.Position, destinationPortal.Map)); + if (firstPawn != null) + { + CameraJumper.TrySelect(firstPawn); + } + } + + Messages.Message("WormholeTransferComplete".Translate(), MessageTypeDefOf.PositiveEvent); + return true; + } + + private void CalculateAndRecacheTransferables() + { + transferables = new List(); + AddPawnsToTransferables(); + AddItemsToTransferables(); + + pawnsTransfer = new TransferableOneWayWidget(transferables.Where(x => x.ThingDef.category == ThingCategory.Pawn), null, null, "TransferableCount".Translate(), + drawMass: true, + ignorePawnInventoryMass: IgnorePawnsInventoryMode.Ignore, + includePawnsMassInMassUsage: true, + availableMassGetter: () => float.MaxValue, // 无质量限制 + extraHeaderSpace: 0f, + ignoreSpawnedCorpseGearAndInventoryMass: false, + tile: sourcePortal.Map.Tile, + drawMarketValue: true, + drawEquippedWeapon: true); + + itemsTransfer = new TransferableOneWayWidget(transferables.Where(x => x.ThingDef.category != ThingCategory.Pawn), null, null, "TransferableCount".Translate(), + drawMass: true, + ignorePawnInventoryMass: IgnorePawnsInventoryMode.Ignore, + includePawnsMassInMassUsage: true, + availableMassGetter: () => float.MaxValue, + extraHeaderSpace: 0f, + ignoreSpawnedCorpseGearAndInventoryMass: false, + tile: sourcePortal.Map.Tile, + drawMarketValue: true); + } + + private void AddToTransferables(Thing t) + { + var transferable = TransferableUtility.TransferableMatching(t, transferables, TransferAsOneMode.PodsOrCaravanPacking); + if (transferable == null) + { + transferable = new TransferableOneWay(); + transferables.Add(transferable); + } + transferable.things.Add(t); + } + + private void AddPawnsToTransferables() + { + foreach (Pawn p in sourcePortal.Map.mapPawns.AllPawnsSpawned.Where(p => p.Faction == Faction.OfPlayer)) + { + AddToTransferables(p); + } + } + + private void AddItemsToTransferables() + { + foreach (Thing t in sourcePortal.Map.listerThings.AllThings.Where(t => t.def.category == ThingCategory.Item && t.def.EverHaulable && t.Position.IsValid && !t.Position.Fogged(t.Map))) + { + AddToTransferables(t); + } + } + } +} \ No newline at end of file