This commit is contained in:
2025-09-22 15:40:32 +08:00
parent 0ac7be78ad
commit db7e4393bc
12 changed files with 1701 additions and 0 deletions

View File

@@ -208,6 +208,11 @@
<Compile Include="Building_ARANutrientDispenser.cs" />
<Compile Include="ARAFoodDispenserProperties.cs" />
<Compile Include="Patch_DispenserFoodSearch.cs" />
<Compile Include="Buildings\Building_CatastropheMissileSilo.cs" />
<Compile Include="Verbs\Verb_LaunchCatastropheMissile.cs" />
<Compile Include="World\WorldObject_CatastropheMissile.cs" />
<Compile Include="HarmonyPatches\Patch_ForceTargetable.cs" />
<Compile Include="Comps\CompForceTargetable.cs" />
</ItemGroup>
<ItemGroup>
<Compile Include="Wormhole\Building_WormholePortal_A.cs" />

View File

@@ -0,0 +1,119 @@
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_TurretGun
{
public GlobalTargetInfo longTarget;
public static readonly Texture2D FireMissionTex = ContentFinder<Texture2D>.Get("UI/Commands/Attack", true);
public override void ExposeData()
{
base.ExposeData();
Scribe_TargetInfo.Look(ref this.longTarget, "longTarget");
}
public override IEnumerable<Gizmo> GetGizmos()
{
foreach (Gizmo c in base.GetGizmos())
{
yield return c;
}
// Gizmo to set the long range target
Command_Action setTarget = new Command_Action
{
defaultLabel = "CommandSetGlobalTarget".Translate(),
defaultDesc = "CommandSetGlobalTargetDesc".Translate(),
icon = FireMissionTex,
action = new Action(this.StartChoosingDestination)
};
if (!this.powerComp.PowerOn)
{
setTarget.Disable("NoPower".Translate().CapitalizeFirst());
}
yield return setTarget;
// Gizmo to clear the long range target
if (this.longTarget.IsValid)
{
Command_Action clearTarget = new Command_Action
{
defaultLabel = "CommandClearGlobalTarget".Translate(),
defaultDesc = "CommandClearGlobalTargetDesc".Translate(),
icon = ContentFinder<Texture2D>.Get("UI/Commands/Cancel"),
action = () => { this.longTarget = GlobalTargetInfo.Invalid; }
};
yield return clearTarget;
}
}
private void StartChoosingDestination()
{
CameraJumper.TryJump(CameraJumper.GetWorldTarget(this), CameraJumper.MovementMode.Pan);
Find.WorldSelector.ClearSelection();
Find.WorldTargeter.BeginTargeting(
new Func<GlobalTargetInfo, bool>(this.ChoseWorldTarget),
true,
FireMissionTex,
true,
() => GenDraw.DrawWorldRadiusRing(this.Map.Tile, 99999),
null, null, null, true);
}
private bool ChoseWorldTarget(GlobalTargetInfo target)
{
if (!target.IsValid)
{
Messages.Message("MessageTargetInvalid".Translate(), MessageTypeDefOf.RejectInput, true);
return false;
}
if (target.Map == this.Map)
{
Messages.Message("Cannot target own map for global strike.", MessageTypeDefOf.RejectInput, true);
return false;
}
// The target must be a map parent that has a loaded map.
if (target.WorldObject is MapParent mapParent && mapParent.HasMap)
{
var originalMap = this.Map;
Action onFinished = () => {
if (Current.Game.CurrentMap != originalMap) Current.Game.CurrentMap = originalMap;
};
Current.Game.CurrentMap = mapParent.Map;
Find.Targeter.BeginTargeting(new TargetingParameters { canTargetLocations = true },
(LocalTargetInfo localTarget) => // This is called when the user clicks a cell in the target map
{
this.FireMission(new GlobalTargetInfo(localTarget.Cell, mapParent.Map));
},
null, onFinished, FireMissionTex, true);
return true;
}
else
{
Messages.Message("MessageTargetMustBeMap".Translate(), MessageTypeDefOf.RejectInput, true);
return false;
}
}
public void FireMission(GlobalTargetInfo target)
{
this.longTarget = target;
this.OrderAttack(new LocalTargetInfo(this));
Messages.Message("Global target acquired. Firing sequence initiated.", MessageTypeDefOf.PositiveEvent);
}
}
}

View File

@@ -0,0 +1,18 @@
using Verse;
namespace ArachnaeSwarm
{
public class CompProperties_ForceTargetable : CompProperties
{
public CompProperties_ForceTargetable()
{
this.compClass = typeof(CompForceTargetable);
}
}
public class CompForceTargetable : ThingComp
{
// This component doesn't need any specific logic.
// Its mere presence on a turret is checked by the Harmony patch.
}
}

View File

@@ -0,0 +1,23 @@
using HarmonyLib;
using Verse;
using RimWorld;
namespace ArachnaeSwarm
{
[HarmonyPatch(typeof(Building_TurretGun), "get_CanSetForcedTarget")]
public static class Patch_Building_TurretGun_CanSetForcedTarget
{
public static void Postfix(Building_TurretGun __instance, ref bool __result)
{
if (__result)
{
return;
}
if (__instance.GetComp<CompForceTargetable>() != null && __instance.Faction == Faction.OfPlayer)
{
__result = true;
}
}
}
}

View File

@@ -0,0 +1,78 @@
using RimWorld;
using RimWorld.Planet;
using UnityEngine;
using Verse;
using Verse.Sound;
namespace ArachnaeSwarm
{
public class Verb_LaunchCatastropheMissile : Verb_Shoot
{
public override bool CanHitTargetFrom(IntVec3 root, LocalTargetInfo targ)
{
var silo = this.Caster as Building_CatastropheMissileSilo;
if (silo != null && silo.longTarget.IsValid)
{
return true;
}
return base.CanHitTargetFrom(root, targ);
}
protected override bool TryCastShot()
{
var silo = this.Caster as Building_CatastropheMissileSilo;
if (silo != null && silo.longTarget.IsValid)
{
return this.TryCastGlobalShot(silo);
}
// If no long target, perform a normal local shot
return base.TryCastShot();
}
private bool TryCastGlobalShot(Building_CatastropheMissileSilo silo)
{
var refuelableComp = silo.TryGetComp<CompRefuelable>();
if (refuelableComp != null && !refuelableComp.HasFuel)
{
Messages.Message("NoMissileToLaunch".Translate(), silo, MessageTypeDefOf.RejectInput);
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 = DefDatabase<ThingDef>.GetNamed("Projectile_CatastropheMissile");
Find.WorldObjects.Add(missile);
if(refuelableComp != null)
{
refuelableComp.ConsumeFuel(1);
}
SoundDef.Named("RocketLaunch").PlayOneShot(new TargetInfo(silo.Position, silo.Map));
// Reset target after launch
silo.longTarget = GlobalTargetInfo.Invalid;
// Manually reset cooldown
if (this.burstShotsLeft < this.verbProps.burstShotCount)
{
this.burstShotsLeft = 0;
}
if (this.verbProps.burstShotCount > 0)
{
this.ticksToNextBurstShot = this.verbProps.ticksBetweenBurstShots;
}
this.state = VerbState.Idle;
return true;
}
}
}

View File

@@ -0,0 +1,73 @@
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);
protected override void Tick()
{
base.Tick();
float distance = GenMath.SphericalDistance(StartPos.normalized, EndPos.normalized);
if(distance > 0)
{
traveledPct += TravelSpeed / distance;
}
else
{
traveledPct = 1;
}
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 = CellFinder.RandomEdgeCell(targetMap);
Projectile_CruiseMissile missile = (Projectile_CruiseMissile)GenSpawn.Spawn(this.Projectile, entryCell, targetMap, WipeMode.Vanish);
missile.Launch(null, this.destinationCell, this.destinationCell, ProjectileHitFlags.IntendedTarget);
}
Find.WorldObjects.Remove(this);
}
}
}