This commit is contained in:
2025-09-22 21:50:05 +08:00
parent 8699c4c63e
commit 268ea3c681
7 changed files with 262 additions and 264 deletions

View File

@@ -209,7 +209,6 @@
<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" />

View File

@@ -2,7 +2,6 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using UnityEngine;
using Verse;
using Verse.AI;
@@ -15,18 +14,12 @@ namespace ArachnaeSwarm
[StaticConstructorOnStartup]
public class Building_CatastropheMissileSilo : Building_TurretGun
{
// Reflection to access the private 'holdFire' field in the base class, as there is no public accessor.
private static readonly FieldInfo holdFireField =
typeof(Building_TurretGun).GetField("holdFire", BindingFlags.NonPublic | BindingFlags.Instance);
public GlobalTargetInfo longTarget;
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 is the definitive fix for the default target issue on spawn.
// It runs only once when the building is first created, not on game load.
if (!respawningAfterLoad)
{
this.longTarget = GlobalTargetInfo.Invalid;
@@ -41,49 +34,36 @@ namespace ArachnaeSwarm
protected override void Tick()
{
// Always run base.Tick() first. This handles all local targeting, aiming, warmup,
// cooldowns, and calling the Verb for local shots. This is the key to fixing the bug.
// Base tick handles all local targeting, cooldowns, and fuel consumption via XML.
base.Tick();
// --- Mutual Exclusivity: Prioritize Local Target ---
// If a local target is set (either by player or automatically), clear the remote target.
// If a local target is active, prevent remote targeting.
if (this.forcedTarget.IsValid && this.longTarget.IsValid)
{
this.longTarget = GlobalTargetInfo.Invalid;
}
// --- Debug Logging (every 120 ticks, approx 2 seconds) ---
if (Find.TickManager.TicksGame % 120 == 0)
{
bool isHoldingFireForLog = (bool)holdFireField.GetValue(this);
string reason;
CanFireGlobal(out reason); // To get the reason string
Log.Message($"[Silo Debug] Tick: {Find.TickManager.TicksGame}\n" +
$"- Cooldown: {this.burstCooldownTicksLeft}\n" +
$"- Active (Power): {base.Active}\n" +
$"- HoldFire: {isHoldingFireForLog}\n" +
$"- CanFireGlobal (Fuel?): {CanFireGlobal(out reason)} (Reason: {reason})\n" +
$"- Local Target: {this.forcedTarget.ToString()}\n" +
$"- Remote Target: {this.longTarget.ToString()}");
}
// --- Remote Firing Logic ---
bool isHoldingFire = (bool)holdFireField.GetValue(this);
if (this.longTarget.IsValid && !isHoldingFire && this.burstCooldownTicksLeft <= 0 && base.Active && CanFireGlobal(out _))
// If a remote target is set and the turret is ready, fire.
// The base.Tick() cooldown handling prevents this from running if a local shot was just fired.
if (this.longTarget.IsValid && this.burstCooldownTicksLeft <= 0 && base.Active && CanFireGlobal(out _))
{
if (!this.forcedTarget.IsValid)
{
this.FireMission(this.longTarget);
}
this.FireMission(this.longTarget);
}
}
public override string GetInspectString()
{
StringBuilder sb = new StringBuilder(base.GetInspectString());
if (burstCooldownTicksLeft > 0)
{
if (sb.Length > 0) sb.AppendLine();
sb.Append("Cooldown".Translate() + ": " + this.burstCooldownTicksLeft.ToStringTicksToPeriod());
}
if (this.longTarget.IsValid)
{
sb.AppendLine();
if (sb.Length > 0) sb.AppendLine();
sb.Append("RemoteTargetSet".Translate(this.longTarget.Label));
}
return sb.ToString();
@@ -91,13 +71,11 @@ namespace ArachnaeSwarm
public override IEnumerable<Gizmo> GetGizmos()
{
// Yield base gizmos first, which includes "Set forced target" and other standard buttons.
foreach (var g in base.GetGizmos())
{
yield return g;
}
// Then add our custom "Global Strike" gizmo
Command_Action fireGlobal = new Command_Action
{
defaultLabel = "CommandFireGlobal".Translate(),
@@ -110,14 +88,12 @@ namespace ArachnaeSwarm
{
fireGlobal.Disable(reason);
}
// Global Strike button is disabled if a forced target is already set, to ensure mutual exclusivity.
if (this.forcedTarget.IsValid)
{
fireGlobal.Disable("LocalTargetForced".Translate());
}
yield return fireGlobal;
// Add a specific button to clear the remote target
if (this.longTarget.IsValid)
{
Command_Action clearRemote = new Command_Action
@@ -147,7 +123,6 @@ namespace ArachnaeSwarm
missile.Projectile = DefDatabase<ThingDef>.GetNamed("Projectile_CatastropheMissile");
Find.WorldObjects.Add(missile);
// Rimatomics style: dummy projectile flies off-map in the direction the turret is facing.
Vector3 shellDirection = Vector3.forward.RotatedBy(this.top.CurRotation);
IntVec3 outcell = (this.DrawPos + shellDirection * 500f).ToIntVec3();
@@ -161,14 +136,13 @@ namespace ArachnaeSwarm
}
SoundDef.Named("RocketLaunch").PlayOneShot(new TargetInfo(this.Position, this.Map));
// Manually reset cooldown.
this.BurstComplete();
}
private bool CanFireGlobal(out string reason)
{
var refuelableComp = this.TryGetComp<CompRefuelable>();
if (refuelableComp != null && !this.refuelableComp.HasFuel)
if (refuelableComp != null && !refuelableComp.HasFuel)
{
reason = "NoFuel".Translate().CapitalizeFirst();
return false;
@@ -214,12 +188,11 @@ namespace ArachnaeSwarm
Find.Targeter.BeginTargeting(new TargetingParameters
{
canTargetLocations = true,
canTargetPawns = true, // Allow targeting pawns
canTargetBuildings = true // Allow targeting buildings
canTargetPawns = true,
canTargetBuildings = true
},
(LocalTargetInfo localTarget) =>
{
// Convert LocalTargetInfo to GlobalTargetInfo, handling Thing targets.
if (localTarget.HasThing)
{
this.longTarget = new GlobalTargetInfo(localTarget.Thing);
@@ -228,7 +201,7 @@ namespace ArachnaeSwarm
{
this.longTarget = new GlobalTargetInfo(localTarget.Cell, mapParent.Map);
}
this.forcedTarget = LocalTargetInfo.Invalid; // Clear local target when setting remote
this.forcedTarget = LocalTargetInfo.Invalid;
},
null, onFinished, FireMissionTex, true);

View File

@@ -1,18 +0,0 @@
using RimWorld;
using RimWorld.Planet;
using UnityEngine;
using Verse;
using Verse.AI;
using Verse.Sound;
namespace ArachnaeSwarm
{
public class Verb_LaunchCatastropheMissile : Verb_Shoot
{
// This verb is now only for local defense. The global launch is handled by the Building.
protected override bool TryCastShot()
{
return base.TryCastShot();
}
}
}