整理scoure

This commit is contained in:
2025-10-31 09:57:45 +08:00
parent 9e6aa98830
commit 8fee1bcfba
103 changed files with 5547 additions and 916 deletions

View File

@@ -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<CompLaunchable>()?.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,
};
}
}
}

View File

@@ -0,0 +1,598 @@
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, IAttackTarget, 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 ---
Thing IAttackTarget.Thing => this;
public LocalTargetInfo TargetCurrentlyAimingAt => CurrentTarget;
public float TargetPriorityFactor => 1f;
public virtual Material TurretTopMaterial => def.building.turretTopMat;
protected bool IsStunned
{
get
{
if (!triedGettingStunner)
{
stunner = GetComp<CompStunnable>()?.StunHandler;
triedGettingStunner = true;
}
return stunner != null && stunner.Stunned;
}
}
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<CompEquippable>();
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 || GetComp<CompForceTargetable>() != 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<CompChangeableProjectile>()?.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<CompCanBeDormant>();
initiatableComp = GetComp<CompInitiatable>();
powerComp = GetComp<CompPowerTrader>();
mannableComp = GetComp<CompMannable>();
interactableComp = GetComp<CompInteractable>();
refuelableComp = GetComp<CompRefuelable>();
powerCellComp = GetComp<CompMechPowerCell>();
hackableComp = GetComp<CompHackable>();
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<CompChangeableProjectile>();
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<Gizmo> GetGizmos()
{
foreach (Gizmo gizmo in base.GetGizmos()) yield return gizmo;
if (CanExtractShell)
{
CompChangeableProjectile compChangeableProjectile = gun.TryGetComp<CompChangeableProjectile>();
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<CompChangeableProjectile>();
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<Texture2D>.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<Texture2D>.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<Texture2D>.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;
}
// 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<CompChangeableProjectile>();
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<CompChangeableProjectile>().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<Verb> allVerbs = gun.TryGetComp<CompEquippable>().AllVerbs;
for (int i = 0; i < allVerbs.Count; i++)
{
allVerbs[i].caster = this;
allVerbs[i].castCompleteCallback = BurstComplete;
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,167 @@
using RimWorld;
using Verse;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using System.Reflection;
namespace WulaFallenEmpire
{
/// <summary>
/// 口袋空间退出点建筑 - 继承自MapPortal以获得完整的双向传送功能
/// </summary>
public class Building_PocketMapExit : MapPortal
{
/// <summary>目标地图</summary>
public Map targetMap;
/// <summary>目标位置</summary>
public IntVec3 targetPos;
/// <summary>父穿梭机</summary>
public Building_ArmedShuttleWithPocket parentShuttle;
public override void ExposeData()
{
base.ExposeData();
Scribe_References.Look(ref targetMap, "targetMap");
Scribe_Values.Look(ref targetPos, "targetPos");
Scribe_References.Look(ref parentShuttle, "parentShuttle");
}
/// <summary>
/// 重写获取其他地图返回主地图模仿原版MapPortal.GetOtherMap
/// </summary>
public override Map GetOtherMap()
{
// 动态更新目标地图,处理穿梭机移动的情况
UpdateTargetFromParentShuttle();
return targetMap;
}
/// <summary>
/// 重写获取目标位置返回主地图上的穿梭机位置模仿原版MapPortal.GetDestinationLocation
/// </summary>
public override IntVec3 GetDestinationLocation()
{
// 动态更新目标位置,处理穿梭机移动的情况
UpdateTargetFromParentShuttle();
return targetPos;
}
/// <summary>
/// 从父穿梭机动态更新目标位置,处理穿梭机移动的情况
/// </summary>
private void UpdateTargetFromParentShuttle()
{
if (parentShuttle != null && parentShuttle.Spawned)
{
// 如果穿梭机还在地图上,更新目标位置
if (targetMap != parentShuttle.Map || targetPos != parentShuttle.Position)
{
targetMap = parentShuttle.Map;
targetPos = parentShuttle.Position;
Log.Message($"[WULA] Updated exit target to shuttle location: {targetMap?.uniqueID} at {targetPos}");
}
}
else if (parentShuttle != null && !parentShuttle.Spawned)
{
// 穿梭机不在地图上(可能在飞行中)
// 保持原有目标,但记录警告
if (this.IsHashIntervalTick(2500)) // 每隔一段时间检查一次
{
Log.Warning($"[WULA] Parent shuttle is not spawned, exit target may be outdated. Last known: {targetMap?.uniqueID} at {targetPos}");
}
}
}
/// <summary>
/// 重写是否可进入检查目标地图是否存在及传送状态模仿原版MapPortal.IsEnterable
/// </summary>
public override bool IsEnterable(out string reason)
{
if (targetMap == null)
{
reason = "WULA.PocketSpace.NoTargetMap".Translate();
return false;
}
// 检查父穿梭机的传送状态
if (parentShuttle != null)
{
// 使用反射获取 transportDisabled 字段值
var transportDisabledField = typeof(Building_ArmedShuttleWithPocket).GetField("transportDisabled",
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
if (transportDisabledField != null)
{
bool transportDisabled = (bool)transportDisabledField.GetValue(parentShuttle);
if (transportDisabled)
{
reason = "WULA.PocketSpace.TransportDisabled".Translate();
return false;
}
}
}
reason = "";
return true;
}
/// <summary>
/// 重写进入事件处理从口袋空间退出到主地图模仿原版MapPortal.OnEntered
/// </summary>
public override void OnEntered(Pawn pawn)
{
// 不调用 base.OnEntered因为我们不需要原版的通知机制
// 直接处理退出逻辑
if (targetMap != null && pawn.Spawned)
{
ExitPocketSpace(pawn);
}
}
/// <summary>
/// 重写进入按钮文本
/// </summary>
public override string EnterString => "WULA.PocketSpace.ExitToMainMap".Translate();
/// <summary>
/// 重写进入按钮图标,使用装载按钮的贴图
/// </summary>
protected override Texture2D EnterTex => ContentFinder<Texture2D>.Get("UI/Commands/LoadTransporter");
/// <summary>
/// 单个人员退出口袋空间简化版本利用MapPortal功能
/// </summary>
private void ExitPocketSpace(Pawn pawn)
{
if (targetMap == null || !pawn.Spawned) return;
try
{
// 在目标地图找一个安全位置
IntVec3 exitPos = CellFinder.RandomClosewalkCellNear(targetPos, targetMap, 3, p => p.Standable(targetMap));
// 传送人员
pawn.DeSpawn();
GenPlace.TryPlaceThing(pawn, exitPos, targetMap, ThingPlaceMode.Near);
// 切换到主地图
if (pawn.IsColonistPlayerControlled)
{
Current.Game.CurrentMap = targetMap;
Find.CameraDriver.JumpToCurrentMapLoc(exitPos);
}
Messages.Message("WULA.PocketSpace.ExitSuccess".Translate(pawn.LabelShort), MessageTypeDefOf.PositiveEvent);
}
catch (System.Exception ex)
{
Log.Error($"[WULA] Error exiting pocket space: {ex}");
}
}
}
}

View File

@@ -0,0 +1,250 @@
using System;
using System.Collections.Generic;
using System.Linq;
using RimWorld;
using RimWorld.Planet;
using UnityEngine;
using Verse;
using Verse.Sound;
namespace WulaFallenEmpire
{
public class Dialog_ArmedShuttleTransfer : Window
{
private enum Tab
{
Pawns,
Items
}
private const float TitleRectHeight = 35f;
private const float BottomAreaHeight = 55f;
private readonly Vector2 BottomButtonSize = new Vector2(160f, 40f);
private Building_ArmedShuttleWithPocket shuttle;
private List<TransferableOneWay> transferables;
private TransferableOneWayWidget pawnsTransfer;
private TransferableOneWayWidget itemsTransfer;
private Tab tab;
private static List<TabRecord> tabsList = new List<TabRecord>();
public override Vector2 InitialSize => new Vector2(1024f, UI.screenHeight);
protected override float Margin => 0f;
public Dialog_ArmedShuttleTransfer(Building_ArmedShuttleWithPocket shuttle)
{
this.shuttle = shuttle;
forcePause = true;
absorbInputAroundWindow = true;
}
public override void PostOpen()
{
base.PostOpen();
CalculateAndRecacheTransferables();
}
public override void DoWindowContents(Rect inRect)
{
Rect rect = new Rect(0f, 0f, inRect.width, TitleRectHeight);
using (new TextBlock(GameFont.Medium, TextAnchor.MiddleCenter))
{
Widgets.Label(rect, shuttle.EnterString);
}
tabsList.Clear();
tabsList.Add(new TabRecord("PawnsTab".Translate(), delegate
{
tab = Tab.Pawns;
}, tab == Tab.Pawns));
tabsList.Add(new TabRecord("ItemsTab".Translate(), delegate
{
tab = Tab.Items;
}, tab == Tab.Items));
inRect.yMin += 67f;
Widgets.DrawMenuSection(inRect);
TabDrawer.DrawTabs(inRect, tabsList);
inRect = inRect.ContractedBy(17f);
Widgets.BeginGroup(inRect);
Rect rect2 = inRect.AtZero();
DoBottomButtons(rect2);
Rect inRect2 = rect2;
inRect2.yMax -= 76f;
bool anythingChanged = false;
switch (tab)
{
case Tab.Pawns:
pawnsTransfer.OnGUI(inRect2, out anythingChanged);
break;
case Tab.Items:
itemsTransfer.OnGUI(inRect2, out anythingChanged);
break;
}
Widgets.EndGroup();
}
private void DoBottomButtons(Rect rect)
{
float buttonY = rect.height - BottomAreaHeight - 17f;
if (Widgets.ButtonText(new Rect(rect.width / 2f - BottomButtonSize.x / 2f, buttonY, BottomButtonSize.x, BottomButtonSize.y), "ResetButton".Translate()))
{
SoundDefOf.Tick_Low.PlayOneShotOnCamera();
CalculateAndRecacheTransferables();
}
if (Widgets.ButtonText(new Rect(0f, buttonY, BottomButtonSize.x, BottomButtonSize.y), "CancelButton".Translate()))
{
Close();
}
if (Widgets.ButtonText(new Rect(rect.width - BottomButtonSize.x, buttonY, BottomButtonSize.x, BottomButtonSize.y), "AcceptButton".Translate()) && TryAccept())
{
SoundDefOf.Tick_High.PlayOneShotOnCamera();
Close(doCloseSound: false);
}
}
private bool TryAccept()
{
// 获取选中的Pawn和物品
List<Pawn> pawnsToTransfer = TransferableUtility.GetPawnsFromTransferables(transferables);
List<Thing> itemsToTransfer = new List<Thing>();
foreach (TransferableOneWay transferable in transferables)
{
if (transferable.ThingDef.category != ThingCategory.Pawn)
{
itemsToTransfer.AddRange(transferable.things.Take(transferable.CountToTransfer));
}
}
// 传送Pawn到口袋空间
int transferredPawnCount = 0;
foreach (Pawn pawn in pawnsToTransfer)
{
if (shuttle.TransferPawnToPocketSpace(pawn))
{
transferredPawnCount++;
}
}
int transferredItemCount = 0;
foreach (Thing item in itemsToTransfer)
{
// 从当前地图移除物品
item.DeSpawn();
// 尝试放置到口袋空间地上
IntVec3 dropPos = CellFinder.RandomClosewalkCellNear(shuttle.PocketMap.Center, shuttle.PocketMap, 5); // 随机位置,避免重叠
if (dropPos.IsValid)
{
GenPlace.TryPlaceThing(item, dropPos, shuttle.PocketMap, ThingPlaceMode.Near);
transferredItemCount++;
}
else
{
Log.Error($"[WULA-ERROR] Could not find valid drop position for item {item.LabelShort} in pocket map.");
item.Destroy(); // 实在没地方放,就销毁
}
}
if (transferredPawnCount > 0 || transferredItemCount > 0)
{
Messages.Message("WULA.PocketSpace.TransferSuccess".Translate(transferredPawnCount + transferredItemCount), MessageTypeDefOf.PositiveEvent);
// 切换到口袋地图视角如果传送了Pawn
if (transferredPawnCount > 0)
{
Current.Game.CurrentMap = shuttle.PocketMap;
Find.CameraDriver.JumpToCurrentMapLoc(shuttle.PocketMap.Center);
}
}
else
{
Messages.Message("WULA.PocketSpace.NoPawnsOrItemsSelected".Translate(), MessageTypeDefOf.RejectInput);
return false;
}
return true;
}
private void CalculateAndRecacheTransferables()
{
transferables = new List<TransferableOneWay>();
// 根据需要添加现有物品到transferables如果穿梭机已有物品
// 目前我们从头开始构建列表只添加地图上的物品和Pawn
AddPawnsToTransferables();
AddItemsToTransferables();
// 重新创建TransferableOneWayWidget实例
pawnsTransfer = new TransferableOneWayWidget(null, null, null, "TransferMapPortalColonyThingCountTip".Translate(),
drawMass: true,
ignorePawnInventoryMass: IgnorePawnsInventoryMode.IgnoreIfAssignedToUnload,
includePawnsMassInMassUsage: true,
availableMassGetter: () => float.MaxValue,
extraHeaderSpace: 0f,
ignoreSpawnedCorpseGearAndInventoryMass: false,
tile: shuttle.Map.Tile,
drawMarketValue: false,
drawEquippedWeapon: true);
CaravanUIUtility.AddPawnsSections(pawnsTransfer, transferables);
itemsTransfer = new TransferableOneWayWidget(transferables.Where(x => x.ThingDef.category != ThingCategory.Pawn), null, null, "TransferMapPortalColonyThingCountTip".Translate(),
drawMass: true,
ignorePawnInventoryMass: IgnorePawnsInventoryMode.IgnoreIfAssignedToUnload,
includePawnsMassInMassUsage: true,
availableMassGetter: () => float.MaxValue,
extraHeaderSpace: 0f,
ignoreSpawnedCorpseGearAndInventoryMass: false,
tile: shuttle.Map.Tile);
}
private void AddToTransferables(Thing t)
{
TransferableOneWay transferableOneWay = TransferableUtility.TransferableMatching(t, transferables, TransferAsOneMode.PodsOrCaravanPacking);
if (transferableOneWay == null)
{
transferableOneWay = new TransferableOneWay();
transferables.Add(transferableOneWay);
}
if (transferableOneWay.things.Contains(t))
{
Log.Error("Tried to add the same thing twice to TransferableOneWay: " + t);
}
else
{
transferableOneWay.things.Add(t);
}
}
private void AddPawnsToTransferables()
{
foreach (Pawn item in CaravanFormingUtility.AllSendablePawns(shuttle.Map, allowEvenIfDowned: true, allowEvenIfInMentalState: false, allowEvenIfPrisonerNotSecure: false, allowCapturableDownedPawns: false, allowLodgers: true))
{
AddToTransferables(item);
}
}
private void AddItemsToTransferables()
{
// 考虑是否需要处理口袋地图中的物品
bool isPocketMap = shuttle.Map.IsPocketMap;
foreach (Thing item in CaravanFormingUtility.AllReachableColonyItems(shuttle.Map, isPocketMap, isPocketMap))
{
AddToTransferables(item);
}
}
public override void OnAcceptKeyPressed()
{
if (TryAccept())
{
SoundDefOf.Tick_High.PlayOneShotOnCamera();
Close(doCloseSound: false);
}
}
}
}

View File

@@ -0,0 +1,278 @@
using System;
using System.Collections.Generic;
using System.Linq;
using RimWorld;
using Verse;
namespace WulaFallenEmpire
{
/// <summary>
/// 13x13小型口袋空间生成器
/// 创建一个简单的13x13空间边缘是墙中间是空地适合作为穿梭机内部空间
/// </summary>
public class GenStep_WulaPocketSpaceSmall : GenStep
{
public override int SeedPart => 928735; // 不同于AncientStockpile的种子
// 允许通过XML配置指定要生成的预制件Def名称
public string prefabDefName;
public override void Generate(Map map, GenStepParams parms)
{
try
{
Log.Message($"[WULA] Generating WULA pocket space, map size: {map.Size}");
// 获取地图边界
IntVec3 mapSize = map.Size;
// 生成外围岩石墙壁
GenerateWalls(map);
// 生成内部地板
GenerateFloor(map);
Log.Message("[WULA] WULA pocket space generation completed");
// 添加预制件生成
// 如果指定了预制件Def名称则加载并生成
if (!string.IsNullOrEmpty(prefabDefName))
{
PrefabDef customPrefabDef = DefDatabase<PrefabDef>.GetNamed(prefabDefName, false);
if (customPrefabDef != null)
{
GeneratePrefab(map, customPrefabDef);
Log.Message($"[WULA] Generated custom prefab: {customPrefabDef.defName}");
}
else
{
Log.Warning($"[WULA] Custom prefab '{prefabDefName}' not found. Skipping prefab generation.");
}
}
}
catch (Exception ex)
{
Log.Error($"[WULA] Error generating WULA pocket space: {ex}");
}
}
/// <summary>
/// 生成外围墙壁
/// </summary>
private void GenerateWalls(Map map)
{
IntVec3 mapSize = map.Size;
// 获取地形和物品定义
TerrainDef roughTerrain = DefDatabase<TerrainDef>.GetNamed("Granite_Rough", false) ??
DefDatabase<TerrainDef>.GetNamed("Granite_Smooth", false) ??
DefDatabase<TerrainDef>.GetNamed("Sandstone_Rough", false);
ThingDef rockWallDef = DefDatabase<ThingDef>.GetNamed("Wall_Rock", false) ??
DefDatabase<ThingDef>.GetNamed("Wall", false);
// 遍历地图边缘放置WulaWall
for (int x = 0; x < mapSize.x; x++)
{
for (int z = 0; z < mapSize.z; z++)
{
// 如果是边缘位置放置WulaWall
if (x == 0 || x == mapSize.x - 1 || z == 0 || z == mapSize.z - 1)
{
IntVec3 pos = new IntVec3(x, 0, z);
// 设置地形为岩石基础
if (roughTerrain != null)
{
map.terrainGrid.SetTerrain(pos, roughTerrain);
}
// 放置WulaWall
ThingDef wallDef = DefDatabase<ThingDef>.GetNamed("WulaWall", false);
if (wallDef != null)
{
// WulaWall是madeFromStuff的建筑需要指定材料
ThingDef steelDef = DefDatabase<ThingDef>.GetNamed("Steel", false);
Thing wall = ThingMaker.MakeThing(wallDef, steelDef);
wall.SetFaction(null);
GenPlace.TryPlaceThing(wall, pos, map, ThingPlaceMode.Direct);
}
else if (rockWallDef != null)
{
// 如果WulaWall不存在使用原版岩石墙作为备选
Thing wall = ThingMaker.MakeThing(rockWallDef);
wall.SetFaction(null);
GenPlace.TryPlaceThing(wall, pos, map, ThingPlaceMode.Direct);
Log.Warning("[WULA] WulaWall not found, using fallback wall");
}
}
}
}
}
/// <summary>
/// 生成内部地板
/// </summary>
private void GenerateFloor(Map map)
{
IntVec3 mapSize = map.Size;
// 为内部区域设置WulaFloor
TerrainDef floorDef = DefDatabase<TerrainDef>.GetNamed("WulaFloor", false);
TerrainDef fallbackFloor = floorDef ??
DefDatabase<TerrainDef>.GetNamed("Steel", false) ??
DefDatabase<TerrainDef>.GetNamed("MetalTile", false) ??
DefDatabase<TerrainDef>.GetNamed("Concrete", false);
if (floorDef == null)
{
Log.Warning("[WULA] WulaFloor not found, using fallback floor");
}
// 清理内部区域并设置正确的地板
for (int x = 1; x < mapSize.x - 1; x++)
{
for (int z = 1; z < mapSize.z - 1; z++)
{
IntVec3 pos = new IntVec3(x, 0, z);
// 清理该位置的所有岩石和阻挡物
ClearCellAndSetFloor(map, pos, fallbackFloor);
}
}
Log.Message($"[WULA] Set floor for internal area ({mapSize.x-2}x{mapSize.z-2}) to {(floorDef?.defName ?? fallbackFloor?.defName)}");
}
/// <summary>
/// 清理单元格并设置地板
/// </summary>
private void ClearCellAndSetFloor(Map map, IntVec3 pos, TerrainDef floorDef)
{
if (!pos.InBounds(map)) return;
try
{
// 获取该位置的所有物品
List<Thing> thingsAtPos = pos.GetThingList(map).ToList(); // 创建副本避免修改时出错
// 清理所有建筑物和岩石(强力清理,确保地板可以放置)
foreach (Thing thing in thingsAtPos)
{
bool shouldRemove = false;
// 检查是否为建筑物
if (thing.def.category == ThingCategory.Building)
{
// 如果是自然岩石
if (thing.def.building?.isNaturalRock == true)
{
shouldRemove = true;
}
// 或者是岩石相关的建筑
else if (thing.def.defName.Contains("Rock") ||
thing.def.defName.Contains("Slate") ||
thing.def.defName.Contains("Granite") ||
thing.def.defName.Contains("Sandstone") ||
thing.def.defName.Contains("Limestone") ||
thing.def.defName.Contains("Marble") ||
thing.def.defName.Contains("Quartzite") ||
thing.def.defName.Contains("Jade"))
{
shouldRemove = true;
}
// 或者是其他阻挡的建筑物(除了我们的乌拉墙)
else if (!thing.def.defName.Contains("Wula") && thing.def.Fillage == FillCategory.Full)
{
shouldRemove = true;
}
}
if (shouldRemove)
{
if (Prefs.DevMode) // 只在开发模式下输出详细日志
{
Log.Message($"[WULA] Removing {thing.def.defName} at {pos} to make space for floor");
}
thing.Destroy(DestroyMode.Vanish);
}
}
// 在清理后稍微延迟,再检查一次(确保彻底清理)
thingsAtPos = pos.GetThingList(map).ToList();
foreach (Thing thing in thingsAtPos)
{
if (thing.def.category == ThingCategory.Building && thing.def.Fillage == FillCategory.Full)
{
Log.Warning($"[WULA] Force removing remaining building {thing.def.defName} at {pos}");
thing.Destroy(DestroyMode.Vanish);
}
}
// 设置地板地形
if (floorDef != null)
{
map.terrainGrid.SetTerrain(pos, floorDef);
if (Prefs.DevMode)
{
Log.Message($"[WULA] Set terrain at {pos} to {floorDef.defName}");
}
}
}
catch (Exception ex)
{
Log.Error($"[WULA] Error clearing cell at {pos}: {ex}");
}
}
/// <summary>
/// 生成预制件
/// </summary>
private void GeneratePrefab(Map map, PrefabDef prefabDef)
{
if (prefabDef == null)
{
Log.Error("[WULA] PrefabDef is null, cannot generate prefab.");
return;
}
// 获取预制件的中心点,将其放置在口袋空间的中心
IntVec3 mapCenter = map.Center;
IntVec3 prefabOrigin = mapCenter - new IntVec3(prefabDef.size.x / 2, 0, prefabDef.size.z / 2);
// 生成物品
foreach (var thingData in prefabDef.GetThings())
{
IntVec3 thingPos = prefabOrigin + thingData.cell;
if (thingPos.InBounds(map))
{
Thing thing = ThingMaker.MakeThing(thingData.data.def, thingData.data.stuff);
if (thing != null)
{
// PrefabThingData 不包含 factionDef派系通常在生成时由上下文决定
// thing.SetFaction(thingData.data.factionDef != null ? Faction.OfPlayerSilentFail : null);
GenPlace.TryPlaceThing(thing, thingPos, map, ThingPlaceMode.Direct);
}
}
}
// 生成地形
foreach (var terrainData in prefabDef.GetTerrain())
{
IntVec3 terrainPos = prefabOrigin + terrainData.cell;
if (terrainPos.InBounds(map))
{
map.terrainGrid.SetTerrain(terrainPos, terrainData.data.def);
}
}
// 递归生成子预制件(如果存在)
foreach (var subPrefabData in prefabDef.GetPrefabs())
{
// 这里需要递归调用GeneratePrefab但为了简化暂时只处理顶层
// 实际项目中,可能需要更复杂的逻辑来处理子预制件的位置和旋转
Log.Warning($"[WULA] Sub-prefabs are not fully supported in this simple generator: {subPrefabData.data.def.defName}");
}
}
}
}

View File

@@ -0,0 +1,71 @@
using System;
using System.Collections.Generic;
using Verse;
namespace WulaFallenEmpire
{
/// <summary>
/// 用于武装穿梭机口袋空间的IThingHolder实现与CompTransporter的容器分离
/// </summary>
public class PocketSpaceThingHolder : IThingHolder, IExposable
{
/// <summary>持有的物品容器</summary>
public ThingOwner<Thing> innerContainer;
/// <summary>该容器的拥有者通常是Building_ArmedShuttleWithPocket</summary>
private IThingHolder owner;
/// <summary>实现IThingHolder.ParentHolder属性</summary>
public IThingHolder ParentHolder => owner;
public PocketSpaceThingHolder()
{
innerContainer = new ThingOwner<Thing>(this);
}
public PocketSpaceThingHolder(IThingHolder owner) : this()
{
this.owner = owner;
}
/// <summary>
/// 获取直接持有的物品
/// </summary>
public ThingOwner GetDirectlyHeldThings()
{
return innerContainer;
}
/// <summary>
/// 获取子持有者
/// </summary>
public void GetChildHolders(List<IThingHolder> outChildren)
{
// 目前没有子持有者,留空
}
/// <summary>
/// 通知物品被添加
/// </summary>
public void Notify_ThingAdded(Thing t)
{
// 这里可以添加逻辑来处理物品被添加到口袋空间的情况
Log.Message($"[WULA] Item {t.LabelCap} added to pocket space container.");
}
/// <summary>
/// 通知物品被移除
/// </summary>
public void Notify_ThingRemoved(Thing t)
{
// 这里可以添加逻辑来处理物品被从口袋空间移除的情况
Log.Message($"[WULA] Item {t.LabelCap} removed from pocket space container.");
}
public void ExposeData()
{
Scribe_Deep.Look(ref innerContainer, "innerContainer", this);
// owner 通常在构造函数中设置,不需要序列化
}
}
}