# 天灾导弹防御塔 - C# 实现计划 本文档包含构建“天灾导弹防御塔”所需的全部C#类的源代码。这些代码基于 `Rimatomics` 的跨地图打击框架,并与我们自己的 `Projectile_CruiseMissile` 弹头相结合。 --- ## 1. 建筑类: `Building_CatastropheMissileSilo.cs` **路径**: `Source/ArachnaeSwarm/Buildings/Building_CatastropheMissileSilo.cs` **功能**: 这是导弹发射井的建筑本体,负责处理玩家的瞄准指令。它几乎是 `Building_Railgun` 的翻版,但进行了一些重命名和命名空间调整。 ```csharp using System; using System.Collections.Generic; using System.Linq; using RimWorld; using RimWorld.Planet; using UnityEngine; using Verse; using Verse.Sound; namespace ArachnaeSwarm { [StaticConstructorOnStartup] public class Building_CatastropheMissileSilo : Building { public CompPowerTrader powerComp; public CompRefuelable refuelableComp; public Verb_LaunchCatastropheMissile verb; private GlobalTargetInfo longTargetInt; private List selectedSilos; public static readonly Texture2D FireMissionTex = ContentFinder.Get("UI/Commands/Attack", true); public override void SpawnSetup(Map map, bool respawningAfterLoad) { base.SpawnSetup(map, respawningAfterLoad); this.powerComp = base.GetComp(); this.refuelableComp = base.GetComp(); // This is a placeholder, verb will be initialized from the weapon ThingDef this.verb = (Verb_LaunchCatastropheMissile)Activator.CreateInstance(typeof(Verb_LaunchCatastropheMissile)); this.verb.caster = this; } public override void ExposeData() { base.ExposeData(); Scribe_TargetInfo.Look(ref this.longTargetInt, "longTargetInt"); } public override IEnumerable GetGizmos() { foreach (Gizmo c in base.GetGizmos()) { yield return c; } Command_Action launch = new Command_Action { defaultLabel = "CommandFireMission".Translate(), defaultDesc = "CommandFireMissionDesc".Translate(), icon = FireMissionTex, action = new Action(this.StartChoosingDestination) }; if (!CanFire()) { launch.Disable("CannotFire".Translate() + ": " + GetDisabledReason()); } yield return launch; } private bool CanFire() { return powerComp.PowerOn && refuelableComp.HasFuel; } private string GetDisabledReason() { if (!powerComp.PowerOn) return "NoPower".Translate().CapitalizeFirst(); if (!refuelableComp.HasFuel) return "NoFuel".Translate().CapitalizeFirst(); return "Unknown"; } private void StartChoosingDestination() { this.selectedSilos = Find.Selector.SelectedObjects.OfType().ToList(); CameraJumper.TryJump(CameraJumper.GetWorldTarget(this), CameraJumper.MovementMode.Pan); Find.WorldSelector.ClearSelection(); Find.WorldTargeter.BeginTargeting( new Func(this.ChoseWorldTarget), true, // Can target self FireMissionTex, true, () => GenDraw.DrawWorldRadiusRing(this.Map.Tile, this.MaxWorldRange), (GlobalTargetInfo t) => "Select target", null, null, true); } private bool ChoseWorldTarget(GlobalTargetInfo target) { if (!target.IsValid) { Messages.Message("MessageTargetInvalid".Translate(), MessageTypeDefOf.RejectInput, true); return false; } int distance = Find.WorldGrid.TraversalDistanceBetween(this.Map.Tile, target.Tile, true, int.MaxValue); if (distance > this.MaxWorldRange) { Messages.Message("MessageTargetBeyondMaximumRange".Translate(), this, MessageTypeDefOf.RejectInput, true); return false; } MapParent mapParent = target.WorldObject as MapParent; if (mapParent != null && mapParent.HasMap) { Map targetMap = mapParent.Map; var originalMap = base.Map; Action onFinished = () => { if (Current.Game.CurrentMap != originalMap) Current.Game.CurrentMap = originalMap; }; Current.Game.CurrentMap = targetMap; Find.Targeter.BeginTargeting( new TargetingParameters { canTargetLocations = true }, (LocalTargetInfo localTarget) => { foreach (var silo in this.selectedSilos) { silo.FireMission(targetMap.Tile, localTarget, targetMap.uniqueID); } }, null, onFinished, FireMissionTex, true); return true; } else // For non-map targets (caravans, sites) { foreach (var silo in this.selectedSilos) { silo.FireMission(target.Tile, new LocalTargetInfo(target.Cell), -1); } return true; } } public void FireMission(int tile, LocalTargetInfo targ, int mapId) { if (!targ.IsValid) { this.longTargetInt = GlobalTargetInfo.Invalid; return; } Map targetMap = (mapId != -1) ? Find.Maps.FirstOrDefault(m => m.uniqueID == mapId) : null; this.longTargetInt = new GlobalTargetInfo(targ.Cell, targetMap); this.verb.verbProps.defaultProjectile.GetModExtension(); // Ensure verb has properties. this.verb.TryStartCastOn(this.longTargetInt); } public int MaxWorldRange => 99999; // Effectively global range public GlobalTargetInfo LongTarget => longTargetInt; } } ``` --- ## 2. 动作类: `Verb_LaunchCatastropheMissile.cs` **路径**: `Source/ArachnaeSwarm/Verbs/Verb_LaunchCatastropheMissile.cs` **功能**: 定义发射动作。它会创建 `WorldObject_CatastropheMissile` 并将其发射到世界地图。 ```csharp using RimWorld; using RimWorld.Planet; using UnityEngine; using Verse; namespace ArachnaeSwarm { public class Verb_LaunchCatastropheMissile : Verb_Shoot { public override bool CanHitTargetFrom(IntVec3 root, LocalTargetInfo targ) { return true; // Always true for world-map targeting } protected override bool TryCastShot() { Building_CatastropheMissileSilo silo = this.caster as Building_CatastropheMissileSilo; if (silo == null || !silo.LongTarget.IsValid) return false; WorldObject_CatastropheMissile missile = (WorldObject_CatastropheMissile)WorldObjectMaker.MakeWorldObject( DefDatabase.GetNamed("CatastropheMissile_Flying") ); missile.Tile = silo.Map.Tile; missile.destinationTile = silo.LongTarget.Tile; missile.destinationCell = silo.LongTarget.Cell; missile.Projectile = this.verbProps.defaultProjectile; Find.WorldObjects.Add(missile); // Consume fuel silo.refuelableComp.ConsumeFuel(silo.refuelableComp.Props.fuelConsumptionRate); // Visual/Sound effects at launch site MoteMaker.MakeStaticMote(silo.TrueCenter(), silo.Map, ThingDefOf.Mote_ExplosionFlash, 10f); SoundDefOf.RocketLaunch.PlayOneShot(new TargetInfo(silo.Position, silo.Map)); return true; } } } ``` --- ## 3. 飞行物类: `WorldObject_CatastropheMissile.cs` **路径**: `Source/ArachnaeSwarm/World/WorldObject_CatastropheMissile.cs` **功能**: 模拟导弹在世界地图上的飞行,并在抵达时生成 `Projectile_CruiseMissile`。 ```csharp using RimWorld.Planet; using UnityEngine; using Verse; using RimWorld; namespace ArachnaeSwarm { public class WorldObject_CatastropheMissile : WorldObject { public int destinationTile = -1; public IntVec3 destinationCell = IntVec3.Invalid; public ThingDef Projectile; private int initialTile = -1; private float traveledPct; private const float TravelSpeed = 0.0002f; // Faster than sabot public override void ExposeData() { base.ExposeData(); Scribe_Values.Look(ref destinationTile, "destinationTile", 0); Scribe_Values.Look(ref destinationCell, "destinationCell"); Scribe_Defs.Look(ref Projectile, "Projectile"); Scribe_Values.Look(ref initialTile, "initialTile", 0); Scribe_Values.Look(ref traveledPct, "traveledPct", 0f); } public override void PostAdd() { base.PostAdd(); this.initialTile = this.Tile; } private Vector3 StartPos => Find.WorldGrid.GetTileCenter(this.initialTile); private Vector3 EndPos => Find.WorldGrid.GetTileCenter(this.destinationTile); public override Vector3 DrawPos => Vector3.Slerp(StartPos, EndPos, traveledPct); public override void Tick() { base.Tick(); traveledPct += TravelSpeed / GenMath.SphericalDistance(StartPos.normalized, EndPos.normalized); if (traveledPct >= 1f) { Arrived(); } } private void Arrived() { Map targetMap = Current.Game.FindMap(this.destinationTile); if (targetMap != null) { // Target is a loaded map, spawn the projectile to hit it IntVec3 entryCell = Find.WorldGrid.GetRotatedPos(new IntVec2(0, 1), targetMap.info.parent.Rotation).ToIntVec3() * (targetMap.Size.x / 2); entryCell.y = 0; entryCell.z += targetMap.Size.z / 2; Projectile_CruiseMissile missile = (Projectile_CruiseMissile)GenSpawn.Spawn(this.Projectile, entryCell, targetMap, WipeMode.Vanish); missile.Launch(this, this.destinationCell, this.destinationCell, ProjectileHitFlags.IntendedTarget); } else { // Target is not a loaded map (e.g., caravan, site), do direct damage GenExplosion.DoExplosion( this.destinationCell, null, // No map this.Projectile.GetModExtension()?.customExplosionRadius ?? 5f, this.Projectile.GetModExtension()?.customDamageDef ?? DamageDefOf.Bomb, null, // Launcher this.Projectile.GetModExtension()?.customDamageAmount ?? 50 ); } Find.WorldObjects.Remove(this); } } } --- ## 4. XML 定义 以下是实现天灾导弹防御塔所需的所有XML定义。 ### 4.1 建筑定义: `ThingDef_Building_CatastropheMissileSilo.xml` **路径**: `1.6/Defs/ThingDefs_Buildings/ThingDef_Building_CatastropheMissileSilo.xml` **功能**: 定义导弹发射井这个建筑,链接到 `Building_CatastropheMissileSilo` 类,并配置其电力、燃料和内置武器。 ```xml CatastropheMissileSilo 一个重型加固的导弹发射设施,能够将“天灾”级巡航导弹发射到全球任何一个角落。需要大量的电力和化学燃料来运作。 ArachnaeSwarm.Building_CatastropheMissileSilo Things/Building/Security/ShipMissileTurret Graphic_Single (3,3) (3,3) 500 8000 1000 0.5 400 150 10 4
  • CompPowerTrader 500
  • 10 10.0
  • Chemfuel
  • true
  • ArachnaeSwarm.Verb_LaunchCatastropheMissile false Projectile_CatastropheMissile
  • true
  • ShipbuildingBasics
  • ``` ### 4.2 武器/投射物定义: `ThingDef_Projectile_CatastropheMissile.xml` **路径**: `1.6/Defs/ThingDefs_Misc/ThingDef_Projectile_CatastropheMissile.xml` **功能**: 定义导弹本身 (`Projectile_CatastropheMissile`) 和它作为投射物的行为 (`ThingDef` of `Projectile_CruiseMissile`)。这里是配置集束弹头和弹道参数的地方。 ```xml Projectile_CatastropheMissile ArachnaeSwarm.Projectile_CruiseMissile Things/Projectile/Missile Graphic_Single Bomb 200 10 5.9 MortarBomb_Explode
  • Bomb 150 5.9 MortarBomb_Explode true 8 2.9 50 15 Bomb Fragment_Explode 0.05 5 20 0.1 0.2 0.5
  • ``` ### 4.3 飞行物定义: `WorldObjectDef_CatastropheMissile.xml` **路径**: `1.6/Defs/WorldObjectDefs/WorldObjectDef_CatastropheMissile.xml` **功能**: 定义在世界地图上飞行的那个导弹实体。 ```xml CatastropheMissile_Flying ArachnaeSwarm.WorldObject_CatastropheMissile World/WorldObjects/Missile true false false true ```