diff --git a/1.6/1.6/Assemblies/ArachnaeSwarm.dll b/1.6/1.6/Assemblies/ArachnaeSwarm.dll index a190acc..87f0f72 100644 Binary files a/1.6/1.6/Assemblies/ArachnaeSwarm.dll and b/1.6/1.6/Assemblies/ArachnaeSwarm.dll differ diff --git a/1.6/1.6/Defs/ThingDefs_Buildings/ThingDef_Building_CatastropheMissileSilo.xml b/1.6/1.6/Defs/ThingDefs_Buildings/ThingDef_Building_CatastropheMissileSilo.xml index f58d8de..e09d0ef 100644 --- a/1.6/1.6/Defs/ThingDefs_Buildings/ThingDef_Building_CatastropheMissileSilo.xml +++ b/1.6/1.6/Defs/ThingDefs_Buildings/ThingDef_Building_CatastropheMissileSilo.xml @@ -50,8 +50,7 @@ true
  • -
  • -
  • +
  • CatastropheMissile_Weapon @@ -60,7 +59,7 @@
  • Artillery
  • - Security + ARA_Buildings 8
  • ShipbuildingBasics
  • diff --git a/1.6/1.6/Defs/ThingDefs_Misc/ThingDef_Projectile_CatastropheMissile.xml b/1.6/1.6/Defs/ThingDefs_Misc/ThingDef_Projectile_CatastropheMissile.xml index 957024e..56e0dc2 100644 --- a/1.6/1.6/Defs/ThingDefs_Misc/ThingDef_Projectile_CatastropheMissile.xml +++ b/1.6/1.6/Defs/ThingDefs_Misc/ThingDef_Projectile_CatastropheMissile.xml @@ -6,16 +6,26 @@ ArachnaeSwarm.Projectile_CruiseMissile - Wula/Projectile/WULA_Loitering_Munition - Graphic_Single + Graphic_Single_AgeSecs + Things/Projectile/FleshmassSpitterProjectileSheet + (3,3) + MoteGlow + True + 1 ARA_AcidBurn 200 - 50 + 80 + 15 + true0 + Filth_SpentAcid + 4 + Shell_AcidSpitImpact + 60 + false 5.9 MortarBomb_Explode - true
  • @@ -38,6 +48,49 @@ 0.5
  • + +
  • + Shell_AcidSpitStream +
  • +
  • + Shell_AcidSpitLaunched +
  • +
    + + + + Projectile_CatastropheMissile_Fake + + ArachnaeSwarm.Projectile_CruiseMissile + + Graphic_Single_AgeSecs + Things/Projectile/FleshmassSpitterProjectileSheet + (3,3) + MoteGlow + + + True + 1 + ARA_AcidBurn + 0 + 50 + 15 + true + + +
  • + true + false +
  • +
    + +
  • + Shell_AcidSpitStream +
  • +
  • + Shell_AcidSpitLaunched +
  • +
    @@ -77,7 +130,7 @@
  • ArachnaeSwarm.Verb_LaunchCatastropheMissile true - Projectile_CatastropheMissile + Projectile_CatastropheMissile 3.0 1 true diff --git a/1.6/1.6/Languages/ChineseSimplified (简体中文)/Keyed/ArachnaeSwarm_MissileSilo.xml b/1.6/1.6/Languages/ChineseSimplified (简体中文)/Keyed/ArachnaeSwarm_MissileSilo.xml new file mode 100644 index 0000000..b65457c --- /dev/null +++ b/1.6/1.6/Languages/ChineseSimplified (简体中文)/Keyed/ArachnaeSwarm_MissileSilo.xml @@ -0,0 +1,17 @@ + + + + 发射远程打击 + 选择一个世界地图上的目标进行远程打击。 + + 发射本地打击 + 手动命令炮塔攻击一个本地地图上的目标。 + + 远程目标已设定:{0} + 远程打击的目标必须是一个已探索的地点。 + 没有可用的导弹。 + + 取消所有目标 + 取消当前设定的所有本地和远程目标。 + + \ No newline at end of file diff --git a/1.6/1.6/Languages/ChineseSimplified (简体中文)/Wormhole_Keys.xml b/1.6/1.6/Languages/ChineseSimplified (简体中文)/Wormhole_Keys.xml deleted file mode 100644 index 12fc1e7..0000000 --- a/1.6/1.6/Languages/ChineseSimplified (简体中文)/Wormhole_Keys.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - 部署虫洞传送门 - 选择一名驾驶员来启动一个B端传送门。 - 没有可用的驾驶员 - - \ No newline at end of file diff --git a/Source/ArachnaeSwarm/Buildings/Building_CatastropheMissileSilo.cs b/Source/ArachnaeSwarm/Buildings/Building_CatastropheMissileSilo.cs index a7c0ed3..591e1a5 100644 --- a/Source/ArachnaeSwarm/Buildings/Building_CatastropheMissileSilo.cs +++ b/Source/ArachnaeSwarm/Buildings/Building_CatastropheMissileSilo.cs @@ -1,58 +1,160 @@ using System; using System.Collections.Generic; using System.Linq; -using RimWorld; -using RimWorld.Planet; +using System.Text; using UnityEngine; using Verse; using Verse.AI; using Verse.Sound; +using RimWorld; +using RimWorld.Planet; namespace ArachnaeSwarm { [StaticConstructorOnStartup] public class Building_CatastropheMissileSilo : Building_TurretGun { + public GlobalTargetInfo longTarget; public static readonly Texture2D FireMissionTex = ContentFinder.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; + } + } + public override void ExposeData() { base.ExposeData(); + // We no longer save/load longTarget here. It is only set by player action. + // Scribe_TargetInfo.Look(ref this.longTarget, "longTarget"); // REMOVED } protected override void Tick() { - base.Tick(); + // --- Attack Priority Logic --- + // Prio 1: Forced Local Target. Let the base class handle it. + if (this.forcedTarget.IsValid) + { + base.Tick(); + return; + } + // Prio 2: Remote Target + if (this.longTarget.IsValid) + { + if (base.Active && this.burstCooldownTicksLeft <= 0 && CanFireGlobal(out _)) + { + this.FireMission(this.longTarget); + } + else + { + // Manually tick cooldown and turret top rotation when in remote mode to prevent auto-targeting + if(this.burstCooldownTicksLeft > 0) this.burstCooldownTicksLeft--; + this.top.TurretTopTick(); + } + } + // Prio 3: No manual target, fall back to base auto-targeting. + else + { + base.Tick(); + } + } + + public override string GetInspectString() + { + StringBuilder sb = new StringBuilder(base.GetInspectString()); + if (this.longTarget.IsValid) + { + sb.AppendLine(); + sb.Append("RemoteTargetSet".Translate(this.longTarget.Label)); + } + return sb.ToString(); } public override IEnumerable GetGizmos() { - // Add the default turret gizmos (like "Set forced target") - foreach (Gizmo c in base.GetGizmos()) + // Yield base gizmos first, which includes "Set forced target" + foreach (var g in base.GetGizmos()) { - yield return c; + yield return g; } - // Add our custom global strike gizmo - Command_Action launch = new Command_Action + // Then add our custom "Global Strike" gizmo + Command_Action fireGlobal = new Command_Action { defaultLabel = "CommandFireGlobal".Translate(), defaultDesc = "CommandFireGlobalDesc".Translate(), icon = FireMissionTex, - action = new Action(this.StartChoosingDestination) + action = new Action(StartChoosingDestination) }; - + if (!CanFireGlobal(out string reason)) { - launch.Disable(reason); + fireGlobal.Disable(reason); + } + // Disable if a local forced target is already set + 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 + { + defaultLabel = "CommandClearRemoteTarget".Translate(), + defaultDesc = "CommandClearRemoteTargetDesc".Translate(), + icon = ContentFinder.Get("UI/Designators/Cancel"), + action = () => + { + this.longTarget = GlobalTargetInfo.Invalid; + } + }; + yield return clearRemote; } - yield return launch; } + + public void FireMission(GlobalTargetInfo target) + { + if (!CanFireGlobal(out _)) return; + WorldObject_CatastropheMissile missile = (WorldObject_CatastropheMissile)WorldObjectMaker.MakeWorldObject( + DefDatabase.GetNamed("CatastropheMissile_Flying") + ); + missile.Tile = this.Map.Tile; + missile.destinationTile = target.Tile; + missile.destinationCell = target.Cell; + missile.Projectile = DefDatabase.GetNamed("Projectile_CatastropheMissile"); + Find.WorldObjects.Add(missile); + + if (CellFinder.TryFindRandomEdgeCellWith(c => this.Map.reachability.CanReach(this.Position, c, PathEndMode.OnCell, TraverseParms.For(TraverseMode.NoPassClosedDoors, Danger.Deadly)), this.Map, 0f, out IntVec3 edgeCell)) + { + Projectile_CruiseMissile dummy = (Projectile_CruiseMissile)GenSpawn.Spawn(DefDatabase.GetNamed("Projectile_CatastropheMissile_Fake"), this.Position, this.Map); + dummy?.Launch(this, this.DrawPos, new LocalTargetInfo(edgeCell), new LocalTargetInfo(edgeCell), ProjectileHitFlags.None); + } + + var refuelableComp = this.TryGetComp(); + if(refuelableComp != null) + { + refuelableComp.ConsumeFuel(1); + } + 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(); - if (refuelableComp != null && !refuelableComp.HasFuel) + if (refuelableComp != null && !refuelableComp.HasFuel) { reason = "NoFuel".Translate().CapitalizeFirst(); return false; @@ -65,13 +167,12 @@ namespace ArachnaeSwarm { CameraJumper.TryJump(CameraJumper.GetWorldTarget(this), CameraJumper.MovementMode.Pan); Find.WorldSelector.ClearSelection(); - Find.WorldTargeter.BeginTargeting( - new Func(this.ChoseWorldTarget), + new Func(this.ChoseWorldTarget), true, - FireMissionTex, - true, - () => GenDraw.DrawWorldRadiusRing(this.Map.Tile, 99999), + FireMissionTex, + true, + () => GenDraw.DrawWorldRadiusRing(this.Map.Tile, 99999), null, null, null, true); } @@ -82,12 +183,13 @@ namespace ArachnaeSwarm Messages.Message("MessageTargetInvalid".Translate(), MessageTypeDefOf.RejectInput, true); return false; } - if (target.Map == this.Map) + if (target.Tile == this.Map.Tile) { Messages.Message("Cannot target own map for global strike.", MessageTypeDefOf.RejectInput, true); return false; } + // Allow targeting Pawns and Buildings on the world map if (target.WorldObject is MapParent mapParent && mapParent.HasMap) { var originalMap = this.Map; @@ -96,53 +198,33 @@ namespace ArachnaeSwarm }; Current.Game.CurrentMap = mapParent.Map; - Find.Targeter.BeginTargeting(new TargetingParameters { canTargetLocations = true }, + Find.Targeter.BeginTargeting(new TargetingParameters + { + canTargetLocations = true, + canTargetPawns = true, // Allow targeting pawns + canTargetBuildings = true // Allow targeting buildings + }, (LocalTargetInfo localTarget) => { - this.FireMission(new GlobalTargetInfo(localTarget.Cell, mapParent.Map)); - }, + // Convert LocalTargetInfo to GlobalTargetInfo, handling Thing targets. + if (localTarget.HasThing) + { + this.longTarget = new GlobalTargetInfo(localTarget.Thing); + } + else + { + this.longTarget = new GlobalTargetInfo(localTarget.Cell, mapParent.Map); + } + }, null, onFinished, FireMissionTex, true); - + return true; } - else + else { Messages.Message("MessageTargetMustBeMap".Translate(), MessageTypeDefOf.RejectInput, true); return false; } } - - public void FireMission(GlobalTargetInfo target) - { - if (!CanFireGlobal(out _)) return; - - var refuelableComp = this.TryGetComp(); - - // --- Launch Logic starts here --- - - // 1. Create the world-traveling object immediately - WorldObject_CatastropheMissile missile = (WorldObject_CatastropheMissile)WorldObjectMaker.MakeWorldObject( - DefDatabase.GetNamed("CatastropheMissile_Flying") - ); - missile.Tile = this.Map.Tile; - missile.destinationTile = target.Tile; - missile.destinationCell = target.Cell; - missile.Projectile = DefDatabase.GetNamed("Projectile_CatastropheMissile"); - Find.WorldObjects.Add(missile); - - // 2. Launch a local dummy projectile for visual effect, that will never impact. - if (CellFinder.TryFindRandomEdgeCellWith(c => this.Map.reachability.CanReach(this.Position, c, PathEndMode.OnCell, TraverseParms.For(TraverseMode.NoPassClosedDoors, Danger.Deadly)), this.Map, 0f, out IntVec3 edgeCell)) - { - Projectile dummy = (Projectile)GenSpawn.Spawn(DefDatabase.GetNamed("Projectile_CatastropheMissile"), this.Position, this.Map); - dummy.Launch(this, this.DrawPos, new LocalTargetInfo(edgeCell), new LocalTargetInfo(edgeCell), ProjectileHitFlags.None); - } - - // 3. Consume resources and start cooldown - if(refuelableComp != null) - { - refuelableComp.ConsumeFuel(1); - } - SoundDef.Named("RocketLaunch").PlayOneShot(new TargetInfo(this.Position, this.Map)); - } } } \ No newline at end of file diff --git a/Source/ArachnaeSwarm/Verbs/Projectiles/Projectile_CruiseMissile.cs b/Source/ArachnaeSwarm/Verbs/Projectiles/Projectile_CruiseMissile.cs index ee8fdac..5ef3aa4 100644 --- a/Source/ArachnaeSwarm/Verbs/Projectiles/Projectile_CruiseMissile.cs +++ b/Source/ArachnaeSwarm/Verbs/Projectiles/Projectile_CruiseMissile.cs @@ -8,6 +8,7 @@ namespace ArachnaeSwarm { public class CruiseMissileProperties : DefModExtension { + public bool isDummy = false; public DamageDef customDamageDef; public int customDamageAmount = 5; public float customExplosionRadius = 1.1f; @@ -38,11 +39,19 @@ namespace ArachnaeSwarm private Vector3 Randdd; private Vector3 position2; public Vector3 ExPos; + public bool isDummy = false; + + public override void ExposeData() + { + base.ExposeData(); + Scribe_Values.Look(ref isDummy, "isDummy", false); + } public override void SpawnSetup(Map map, bool respawningAfterLoad) { base.SpawnSetup(map, respawningAfterLoad); settings = def.GetModExtension() ?? new CruiseMissileProperties(); + this.isDummy = settings.isDummy; } private void RandFactor() @@ -113,6 +122,11 @@ namespace ArachnaeSwarm var map = base.Map; base.Impact(hitThing, blockedByShield); + if (isDummy) + { + return; + } + DoExplosion( base.Position, map,