暂存
This commit is contained in:
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;
|
||||
}
|
||||
}
|
||||
@@ -92,7 +92,6 @@
|
||||
<Compile Include="Abilities\TrackingCharge\CompProperties_TrackingCharge.cs" />
|
||||
<Compile Include="Abilities\TrackingCharge\PawnFlyer_TrackingCharge.cs" />
|
||||
<Compile Include="Abilities\TrackingCharge\Verb_CastAbilityTrackingCharge.cs" />
|
||||
<Compile Include="ARADefOf.cs" />
|
||||
<Compile Include="Building_Comps\ARA_BuildingTerrainSpawn\CompDelayedTerrainSpawn.cs" />
|
||||
<Compile Include="Building_Comps\ARA_BuildingTerrainSpawn\CompProperties_DelayedTerrainSpawn.cs" />
|
||||
<Compile Include="Building_Comps\ARA_CompInteractiveProducer\CompInteractiveProducer.cs" />
|
||||
@@ -173,7 +172,6 @@
|
||||
<Compile Include="Thing_Comps\ARA_CustomUniqueWeapon\CompCustomUniqueWeapon.cs" />
|
||||
<Compile Include="Thing_Comps\ARA_CustomUniqueWeapon\CompProperties_CustomUniqueWeapon.cs" />
|
||||
<Compile Include="Thing_Comps\OPToxicGas.cs" />
|
||||
<Compile Include="Thoughts\ARA_CreepyCrawly.cs" />
|
||||
<Compile Include="Verbs\Cleave\CompCleave.cs" />
|
||||
<Compile Include="Verbs\Cleave\Verb_MeleeAttack_Cleave.cs" />
|
||||
<Compile Include="Verbs\ExplosiveBeam\Verb_ShootBeamExplosive.cs" />
|
||||
@@ -196,6 +194,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文件夹中的临时文件 -->
|
||||
|
||||
66
Source/ArachnaeSwarm/Building_ARANutrientDispenser.cs
Normal file
66
Source/ArachnaeSwarm/Building_ARANutrientDispenser.cs
Normal file
@@ -0,0 +1,66 @@
|
||||
using RimWorld;
|
||||
using Verse;
|
||||
using Verse.Sound; // Ensure this is present
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
148
Source/ArachnaeSwarm/Patch_DispenserFoodSearch.cs
Normal file
148
Source/ArachnaeSwarm/Patch_DispenserFoodSearch.cs
Normal file
@@ -0,0 +1,148 @@
|
||||
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;
|
||||
|
||||
// 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