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