diff --git a/1.6/1.6/Assemblies/WulaFallenEmpire.dll b/1.6/1.6/Assemblies/WulaFallenEmpire.dll index 988b8b2d..2ee7f0f3 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/ThingDefs_Buildings/WULA_Misc_Buildings.xml b/1.6/1.6/Defs/ThingDefs_Buildings/WULA_Misc_Buildings.xml index d680e89a..dd7994b7 100644 --- a/1.6/1.6/Defs/ThingDefs_Buildings/WULA_Misc_Buildings.xml +++ b/1.6/1.6/Defs/ThingDefs_Buildings/WULA_Misc_Buildings.xml @@ -1,5 +1,61 @@ + + + WULA_AreaTeleportBeacon + + 负责协调殖民地与轨道上的乌拉帝国舰队进行材料输送的信标,空投建筑会优先从信标覆盖区域吸纳资源完成空投。 + Building_OrbitalTradeBeacon + +
  • BuildingsMisc
  • +
    + + Wula/Building/WULA_OrbitalTradeBeacon + Graphic_Single + + (0.3, 0.2, 0.3) + (0,0,-0.1) + + + Building + MinifiedThing + + 75 + 800 + 0.5 + 5 + + MapMeshAndRealTime + true + 0.15 + + 40 + 1 + + + BuildingDestroyed_Metal_Small + + +
  • + 7 + 120 + WULA_Colony_License_LV1_Technology +
  • +
    + false + 14 + WULA_Buildings + 2100 + false + +
  • PlaceWorker_ShowTradeBeaconRadius
  • +
    + Misc2 + +
  • WULA_Colony_License_LV1_Technology
  • +
    +
    + WULA_OrbitalTradeBeacon diff --git a/Source/WulaFallenEmpire/BuildingComp/WULA_PrefabSpawner/CompProperties_PrefabSpawner.cs b/Source/WulaFallenEmpire/BuildingComp/WULA_PrefabSpawner/CompProperties_PrefabSpawner.cs deleted file mode 100644 index d8442400..00000000 --- a/Source/WulaFallenEmpire/BuildingComp/WULA_PrefabSpawner/CompProperties_PrefabSpawner.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Verse; - -namespace WulaFallenEmpire -{ - public class CompProperties_PrefabSpawner : CompProperties - { - public string prefabDefName; - public bool consumesMaterials = true; - - public CompProperties_PrefabSpawner() - { - compClass = typeof(CompPrefabSpawner); - } - } -} \ No newline at end of file diff --git a/Source/WulaFallenEmpire/BuildingComp/WULA_Teleporter/CompMapTeleporter.cs b/Source/WulaFallenEmpire/BuildingComp/WULA_Teleporter/CompMapTeleporter.cs new file mode 100644 index 00000000..e4484265 --- /dev/null +++ b/Source/WulaFallenEmpire/BuildingComp/WULA_Teleporter/CompMapTeleporter.cs @@ -0,0 +1,290 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using RimWorld; +using RimWorld.Planet; +using UnityEngine; +using Verse; +using Verse.AI; +using Verse.Sound; + +namespace WulaFallenEmpire +{ + public class CompMapTeleporter : ThingComp + { + public CompProperties_MapTeleporter Props => (CompProperties_MapTeleporter)props; + + private bool isWarmingUp = false; + private int warmupTicksLeft = 0; + private GlobalTargetInfo target; + + public override void PostExposeData() + { + base.PostExposeData(); + Scribe_Values.Look(ref isWarmingUp, "isWarmingUp", false); + Scribe_Values.Look(ref warmupTicksLeft, "warmupTicksLeft", 0); + Scribe_TargetInfo.Look(ref target, "target"); + } + + public override void CompTick() + { + base.CompTick(); + if (isWarmingUp) + { + warmupTicksLeft--; + if (warmupTicksLeft <= 0) + { + TryTeleport(); + isWarmingUp = false; + } + else if (warmupTicksLeft % 60 == 0) + { + Props.warmupEffecter?.Spawn(parent, parent.Map).Cleanup(); + } + } + } + + public override IEnumerable CompGetGizmosExtra() + { + foreach (var gizmo in base.CompGetGizmosExtra()) + { + yield return gizmo; + } + + if (parent.Faction != Faction.OfPlayer) + yield break; + + if (isWarmingUp) + { + yield return new Command_Action + { + defaultLabel = "WULA_CancelTeleport".Translate(), + defaultDesc = "WULA_CancelTeleportDesc".Translate(), + icon = ContentFinder.Get("UI/Designators/Cancel"), + action = CancelTeleport + }; + } + else + { + string reason = GetDisabledReason(); + Command_Action teleportCmd = new Command_Action + { + defaultLabel = "WULA_InitiateTeleport".Translate(), + defaultDesc = GetDescription(), + icon = ContentFinder.Get("UI/Commands/LaunchShip"), + action = StartTargeting, + disabledReason = reason + }; + + if (!string.IsNullOrEmpty(reason)) + { + teleportCmd.Disable(reason); + } + + yield return teleportCmd; + } + } + + private string GetDisabledReason() + { + if (Props.requiredResearch != null && !Props.requiredResearch.IsFinished) + { + return "WULA_MissingResearch".Translate(Props.requiredResearch.label); + } + + return null; + } + + private string GetDescription() + { + System.Text.StringBuilder sb = new System.Text.StringBuilder(); + sb.Append("WULA_InitiateTeleportDesc".Translate()); + + if (Props.requiredResearch != null) + { + sb.AppendLine().Append("WULA_RequiresResearch".Translate(Props.requiredResearch.label)); + } + + return sb.ToString(); + } + + private void StartTargeting() + { + CameraJumper.TryJump(CameraJumper.GetWorldTarget(parent)); + Find.WorldSelector.ClearSelection(); + Find.WorldTargeter.BeginTargeting(ChoseWorldTarget, true, null, true, null, null); + } + + private bool ChoseWorldTarget(GlobalTargetInfo targetInfo) + { + if (!targetInfo.IsValid) return false; + + this.target = targetInfo; + + MapParent mapParent = Find.WorldObjects.MapParentAt(targetInfo.Tile); + if (mapParent != null && mapParent.HasMap) + { + CameraJumper.TryJump(mapParent.Map.Center, mapParent.Map); + Find.DesignatorManager.Select(new Designator_TeleportArrival(this, mapParent.Map)); + return true; + } + + StartWarmup(); + return true; + } + + public void ConfirmArrival(IntVec3 cell, Map map) + { + this.target = new GlobalTargetInfo(cell, map); + StartWarmup(); + } + + private void StartWarmup() + { + isWarmingUp = true; + warmupTicksLeft = Props.warmupTicks; + Props.warmupSound?.PlayOneShot(parent); + Messages.Message("WULA_TeleportWarmupStarted".Translate(), parent, MessageTypeDefOf.NeutralEvent); + } + + private void CancelTeleport() + { + isWarmingUp = false; + warmupTicksLeft = 0; + Messages.Message("WULA_TeleportCancelled".Translate(), parent, MessageTypeDefOf.NeutralEvent); + } + + private void TryTeleport() + { + if (!target.IsValid) + { + CancelTeleport(); + return; + } + + Map targetMap = target.Map; + IntVec3 targetCell = target.Cell; + + if (targetMap == null) + { + targetMap = GetOrGenerateTargetMap(target.Tile); + if (targetMap == null) + { + Log.Error("Failed to get or generate target map."); + CancelTeleport(); + return; + } + targetCell = targetMap.Center; + } + + TeleportContents(targetMap, targetCell); + } + + private Map GetOrGenerateTargetMap(int tile) + { + MapParent mapParent = Find.WorldObjects.MapParentAt(tile); + + if (mapParent == null) + { + SettleUtility.AddNewHome(tile, Faction.OfPlayer); + mapParent = Find.WorldObjects.MapParentAt(tile); + } + + if (mapParent != null) + { + if (!mapParent.HasMap) + { + IntVec3 mapSize = Find.World.info.initialMapSize; + return GetOrGenerateMapUtility.GetOrGenerateMap(tile, mapSize, null); + } + return mapParent.Map; + } + + return null; + } + + private struct CellData + { + public IntVec3 relativePos; + public TerrainDef terrain; + public List things; + } + + private void TeleportContents(Map targetMap, IntVec3 targetCenter) + { + IEnumerable cells = GenRadial.RadialCellsAround(parent.Position, Props.radius, true); + List dataToTeleport = new List(); + IntVec3 center = parent.Position; + + // 1. 收集数据 + foreach (IntVec3 cell in cells) + { + if (!cell.InBounds(parent.Map)) continue; + + CellData data = new CellData + { + relativePos = cell - center, + terrain = cell.GetTerrain(parent.Map), + things = new List() + }; + + List thingList = parent.Map.thingGrid.ThingsListAt(cell); + for (int i = thingList.Count - 1; i >= 0; i--) + { + Thing t = thingList[i]; + if (t.def.category == ThingCategory.Item || + t.def.category == ThingCategory.Pawn || + t.def.category == ThingCategory.Building) + { + if (t != parent && !t.def.destroyable) continue; + if (t != parent) data.things.Add(t); + } + } + dataToTeleport.Add(data); + } + + // 2. 执行传送 + foreach (CellData data in dataToTeleport) + { + IntVec3 newPos = targetCenter + data.relativePos; + newPos = newPos.ClampInsideMap(targetMap); + + // 2.1 传送地形 + if (data.terrain != null) + { + List targetThings = targetMap.thingGrid.ThingsListAt(newPos); + for (int i = targetThings.Count - 1; i >= 0; i--) + { + if (targetThings[i].def.destroyable) targetThings[i].Destroy(); + } + + targetMap.terrainGrid.SetTerrain(newPos, data.terrain); + parent.Map.terrainGrid.SetTerrain(center + data.relativePos, TerrainDefOf.Soil); + } + + // 2.2 传送物体 + foreach (Thing t in data.things) + { + if (t.Destroyed) continue; + + if (t.Spawned) t.DeSpawn(); + GenSpawn.Spawn(t, newPos, targetMap, t.Rotation); + + if (t is Pawn p) + { + p.jobs.StopAll(); + } + } + } + + // 3. 传送自身 + if (parent.Spawned) parent.DeSpawn(); + GenSpawn.Spawn(parent, targetCenter, targetMap, parent.Rotation); + + // 4. 完成 + CameraJumper.TryJump(targetCenter, targetMap); + Props.teleportSound?.PlayOneShot(new TargetInfo(targetCenter, targetMap, false)); + Messages.Message("WULA_TeleportSuccessful".Translate(), new TargetInfo(targetCenter, targetMap, false), MessageTypeDefOf.PositiveEvent); + } + } +} \ No newline at end of file diff --git a/Source/WulaFallenEmpire/BuildingComp/WULA_Teleporter/CompProperties_MapTeleporter.cs b/Source/WulaFallenEmpire/BuildingComp/WULA_Teleporter/CompProperties_MapTeleporter.cs new file mode 100644 index 00000000..ed62f535 --- /dev/null +++ b/Source/WulaFallenEmpire/BuildingComp/WULA_Teleporter/CompProperties_MapTeleporter.cs @@ -0,0 +1,21 @@ +using RimWorld; +using Verse; + +namespace WulaFallenEmpire +{ + public class CompProperties_MapTeleporter : CompProperties + { + public float radius = 5f; + public int warmupTicks = 120; + public EffecterDef warmupEffecter; + public SoundDef warmupSound; + public SoundDef teleportSound; + + public ResearchProjectDef requiredResearch; + + public CompProperties_MapTeleporter() + { + compClass = typeof(CompMapTeleporter); + } + } +} \ No newline at end of file diff --git a/Source/WulaFallenEmpire/Designator/Designator_TeleportArrival.cs b/Source/WulaFallenEmpire/Designator/Designator_TeleportArrival.cs new file mode 100644 index 00000000..490d3f1c --- /dev/null +++ b/Source/WulaFallenEmpire/Designator/Designator_TeleportArrival.cs @@ -0,0 +1,89 @@ +using System.Collections.Generic; +using RimWorld; +using UnityEngine; +using Verse; + +namespace WulaFallenEmpire +{ + public class Designator_TeleportArrival : Designator + { + private CompMapTeleporter teleporter; + private Map targetMap; + + public override string Label => "WULA_SelectArrivalPoint".Translate(); + public override string Desc => "WULA_SelectArrivalPointDesc".Translate(); + + public Designator_TeleportArrival(CompMapTeleporter teleporter, Map targetMap) + { + this.teleporter = teleporter; + this.targetMap = targetMap; + this.useMouseIcon = true; + this.soundDragSustain = SoundDefOf.Designate_DragStandard; + this.soundDragChanged = SoundDefOf.Designate_DragStandard_Changed; + this.soundSucceeded = SoundDefOf.Designate_PlaceBuilding; + } + + public override AcceptanceReport CanDesignateCell(IntVec3 loc) + { + if (!loc.InBounds(targetMap)) return false; + + // 检查半径区域是否有效 + float radius = teleporter.Props.radius; + foreach (IntVec3 cell in GenRadial.RadialCellsAround(loc, radius, true)) + { + if (!cell.InBounds(targetMap)) return "WULA_OutOfBounds".Translate(); + + // 检查地图边缘 + if (cell.InNoBuildEdgeArea(targetMap)) + { + return "WULA_InNoBuildArea".Translate(); + } + + // 检查迷雾 + if (cell.Fogged(targetMap)) + { + return "WULA_BlockedByFog".Translate(); + } + + // 检查是否有不可覆盖的建筑 + List things = targetMap.thingGrid.ThingsListAt(cell); + foreach (Thing t in things) + { + if (t.def.category == ThingCategory.Building) + { + if (!t.def.destroyable) + { + return "WULA_BlockedByIndestructible".Translate(t.Label); + } + } + } + + // 检查地形是否支持建造 + TerrainDef terrain = cell.GetTerrain(targetMap); + if (terrain.passability == Traversability.Impassable && !terrain.IsWater) + { + return "WULA_TerrainImpassable".Translate(); + } + } + + return true; + } + + public override void DesignateSingleCell(IntVec3 c) + { + teleporter.ConfirmArrival(c, targetMap); + Find.DesignatorManager.Deselect(); + } + + public override void Selected() + { + GenDraw.DrawRadiusRing(UI.MouseCell(), teleporter.Props.radius); + } + + public override void DrawMouseAttachments() + { + base.DrawMouseAttachments(); + GenDraw.DrawRadiusRing(UI.MouseCell(), teleporter.Props.radius); + } + } +} \ No newline at end of file diff --git a/Source/WulaFallenEmpire/EventSystem/Letter_EventChoice.cs b/Source/WulaFallenEmpire/EventSystem/Letter_EventChoice.cs deleted file mode 100644 index cbf39751..00000000 --- a/Source/WulaFallenEmpire/EventSystem/Letter_EventChoice.cs +++ /dev/null @@ -1,94 +0,0 @@ -using RimWorld; -using RimWorld.QuestGen; -using System; -using System.Collections.Generic; -using Verse; - -namespace WulaFallenEmpire -{ - public class Letter_EventChoice : ChoiceLetter - { - // These fields are now inherited from the base Letter class - // public string letterLabel; - // public string letterTitle; - // public string letterText; - public List options; - public new Quest quest; - - public override IEnumerable Choices - { - get - { - if (options.NullOrEmpty()) - { - yield break; - } - - foreach (var optionDef in options) - { - var currentOption = optionDef; - Action choiceAction = delegate - { - if (!currentOption.optionEffects.NullOrEmpty()) - { - foreach (var conditionalEffect in currentOption.optionEffects) - { - string reason; - if (AreConditionsMet(conditionalEffect.conditions, out reason)) - { - conditionalEffect.Execute(null); - } - } - } - if (quest != null && !quest.hidden && !quest.Historical) - { - quest.End(QuestEndOutcome.Success); - } - Find.LetterStack.RemoveLetter(this); - }; - - var diaOption = new DiaOption(currentOption.label.Translate()) - { - action = choiceAction, - resolveTree = true - }; - yield return diaOption; - } - } - } - - public override bool CanDismissWithRightClick => false; - - private bool AreConditionsMet(List conditions, out string reason) - { - reason = ""; - if (conditions.NullOrEmpty()) - { - return true; - } - - foreach (var condition in conditions) - { - if (!condition.IsMet(out string singleReason)) - { - reason = singleReason; - return false; - } - } - return true; - } - - public override void ExposeData() - { - base.ExposeData(); - // Scribe_Values.Look(ref letterLabel, "letterLabel"); // Now uses base.label - // Scribe_Values.Look(ref letterTitle, "letterTitle"); // Now uses base.title - // Scribe_Values.Look(ref letterText, "letterText"); // Now uses base.text - Scribe_Collections.Look(ref options, "options", LookMode.Deep); - if (Scribe.mode != LoadSaveMode.Saving || quest != null) - { - Scribe_References.Look(ref quest, "quest"); - } - } - } -} \ No newline at end of file diff --git a/Source/WulaFallenEmpire/EventSystem/QuestNode_Root_EventLetter.cs b/Source/WulaFallenEmpire/EventSystem/QuestNode_Root_EventLetter.cs deleted file mode 100644 index 5142eff6..00000000 --- a/Source/WulaFallenEmpire/EventSystem/QuestNode_Root_EventLetter.cs +++ /dev/null @@ -1,49 +0,0 @@ -using RimWorld; -using RimWorld.QuestGen; -using System; -using System.Collections.Generic; -using Verse; - -namespace WulaFallenEmpire -{ - public class QuestNode_Root_EventLetter : QuestNode - { - // Fields to be set from the QuestScriptDef XML - public SlateRef letterLabel; - public SlateRef letterTitle; - public SlateRef letterText; - public List