Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

try harder to avoid disliked food #831

Merged
merged 4 commits into from
Dec 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 54 additions & 19 deletions Source/Source/JobGiver_ScroungeFood.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Linq;
using System.Collections.Generic;
using Hospitality.Utilities;
using RimWorld;
using UnityEngine;
Expand Down Expand Up @@ -36,7 +37,7 @@ public override Job TryGiveJob(Pawn guest)
guestComp.lastFoodCheckTick = GenTicks.TicksGame + 500; // Recheck ever x ticks

var canManipulateTools = guest.RaceProps.ToolUser && guest.health.capacities.CapableOf(PawnCapacityDefOf.Manipulation);
var food = canManipulateTools ? BestFoodInInventory(guest, guest) : null;
var food = canManipulateTools ? BestFoodInInventory(guest, guest, out var dummy) : null;
if (food != null) return null;

var pressure = guest.needs.food.CurCategory switch
Expand All @@ -58,7 +59,7 @@ public override Job TryGiveJob(Pawn guest)
//Log.Message($"{guest.LabelCap} tried to find scroungable food. Found {target?.Label}. pressure = {pressure} maxStealOpinion = {maxStealOpinion} swipe = {swipe}");
if (target == null) return null;

food = BestFoodInInventory(target, guest);
food = BestFoodInInventory(target, guest, out var mood);
if (food == null) return null;
var amount = GetAmount(food);
return new Job(swipe ? InternalDefOf.SwipeFood : InternalDefOf.ScroungeFood, target, food) { overeat = swipe, count = amount }; // overeat stores swiping
Expand All @@ -77,28 +78,44 @@ private static Pawn FindTarget(Pawn guest, int maxStealOpinion)
if (targetPawn != null) return targetPawn;
targetPawn = guest.MapHeld.lordManager.lords.Where(l => l != lord).Select(l => TryFindGroupPawn(guest, maxStealOpinion, lord)).FirstOrDefault();
if (targetPawn != null) return targetPawn;
targetPawn = guest.MapHeld.mapPawns.pawnsSpawned.FirstOrDefault(p => Qualifies(p, guest, maxStealOpinion));
targetPawn = TryFindPawn(guest, maxStealOpinion, guest.MapHeld.mapPawns.pawnsSpawned);
return targetPawn;
}

private static Pawn TryFindGroupPawn(Pawn guest, int maxStealOpinion, Lord lord)
{
return lord.ownedPawns.FirstOrDefault(p => Qualifies(p, guest, maxStealOpinion));
return TryFindPawn(guest, maxStealOpinion, lord.ownedPawns);
}

private static bool Qualifies(Pawn target, Pawn guest, int maxStealOpinion)
private static Pawn TryFindPawn(Pawn guest, int maxStealOpinion, IEnumerable<Pawn> pawns)
{
if (target == null || guest == null) return false;
if (target == guest) return false;
if (target.inventory == null) return false;
if (target.relations == null) return false;
if (target.InAggroMentalState) return false;
if (target.HostileTo(guest)) return false;
Pawn bestPawn = null;
foreach (Pawn p in pawns)
{
int factor = QualifiesFactor(p, guest, maxStealOpinion);
if (factor == 0)
continue;
if (factor == 2)
return p;
bestPawn = p;
}
return bestPawn;
}

// 0 - no, 1 - usable but bad food, 2 - usable
private static int QualifiesFactor(Pawn target, Pawn guest, int maxStealOpinion)
{
if (target == null || guest == null) return 0;
if (target == guest) return 0;
if (target.inventory == null) return 0;
if (target.relations == null) return 0;
if (target.InAggroMentalState) return 0;
if (target.HostileTo(guest)) return 0;

var awake = target.Awake();
if (guest.relations != null)
{
if (!awake && guest.relations.OpinionOf(target) > maxStealOpinion) return false;
if (!awake && guest.relations.OpinionOf(target) > maxStealOpinion) return 0;
}

if (target.relations != null)
Expand All @@ -107,27 +124,45 @@ private static bool Qualifies(Pawn target, Pawn guest, int maxStealOpinion)
if (target.story?.traits != null)
{
if (target.story.traits.HasTrait(TraitDefOf.Kind)) minAwakeOpinion -= 35;
if (target.story.traits.HasTrait(TraitDefOf.Kind)) minAwakeOpinion += 50;
if (target.story.traits.HasTrait(TraitDefOf.Greedy)) minAwakeOpinion += 50;
}

if (awake && target.relations.OpinionOf(guest) < minAwakeOpinion) return false;
if (awake && target.relations.OpinionOf(guest) < minAwakeOpinion) return 0;
}

var food = BestFoodInInventory(target, guest);
return food != null;
var food = BestFoodInInventory(target, guest, out var mood);
if (food != null)
return mood >= 0 ? 2 : 1;
return 0;
}

internal static Thing BestFoodInInventory(Pawn holder, Pawn eater)
internal static Thing BestFoodInInventory(Pawn holder, Pawn eater, out float mood)
{
mood = -100;
if (holder.inventory == null) return null;

var innerContainer = holder.inventory.innerContainer;
Thing usableThing = null;
for (var i = 0; i < innerContainer.Count; i++)
{
var thing = innerContainer.innerList[i];
if (thing.def.IsNutritionGivingIngestible && thing.IngestibleNow && eater.WillEat(thing, eater) && (int)thing.def.ingestible.preferability >= (int)FoodPreferability.RawBad && !thing.def.IsDrug) return thing;
if (thing.def.IsNutritionGivingIngestible && thing.IngestibleNow && eater.WillEat(thing, eater)
&& (int)thing.def.ingestible.preferability >= (int)FoodPreferability.RawBad && !thing.def.IsDrug)
{
float thingMood = FoodUtility.MoodFromIngesting(eater, thing, thing.def);
if (thingMood >= 0)
{
mood = thingMood;
return thing;
}
if (thingMood > mood)
{
mood = thingMood;
usableThing = thing;
}
}
}

return null;
return usableThing;
}
}
18 changes: 15 additions & 3 deletions Source/Source/JoyGiver_BuyStuff.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,18 @@ public override Job TryGiveJob(Pawn pawn)
var requiresFoodFactor = GuestUtility.GetRequiresFoodFactor(pawn);

// Try some things
var selection = things.TakeRandom(5).Where(t => pawn.CanReach(t.Position, PathEndMode.Touch, Danger.None, false, false, TraverseMode.PassDoors)).ToArray();
IEnumerable<Thing> selectedThings;
if (requiresFoodFactor <= 0.8)
selectedThings = things.TakeRandom(5);
else
{
// Do not pick at random if the pawn needs food, select only food, and try to avoid disliked food.
selectedThings = things.Where(t => t.IsFood() && pawn.RaceProps.CanEverEat(t) && FoodUtility.MoodFromIngesting(pawn, t, t.def) >= 0);
if (selectedThings.FirstOrDefault() == null)
selectedThings = things.Where(t => t.IsFood() && pawn.RaceProps.CanEverEat(t));
selectedThings = selectedThings.ToList().TakeRandom(5);
}
var selection = selectedThings.Where(t => pawn.CanReach(t.Position, PathEndMode.Touch, Danger.None, false, false, TraverseMode.PassDoors)).ToArray();
foreach (var t in selection)
{
RegisterLookedAt(pawn, t.Position);
Expand Down Expand Up @@ -99,8 +110,9 @@ private static float Likey(Pawn pawn, Thing thing, float requiresFoodFactor)
//Log.Message($"{pawn.LabelShort} looked at {thing.LabelShort} at {thing.Position}.");
//Log.Message($"{pawn.LabelShort} added {requiresFoodFactor} to the score for his hunger and {appFactor} for food optimality.");
appFactor += requiresFoodFactor;
if (thing.def.IsWithinCategory(ThingCategoryDefOf.PlantFoodRaw)) appFactor -= 0.25f;
if (thing.def.IsWithinCategory(ThingCategoryDefOf.MeatRaw)) appFactor -= 0.5f;
// FoodOptimality() factors in mood effect, but still returns positive results even for abhorrent food.
// Adjust explicitly to make pawns avoid food that would result in mood debuffs.
appFactor += FoodUtility.MoodFromIngesting(pawn, thing, thing.def) / 10f;
}
// Other consumables
else if (thing.IsIngestible() && thing.def.ingestible.joy > 0)
Expand Down