This commit is contained in:
2025-10-12 18:16:36 +08:00
parent f16972ffc9
commit 361098e7ed
11 changed files with 344 additions and 25 deletions

Binary file not shown.

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8" ?>
<Defs>
<HediffDef>
<defName>ARA_PowerArmor_NoFuel</defName>
<label>外骨骼营养不良</label>
<description>生物外骨骼因缺少养分而营养不良,这会对宿主造成不良影响.</description>
<hediffClass>HediffWithComps</hediffClass>
<defaultLabelColor>(0.6, 0.6, 0.6)</defaultLabelColor>
<isBad>true</isBad>
<scenarioCanAdd>false</scenarioCanAdd>
<stages>
<li>
<capMods>
<li>
<capacity>Moving</capacity>
<offset>-0.4</offset>
</li>
</capMods>
</li>
</stages>
</HediffDef>
</Defs>

View File

@@ -349,6 +349,7 @@
<li>ARA_Latex_Catsuit</li>
<li>ARA_Pantyhose_Black</li>
<li>ARA_Pantyhose_White</li>
<li>ARA_SpiderOne_PowerArmor</li>
</apparelList>
<blackApparelList>
<li>Apparel_AdvancedHelmet</li>

View File

@@ -1,31 +1,54 @@
<?xml version="1.0" encoding="utf-8" ?>
<Defs>
<!-- 动力甲 - 建筑形态 (基类) -->
<ThingDef Name="ARA_PowerArmorBuilding_Base" ParentName="BuildingBase" Abstract="True">
<thingClass>Building</thingClass>
<!-- 动力甲 - 建筑形态 -->
<ThingDef Name="ARA_PowerArmorBuilding_Base" ParentName="BuildingBase">
<defName>ARA_Building_SpiderOne</defName>
<label>阿拉克涅动力装甲</label>
<description>阿拉克涅动力装甲</description>
<graphicData>
<graphicClass>Graphic_Multi</graphicClass>
<drawSize>(3,3)</drawSize>
<drawSize>(1,1)</drawSize>
<texPath>ArachnaeSwarm/Apparel/ARA_Bunny_Girl_Uniform</texPath>
</graphicData>
<thingClass>Building</thingClass>
<altitudeLayer>Building</altitudeLayer>
<passability>PassThroughOnly</passability>
<pathCost>70</pathCost>
<castEdgeShadows>true</castEdgeShadows>
<fillPercent>0.5</fillPercent>
<canOverlapZones>false</canOverlapZones>
<size>(2,2)</size>
<size>(1,1)</size>
<designationCategory>Misc</designationCategory>
<rotatable>true</rotatable>
<selectable>true</selectable>
<tickerType>Never</tickerType>
<tickerType>Normal</tickerType>
<drawerType>MapMeshAndRealTime</drawerType>
<hasInteractionCell>true</hasInteractionCell>
<interactionCellOffset>(0,0,-1)</interactionCellOffset>
<statBases>
<MaxHitPoints>500</MaxHitPoints>
<WorkToBuild>2000</WorkToBuild>
<Mass>50</Mass>
<Flammability>0.5</Flammability>
</statBases>
<comps>
<li Class="ArachnaeSwarm.CompProperties_PowerArmorStation">
<apparelDef>ARA_SpiderOne_PowerArmor</apparelDef>
</li>
<li Class="ArachnaeSwarm.CompProperties_RefuelableNutrition">
<fuelCapacity>10.0</fuelCapacity>
<fuelFilter>
<categories>
<li>FoodMeals</li>
<li>FoodRaw</li>
</categories>
</fuelFilter>
<initialFuelPercent>1</initialFuelPercent>
<autoRefuelPercent>0.5</autoRefuelPercent>
<showFuelGizmo>true</showFuelGizmo>
</li>
</comps>
</ThingDef>
<ThingDef ParentName="ARA_ClothBase">
@@ -40,6 +63,8 @@
<li Class="ArachnaeSwarm.PowerArmorExtension">
<structurePointsMax>500</structurePointsMax>
<buildingDef>ARA_Building_SpiderOne</buildingDef>
<hediffOnEmptyFuel>ARA_PowerArmor_NoFuel</hediffOnEmptyFuel>
<fuelConsumptionRate>0.5</fuelConsumptionRate>
</li>
</modExtensions>
<recipeMaker>
@@ -83,19 +108,18 @@
<li>ARA_BioforgeIncubator_Thing</li>
</cocoonDefs>
</li>
</comps>
</ThingDef>
<ThingDef ParentName="ARA_PowerArmorBuilding_Base">
<defName>ARA_Building_SpiderOne</defName>
<label>阿拉克涅动力装甲</label>
<description>阿拉克涅动力装甲</description>
<graphicData>
<texPath>ArachnaeSwarm/Apparel/ARA_Bunny_Girl_Uniform</texPath>
</graphicData>
<comps>
<li Class="ArachnaeSwarm.CompProperties_PowerArmorStation">
<apparelDef>ARA_SpiderOne_PowerArmor</apparelDef>
<li Class="ArachnaeSwarm.CompProperties_RefuelableNutrition">
<fuelCapacity>10.0</fuelCapacity>
<fuelFilter>
<categories>
<li>FoodMeals</li>
<li>FoodRaw</li>
</categories>
</fuelFilter>
<initialFuelPercent>1</initialFuelPercent>
<autoRefuelPercent>0.5</autoRefuelPercent>
<showFuelGizmo>true</showFuelGizmo>
<consumeFuelOnlyWhenUsed>true</consumeFuelOnlyWhenUsed>
</li>
</comps>
</ThingDef>

View File

@@ -77,8 +77,28 @@
<li Class="ArachnaeSwarm.PowerArmorExtension">
<buildingDef>ARA_Building_SpiderOne</buildingDef>
<structurePointsMax>625</structurePointsMax>
<hediffOnEmptyFuel>ARA_PowerArmor_NoFuel</hediffOnEmptyFuel>
<fuelConsumptionRate>0.5</fuelConsumptionRate>
</li>
</modExtensions>
<comps>
<li Class="ArachnaeSwarm.CompProperties_RefuelableNutrition">
<fuelCapacity>10.0</fuelCapacity>
<fuelFilter>
<thingDefs>
<li>Meat_Human</li>
</thingDefs>
<categories>
<li>FoodMeals</li>
<li>FoodRaw</li>
</categories>
</fuelFilter>
<initialFuelPercent>1</initialFuelPercent>
<autoRefuelPercent>0.5</autoRefuelPercent>
<showFuelGizmo>true</showFuelGizmo>
<consumeFuelOnlyWhenUsed>true</consumeFuelOnlyWhenUsed>
</li>
</comps>
</ThingDef>
<ThingDef ParentName="ARA_PowerArmorBuilding_Base">
@@ -92,6 +112,21 @@
<li Class="ArachnaeSwarm.CompProperties_PowerArmorStation">
<apparelDef>ARA_Apparel_SpiderOne</apparelDef>
</li>
<li Class="ArachnaeSwarm.CompProperties_RefuelableNutrition">
<fuelCapacity>10.0</fuelCapacity>
<fuelFilter>
<thingDefs>
<li>Meat_Human</li>
</thingDefs>
<categories>
<li>FoodMeals</li>
<li>FoodRaw</li>
</categories>
</fuelFilter>
<initialFuelPercent>1</initialFuelPercent>
<autoRefuelPercent>0.5</autoRefuelPercent>
<showFuelGizmo>true</showFuelGizmo>
</li>
</comps>
</ThingDef>

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8" ?>
<LanguageData>
<!-- Power Armor Interaction -->
<EnterPowerArmor>Enter {0}</EnterPowerArmor>
<CannotEnterPowerArmor>Cannot enter power armor</CannotEnterPowerArmor>
<PowerArmorBroken>{0} has been broken!</PowerArmorBroken>
<!-- Gizmo Labels -->
<StructurePoints>Structure</StructurePoints>
</LanguageData>

View File

@@ -271,6 +271,8 @@
<Compile Include="PowerArmor\CompPowerArmorStation.cs" />
<Compile Include="PowerArmor\Gizmo_StructurePanel.cs" />
<Compile Include="PowerArmor\JobDriver_EnterPowerArmor.cs" />
<Compile Include="PowerArmor\Gizmo_FuelPanel.cs" />
<Compile Include="PowerArmor\Harmony_ThingWithComps_GetFloatMenuOptions.cs" />
</ItemGroup>
<ItemGroup>
<Compile Include="Jobs\JobDriver_SuperCarry\SuperCarryExtension.cs" />

View File

@@ -17,6 +17,8 @@ namespace ArachnaeSwarm
{
public ThingDef buildingDef;
public float structurePointsMax = 500f;
public HediffDef hediffOnEmptyFuel;
public float fuelConsumptionRate = 0.5f; // Nutrition per day
}
[StaticConstructorOnStartup]
@@ -51,6 +53,57 @@ namespace ArachnaeSwarm
public Building sourceBuilding;
#endregion
#region Ticker
protected override void Tick()
{
base.Tick(); // Call Apparel's Tick
if (this.Wearer == null) return; // Only tick if worn
var fuelComp = this.GetComp<CompRefuelableNutrition>();
if (fuelComp != null)
{
// Explicitly call the component's Tick method to ensure fuel consumption logic is executed.
// This is needed because Apparel's base Tick does not automatically call component Ticks.
// First, update consumption rate and hediffs
if (this.Wearer.IsHashIntervalTick(60)) // Check every second
{
if (this.Wearer.pather != null && this.Wearer.pather.MovingNow)
{
fuelComp.currentConsumptionRate = Ext?.fuelConsumptionRate ?? 0.5f;
}
else
{
fuelComp.currentConsumptionRate = 0f;
}
// Handle hediff for empty fuel
var hediffDef = Ext?.hediffOnEmptyFuel;
if (hediffDef != null)
{
var hediff = this.Wearer.health.hediffSet.GetFirstHediffOfDef(hediffDef);
if (!fuelComp.HasFuel)
{
if (hediff == null)
{
this.Wearer.health.AddHediff(hediffDef);
}
}
else
{
if (hediff != null)
{
this.Wearer.health.RemoveHediff(hediff);
}
}
}
}
// Then, explicitly call the component's Tick method to ensure fuel consumption logic is executed with the UPDATED rate.
fuelComp.CompTick();
}
} // Correctly close the Tick method
#endregion
#region Data
public override void ExposeData()
{
@@ -63,11 +116,21 @@ namespace ArachnaeSwarm
#region Gizmo
public override IEnumerable<Gizmo> GetWornGizmos()
{
// Always yield base gizmos first.
foreach (var gizmo in base.GetWornGizmos())
{
yield return gizmo;
}
// Yield our custom structure points gizmo.
yield return new Gizmo_StructurePanel(this);
// Yield our custom fuel panel gizmo.
var fuelComp = this.GetComp<CompRefuelableNutrition>();
if (fuelComp != null)
{
yield return new Gizmo_FuelPanel(fuelComp);
}
}
#endregion
@@ -76,17 +139,53 @@ namespace ArachnaeSwarm
{
base.Notify_Unequipped(pawn);
ThingDef buildingToSpawn = sourceBuilding?.def ?? Ext?.buildingDef;
Building building = sourceBuilding;
if (buildingToSpawn == null)
// If the source building reference is lost, create a new one as a fallback.
if (building == null)
{
Log.Error($"[ArachnaeSwarm] Power Armor {this.def.defName} unequipped, but has no buildingDef defined in its PowerArmorExtension or a source building.");
return;
ThingDef buildingDef = Ext?.buildingDef;
if (buildingDef == null)
{
Log.Error($"[ArachnaeSwarm] Power Armor {this.def.defName} unequipped, but has no buildingDef defined in its PowerArmorExtension and the source building reference was lost.");
this.Destroy(DestroyMode.Vanish);
return;
}
building = (Building)ThingMaker.MakeThing(buildingDef);
}
Building building = (Building)ThingMaker.MakeThing(buildingToSpawn);
building.HitPoints = Mathf.RoundToInt(this.StructurePoints);
// Sync health back to the building, ensuring it's at least 1 to prevent it from being destroyed instantly.
building.HitPoints = Mathf.Max(1, Mathf.RoundToInt(this.StructurePoints));
// Sync fuel back to the building
var apparelFuelComp = this.GetComp<CompRefuelableNutrition>();
var buildingFuelComp = building.GetComp<CompRefuelableNutrition>();
if (apparelFuelComp != null && buildingFuelComp != null)
{
// Reset building fuel and then set it to the apparel's current fuel to avoid overflow.
buildingFuelComp.ConsumeFuel(buildingFuelComp.Fuel);
buildingFuelComp.ReceiveFuel(apparelFuelComp.Fuel);
}
Log.Message($"[PA_Debug] Notify_Unequipped: Before spawning building (ID: {building.thingIDNumber}) - HitPoints: {building.HitPoints}, StackCount: {building.stackCount}");
// Ensure stackCount is at least 1 for buildings, as 0 stackCount causes errors during spawning
if (building.stackCount <= 0)
{
building.stackCount = 1;
Log.Warning($"[PA_Debug] Notify_Unequipped: Corrected building (ID: {building.thingIDNumber}) stackCount to 1 as it was 0.");
}
// Set the faction to the pawn's faction before spawning
building.SetFaction(pawn.Faction);
// Re-spawn the original building instance
GenPlace.TryPlaceThing(building, pawn.Position, pawn.Map, ThingPlaceMode.Near);
Log.Message($"[PA_Debug] Notify_Unequipped: After spawning building (ID: {building.thingIDNumber}) - HitPoints: {building.HitPoints}, StackCount: {building.stackCount}");
// Destroy the apparel to prevent duplication
this.Destroy(DestroyMode.Vanish);
Log.Message($"[PA_Debug] Notify_Unequipped: Apparel {this.Label} (ID: {this.thingIDNumber}) destroyed.");
}
#endregion

View File

@@ -0,0 +1,55 @@
using UnityEngine;
using Verse;
using RimWorld; // For SolidColorMaterials
namespace ArachnaeSwarm
{
[StaticConstructorOnStartup]
public class Gizmo_FuelPanel : Gizmo
{
private static readonly Texture2D FullBarTex = SolidColorMaterials.NewSolidColorTexture(new Color(0.9f, 0.7f, 0.2f)); // Orange for fuel
private static readonly Texture2D EmptyBarTex = SolidColorMaterials.NewSolidColorTexture(new Color(0.1f, 0.1f, 0.1f));
private CompRefuelableNutrition fuelComp;
public Gizmo_FuelPanel(CompRefuelableNutrition fuelComp)
{
this.fuelComp = fuelComp;
this.Order = -90f; // Slightly higher order than structure panel
}
public override float GetWidth(float maxWidth)
{
return 140f;
}
public override GizmoResult GizmoOnGUI(Vector2 topLeft, float maxWidth, GizmoRenderParms parms)
{
Rect overRect = new Rect(topLeft.x, topLeft.y, GetWidth(maxWidth), 75f);
Find.WindowStack.ImmediateWindow(984989, overRect, WindowLayer.GameUI, delegate
{
Rect rect = overRect.AtZero().ContractedBy(6f);
// Draw label
Rect labelRect = rect;
labelRect.height = overRect.height / 2f;
Text.Font = GameFont.Tiny;
Widgets.Label(labelRect, "Fuel".Translate()); // Use "Fuel" or custom text
// Draw bar
Rect barRect = rect;
barRect.yMin = overRect.height / 2f;
float fillPercent = fuelComp.FuelPercentOfMax;
Widgets.FillableBar(barRect, fillPercent, FullBarTex, EmptyBarTex, false);
// Draw text on bar
Text.Font = GameFont.Small;
Text.Anchor = TextAnchor.MiddleCenter;
Widgets.Label(barRect, $"{fuelComp.Fuel:F0} / {fuelComp.Props.fuelCapacity:F0}");
Text.Anchor = TextAnchor.UpperLeft;
});
return new GizmoResult(GizmoState.Clear);
}
}
}

View File

@@ -0,0 +1,59 @@
using HarmonyLib;
using RimWorld;
using System.Collections.Generic;
using Verse;
using Verse.AI;
namespace ArachnaeSwarm
{
[HarmonyPatch(typeof(ThingWithComps), "GetFloatMenuOptions")]
public static class Harmony_ThingWithComps_GetFloatMenuOptions
{
[HarmonyPostfix]
public static IEnumerable<FloatMenuOption> Postfix(IEnumerable<FloatMenuOption> values, ThingWithComps __instance, Pawn selPawn)
{
// First, return all original options
foreach (var value in values)
{
yield return value;
}
// --- DEBUG LOGGING ---
// Use a more specific check to avoid log spam
if (__instance.def.defName != null && __instance.def.defName.StartsWith("ARA_"))
{
Log.Message($"[PA_Debug] GetFloatMenuOptions Postfix triggered for: {__instance.def.defName}");
}
// Check if the thing is our power armor building
var comp = __instance.GetComp<CompPowerArmorStation>();
if (comp == null && __instance.def.defName != null && __instance.def.defName.StartsWith("ARA_"))
{
Log.Message($"[PA_Debug] CompPowerArmorStation is NULL for {__instance.def.defName}");
}
if (comp != null)
{
Log.Message($"[PA_Debug] CompPowerArmorStation FOUND for {__instance.def.defName}. Checking reachability.");
// Check if the pawn can interact
if (!selPawn.CanReserveAndReach(__instance, PathEndMode.InteractionCell, Danger.Deadly))
{
yield return new FloatMenuOption("CannotEnterPowerArmor".Translate() + ": " + "CannotReach".Translate(), null);
}
else
{
// Action to give the job
void enterAction()
{
Job job = JobMaker.MakeJob(DefDatabase<JobDef>.GetNamed("ARA_EnterPowerArmor"), __instance);
selPawn.jobs.TryTakeOrderedJob(job, JobTag.Misc);
}
yield return new FloatMenuOption("EnterPowerArmor".Translate(__instance.Label), enterAction);
}
}
}
}
}

View File

@@ -42,6 +42,14 @@ namespace ArachnaeSwarm
// Sync health from building to apparel
apparel.StructurePoints = building.HitPoints;
// Sync fuel from building to apparel
var buildingFuelComp = building.GetComp<CompRefuelableNutrition>();
var apparelFuelComp = apparel.GetComp<CompRefuelableNutrition>();
if (buildingFuelComp != null && apparelFuelComp != null)
{
apparelFuelComp.ReceiveFuel(buildingFuelComp.Fuel);
}
// Wear the apparel
actor.apparel.Wear(apparel, true, true);