暂存
This commit is contained in:
468
CatastropheMissileSilo_Implementation_Plan.md
Normal file
468
CatastropheMissileSilo_Implementation_Plan.md
Normal file
@@ -0,0 +1,468 @@
|
||||
# 天灾导弹防御塔 - 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>
|
||||
```
|
||||
Reference in New Issue
Block a user