Merge branch 'master' of https://git.ra3battle.cn/Kalospacer/ArachnaeSwarm
This commit is contained in:
Binary file not shown.
@@ -320,6 +320,7 @@
|
||||
<!-- 食物列表 -->
|
||||
<whiteFoodList>
|
||||
<li>ARA_InsectJelly</li>
|
||||
<li>ARA_NutrientPasteMeal</li>
|
||||
</whiteFoodList>
|
||||
<onlyEatRaceRestrictedFood>true</onlyEatRaceRestrictedFood>
|
||||
<!-- 武器列表 -->
|
||||
|
||||
@@ -410,6 +410,7 @@
|
||||
<!-- 食物列表 -->
|
||||
<whiteFoodList>
|
||||
<li>ARA_InsectJelly</li>
|
||||
<li>ARA_NutrientPasteMeal</li>
|
||||
</whiteFoodList>
|
||||
<onlyEatRaceRestrictedFood>true</onlyEatRaceRestrictedFood>
|
||||
<!-- 可以驯服的宠物,主要是防止小虫由别人驯服 -->
|
||||
|
||||
@@ -33,6 +33,31 @@
|
||||
<allowedArchonexusCount>150</allowedArchonexusCount>
|
||||
</ThingDef>
|
||||
|
||||
<!-- The meal produced by the new dispenser -->
|
||||
<ThingDef ParentName="MealBase">
|
||||
<defName>ARA_NutrientPasteMeal</defName>
|
||||
<label>阿拉克涅蜜晶糕</label>
|
||||
<description>一种由虫蜜合成的黏糊糕点,能提供虫族生存所需,味道对于虫族来说不太可口,不过普通的人类也许会喜欢这种虫族合成的糕点。</description>
|
||||
<graphicData>
|
||||
<texPath>Things/Item/Meal/NutrientPaste</texPath>
|
||||
<graphicClass>Graphic_StackCount</graphicClass>
|
||||
</graphicData>
|
||||
<statBases>
|
||||
<MarketValue>10</MarketValue>
|
||||
<Nutrition>2.0</Nutrition>
|
||||
</statBases>
|
||||
<ingestible>
|
||||
<foodType>AnimalProduct</foodType>
|
||||
<preferability>MealFine</preferability>
|
||||
<joy>0.04</joy>
|
||||
<joyKind>Gluttonous</joyKind>
|
||||
<ingestEffect>EatVegetarian</ingestEffect>
|
||||
<ingestSound>Meal_Eat</ingestSound>
|
||||
<lowPriorityCaravanFood>true</lowPriorityCaravanFood>
|
||||
<babiesCanIngest>true</babiesCanIngest>
|
||||
</ingestible>
|
||||
</ThingDef>
|
||||
|
||||
<ThingDef ParentName="ResourceBase">
|
||||
<defName>ARA_Carapace</defName>
|
||||
<label>甲壳素</label>
|
||||
|
||||
83
1.6/1.6/Defs/Thing_building/ARA_NutrientDispenser.xml
Normal file
83
1.6/1.6/Defs/Thing_building/ARA_NutrientDispenser.xml
Normal file
@@ -0,0 +1,83 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<Defs>
|
||||
|
||||
<ThingDef ParentName="BuildingBase">
|
||||
<defName>ARANutrientDispenser</defName>
|
||||
<label>阿拉克涅蜜晶巢穴</label>
|
||||
<description>一个生产阿拉克涅蜜晶糕的虫虫巢穴。通过分解虫蜜来生产可食用的蜜晶糕。</description>
|
||||
<thingClass>ArachnaeSwarm.Building_ARANutrientDispenser</thingClass>
|
||||
<graphicData>
|
||||
<texPath>Things/Building/Production/NutrientDispenser</texPath>
|
||||
<graphicClass>Graphic_Multi</graphicClass>
|
||||
<color>(232,255,191)</color>
|
||||
<shaderType>CutoutComplex</shaderType>
|
||||
<drawSize>(3,4)</drawSize>
|
||||
<damageData>
|
||||
<rect>(0.02,0.25,2.96,2.85)</rect>
|
||||
<cornerTL>Damage/Corner</cornerTL>
|
||||
<cornerTR>Damage/Corner</cornerTR>
|
||||
</damageData>
|
||||
<shadowData>
|
||||
<volume>(2.87,0.75,3.05)</volume>
|
||||
<offset>(0,0,0.38)</offset>
|
||||
</shadowData>
|
||||
</graphicData>
|
||||
<altitudeLayer>Building</altitudeLayer>
|
||||
<passability>Impassable</passability>
|
||||
<pathCost>150</pathCost>
|
||||
<fillPercent>1.0</fillPercent>
|
||||
<statBases>
|
||||
<MaxHitPoints>250</MaxHitPoints>
|
||||
<WorkToBuild>2000</WorkToBuild>
|
||||
<Flammability>0.5</Flammability>
|
||||
</statBases>
|
||||
<size>(3,4)</size>
|
||||
<costList>
|
||||
<ARA_Carapace>50</ARA_Carapace>
|
||||
</costList>
|
||||
<tickerType>Normal</tickerType>
|
||||
<terrainAffordanceNeeded>ARA_Creep</terrainAffordanceNeeded>
|
||||
<comps>
|
||||
<li Class="ArachnaeSwarm.CompProperties_RefuelableNutrition">
|
||||
<fuelCapacity>10.0</fuelCapacity>
|
||||
<fuelFilter>
|
||||
<thingDefs>
|
||||
<li>ARA_InsectJelly</li>
|
||||
</thingDefs>
|
||||
</fuelFilter>
|
||||
<fuelGizmoLabel>虫蜜</fuelGizmoLabel>
|
||||
<showAllowAutoRefuelToggle>true</showAllowAutoRefuelToggle>
|
||||
<targetFuelLevelConfigurable>true</targetFuelLevelConfigurable>
|
||||
<consumeFuelOnlyWhenUsed>true</consumeFuelOnlyWhenUsed>
|
||||
</li>
|
||||
<li Class="CompProperties_AffectedByFacilities">
|
||||
<linkableFacilities>
|
||||
<li>ARA_NutrientNetworkTower</li>
|
||||
</linkableFacilities>
|
||||
</li>
|
||||
<!-- This component acts as a perpetual power source with no consumption, ensuring powerComp.PowerOn is always true -->
|
||||
<li Class="CompProperties_Power">
|
||||
<compClass>CompPowerPlant</compClass>
|
||||
<basePowerConsumption>-1</basePowerConsumption> <!-- Negative value makes it a generator -->
|
||||
<transmitsPower>false</transmitsPower> <!-- This is CRITICAL. It prevents the building from powering the whole grid. -->
|
||||
</li>
|
||||
</comps>
|
||||
<modExtensions>
|
||||
<li Class="ArachnaeSwarm.ARAFoodDispenserProperties">
|
||||
<thingToDispense>ARA_NutrientPasteMeal</thingToDispense>
|
||||
<nutritionCostPerDispense>0.5</nutritionCostPerDispense>
|
||||
<soundDispense>DispensePaste</soundDispense>
|
||||
</li>
|
||||
</modExtensions>
|
||||
<building>
|
||||
<isMealSource>true</isMealSource>
|
||||
</building>
|
||||
<interactionCellOffset>(0,0,3)</interactionCellOffset>
|
||||
<hasInteractionCell>true</hasInteractionCell>
|
||||
<designationCategory>ARA_Buildings</designationCategory>
|
||||
<researchPrerequisites>
|
||||
<li>NutrientPaste</li>
|
||||
</researchPrerequisites>
|
||||
</ThingDef>
|
||||
|
||||
</Defs>
|
||||
@@ -19,7 +19,7 @@
|
||||
<tickerType>Normal</tickerType> <!-- 改为 Normal 以匹配 CompRefuelable 的要求 -->
|
||||
<fillPercent>0.5</fillPercent>
|
||||
<statBases>
|
||||
<MaxHitPoints>300</MaxHitPoints>
|
||||
<MaxHitPoints>3000</MaxHitPoints>
|
||||
<WorkToBuild>3000</WorkToBuild>
|
||||
<Mass>20</Mass>
|
||||
<Flammability>0.5</Flammability>
|
||||
@@ -45,6 +45,7 @@
|
||||
<li>ARA_JellyVat</li>
|
||||
<li>ARA_GrowthVat</li>
|
||||
<li>ARA_MorphableResearchBench</li>
|
||||
<li>ARANutrientDispenser</li>
|
||||
</linkableBuildings>
|
||||
<maxDistance>80</maxDistance> <!-- 供能范围 -->
|
||||
<maxSimultaneous>10</maxSimultaneous>
|
||||
@@ -59,9 +60,10 @@
|
||||
<li>ARA_JellyVat</li>
|
||||
<li>ARA_GrowthVat</li>
|
||||
<li>ARA_MorphableResearchBench</li>
|
||||
<li>ARANutrientDispenser</li>
|
||||
</linkableBuildings>
|
||||
<maxDistance>80</maxDistance>
|
||||
<lineTexturePath>Things/Special/Power/Wire</lineTexturePath>
|
||||
<lineTexturePath>ArachnaeSwarm/Building/Nutrition_Pie</lineTexturePath>
|
||||
</li>
|
||||
|
||||
<!-- 自身的燃料库 -->
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
<commonality>0</commonality>
|
||||
<degreeDatas>
|
||||
<li>
|
||||
<degree>0</degree>
|
||||
<label>节肢类昆虫</label>
|
||||
<description>{PAWN_nameDef} 是一只巨大的节肢类昆虫,多对附肢、镜面反光的外骨骼和扭动的分节身体足以引发人类心底埋藏的强烈恐惧感。\n\n额,你该不会真以为它们是一群美少女吧?</description>
|
||||
<marketValueFactorOffset>-1</marketValueFactorOffset>
|
||||
|
||||
BIN
Content/Textures/ArachnaeSwarm/Wire.png
Normal file
BIN
Content/Textures/ArachnaeSwarm/Wire.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.8 KiB |
12
Source/ArachnaeSwarm/ARAFoodDispenserProperties.cs
Normal file
12
Source/ArachnaeSwarm/ARAFoodDispenserProperties.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using Verse;
|
||||
using Verse.Sound;
|
||||
|
||||
namespace ArachnaeSwarm
|
||||
{
|
||||
public class ARAFoodDispenserProperties : DefModExtension
|
||||
{
|
||||
public ThingDef thingToDispense;
|
||||
public float nutritionCostPerDispense = 0.3f;
|
||||
public SoundDef soundDispense;
|
||||
}
|
||||
}
|
||||
@@ -197,6 +197,9 @@
|
||||
<Compile Include="Verbs\VerbProperties_Excalibur.cs" />
|
||||
<Compile Include="Verbs\WeaponStealBeam\Verb_ShootWeaponStealBeam.cs" />
|
||||
<Compile Include="Verbs\WeaponStealBeam\VerbProperties_WeaponStealBeam.cs" />
|
||||
<Compile Include="Building_ARANutrientDispenser.cs" />
|
||||
<Compile Include="ARAFoodDispenserProperties.cs" />
|
||||
<Compile Include="Patch_DispenserFoodSearch.cs" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<!-- 自定义清理任务,删除obj文件夹中的临时文件 -->
|
||||
|
||||
77
Source/ArachnaeSwarm/Building_ARANutrientDispenser.cs
Normal file
77
Source/ArachnaeSwarm/Building_ARANutrientDispenser.cs
Normal file
@@ -0,0 +1,77 @@
|
||||
using RimWorld;
|
||||
using Verse;
|
||||
using Verse.Sound; // Ensure this is present
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace ArachnaeSwarm
|
||||
{
|
||||
// By inheriting from Building_NutrientPasteDispenser, we automatically pass all "is Building_NutrientPasteDispenser" checks in the game's code.
|
||||
public class Building_ARANutrientDispenser : Building_NutrientPasteDispenser
|
||||
{
|
||||
private CompRefuelableNutrition nutritionComp;
|
||||
private ARAFoodDispenserProperties dispenserProps;
|
||||
|
||||
public override void SpawnSetup(Map map, bool respawningAfterLoad)
|
||||
{
|
||||
// We MUST call the base method for it to initialize the `powerComp` field,
|
||||
// which we will provide via a dummy CompPowerPlant in the XML. This prevents NullReferenceExceptions.
|
||||
base.SpawnSetup(map, respawningAfterLoad);
|
||||
this.nutritionComp = this.GetComp<CompRefuelableNutrition>();
|
||||
this.dispenserProps = this.def.GetModExtension<ARAFoodDispenserProperties>();
|
||||
}
|
||||
|
||||
// We don't need to override CanDispenseNow. The base method is perfect if we handle its dependencies.
|
||||
// The base CanDispenseNow checks powerComp.PowerOn (which our XML hack makes always true)
|
||||
// and HasEnoughFeedstockInHoppers(), which we DO override below.
|
||||
|
||||
public override Thing TryDispenseFood()
|
||||
{
|
||||
// The base method is tightly coupled with hoppers. We must fully override it.
|
||||
if (!this.HasEnoughFeedstockInHoppers()) // Directly check our condition
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
this.nutritionComp.ConsumeFuel(this.dispenserProps.nutritionCostPerDispense);
|
||||
|
||||
if (this.dispenserProps.soundDispense != null)
|
||||
{
|
||||
this.dispenserProps.soundDispense.PlayOneShot(new TargetInfo(this.Position, this.Map));
|
||||
}
|
||||
|
||||
return ThingMaker.MakeThing(this.DispensableDef);
|
||||
}
|
||||
|
||||
public override ThingDef DispensableDef
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this.dispenserProps == null) return base.DispensableDef;
|
||||
return this.dispenserProps.thingToDispense;
|
||||
}
|
||||
}
|
||||
|
||||
// --- Hopper-related overrides to decouple from them ---
|
||||
public override Building AdjacentReachableHopper(Pawn reacher) => null;
|
||||
|
||||
public override Thing FindFeedInAnyHopper() => null;
|
||||
|
||||
public override bool HasEnoughFeedstockInHoppers()
|
||||
{
|
||||
// This is the crucial hook. The base CanDispenseNow and the AI's food search call this method.
|
||||
// We replace the hopper check with our fuel check.
|
||||
if (this.dispenserProps == null) return false;
|
||||
return this.nutritionComp != null && this.nutritionComp.Fuel >= this.dispenserProps.nutritionCostPerDispense;
|
||||
}
|
||||
|
||||
public override IEnumerable<Gizmo> GetGizmos()
|
||||
{
|
||||
// base.GetGizmos() provides the "Build Hopper" gizmo, which we don't want.
|
||||
// We filter it out here.
|
||||
return base.GetGizmos().Where(gizmo =>
|
||||
!(gizmo is Designator_Build designator && designator.PlacingDef == ThingDefOf.Hopper)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
170
Source/ArachnaeSwarm/Patch_DispenserFoodSearch.cs
Normal file
170
Source/ArachnaeSwarm/Patch_DispenserFoodSearch.cs
Normal file
@@ -0,0 +1,170 @@
|
||||
using HarmonyLib;
|
||||
using RimWorld;
|
||||
using Verse;
|
||||
using System.Linq;
|
||||
|
||||
namespace ArachnaeSwarm
|
||||
{
|
||||
[HarmonyPatch(typeof(FoodUtility), nameof(FoodUtility.BestFoodSourceOnMap))]
|
||||
public static class Patch_DispenserFoodSearch
|
||||
{
|
||||
// A state variable to pass our found dispenser from Prefix to Postfix
|
||||
private static Building_ARANutrientDispenser foundCustomDispenser = null;
|
||||
|
||||
/// <summary>
|
||||
/// Runs BEFORE the original BestFoodSourceOnMap.
|
||||
/// Its job is to specifically find OUR custom dispenser.
|
||||
/// </summary>
|
||||
[HarmonyPrefix]
|
||||
public static bool Prefix(Pawn getter, Pawn eater, bool desperate = false, FoodPreferability maxPref = FoodPreferability.MealLavish)
|
||||
{
|
||||
// Reset state at the beginning of each check
|
||||
foundCustomDispenser = null;
|
||||
|
||||
// First, check if our custom dispenser can even be a candidate.
|
||||
// We find a representative def for our dispenser to get the meal properties.
|
||||
// This is a bit of a hack, but necessary to know the preferability without an instance.
|
||||
var representativeDef = ThingDef.Named("ARANutrientDispenser"); // Assuming this is the defName of your dispenser
|
||||
var props = representativeDef?.GetModExtension<ARAFoodDispenserProperties>();
|
||||
var customMealDef = props?.thingToDispense;
|
||||
|
||||
if (customMealDef == null) {
|
||||
// If we can't find our meal def, something is wrong with the XML.
|
||||
// It's safer to not interfere.
|
||||
return true;
|
||||
}
|
||||
|
||||
// Don't interfere if the pawn is looking for something better than what we can offer.
|
||||
if (maxPref < customMealDef.ingestible.preferability)
|
||||
{
|
||||
return true; // Let the original method run, it's looking for fancy food.
|
||||
}
|
||||
|
||||
// Find the best available custom dispenser on the map
|
||||
var bestDispenser = FindBestCustomDispenser(getter, eater, desperate);
|
||||
|
||||
if (bestDispenser != null)
|
||||
{
|
||||
// We found one! Store it for the Postfix.
|
||||
foundCustomDispenser = bestDispenser;
|
||||
Log.Message($"[ArachnaeSwarm Prefix] Found a potential custom dispenser for {eater.LabelShort}: {bestDispenser.Label}");
|
||||
}
|
||||
|
||||
// ALWAYS let the original method run.
|
||||
// This allows us to compare its result with ours in the Postfix, ensuring maximum compatibility.
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs AFTER the original BestFoodSourceOnMap.
|
||||
/// Compares the original method's result with our custom dispenser (if any) and picks the best one.
|
||||
/// </summary>
|
||||
[HarmonyPostfix]
|
||||
public static void Postfix(ref Thing __result, ref ThingDef foodDef, Pawn eater, Pawn getter)
|
||||
{
|
||||
if (foundCustomDispenser == null)
|
||||
{
|
||||
// Our prefix didn't find any custom dispensers, so we don't need to do anything.
|
||||
return;
|
||||
}
|
||||
|
||||
var customDispenserMealDef = foundCustomDispenser.DispensableDef;
|
||||
if (customDispenserMealDef == null)
|
||||
{
|
||||
Log.Warning($"[ArachnaeSwarm Postfix] Custom dispenser {foundCustomDispenser.Label} has a null DispensableDef.");
|
||||
return;
|
||||
}
|
||||
|
||||
// If the original method found NO food, then our dispenser is the best (and only) choice.
|
||||
if (__result == null)
|
||||
{
|
||||
Log.Message($"[ArachnaeSwarm Postfix] Original method found no food. Using our custom dispenser: {foundCustomDispenser.Label}");
|
||||
__result = foundCustomDispenser;
|
||||
foodDef = customDispenserMealDef;
|
||||
return;
|
||||
}
|
||||
|
||||
// Both we and the original method found food. We must now choose the better one.
|
||||
// "FoodOptimality" is the score RimWorld uses to decide. Higher is better.
|
||||
float ourScore = FoodUtility.FoodOptimality(eater, foundCustomDispenser, customDispenserMealDef, (getter.Position - foundCustomDispenser.Position).LengthManhattan);
|
||||
float theirScore = FoodUtility.FoodOptimality(eater, __result, foodDef, (getter.Position - __result.Position).LengthManhattan);
|
||||
|
||||
Log.Message($"[ArachnaeSwarm Postfix] Comparing food sources: Our Dispenser (Score: {ourScore:F2}) vs Original Result '{__result.Label}' (Score: {theirScore:F2}).");
|
||||
|
||||
// If our dispenser is a better choice, override the result.
|
||||
if (ourScore > theirScore)
|
||||
{
|
||||
Log.Message($"[ArachnaeSwarm Postfix] Our dispenser is better. Overriding result.");
|
||||
__result = foundCustomDispenser;
|
||||
foodDef = customDispenserMealDef;
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Message($"[ArachnaeSwarm Postfix] Original result is better or equal. Keeping it.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A helper to find the best custom dispenser for a pawn.
|
||||
/// </summary>
|
||||
private static Building_ARANutrientDispenser FindBestCustomDispenser(Pawn getter, Pawn eater, bool desperate)
|
||||
{
|
||||
if (getter.Map == null) return null;
|
||||
|
||||
var allCustomDispensers = getter.Map.listerBuildings.AllBuildingsColonistOfClass<Building_ARANutrientDispenser>();
|
||||
|
||||
Building_ARANutrientDispenser bestDispenser = null;
|
||||
float bestScore = float.MinValue;
|
||||
|
||||
foreach (var dispenser in allCustomDispensers)
|
||||
{
|
||||
var currentMealDef = dispenser.DispensableDef;
|
||||
if (currentMealDef == null) continue;
|
||||
|
||||
// --- Start of refined access checks ---
|
||||
|
||||
// 1. Block hostile pawns.
|
||||
if (getter.HostileTo(Faction.OfPlayer))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// 2. Block wild animals. Tamed player-faction animals are allowed.
|
||||
if (getter.RaceProps.Animal && getter.Faction != Faction.OfPlayer)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// 3. Use the game's built-in social properness check for things like prisoner access.
|
||||
if (!dispenser.IsSociallyProper(getter))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// --- End of refined access checks ---
|
||||
|
||||
// Check if the dispenser is usable
|
||||
if (!dispenser.CanDispenseNow || dispenser.IsForbidden(getter) || !eater.WillEat(currentMealDef, getter))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check reachability
|
||||
if (!getter.CanReach(dispenser, Verse.AI.PathEndMode.InteractionCell, Danger.Some))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Calculate score
|
||||
float score = FoodUtility.FoodOptimality(eater, dispenser, currentMealDef, (getter.Position - dispenser.Position).LengthManhattan);
|
||||
if (score > bestScore)
|
||||
{
|
||||
bestScore = score;
|
||||
bestDispenser = dispenser;
|
||||
}
|
||||
}
|
||||
|
||||
return bestDispenser;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user