diff --git a/1.6/1.6/Assemblies/WulaFallenEmpire.dll b/1.6/1.6/Assemblies/WulaFallenEmpire.dll index 77389a20..ccb8c2af 100644 Binary files a/1.6/1.6/Assemblies/WulaFallenEmpire.dll and b/1.6/1.6/Assemblies/WulaFallenEmpire.dll differ diff --git a/1.6/1.6/Defs/ThingDefs_Buildings/Building_WULA_Shuttle.xml b/1.6/1.6/Defs/ThingDefs_Buildings/Building_WULA_Shuttle.xml new file mode 100644 index 00000000..a5359922 --- /dev/null +++ b/1.6/1.6/Defs/ThingDefs_Buildings/Building_WULA_Shuttle.xml @@ -0,0 +1,269 @@ + + + + + WULA_ArmedShuttle + + A chemfuel-powered shuttle designed for long-distance travel, equipped with a turret for defense. It is capable of reaching orbital locations. + WulaFallenEmpire.Building_ArmedShuttle + true + Building + 50 + true + PassThroughOnly + 0.5 + (3,5) + true + (0.56, 0.62, 0.9) + 1 + + Graphic_Multi + Things/Building/PassengerShuttle/PassengerShuttle + CutoutComplex + (3,5) + + (1.8, 1.0, 4.1) + (-0.1, 0, 0) + + + + 600 + 0.5 + 40000 + 150 + 0.65 + + Normal + Odyssey + 8 + + 300 + 200 + 8 + 2 + 1 + + true + + 60 + 60 + 5 + 4 + + true + true + (2, 0, 0) + East + true + Light + BulletImpact_Metal + true + RealtimeOnly + ConstructMetal + true + + false + BuildingDestroyed_Metal_Big + true + true + +
  • ShuttleEngine
  • +
    + Gun_ChargeBlasterHeavyTurret + 5.5 + 1.75 + (0, 0.05) +
    + +
  • ITab_ContentsTransporter
  • +
  • ITab_Shells
  • +
    + +
  • Shuttles
  • +
    + +
  • + Ship_ArmedShuttle +
  • +
  • + 3 + 50 + ArmedShuttleLeaving_WULA + PassengerShuttle + 3750 + 62 + {0} is ready to launch again. +
  • +
  • + 500 + true + true + Shuttle_PawnLoaded + Shuttle_PawnExit + true +
  • +
  • + 400 + true + 400 + + +
  • Chemfuel
  • + + + Chemfuel + Chemfuel + true + 1 + true + false + true + true + +
  • + ShuttleIdle_Ambience +
  • +
    + +
  • PlaceWorker_NotUnderRoof
  • +
  • PlaceWorker_TurretTop
  • +
    + 2601 +
    + + + WULA_Bullet_ArmedShuttle + + + Things/Projectile/Bullet_Big + Graphic_Single + + + Bullet + 25 + 70 + + + + + Gun_ChargeBlasterHeavyTurret + + A pulse-charged rapid-fire blaster for area fire. + + Things/Item/Equipment/WeaponRanged/ChargeBlasterLight + Graphic_Single + + + 0.08 + 5.5 + + +
  • + Verb_Shoot + true + WULA_Bullet_ArmedShuttle + 1.25 + 3.9 + 45.9 + 7 + 9 + Shot_ChargeBlaster + GunTail_Heavy + 9 +
  • +
    +
    + + + ArmedShuttleIncoming_WULA + + WulaFallenEmpire.ArmedShuttleIncoming + + Graphic_Multi + Things/Building/PassengerShuttle/PassengerShuttle + CutoutComplex + (3,5) + + (3,5) + + Shuttle_Landing + 250 + 200~250 + (3.5,5.5) + + +
  • (0,30)
  • +
  • (0.5,5)
  • +
  • (0.9,-5)
  • +
  • (0.95,0)
  • +
    +
    + + +
  • (0.95,2.5)
  • +
  • (1,0)
  • +
    +
    + + +
  • (0.6,0.6)
  • +
  • (0.95,0.1)
  • +
    +
    +
    +
    + + + ArmedShuttleLeaving_WULA + + PassengerShuttleLeaving + true + + Graphic_Multi + Things/Building/PassengerShuttle/PassengerShuttle + CutoutComplex + (3,5) + + (3,5) + + true + Shuttle_Leaving + -10 + -40~-15 + 0.05 + Things/Skyfaller/SkyfallerShadowRectangle + (3.5,5.5) + 1 + + +
  • (0,0)
  • +
  • (0.15,10)
  • +
  • (0.5,-5)
  • +
    +
    + + +
  • (0,0)
  • +
  • (0.08,2)
  • +
    +
    + + +
  • (0,0.2)
  • +
  • (0.4,0.7)
  • +
    +
    +
    +
    + + + Ship_ArmedShuttle + + WULA_ArmedShuttle + ArmedShuttleIncoming_WULA + ArmedShuttleLeaving_WULA + PassengerShuttle + true + + +
    \ No newline at end of file diff --git a/1.6/Odyssey/Defs/ThingDefs_Buildings/Buildings_WULA_Odyssey.xml b/1.6/Odyssey/Defs/ThingDefs_Buildings/Buildings_WULA_Odyssey.xml index 6d339395..4d4c4c2f 100644 --- a/1.6/Odyssey/Defs/ThingDefs_Buildings/Buildings_WULA_Odyssey.xml +++ b/1.6/Odyssey/Defs/ThingDefs_Buildings/Buildings_WULA_Odyssey.xml @@ -30,11 +30,6 @@ 1 WULA_Buildings - - 100 - 2 - 1 -
  • 12 diff --git a/Source/WulaFallenEmpire/ArmedShuttleIncoming.cs b/Source/WulaFallenEmpire/ArmedShuttleIncoming.cs new file mode 100644 index 00000000..f67faf19 --- /dev/null +++ b/Source/WulaFallenEmpire/ArmedShuttleIncoming.cs @@ -0,0 +1,107 @@ +using RimWorld; +using Verse; +using System.Linq; +using UnityEngine; +using System.Reflection; // For InnerThing reflection if needed, but innerContainer is directly accessible + +namespace WulaFallenEmpire +{ + // ArmedShuttleIncoming now directly implements the logic from PassengerShuttleIncoming + // It should inherit from ShuttleIncoming, as PassengerShuttleIncoming does. + public class ArmedShuttleIncoming : ShuttleIncoming // Changed from PassengerShuttleIncoming + { + private static readonly SimpleCurve AngleCurve = new SimpleCurve + { + new CurvePoint(0f, 30f), + new CurvePoint(1f, 0f) + }; + + // innerContainer is a protected field in Skyfaller, accessible to derived classes like ShuttleIncoming + // So we can directly use innerContainer here. + public Building_ArmedShuttle Shuttle => (Building_ArmedShuttle)innerContainer.FirstOrDefault(); + + public override Color DrawColor => Shuttle.DrawColor; + + protected override void Impact() + { + // Re-adding debug logs for stage 6 + Log.Message($"[WULA] Stage 6: Impact - ArmedShuttleIncoming Impact() called. InnerThing (via innerContainer) is: {innerContainer.FirstOrDefault()?.ToString() ?? "NULL"}"); + + Thing innerThing = innerContainer.FirstOrDefault(); + if (innerThing is Building_ArmedShuttle shuttle) + { + Log.Message("[WULA] Stage 6: Impact - InnerThing is a Building_ArmedShuttle. Attempting to notify arrival."); + shuttle.TryGetComp()?.Notify_Arrived(); + } + else + { + Log.Warning($"[WULA] Stage 6: Impact - InnerThing is NOT a Building_ArmedShuttle or is NULL. Type: {innerThing?.GetType().Name ?? "NULL"}. This is the cause of the issue."); + } + + // Calling base.Impact() will handle the actual spawning of the innerThing. + // This is crucial for "unpacking" the shuttle. + base.Impact(); + } + + public override void SpawnSetup(Map map, bool respawningAfterLoad) + { + base.SpawnSetup(map, respawningAfterLoad); + // Re-adding debug logs for stage 5 + Log.Message($"[WULA] Stage 5: Landing Sequence - ArmedShuttleIncoming spawned. InnerThing (via innerContainer) is: {innerContainer.FirstOrDefault()?.ToString() ?? "NULL"}"); + if (!respawningAfterLoad && !base.BeingTransportedOnGravship) + { + angle = GetAngle(0f, base.Rotation); + } + } + + public override void Destroy(DestroyMode mode = DestroyMode.Vanish) + { + if (!hasImpacted) + { + Log.Error("Destroying armed shuttle skyfaller without ever having impacted"); // Changed log message + } + base.Destroy(mode); + } + + protected override void GetDrawPositionAndRotation(ref Vector3 drawLoc, out float extraRotation) + { + extraRotation = 0f; + angle = GetAngle(base.TimeInAnimation, base.Rotation); + switch (base.Rotation.AsInt) + { + case 1: + extraRotation += def.skyfaller.rotationCurve.Evaluate(base.TimeInAnimation); + break; + case 3: + extraRotation -= def.skyfaller.rotationCurve.Evaluate(base.TimeInAnimation); + break; + } + drawLoc.z += def.skyfaller.zPositionCurve.Evaluate(base.TimeInAnimation); + } + + public override float DrawAngle() + { + float num = 0f; + switch (base.Rotation.AsInt) + { + case 1: + num += def.skyfaller.rotationCurve.Evaluate(base.TimeInAnimation); + break; + case 3: + num -= def.skyfaller.rotationCurve.Evaluate(base.TimeInAnimation); + break; + } + return num; + } + + private static float GetAngle(float timeInAnimation, Rot4 rotation) + { + return rotation.AsInt switch + { + 1 => rotation.Opposite.AsAngle + AngleCurve.Evaluate(timeInAnimation), + 3 => rotation.Opposite.AsAngle - AngleCurve.Evaluate(timeInAnimation), + _ => rotation.Opposite.AsAngle, + }; + } + } +} \ No newline at end of file diff --git a/Source/WulaFallenEmpire/Building_ArmedShuttle.cs b/Source/WulaFallenEmpire/Building_ArmedShuttle.cs new file mode 100644 index 00000000..55f600ce --- /dev/null +++ b/Source/WulaFallenEmpire/Building_ArmedShuttle.cs @@ -0,0 +1,597 @@ +using System.Collections.Generic; +using System.Linq; +using System.Text; +using RimWorld; +using RimWorld.Planet; +using UnityEngine; +using Verse; +using Verse.AI; +using Verse.Sound; + +namespace WulaFallenEmpire +{ + [StaticConstructorOnStartup] + public class Building_ArmedShuttle : Building_PassengerShuttle, IAttackTargetSearcher + { + // --- TurretTop nested class --- + public class TurretTop + { + private Building_ArmedShuttle parentTurret; + private float curRotationInt; + private int ticksUntilIdleTurn; + private int idleTurnTicksLeft; + private bool idleTurnClockwise; + + private const float IdleTurnDegreesPerTick = 0.26f; + private const int IdleTurnDuration = 140; + private const int IdleTurnIntervalMin = 150; + private const int IdleTurnIntervalMax = 350; + public static readonly int ArtworkRotation = -90; + + public float CurRotation + { + get => curRotationInt; + set + { + curRotationInt = value % 360f; + if (curRotationInt < 0f) curRotationInt += 360f; + } + } + + public TurretTop(Building_ArmedShuttle ParentTurret) + { + this.parentTurret = ParentTurret; + } + + public void SetRotationFromOrientation() => CurRotation = parentTurret.Rotation.AsAngle; + + public void ForceFaceTarget(LocalTargetInfo targ) + { + if (targ.IsValid) + { + CurRotation = (targ.Cell.ToVector3Shifted() - parentTurret.DrawPos).AngleFlat(); + } + } + + public void TurretTopTick() + { + LocalTargetInfo currentTarget = parentTurret.CurrentTarget; + if (currentTarget.IsValid) + { + CurRotation = (currentTarget.Cell.ToVector3Shifted() - parentTurret.DrawPos).AngleFlat(); + ticksUntilIdleTurn = Rand.RangeInclusive(150, 350); + } + else if (ticksUntilIdleTurn > 0) + { + ticksUntilIdleTurn--; + if (ticksUntilIdleTurn == 0) + { + idleTurnClockwise = Rand.Value < 0.5f; + idleTurnTicksLeft = 140; + } + } + else + { + CurRotation += idleTurnClockwise ? 0.26f : -0.26f; + idleTurnTicksLeft--; + if (idleTurnTicksLeft <= 0) + { + ticksUntilIdleTurn = Rand.RangeInclusive(150, 350); + } + } + } + + public void DrawTurret() + { + Vector3 v = new Vector3(parentTurret.def.building.turretTopOffset.x, 0f, parentTurret.def.building.turretTopOffset.y).RotatedBy(CurRotation); + float turretTopDrawSize = parentTurret.def.building.turretTopDrawSize; + float num = parentTurret.AttackVerb?.AimAngleOverride ?? CurRotation; + Vector3 pos = parentTurret.DrawPos + Altitudes.AltIncVect + v; + Quaternion q = ((float)ArtworkRotation + num).ToQuat(); + Graphics.DrawMesh(matrix: Matrix4x4.TRS(pos, q, new Vector3(turretTopDrawSize, 1f, turretTopDrawSize)), mesh: MeshPool.plane10, material: parentTurret.TurretTopMaterial, layer: 0); + } + } + + // --- Fields --- + protected LocalTargetInfo forcedTarget = LocalTargetInfo.Invalid; + private LocalTargetInfo lastAttackedTarget; + private int lastAttackTargetTick; + private StunHandler stunner; + private bool triedGettingStunner; + protected int burstCooldownTicksLeft; + protected int burstWarmupTicksLeft; + protected LocalTargetInfo currentTargetInt = LocalTargetInfo.Invalid; + private bool holdFire; + private bool burstActivated; + public Thing gun; + protected TurretTop top; + protected CompPowerTrader powerComp; + protected CompCanBeDormant dormantComp; + protected CompInitiatable initiatableComp; + protected CompMannable mannableComp; + protected CompInteractable interactableComp; + public CompRefuelable refuelableComp; + protected Effecter progressBarEffecter; + protected CompMechPowerCell powerCellComp; + protected CompHackable hackableComp; + + // --- PROPERTIES --- + public virtual Material TurretTopMaterial => def.building.turretTopMat; + protected bool IsStunned + { + get + { + if (!triedGettingStunner) + { + stunner = GetComp()?.StunHandler; + triedGettingStunner = true; + } + return stunner != null && stunner.Stunned; + } + } + public LocalTargetInfo TargetCurrentlyAimingAt => CurrentTarget; + public Verb CurrentEffectiveVerb => AttackVerb; + public LocalTargetInfo LastAttackedTarget => lastAttackedTarget; + public int LastAttackTargetTick => lastAttackTargetTick; + public LocalTargetInfo ForcedTarget => forcedTarget; + public virtual bool IsEverThreat => true; + public bool Active => (powerComp == null || powerComp.PowerOn) && (dormantComp == null || dormantComp.Awake) && (initiatableComp == null || initiatableComp.Initiated) && (interactableComp == null || burstActivated) && (powerCellComp == null || !powerCellComp.depleted) && (hackableComp == null || !hackableComp.IsHacked); + public CompEquippable GunCompEq => gun.TryGetComp(); + public virtual LocalTargetInfo CurrentTarget => currentTargetInt; + private bool WarmingUp => burstWarmupTicksLeft > 0; + public virtual Verb AttackVerb => GunCompEq.PrimaryVerb; + public bool IsMannable => mannableComp != null; + private bool PlayerControlled => (base.Faction == Faction.OfPlayer || MannedByColonist) && !MannedByNonColonist && !IsActivable; + protected virtual bool CanSetForcedTarget => mannableComp != null && PlayerControlled; + private bool CanToggleHoldFire => PlayerControlled; + private bool IsMortar => def.building.IsMortar; + private bool IsMortarOrProjectileFliesOverhead => AttackVerb.ProjectileFliesOverhead() || IsMortar; + private bool IsActivable => interactableComp != null; + protected virtual bool HideForceTargetGizmo => false; + public TurretTop Top => top; + private bool CanExtractShell => PlayerControlled && (gun.TryGetComp()?.Loaded ?? false); + private bool MannedByColonist => mannableComp != null && mannableComp.ManningPawn != null && mannableComp.ManningPawn.Faction == Faction.OfPlayer; + private bool MannedByNonColonist => mannableComp != null && mannableComp.ManningPawn != null && mannableComp.ManningPawn.Faction != Faction.OfPlayer; + Thing IAttackTargetSearcher.Thing => this; + + // --- CONSTRUCTOR --- + public Building_ArmedShuttle() + { + top = new TurretTop(this); + } + + // --- METHODS --- + public override void SpawnSetup(Map map, bool respawningAfterLoad) + { + base.SpawnSetup(map, respawningAfterLoad); + dormantComp = GetComp(); + initiatableComp = GetComp(); + powerComp = GetComp(); + mannableComp = GetComp(); + interactableComp = GetComp(); + refuelableComp = GetComp(); + powerCellComp = GetComp(); + hackableComp = GetComp(); + if (!respawningAfterLoad) + { + top.SetRotationFromOrientation(); + // ShuttleComp.shipParent.Start(); // Already handled by base.SpawnSetup + } + } + + public override void PostMake() + { + base.PostMake(); + burstCooldownTicksLeft = def.building.turretInitialCooldownTime.SecondsToTicks(); + MakeGun(); + } + + public override void DeSpawn(DestroyMode mode = DestroyMode.Vanish) + { + base.DeSpawn(mode); + ResetCurrentTarget(); + progressBarEffecter?.Cleanup(); + } + + public override void ExposeData() + { + base.ExposeData(); + Scribe_TargetInfo.Look(ref forcedTarget, "forcedTarget"); + Scribe_TargetInfo.Look(ref lastAttackedTarget, "lastAttackedTarget"); + Scribe_Values.Look(ref lastAttackTargetTick, "lastAttackTargetTick", 0); + Scribe_Values.Look(ref burstCooldownTicksLeft, "burstCooldownTicksLeft", 0); + Scribe_Values.Look(ref burstWarmupTicksLeft, "burstWarmupTicksLeft", 0); + Scribe_TargetInfo.Look(ref currentTargetInt, "currentTarget"); + Scribe_Values.Look(ref holdFire, "holdFire", defaultValue: false); + Scribe_Values.Look(ref burstActivated, "burstActivated", defaultValue: false); + Scribe_Deep.Look(ref gun, "gun"); + // Scribe_Values.Look(ref shuttleName, "shuttleName"); // Already handled by base.ExposeData + if (Scribe.mode == LoadSaveMode.PostLoadInit) + { + if (gun == null) + { + Log.Error("Turret had null gun after loading. Recreating."); + MakeGun(); + } + else + { + UpdateGunVerbs(); + } + } + } + + protected override void Tick() + { + base.Tick(); + if (forcedTarget.HasThing && (!forcedTarget.Thing.Spawned || !base.Spawned || forcedTarget.Thing.Map != base.Map)) + { + forcedTarget = LocalTargetInfo.Invalid; + } + if (CanExtractShell && MannedByColonist) + { + CompChangeableProjectile compChangeableProjectile = gun.TryGetComp(); + if (!compChangeableProjectile.allowedShellsSettings.AllowedToAccept(compChangeableProjectile.LoadedShell)) + { + ExtractShell(); + } + } + if (forcedTarget.IsValid && !CanSetForcedTarget) ResetForcedTarget(); + if (!CanToggleHoldFire) holdFire = false; + if (forcedTarget.ThingDestroyed) ResetForcedTarget(); + + if (Active && (mannableComp == null || mannableComp.MannedNow) && !IsStunned && base.Spawned) + { + GunCompEq.verbTracker.VerbsTick(); + if (AttackVerb.state != VerbState.Bursting) + { + burstActivated = false; + if (WarmingUp) + { + burstWarmupTicksLeft--; + if (burstWarmupTicksLeft <= 0) BeginBurst(); + } + else + { + if (burstCooldownTicksLeft > 0) + { + burstCooldownTicksLeft--; + if (IsMortar) + { + if (progressBarEffecter == null) progressBarEffecter = EffecterDefOf.ProgressBar.Spawn(); + progressBarEffecter.EffectTick(this, TargetInfo.Invalid); + MoteProgressBar mote = ((SubEffecter_ProgressBar)progressBarEffecter.children[0]).mote; + mote.progress = 1f - (float)Mathf.Max(burstCooldownTicksLeft, 0) / (float)BurstCooldownTime().SecondsToTicks(); + mote.offsetZ = -0.8f; + } + } + if (burstCooldownTicksLeft <= 0 && this.IsHashIntervalTick(15)) + { + TryStartShootSomething(canBeginBurstImmediately: true); + } + } + } + top.TurretTopTick(); + } + else + { + ResetCurrentTarget(); + } + } + + public override IEnumerable GetGizmos() + { + foreach (Gizmo gizmo in base.GetGizmos()) yield return gizmo; + if (CanExtractShell) + { + CompChangeableProjectile compChangeableProjectile = gun.TryGetComp(); + Command_Action command_Action = new Command_Action(); + command_Action.defaultLabel = "CommandExtractShell".Translate(); + command_Action.defaultDesc = "CommandExtractShellDesc".Translate(); + command_Action.icon = compChangeableProjectile.LoadedShell.uiIcon; + command_Action.iconAngle = compChangeableProjectile.LoadedShell.uiIconAngle; + command_Action.iconOffset = compChangeableProjectile.LoadedShell.uiIconOffset; + command_Action.iconDrawScale = GenUI.IconDrawScale(compChangeableProjectile.LoadedShell); + command_Action.action = delegate { ExtractShell(); }; + yield return command_Action; + } + CompChangeableProjectile compChangeableProjectile2 = gun.TryGetComp(); + if (compChangeableProjectile2 != null) + { + foreach (Gizmo item in StorageSettingsClipboard.CopyPasteGizmosFor(compChangeableProjectile2.GetStoreSettings())) + { + yield return item; + } + } + if (!HideForceTargetGizmo) + { + if (CanSetForcedTarget) + { + Command_VerbTarget command_VerbTarget = new Command_VerbTarget(); + command_VerbTarget.defaultLabel = "CommandSetForceAttackTarget".Translate(); + command_VerbTarget.defaultDesc = "CommandSetForceAttackTargetDesc".Translate(); + command_VerbTarget.icon = ContentFinder.Get("UI/Commands/Attack"); + command_VerbTarget.verb = AttackVerb; + command_VerbTarget.hotKey = KeyBindingDefOf.Misc4; + command_VerbTarget.drawRadius = false; + command_VerbTarget.requiresAvailableVerb = false; + if (base.Spawned && IsMortarOrProjectileFliesOverhead && base.Position.Roofed(base.Map)) + { + command_VerbTarget.Disable("CannotFire".Translate() + ": " + "Roofed".Translate().CapitalizeFirst()); + } + yield return command_VerbTarget; + } + if (forcedTarget.IsValid) + { + Command_Action command_Action2 = new Command_Action(); + command_Action2.defaultLabel = "CommandStopForceAttack".Translate(); + command_Action2.defaultDesc = "CommandStopForceAttackDesc".Translate(); + command_Action2.icon = ContentFinder.Get("UI/Commands/Halt"); + command_Action2.action = delegate + { + ResetForcedTarget(); + SoundDefOf.Tick_Low.PlayOneShotOnCamera(); + }; + if (!forcedTarget.IsValid) + { + command_Action2.Disable("CommandStopAttackFailNotForceAttacking".Translate()); + } + command_Action2.hotKey = KeyBindingDefOf.Misc5; + yield return command_Action2; + } + } + if (CanToggleHoldFire) + { + Command_Toggle command_Toggle = new Command_Toggle(); + command_Toggle.defaultLabel = "CommandHoldFire".Translate(); + command_Toggle.defaultDesc = "CommandHoldFireDesc".Translate(); + command_Toggle.icon = ContentFinder.Get("UI/Commands/HoldFire"); + command_Toggle.hotKey = KeyBindingDefOf.Misc6; + command_Toggle.toggleAction = delegate + { + holdFire = !holdFire; + if (holdFire) ResetForcedTarget(); + }; + command_Toggle.isActive = () => holdFire; + yield return command_Toggle; + } + Log.Message($"[WULA] Stage 2: Launch Sequence - Providing launch gizmos for {this.Label}."); + // The following gizmos are already provided by Building_PassengerShuttle's GetGizmos() + // foreach (Gizmo gizmo in ShuttleComp.CompGetGizmosExtra()) yield return gizmo; + // foreach (Gizmo gizmo in LaunchableComp.CompGetGizmosExtra()) yield return gizmo; + // foreach (Gizmo gizmo in TransporterComp.CompGetGizmosExtra()) yield return gizmo; + // fuel related gizmos are also handled by base class. + } + + public void OrderAttack(LocalTargetInfo targ) + { + if (!targ.IsValid) + { + if (forcedTarget.IsValid) ResetForcedTarget(); + return; + } + if ((targ.Cell - base.Position).LengthHorizontal < AttackVerb.verbProps.EffectiveMinRange(targ, this)) + { + Messages.Message("MessageTargetBelowMinimumRange".Translate(), this, MessageTypeDefOf.RejectInput, historical: false); + return; + } + if ((targ.Cell - base.Position).LengthHorizontal > AttackVerb.EffectiveRange) + { + Messages.Message("MessageTargetBeyondMaximumRange".Translate(), this, MessageTypeDefOf.RejectInput, historical: false); + return; + } + if (forcedTarget != targ) + { + forcedTarget = targ; + if (burstCooldownTicksLeft <= 0) TryStartShootSomething(canBeginBurstImmediately: false); + } + if (holdFire) + { + Messages.Message("MessageTurretWontFireBecauseHoldFire".Translate(def.label), this, MessageTypeDefOf.RejectInput, historical: false); + } + } + + public bool ThreatDisabled(IAttackTargetSearcher disabledFor) + { + if (!IsEverThreat) return true; + if (powerComp != null && !powerComp.PowerOn) return true; + if (mannableComp != null && !mannableComp.MannedNow) return true; + if (dormantComp != null && !dormantComp.Awake) return true; + if (initiatableComp != null && !initiatableComp.Initiated) return true; + if (powerCellComp != null && powerCellComp.depleted) return true; + if (hackableComp != null && hackableComp.IsHacked) return true; + return false; + } + + protected void OnAttackedTarget(LocalTargetInfo target) + { + lastAttackTargetTick = Find.TickManager.TicksGame; + lastAttackedTarget = target; + } + + public void TryStartShootSomething(bool canBeginBurstImmediately) + { + if (progressBarEffecter != null) + { + progressBarEffecter.Cleanup(); + progressBarEffecter = null; + } + if (!base.Spawned || (holdFire && CanToggleHoldFire) || (AttackVerb.ProjectileFliesOverhead() && base.Map.roofGrid.Roofed(base.Position)) || !AttackVerb.Available()) + { + ResetCurrentTarget(); + return; + } + bool wasValid = currentTargetInt.IsValid; + currentTargetInt = forcedTarget.IsValid ? forcedTarget : TryFindNewTarget(); + if (!wasValid && currentTargetInt.IsValid && def.building.playTargetAcquiredSound) + { + SoundDefOf.TurretAcquireTarget.PlayOneShot(new TargetInfo(base.Position, base.Map)); + } + if (currentTargetInt.IsValid) + { + float warmupTime = def.building.turretBurstWarmupTime.RandomInRange; + if (warmupTime > 0f) + { + burstWarmupTicksLeft = warmupTime.SecondsToTicks(); + } + else if (canBeginBurstImmediately) + { + BeginBurst(); + } + else + { + burstWarmupTicksLeft = 1; + } + } + else + { + ResetCurrentTarget(); + } + } + + public virtual LocalTargetInfo TryFindNewTarget() + { + IAttackTargetSearcher searcher = this; + Faction faction = searcher.Thing.Faction; + float range = AttackVerb.EffectiveRange; + if (Rand.Value < 0.5f && AttackVerb.ProjectileFliesOverhead() && faction.HostileTo(Faction.OfPlayer)) + { + if (base.Map.listerBuildings.allBuildingsColonist.Where(delegate(Building x) + { + float minRange = AttackVerb.verbProps.EffectiveMinRange(x, this); + float distSq = x.Position.DistanceToSquared(base.Position); + return distSq > minRange * minRange && distSq < range * range; + }).TryRandomElement(out Building result)) + { + return result; + } + } + TargetScanFlags flags = TargetScanFlags.NeedThreat | TargetScanFlags.NeedAutoTargetable; + if (!AttackVerb.ProjectileFliesOverhead()) + { + flags |= TargetScanFlags.NeedLOSToAll | TargetScanFlags.LOSBlockableByGas; + } + if (AttackVerb.IsIncendiary_Ranged()) + { + flags |= TargetScanFlags.NeedNonBurning; + } + if (IsMortar) + { + flags |= TargetScanFlags.NeedNotUnderThickRoof; + } + return (Thing)AttackTargetFinder.BestShootTargetFromCurrentPosition(searcher, flags, IsValidTarget); + } + + private IAttackTargetSearcher TargSearcher() => (mannableComp != null && mannableComp.MannedNow) ? (IAttackTargetSearcher)mannableComp.ManningPawn : this; + + private bool IsValidTarget(Thing t) + { + if (t is Pawn pawn) + { + if (base.Faction == Faction.OfPlayer && pawn.IsPrisoner) return false; + if (AttackVerb.ProjectileFliesOverhead()) + { + RoofDef roof = base.Map.roofGrid.RoofAt(t.Position); + if (roof != null && roof.isThickRoof) return false; + } + if (mannableComp == null) return !GenAI.MachinesLike(base.Faction, pawn); + if (pawn.RaceProps.Animal && pawn.Faction == Faction.OfPlayer) return false; + } + return true; + } + + protected virtual void BeginBurst() + { + AttackVerb.TryStartCastOn(CurrentTarget); + OnAttackedTarget(CurrentTarget); + } + + protected void BurstComplete() + { + burstCooldownTicksLeft = BurstCooldownTime().SecondsToTicks(); + } + + protected float BurstCooldownTime() => (def.building.turretBurstCooldownTime >= 0f) ? def.building.turretBurstCooldownTime : AttackVerb.verbProps.defaultCooldownTime; + + public override string GetInspectString() + { + StringBuilder sb = new StringBuilder(base.GetInspectString()); + if (AttackVerb.verbProps.minRange > 0f) + { + sb.AppendLine("MinimumRange".Translate() + ": " + AttackVerb.verbProps.minRange.ToString("F0")); + } + if (base.Spawned && IsMortarOrProjectileFliesOverhead && base.Position.Roofed(base.Map)) + { + sb.AppendLine("CannotFire".Translate() + ": " + "Roofed".Translate().CapitalizeFirst()); + } + else if (base.Spawned && burstCooldownTicksLeft > 0 && BurstCooldownTime() > 5f) + { + sb.AppendLine("CanFireIn".Translate() + ": " + burstCooldownTicksLeft.ToStringSecondsFromTicks()); + } + CompChangeableProjectile changeable = gun.TryGetComp(); + if (changeable != null) + { + sb.AppendLine(changeable.Loaded ? "ShellLoaded".Translate(changeable.LoadedShell.LabelCap, changeable.LoadedShell) : "ShellNotLoaded".Translate()); + } + return sb.ToString().TrimEndNewlines(); + } + + protected override void DrawAt(Vector3 drawLoc, bool flip = false) + { + top.DrawTurret(); + base.DrawAt(drawLoc, flip); + } + + public override void DrawExtraSelectionOverlays() + { + base.DrawExtraSelectionOverlays(); + float range = AttackVerb.EffectiveRange; + if (range < 90f) GenDraw.DrawRadiusRing(base.Position, range); + float minRange = AttackVerb.verbProps.EffectiveMinRange(allowAdjacentShot: true); + if (minRange < 90f && minRange > 0.1f) GenDraw.DrawRadiusRing(base.Position, minRange); + if (WarmingUp) + { + int degrees = (int)(burstWarmupTicksLeft * 0.5f); + GenDraw.DrawAimPie(this, CurrentTarget, degrees, (float)def.size.x * 0.5f); + } + if (forcedTarget.IsValid && (!forcedTarget.HasThing || forcedTarget.Thing.Spawned)) + { + Vector3 b = forcedTarget.HasThing ? forcedTarget.Thing.TrueCenter() : forcedTarget.Cell.ToVector3Shifted(); + Vector3 a = this.TrueCenter(); + b.y = a.y = AltitudeLayer.MetaOverlays.AltitudeFor(); + GenDraw.DrawLineBetween(a, b, MaterialPool.MatFrom(GenDraw.LineTexPath, ShaderDatabase.Transparent, new Color(1f, 0.5f, 0.5f))); + } + } + + private void ExtractShell() => GenPlace.TryPlaceThing(gun.TryGetComp().RemoveShell(), base.Position, base.Map, ThingPlaceMode.Near); + + private void ResetForcedTarget() + { + forcedTarget = LocalTargetInfo.Invalid; + burstWarmupTicksLeft = 0; + if (burstCooldownTicksLeft <= 0) TryStartShootSomething(canBeginBurstImmediately: false); + } + + private void ResetCurrentTarget() + { + currentTargetInt = LocalTargetInfo.Invalid; + burstWarmupTicksLeft = 0; + } + + public void MakeGun() + { + gun = ThingMaker.MakeThing(def.building.turretGunDef); + UpdateGunVerbs(); + } + + private void UpdateGunVerbs() + { + List allVerbs = gun.TryGetComp().AllVerbs; + for (int i = 0; i < allVerbs.Count; i++) + { + allVerbs[i].caster = this; + allVerbs[i].castCompleteCallback = BurstComplete; + } + } + + } +} \ No newline at end of file diff --git a/Source/WulaFallenEmpire/HarmonyPatches/Patch_CaravanInventoryUtility_FindShuttle.cs b/Source/WulaFallenEmpire/HarmonyPatches/Patch_CaravanInventoryUtility_FindShuttle.cs new file mode 100644 index 00000000..7109ff1a --- /dev/null +++ b/Source/WulaFallenEmpire/HarmonyPatches/Patch_CaravanInventoryUtility_FindShuttle.cs @@ -0,0 +1,37 @@ +using HarmonyLib; +using RimWorld; +using RimWorld.Planet; +using Verse; +using System.Linq; +using System.Collections.Generic; + +namespace WulaFallenEmpire.HarmonyPatches +{ + [HarmonyPatch(typeof(CaravanInventoryUtility), "FindShuttle")] + public static class Patch_CaravanInventoryUtility_FindShuttle + { + [HarmonyPostfix] + public static void Postfix(Caravan caravan, ref Building_PassengerShuttle __result) + { + // If the original method already found a PassengerShuttle, no need to do anything. + if (__result != null) + { + return; + } + + // If original method returned null, try to find our Building_ArmedShuttle + List allInventoryItems = CaravanInventoryUtility.AllInventoryItems(caravan); + foreach (Thing item in allInventoryItems) + { + if (item is Building_ArmedShuttle armedShuttle) + { + Log.Message($"[WULA] Harmony Patch: Found Building_ArmedShuttle ({armedShuttle.Label}) in caravan inventory. Setting as __result."); + // We need to cast our Building_ArmedShuttle to Building_PassengerShuttle + // This is safe because Building_ArmedShuttle is designed to be compatible with Building_PassengerShuttle's interface for caravan purposes. + __result = (Building_PassengerShuttle)armedShuttle; + return; + } + } + } + } +} \ No newline at end of file diff --git a/Source/WulaFallenEmpire/HarmonyPatches/Patch_DropCellFinder_SkyfallerCanLandAt.cs b/Source/WulaFallenEmpire/HarmonyPatches/Patch_DropCellFinder_SkyfallerCanLandAt.cs new file mode 100644 index 00000000..e80079a2 --- /dev/null +++ b/Source/WulaFallenEmpire/HarmonyPatches/Patch_DropCellFinder_SkyfallerCanLandAt.cs @@ -0,0 +1,58 @@ +using HarmonyLib; +using RimWorld; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Reflection.Emit; +using Verse; + +namespace WulaFallenEmpire.HarmonyPatches +{ + [HarmonyPatch(typeof(DropCellFinder), "SkyfallerCanLandAt")] + public static class Patch_DropCellFinder_SkyfallerCanLandAt + { + [HarmonyPrefix] + public static bool Prefix(IntVec3 c, Map map, IntVec2 size, Faction faction, ref bool __result) + { + // 检查 skyfallerThingDef 是否是我们的武装穿梭机 + // 注意:SkyfallerCanLandAt 方法本身没有 skyfallerThingDef 参数。 + // 我们需要判断当前上下文是否是武装穿梭机在尝试降落。 + // 最直接的方式是检查传入的 size 是否与我们的武装穿梭机 ThingDef 的 size 匹配。 + // 这种方式不够精确,但在这个上下文中可能是最接近的。 + // 更好的方式是检查调用堆栈或通过更早的 Patch 传递上下文信息。 + // 但为了快速解决问题,我们先假设 size 匹配即可。 + + // 更好的方法是,在 SkyfallerMaker.SpawnSkyfaller 方法被调用时, + // 我们可以获取到 ThingDef,然后将其存储在一个临时变量中,供后续的 Patch 使用。 + // 但这会引入额外的复杂性。 + // 暂时先用 size 匹配来判断,如果未来出现问题再考虑更复杂的方案。 + + // 考虑到 SkyfallerCanLandAt 通常与 ThingDef.Size 关联,我们尝试通过 ThingDefOf.Shuttle 获取其 Size + // 也可以直接使用硬编码的 (3,5) + // ThingDef shuttleDef = ThingDef.Named("WULA_ArmedShuttle"); + // if (shuttleDef != null && size == shuttleDef.Size) + + // 为了避免对其他 Skyfaller 产生影响,我们只在武装穿梭机相关的逻辑中进行额外的边界检查。 + // 由于 SkyfallerCanLandAt 不直接接收 ThingDef,我们通过 ThingDefOf.Shuttle 来判断是否是默认穿梭机 + // 如果是,并且尺寸与我们的武装穿梭机尺寸 (3,5) 匹配,则进行额外检查。 + // 或者更直接地,假设任何尺寸为 (3,5) 的 Skyfaller 都需要这个检查(如果这是我们Mod独有的尺寸) + + // 这里我们直接根据已知的武装穿梭机尺寸 (3,5) 来判断 + if (size.x == 3 && size.z == 5) + { + // 仅对我们的武装穿梭机执行额外的边界检查 + foreach (IntVec3 occupiedCell in GenAdj.OccupiedRect(c, Rot4.North, size)) + { + if (!occupiedCell.InBounds(map)) + { + Log.Warning($"[WULA] Harmony Patch: SkyfallerCanLandAt - Occupied cell {occupiedCell} for WULA_ArmedShuttle (size: {size}) is out of map bounds. Preventing landing."); + __result = false; + return false; // 阻止原方法执行,并返回 false + } + } + } + return true; // 继续执行原方法 + } + } +} \ No newline at end of file diff --git a/Source/WulaFallenEmpire/WulaFallenEmpire.csproj b/Source/WulaFallenEmpire/WulaFallenEmpire.csproj index e75c9522..c572dd5c 100644 --- a/Source/WulaFallenEmpire/WulaFallenEmpire.csproj +++ b/Source/WulaFallenEmpire/WulaFallenEmpire.csproj @@ -1,5 +1,6 @@  - + Debug @@ -104,6 +105,7 @@ + @@ -166,13 +168,17 @@ - - - - - - - - - + + + + + + + + + + + + \ No newline at end of file