468 lines
17 KiB
Markdown
468 lines
17 KiB
Markdown
# 天灾导弹防御塔 - 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<Building_CatastropheMissileSilo> selectedSilos;
|
||
|
||
public static readonly Texture2D FireMissionTex = ContentFinder<Texture2D>.Get("UI/Commands/Attack", true);
|
||
|
||
public override void SpawnSetup(Map map, bool respawningAfterLoad)
|
||
{
|
||
base.SpawnSetup(map, respawningAfterLoad);
|
||
this.powerComp = base.GetComp<CompPowerTrader>();
|
||
this.refuelableComp = base.GetComp<CompRefuelable>();
|
||
// 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<Gizmo> 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<Building_CatastropheMissileSilo>().ToList();
|
||
CameraJumper.TryJump(CameraJumper.GetWorldTarget(this), CameraJumper.MovementMode.Pan);
|
||
Find.WorldSelector.ClearSelection();
|
||
|
||
Find.WorldTargeter.BeginTargeting(
|
||
new Func<GlobalTargetInfo, bool>(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<CruiseMissileProperties>(); // 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<WorldObjectDef>.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<CruiseMissileProperties>()?.customExplosionRadius ?? 5f,
|
||
this.Projectile.GetModExtension<CruiseMissileProperties>()?.customDamageDef ?? DamageDefOf.Bomb,
|
||
null, // Launcher
|
||
this.Projectile.GetModExtension<CruiseMissileProperties>()?.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
|
||
<?xml version="1.0" encoding="utf-8" ?>
|
||
<Defs>
|
||
|
||
<ThingDef ParentName="BuildingBase">
|
||
<defName>CatastropheMissileSilo</defName>
|
||
<label>天灾导弹发射井</label>
|
||
<description>一个重型加固的导弹发射设施,能够将“天灾”级巡航导弹发射到全球任何一个角落。需要大量的电力和化学燃料来运作。</description>
|
||
<thingClass>ArachnaeSwarm.Building_CatastropheMissileSilo</thingClass>
|
||
<graphicData>
|
||
<texPath>Things/Building/Security/ShipMissileTurret</texPath> <!-- Placeholder texture -->
|
||
<graphicClass>Graphic_Single</graphicClass>
|
||
<drawSize>(3,3)</drawSize>
|
||
</graphicData>
|
||
<size>(3,3)</size>
|
||
<statBases>
|
||
<MaxHitPoints>500</MaxHitPoints>
|
||
<WorkToBuild>8000</WorkToBuild>
|
||
<Mass>1000</Mass>
|
||
<Flammability>0.5</Flammability>
|
||
</statBases>
|
||
<costList>
|
||
<Steel>400</Steel>
|
||
<Plasteel>150</Plasteel>
|
||
<ComponentIndustrial>10</ComponentIndustrial>
|
||
<ComponentSpacer>4</ComponentSpacer>
|
||
</costList>
|
||
<comps>
|
||
<li Class="CompProperties_Power">
|
||
<compClass>CompPowerTrader</compClass>
|
||
<basePowerConsumption>500</basePowerConsumption>
|
||
</li>
|
||
<li Class="CompProperties_Refuelable">
|
||
<fuelConsumptionRate>10</fuelConsumptionRate> <!-- How much fuel per launch -->
|
||
<fuelCapacity>10.0</fuelCapacity>
|
||
<fuelFilter>
|
||
<thingDefs>
|
||
<li>Chemfuel</li>
|
||
</thingDefs>
|
||
</fuelFilter>
|
||
<showFuelGizmo>true</showFuelGizmo>
|
||
</li>
|
||
</comps>
|
||
<verbs>
|
||
<!-- This defines the weapon itself -->
|
||
<li>
|
||
<verbClass>ArachnaeSwarm.Verb_LaunchCatastropheMissile</verbClass>
|
||
<hasStandardCommand>false</hasStandardCommand>
|
||
<defaultProjectile>Projectile_CatastropheMissile</defaultProjectile>
|
||
</li>
|
||
</verbs>
|
||
<building>
|
||
<ai_combatDangerous>true</ai_combatDangerous>
|
||
</building>
|
||
<researchPrerequisites>
|
||
<li>ShipbuildingBasics</li> <!-- Placeholder research -->
|
||
</researchPrerequisites>
|
||
</ThingDef>
|
||
|
||
</Defs>
|
||
```
|
||
|
||
### 4.2 武器/投射物定义: `ThingDef_Projectile_CatastropheMissile.xml`
|
||
|
||
**路径**: `1.6/Defs/ThingDefs_Misc/ThingDef_Projectile_CatastropheMissile.xml`
|
||
|
||
**功能**: 定义导弹本身 (`Projectile_CatastropheMissile`) 和它作为投射物的行为 (`ThingDef` of `Projectile_CruiseMissile`)。这里是配置集束弹头和弹道参数的地方。
|
||
|
||
```xml
|
||
<?xml version="1.0" encoding="utf-8" ?>
|
||
<Defs>
|
||
|
||
<ThingDef ParentName="BaseBullet">
|
||
<defName>Projectile_CatastropheMissile</defName>
|
||
<label>“天灾”巡航导弹</label>
|
||
<thingClass>ArachnaeSwarm.Projectile_CruiseMissile</thingClass>
|
||
<graphicData>
|
||
<texPath>Things/Projectile/Missile</texPath> <!-- Placeholder texture -->
|
||
<graphicClass>Graphic_Single</graphicClass>
|
||
</graphicData>
|
||
<projectile>
|
||
<damageDef>Bomb</damageDef>
|
||
<damageAmountBase>200</damageAmountBase>
|
||
<speed>10</speed> <!-- This speed is relative to the bezier curve, not linear -->
|
||
<explosionRadius>5.9</explosionRadius>
|
||
<soundExplode>MortarBomb_Explode</soundExplode>
|
||
</projectile>
|
||
|
||
<modExtensions>
|
||
<li Class="ArachnaeSwarm.CruiseMissileProperties">
|
||
<!-- Main Explosion -->
|
||
<customDamageDef>Bomb</customDamageDef>
|
||
<customDamageAmount>150</customDamageAmount>
|
||
<customExplosionRadius>5.9</customExplosionRadius>
|
||
<customSoundExplode>MortarBomb_Explode</customSoundExplode>
|
||
|
||
<!-- Sub Explosions (Cluster) -->
|
||
<useSubExplosions>true</useSubExplosions>
|
||
<subExplosionCount>8</subExplosionCount>
|
||
<subExplosionRadius>2.9</subExplosionRadius>
|
||
<subExplosionDamage>50</subExplosionDamage>
|
||
<subExplosionSpread>15</subExplosionSpread>
|
||
<subDamageDef>Bomb</subDamageDef>
|
||
<subSoundExplode>Fragment_Explode</subSoundExplode>
|
||
|
||
<!-- Trajectory Parameters -->
|
||
<bezierArcHeightFactor>0.05</bezierArcHeightFactor>
|
||
<bezierMinArcHeight>5</bezierMinArcHeight>
|
||
<bezierMaxArcHeight>20</bezierMaxArcHeight>
|
||
<bezierHorizontalOffsetFactor>0.1</bezierHorizontalOffsetFactor>
|
||
<bezierSideOffsetFactor>0.2</bezierSideOffsetFactor>
|
||
<bezierRandomOffsetScale>0.5</bezierRandomOffsetScale>
|
||
</li>
|
||
</modExtensions>
|
||
</ThingDef>
|
||
|
||
</Defs>
|
||
```
|
||
|
||
### 4.3 飞行物定义: `WorldObjectDef_CatastropheMissile.xml`
|
||
|
||
**路径**: `1.6/Defs/WorldObjectDefs/WorldObjectDef_CatastropheMissile.xml`
|
||
|
||
**功能**: 定义在世界地图上飞行的那个导弹实体。
|
||
|
||
```xml
|
||
<?xml version="1.0" encoding="utf-8" ?>
|
||
<Defs>
|
||
|
||
<WorldObjectDef>
|
||
<defName>CatastropheMissile_Flying</defName>
|
||
<label>巡航导弹</label>
|
||
<worldObjectClass>ArachnaeSwarm.WorldObject_CatastropheMissile</worldObjectClass>
|
||
<texture>World/WorldObjects/Missile</texture> <!-- Placeholder texture -->
|
||
<expandingIcon>true</expandingIcon>
|
||
<canBeTargeted>false</canBeTargeted>
|
||
<canHavePlayerEnemyRelation>false</canHavePlayerEnemyRelation>
|
||
<useDynamicDrawer>true</useDynamicDrawer>
|
||
</WorldObjectDef>
|
||
|
||
</Defs>
|
||
``` |