diff --git a/1.6/1.6/Assemblies/ArachnaeSwarm.dll b/1.6/1.6/Assemblies/ArachnaeSwarm.dll
index 9b6b9e8..e63f767 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/BackstoryDefs/ARA_BackstoryDef.xml b/1.6/1.6/Defs/BackstoryDefs/ARA_BackstoryDef.xml
index e906996..cefabd7 100644
--- a/1.6/1.6/Defs/BackstoryDefs/ARA_BackstoryDef.xml
+++ b/1.6/1.6/Defs/BackstoryDefs/ARA_BackstoryDef.xml
@@ -173,6 +173,37 @@
+
+ Arachnae_Node_BS_Adult_Skyraider
+ 阿拉克涅空天种
+ 空天种
+ [PAWN_nameDef]是一只阿拉克涅空天种督虫,空天种作为阿拉克涅督虫中的精锐,进化出了强大的飞行能力,是巢穴中无可争议的空中霸主。\n\n作为为数不多拥有飞行能力的虫族,她可以从空中掠袭猎物并将其带至千米高空之上俯冲投下,只留其余猎物在地面无助的挣扎。
+ Adulthood
+
+ Cleaning
+
+ Mining
+ PlantWork
+
+
+ Crafting
+ Cooking
+ Constructing
+ Caring
+ Social
+ Artistic
+ Intellectual
+
+
+ 5
+ 5
+
+
+
+ ArachnaeNode_spawnCategories_Skyraider
+
+
+
Arachnae_Node_BS_Adult_Facehugger
阿拉克涅原虫种
diff --git a/1.6/1.6/Defs/PawnKindDef/ARA_PawnKinds.xml b/1.6/1.6/Defs/PawnKindDef/ARA_PawnKinds.xml
index 51445df..2087809 100644
--- a/1.6/1.6/Defs/PawnKindDef/ARA_PawnKinds.xml
+++ b/1.6/1.6/Defs/PawnKindDef/ARA_PawnKinds.xml
@@ -213,6 +213,29 @@
0
+
+ ArachnaeNode_Race_Skyraider
+
+ ArachnaeNode_Race_Skyraider
+ PlayerColony
+ 0
+
+
+
+ ArachnaeNode_spawnCategoriesA
+ ArachnaeNode_spawnCategories_Fighter
+
+
+
+
+ ARA_BaseRace_Acid_Launcher
+ ARA_AcidSprayBurst
+ ARA_Toxic_Needle_Fire
+
+
+
+ 0
+
PlayerColony
diff --git a/1.6/1.6/Defs/ThingDef_Races/ARA_RaceNodeSwarm.xml b/1.6/1.6/Defs/ThingDef_Races/ARA_RaceNodeSwarm.xml
index 95e503b..7f0bbaf 100644
--- a/1.6/1.6/Defs/ThingDef_Races/ARA_RaceNodeSwarm.xml
+++ b/1.6/1.6/Defs/ThingDef_Races/ARA_RaceNodeSwarm.xml
@@ -807,7 +807,7 @@
-0.275
-
+
-0.275
@@ -911,7 +911,7 @@
-0.275
-
+
@@ -1004,7 +1004,7 @@
-0.275
-
+
@@ -1138,4 +1138,172 @@
+
+
+ ArachnaeNode_Race_Skyraider
+
+ 阿拉克涅督虫中的精锐,进化出了强大的飞行能力,是巢穴中无可争议的空中霸主。\n\n作为为数不多拥有飞行能力的虫族,她可以从空中掠袭猎物并将其带至千米高空之上俯冲投下,只留其余猎物在地面无助的挣扎。
+
+
+
+
+
+
+
+ ArachnaeSwarm/Things/ARA_HiveNode/Addons/ArachnaeNode_Race_Addons_Fighter_Claw
+ true
+
+
+ ArachnaeSwarm/Things/ARA_HiveNode/Addons/ArachnaeNode_Race_Addons_Fighter_Tail
+ false
+
+
+ -0.275
+
+
+
+
+
+
+
+ true
+
+
+
+
+
+
+
+
+
+
+ Wula/Things/WULA_Mech_Flyer/WULA_Mech_Flyer_Flying_
+
+
+ 2
+
+
+ 2
+
+
+
+
+
+
+ 1.35
+
+
+ (0, 0.1, -0.2)
+
+
+ false
+
+
+ Body
+
+
+ 90
+
+
+
+
+
+
+ 40
+
+
+ 40
+
+
+ 15
+
+
+ 10
+
+
+ 1.0
+
+
+
+
+
+
+
+
+ (0, 0)
+ (0.5, 0.6)
+ (1, 1)
+
+
+
+
+
+
+ (0, 1)
+ (0.5, 0.4)
+ (1, 0)
+
+
+
+
+
+
+
+
+
+
+
+ 4.5
+
+
+
+
+ 2
+
+ 100
+ 60
+ 30
+
+ 1.25
+ 1.25
+
+
+
+
+
+
+
+ 0.4
+ 0.4
+ 0.3
+
+
+
+
+
+
+ Cut
+
+ 20
+ 2.5
+ ARA_Sickles
+
+ 0.5
+
+
+
+
+
+ ArachnaeFighter_Body
+ 0.85
+ 2
+ 5
+
+
+ ArachnaeNode_Myrmecocystus_Adult
+ 0
+
+
+
+
\ No newline at end of file
diff --git a/Source/ArachnaeSwarm/ArachnaeSwarm.csproj b/Source/ArachnaeSwarm/ArachnaeSwarm.csproj
index 48555c9..0b17708 100644
--- a/Source/ArachnaeSwarm/ArachnaeSwarm.csproj
+++ b/Source/ArachnaeSwarm/ArachnaeSwarm.csproj
@@ -1,5 +1,6 @@
-
+
Debug
@@ -137,6 +138,11 @@
+
+
+
+
+
diff --git a/Source/ArachnaeSwarm/CompPawnFlight.cs b/Source/ArachnaeSwarm/CompPawnFlight.cs
new file mode 100644
index 0000000..25adcf6
--- /dev/null
+++ b/Source/ArachnaeSwarm/CompPawnFlight.cs
@@ -0,0 +1,127 @@
+using UnityEngine;
+using Verse;
+using Verse.AI;
+using RimWorld;
+using System.Collections.Generic;
+
+namespace ArachnaeSwarm
+{
+ public class CompPawnFlight : ThingComp
+ {
+ private enum FlightState { Grounded, TakingOff, Flying, Landing }
+
+ private FlightState flightState;
+ private int flightTicks = -1;
+ private int flightCooldownTicks;
+ private int lerpTick;
+
+ private Dictionary> cachedGraphics = new Dictionary>();
+ private PawnRenderNode_AnimatedAttachment activeWingNode;
+
+ private Pawn Pawn => (Pawn)parent;
+ public CompProperties_PawnFlight Props => (CompProperties_PawnFlight)props;
+
+ public bool Flying => flightState != FlightState.Grounded; // Public property for Harmony patch
+ public bool ShouldShowWings => flightState != FlightState.Grounded;
+
+ public override void CompTick()
+ {
+ base.CompTick();
+ if (!parent.Spawned) return;
+
+ FlightState oldState = flightState;
+
+ switch (flightState)
+ {
+ case FlightState.TakingOff:
+ lerpTick++;
+ if (lerpTick >= Props.takeoffDurationTicks) { flightState = FlightState.Flying; lerpTick = 0; }
+ break;
+ case FlightState.Landing:
+ lerpTick++;
+ if (lerpTick >= Props.landingDurationTicks) { flightState = FlightState.Grounded; lerpTick = 0; flightCooldownTicks = (int)(Props.flightCooldownSeconds * 60f); }
+ break;
+ case FlightState.Flying:
+ flightTicks++;
+ if (flightTicks >= Props.maxFlightTimeSeconds * 60f) { flightState = FlightState.Landing; }
+ break;
+ case FlightState.Grounded:
+ if (flightCooldownTicks > 0) { flightCooldownTicks--; }
+ break;
+ }
+
+ if (oldState != flightState)
+ {
+ StateChanged();
+ }
+ }
+
+ private void StateChanged()
+ {
+ Pawn.Drawer.renderer.SetAllGraphicsDirty();
+ }
+
+ public void Notify_JobStarted(Job job)
+ {
+ bool isFlyingOrTakingOff = flightState == FlightState.Flying || flightState == FlightState.TakingOff;
+ bool wantsToFly = (job.def.tryStartFlying || (job.def.ifFlyingKeepFlying && isFlyingOrTakingOff));
+ if (wantsToFly && flightState == FlightState.Grounded && flightCooldownTicks <= 0 && Rand.Chance(Props.flightStartChanceOnJobStart))
+ {
+ flightState = FlightState.TakingOff;
+ flightTicks = 0;
+ lerpTick = 0;
+ StateChanged();
+ }
+ else if (!wantsToFly && isFlyingOrTakingOff)
+ {
+ flightState = FlightState.Landing;
+ lerpTick = 0;
+ StateChanged();
+ }
+ }
+
+ public void LinkToRenderNode(PawnRenderNode_AnimatedAttachment node)
+ {
+ activeWingNode = node;
+ }
+
+ public int GetCurrentFrame(int totalFrames)
+ {
+ if (totalFrames == 0) return 0;
+ int currentTickInAnim = (flightState == FlightState.Flying) ? flightTicks : lerpTick;
+ return (currentTickInAnim / Props.ticksPerFrame) % totalFrames;
+ }
+
+ public List GetGraphicsForRotation(Rot4 rot)
+ {
+ if (cachedGraphics.TryGetValue(rot, out var graphics)) return graphics;
+
+ var newGraphics = new List();
+ bool isFemale = Pawn.gender == Gender.Female && !string.IsNullOrEmpty(Props.flyingAnimationFramePathPrefixFemale);
+ string prefix = isFemale ? Props.flyingAnimationFramePathPrefixFemale : Props.flyingAnimationFramePathPrefix;
+ string suffix = (rot == Rot4.North) ? "_north" : (rot == Rot4.South) ? "_south" : "_east";
+
+ if (rot == Rot4.West) suffix = "_east";
+
+ for (int i = 1; i <= Props.flyingAnimationFrameCount; i++)
+ {
+ string path = prefix + i + suffix;
+ Color color = Props.inheritColors ? Pawn.story.SkinColor : Color.white;
+ var graphic = GraphicDatabase.Get(path, ShaderDatabase.Transparent, Vector2.one * Props.drawSize, color);
+ newGraphics.Add(graphic);
+ }
+
+ cachedGraphics[rot] = newGraphics;
+ return newGraphics;
+ }
+
+ public override void PostExposeData()
+ {
+ base.PostExposeData();
+ Scribe_Values.Look(ref flightTicks, "flightTicks", -1);
+ Scribe_Values.Look(ref flightCooldownTicks, "flightCooldownTicks", 0);
+ Scribe_Values.Look(ref lerpTick, "lerpTick", 0);
+ Scribe_Values.Look(ref flightState, "flightState", FlightState.Grounded);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Source/ArachnaeSwarm/CompProperties_PawnFlight.cs b/Source/ArachnaeSwarm/CompProperties_PawnFlight.cs
new file mode 100644
index 0000000..5d0fd77
--- /dev/null
+++ b/Source/ArachnaeSwarm/CompProperties_PawnFlight.cs
@@ -0,0 +1,34 @@
+using Verse;
+using RimWorld;
+using UnityEngine;
+
+namespace ArachnaeSwarm
+{
+ public class CompProperties_PawnFlight : CompProperties
+ {
+ // --- Animation ---
+ public string flyingAnimationFramePathPrefix;
+ public string flyingAnimationFramePathPrefixFemale;
+ public int flyingAnimationFrameCount = 1;
+ public int ticksPerFrame = 2;
+
+ // --- Render Node Properties (Defined directly here) ---
+ public Vector3 offset = Vector3.zero;
+ public float drawSize = 1f;
+ public bool inheritColors = false;
+ public PawnRenderNodeTagDef parentTagDef; // e.g., "Body"
+ public float baseLayer = 85f;
+
+ // --- Flight Mechanics ---
+ public int takeoffDurationTicks = 50;
+ public int landingDurationTicks = 50;
+ public float maxFlightTimeSeconds = 5f;
+ public float flightCooldownSeconds = 2f;
+ public float flightStartChanceOnJobStart = 0.5f;
+
+ public CompProperties_PawnFlight()
+ {
+ compClass = typeof(CompPawnFlight);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Source/ArachnaeSwarm/DynamicPawnRenderNodeSetup_FlightWings.cs b/Source/ArachnaeSwarm/DynamicPawnRenderNodeSetup_FlightWings.cs
new file mode 100644
index 0000000..150205e
--- /dev/null
+++ b/Source/ArachnaeSwarm/DynamicPawnRenderNodeSetup_FlightWings.cs
@@ -0,0 +1,45 @@
+using System;
+using System.Collections.Generic;
+using Verse;
+using RimWorld;
+using HarmonyLib; // Required for AccessTools
+
+namespace ArachnaeSwarm
+{
+ public class DynamicPawnRenderNodeSetup_FlightWings : DynamicPawnRenderNodeSetup
+ {
+ public override bool HumanlikeOnly => false;
+
+ public override IEnumerable<(PawnRenderNode node, PawnRenderNode parent)> GetDynamicNodes(Pawn pawn, PawnRenderTree tree)
+ {
+ CompPawnFlight flightComp = pawn.GetComp();
+ if (flightComp != null && flightComp.ShouldShowWings)
+ {
+ // Create properties directly from CompProperties
+ var nodeProps = new PawnRenderNodeProperties
+ {
+ nodeClass = typeof(PawnRenderNode_AnimatedAttachment),
+ workerClass = AccessTools.TypeByName("Verse.PawnRenderNodeWorker_Flip"),
+ parentTagDef = flightComp.Props.parentTagDef ?? PawnRenderNodeTagDefOf.Body,
+ baseLayer = flightComp.Props.baseLayer
+ };
+
+ // Create a new DrawData struct and set its offset, then assign it.
+ DrawData drawData = new DrawData();
+ typeof(DrawData).GetField("offset").SetValueDirect(__makeref(drawData), flightComp.Props.offset);
+ nodeProps.drawData = drawData;
+
+ if (tree.ShouldAddNodeToTree(nodeProps))
+ {
+ var newNode = (PawnRenderNode_AnimatedAttachment)Activator.CreateInstance(
+ nodeProps.nodeClass, pawn, nodeProps, tree
+ );
+
+ flightComp.LinkToRenderNode(newNode);
+ yield return (node: newNode, parent: null);
+ }
+ }
+ yield break;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Source/ArachnaeSwarm/HarmonyPatches.cs b/Source/ArachnaeSwarm/HarmonyPatches.cs
new file mode 100644
index 0000000..f2a9aa8
--- /dev/null
+++ b/Source/ArachnaeSwarm/HarmonyPatches.cs
@@ -0,0 +1,70 @@
+using HarmonyLib;
+using Verse;
+using System.Reflection;
+using RimWorld;
+
+namespace ArachnaeSwarm
+{
+ [StaticConstructorOnStartup]
+ public static class HarmonyPatches
+ {
+ private static readonly FieldInfo flightField = AccessTools.Field(typeof(Pawn), "flight");
+
+ static HarmonyPatches()
+ {
+ var harmony = new Harmony("com.arachnaeswarm.flightcomp");
+
+ harmony.Patch(AccessTools.Method(typeof(PawnComponentsUtility), nameof(PawnComponentsUtility.AddComponentsForSpawn)),
+ postfix: new HarmonyMethod(typeof(HarmonyPatches), nameof(DisableVanillaFlightTracker)));
+
+ harmony.Patch(AccessTools.PropertyGetter(typeof(Pawn), nameof(Pawn.Flying)),
+ postfix: new HarmonyMethod(typeof(HarmonyPatches), nameof(OverrideFlyingProperty)));
+
+ harmony.Patch(AccessTools.Method(typeof(Pawn), nameof(Pawn.ExposeData)),
+ prefix: new HarmonyMethod(typeof(HarmonyPatches), nameof(PreventVanillaFlightTrackerSave_Prefix)),
+ postfix: new HarmonyMethod(typeof(HarmonyPatches), nameof(PreventVanillaFlightTrackerSave_Postfix)));
+ }
+
+ public static void DisableVanillaFlightTracker(Pawn pawn)
+ {
+ if (pawn.TryGetComp() != null)
+ {
+ flightField?.SetValue(pawn, null);
+ }
+ }
+
+ public static void OverrideFlyingProperty(Pawn __instance, ref bool __result)
+ {
+ var comp = __instance.TryGetComp();
+ if (comp != null)
+ {
+ __result = comp.Flying;
+ }
+ }
+
+ // Correct fix: Use 'object' to store the instance, avoiding direct type reference at compile time.
+ private static object tempFlightTracker;
+
+ public static void PreventVanillaFlightTrackerSave_Prefix(Pawn __instance)
+ {
+ if (__instance.TryGetComp() != null)
+ {
+ object flightTrackerInstance = flightField?.GetValue(__instance);
+ if (flightTrackerInstance != null)
+ {
+ tempFlightTracker = flightTrackerInstance;
+ flightField.SetValue(__instance, null);
+ }
+ }
+ }
+
+ public static void PreventVanillaFlightTrackerSave_Postfix(Pawn __instance)
+ {
+ if (tempFlightTracker != null)
+ {
+ flightField?.SetValue(__instance, tempFlightTracker);
+ tempFlightTracker = null;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Source/ArachnaeSwarm/PawnRenderNode_AnimatedAttachment.cs b/Source/ArachnaeSwarm/PawnRenderNode_AnimatedAttachment.cs
new file mode 100644
index 0000000..8a954a6
--- /dev/null
+++ b/Source/ArachnaeSwarm/PawnRenderNode_AnimatedAttachment.cs
@@ -0,0 +1,38 @@
+using System.Collections.Generic;
+using UnityEngine;
+using Verse;
+
+namespace ArachnaeSwarm
+{
+ public class PawnRenderNode_AnimatedAttachment : PawnRenderNode
+ {
+ private CompPawnFlight flightComp;
+ private List cachedGraphics;
+
+ public PawnRenderNode_AnimatedAttachment(Pawn pawn, PawnRenderNodeProperties props, PawnRenderTree tree) : base(pawn, props, tree)
+ {
+ flightComp = pawn.GetComp();
+ }
+
+ public override Graphic GraphicFor(Pawn pawn)
+ {
+ if (flightComp == null) return null;
+
+ if (cachedGraphics == null)
+ {
+ cachedGraphics = flightComp.GetGraphicsForRotation(pawn.Rotation);
+ }
+
+ if (cachedGraphics.NullOrEmpty()) return null;
+
+ int frame = flightComp.GetCurrentFrame(cachedGraphics.Count);
+ return cachedGraphics[frame];
+ }
+
+ // We might need to override this if west-facing graphics need to be flipped.
+ // public override Mesh GetMesh(PawnDrawParms parms)
+ // {
+ // return base.GetMesh(parms);
+ // }
+ }
+}
\ No newline at end of file