public static void AddEveryMedicineToRelevantThings2( Pawn pawn, Thing billGiver, List <Thing> relevantThings, Predicate <Thing> baseValidator, Map map) { MedicalCareCategory medicalCareCategory = GetMedicalCareCategory(billGiver); WorkGiver_DoBill_RegionProcessor.thingList = map.listerThings.ThingsInGroup(ThingRequestGroup.Medicine); //WorkGiver_DoBill.tmpMedicine.Clear(); List <Thing> tmpMedicine = new List <Thing>(); for (int index = 0; index < WorkGiver_DoBill_RegionProcessor.thingList.Count; ++index) { Thing thing = WorkGiver_DoBill_RegionProcessor.thingList[index]; if (medicalCareCategory.AllowsMedicine(thing.def) && baseValidator(thing) && pawn.CanReach(thing, PathEndMode.OnCell, Danger.Deadly, false, TraverseMode.ByPawn)) { tmpMedicine.Add(thing); } } tmpMedicine.SortBy(x => - x.GetStatValue(StatDefOf.MedicalPotency, true), x => x.Position.DistanceToSquared(billGiver.Position)); relevantThings.AddRange(tmpMedicine); //WorkGiver_DoBill.tmpMedicine.Clear(); }
//private static void AddEveryMedicineToRelevantThings(Pawn pawn, Thing billGiver, List<Thing> relevantThings, Predicate<Thing> baseValidator, Map map) public static void Postfix(Pawn pawn, Thing billGiver, List <Thing> relevantThings, Map map) { if (HackityGetBill.bill == null) { Verse.Log.Warning($"Smart Medicine Inventory Surgery not going to work for {pawn}; mod conflict in AddEveryMedicineToRelevantThings or TryFindBestBillIngredients?"); return; } Predicate <Thing> baseValidator = (Thing t) => HackityGetBill.bill.IsFixedOrAllowedIngredient(t) && HackityGetBill.bill.recipe.ingredients.Any((IngredientCount ingNeed) => ingNeed.filter.Allows(t)); Log.Message($"AddEveryMedicineToRelevantThings ({pawn}, {billGiver}, {HackityGetBill.bill})"); MedicalCareCategory medicalCareCategory = (MedicalCareCategory)AccessTools.Method(typeof(WorkGiver_DoBill), "GetMedicalCareCategory").Invoke(null, new object[] { billGiver }); bool added = false; Log.Message($"inventory: ({pawn.inventory.GetDirectlyHeldThings().ToStringSafeEnumerable()})"); foreach (Thing t in pawn.inventory.GetDirectlyHeldThings()) { if (medicalCareCategory.AllowsMedicine(t.def) && baseValidator(t)) { Log.Message($"{pawn} considering {t} for surgery on {billGiver}"); added = true; relevantThings.Add(t); } } if (added) { relevantThings.SortBy((Thing x) => - x.GetStatValue(StatDefOf.MedicalPotency, true), (Thing x) => x.Spawned ? x.Position.DistanceToSquared(billGiver.Position) : 0); } HackityGetBill.bill = null; }
//private static void AddEveryMedicineToRelevantThings(Pawn pawn, Thing billGiver, List<Thing> relevantThings, Predicate<Thing> baseValidator, Map map) public static void Postfix(Pawn pawn, Thing billGiver, List <Thing> relevantThings, Map map) { if (HackityGetBill.bill == null) { Verse.Log.Warning($"Smart Medicine Inventory Surgery not going to work for {pawn}; mod conflict in AddEveryMedicineToRelevantThings or TryFindBestBillIngredients?"); return; } Predicate <Thing> baseValidator = (Thing t) => HackityGetBill.bill.IsFixedOrAllowedIngredient(t) && HackityGetBill.bill.recipe.ingredients.Any((IngredientCount ingNeed) => ingNeed.filter.Allows(t)); Log.Message($"AddEveryMedicineToRelevantThings ({pawn}, {billGiver}, {HackityGetBill.bill})"); MedicalCareCategory medicalCareCategory = (MedicalCareCategory)AccessTools.Method(typeof(WorkGiver_DoBill), "GetMedicalCareCategory").Invoke(null, new object[] { billGiver }); Log.Message($"inventory: ({pawn.inventory.GetDirectlyHeldThings().ToStringSafeEnumerable()})"); foreach (Thing t in pawn.inventory.GetDirectlyHeldThings()) { if (medicalCareCategory.AllowsMedicine(t.def) && baseValidator(t)) { Log.Message($"{pawn} considering {t} for surgery on {billGiver}"); relevantThings.Add(t); } } //Tiny addition to use minimal medicine for Anesthetize bill. TODO: Make this a def extension so any recipe could use it, though no one will so why really int statAdjust = (Settings.Get().minimalMedicineForNonUrgent&& HackityGetBill.bill.recipe == Anesthetize ? 1 : -1); relevantThings.SortBy( (Thing x) => statAdjust * x.GetStatValue(StatDefOf.MedicalPotency), //Check if item is in inventory or spawned in map: inventory "distance" is 0 (Thing x) => x.Spawned ? x.Position.DistanceToSquared(billGiver.Position) : 0); HackityGetBill.bill = null; }
public static bool AllowsMedicineForHediff(Pawn deliveree, ThingDef med) { if (PriorityCareComp.MaxPriorityCare(deliveree, out MedicalCareCategory heCare)) { //This is uses to allow higher medicine above normal limit below. //this is NOT used to stop the job is PriorityCare is lowered if (heCare.AllowsMedicine(med)) { return(true); } } //Not required but hey why dont I patch this in for Pharmacist MedicalCareCategory care = deliveree.GetCare(); return(care.AllowsMedicine(med)); }
private static void AddEveryMedicineToRelevantThings(Pawn pawn, Thing billGiver, List <Thing> relevantThings, Predicate <Thing> baseValidator, Map map) { MedicalCareCategory medicalCareCategory = GetMedicalCareCategory(billGiver); List <Thing> list = map.listerThings.ThingsInGroup(ThingRequestGroup.Medicine); tmpMedicine.Clear(); for (int i = 0; i < list.Count; i++) { Thing thing = list[i]; if (medicalCareCategory.AllowsMedicine(thing.def) && baseValidator(thing) && pawn.CanReach(thing, PathEndMode.OnCell, Danger.Deadly)) { tmpMedicine.Add(thing); } } tmpMedicine.SortBy((Thing x) => 0f - x.GetStatValue(StatDefOf.MedicalPotency), (Thing x) => x.Position.DistanceToSquared(billGiver.Position)); relevantThings.AddRange(tmpMedicine); tmpMedicine.Clear(); }
public static bool Prefix(Pawn healer, Pawn patient, ref Thing __result) { // get lowest of pawn care settings & pharmacy settings MedicalCareCategory pharmacistAdvice = PharmacistUtility.TendAdvice(patient); if (pharmacistAdvice <= MedicalCareCategory.NoMeds) { __result = null; return(false); } __result = GenClosest.ClosestThing_Global_Reachable( patient.Position, patient.Map, patient.Map.listerThings.ThingsInGroup(ThingRequestGroup.Medicine), PathEndMode.ClosestTouch, TraverseParms.For(healer), 9999f, (m) => !m.IsForbidden(healer) && pharmacistAdvice.AllowsMedicine(m.def) && healer.CanReserve(m, 1), (m) => m.def.GetStatValueAbstract(StatDefOf.MedicalPotency)); return(false); }
//public static Thing FindBestMedicine(Pawn healer, Pawn patient) public static List <ThingCount> Find(Pawn healer, Pawn patient, out int totalCount) { totalCount = 0; Log.Message($"{healer} is tending to {patient}"); float sufficientQuality = maxMedicineQuality + 1; // nothing is sufficient! if (Settings.Get().minimalMedicineForNonUrgent) { if (patient.health.hediffSet.hediffs.All(h => !h.TendableNow() || !h.IsUrgent())) { sufficientQuality = minMedicineQuality; Log.Message($"Sufficient medicine for non-urgent care is {sufficientQuality}"); } } MedicalCareCategory defaultCare = patient.GetCare(); //Care setting MedicalCareCategory finalCare = MedicalCareCategory.NoCare; var hediffCare = PriorityCareComp.Get(); List <Hediff> hediffsToTend = HediffsToTend(patient); Log.Message($"Tending ({hediffsToTend.ToStringSafeEnumerable()})"); foreach (Hediff h in hediffsToTend) { MedicalCareCategory toUse = defaultCare; if (hediffCare.TryGetValue(h, out MedicalCareCategory heCare)) { toUse = heCare; } finalCare = toUse > finalCare ? toUse : finalCare; } Log.Message($"defaultCare = {defaultCare}, care = {finalCare}"); //Android Droid support; Predicate <Thing> validatorDroid = t => true; Type extMechanicalPawn = AccessTools.TypeByName("Androids.MechanicalPawnProperties"); bool isDroid = extMechanicalPawn != null && (patient.def.modExtensions?.Any(e => extMechanicalPawn.IsAssignableFrom(e.GetType())) ?? false); if (isDroid) { Log.Message($"{patient} is a droid"); Type extRepair = AccessTools.TypeByName("Androids.DroidRepairProperties"); validatorDroid = t => t.def.modExtensions?.Any(e => extRepair.IsAssignableFrom(e.GetType())) ?? false; } //Med Predicate <Thing> validatorMed = t => finalCare.AllowsMedicine(t.def) && validatorDroid(t); //Ground Map map = patient.Map; TraverseParms traverseParams = TraverseParms.For(healer, Danger.Deadly, TraverseMode.ByPawn, false); Predicate <Thing> validator = (Thing t) => validatorMed(t) && map.reachability.CanReach(patient.Position, t, PathEndMode.ClosestTouch, traverseParams) && !t.IsForbidden(healer) && healer.CanReserve(t, FindBestMedicine.maxPawns, 1);//can reserve at least 1 Func <Thing, float> priorityGetter = (Thing t) => MedicineRating(t, sufficientQuality); List <Thing> groundMedicines = patient.Map.listerThings.ThingsInGroup(isDroid?ThingRequestGroup.HaulableEver:ThingRequestGroup.Medicine).FindAll(t => validator(t)); //Pawns Predicate <Pawn> validatorHolder = (Pawn p) => map.reachability.CanReach(patient.Position, p, PathEndMode.ClosestTouch, traverseParams); List <Pawn> pawns = healer.Map.mapPawns.SpawnedPawnsInFaction(Faction.OfPlayer).ListFullCopy(); if (!Settings.Get().useDoctorMedicine) { pawns.Remove(healer); } if (!Settings.Get().usePatientMedicine) { pawns.Remove(patient); } if (!Settings.Get().useColonistMedicine) { pawns.RemoveAll(p => p.IsFreeColonist && p != healer && p != patient); } if (!Settings.Get().useAnimalMedicine) { pawns.RemoveAll(p => !p.IsColonist); } int minDistance = DistanceTo(healer, patient); if (!Settings.Get().useOtherEvenIfFar) { pawns.RemoveAll(p => DistanceTo(p, healer, patient) > minDistance + Settings.Get().distanceToUseFromOther * 2); //*2, there and back } pawns.RemoveAll(p => !validatorHolder(p)); //Evaluate them all List <MedicineEvaluator> allMeds = new List <MedicineEvaluator>(); //Add each ground foreach (Thing t in groundMedicines) { allMeds.Add(new MedicineEvaluator() { thing = t, pawn = null, rating = MedicineRating(t, sufficientQuality), distance = DistanceTo(t, healer, patient) }); } List <MedicineEvaluator> groundEvaluators = allMeds.ListFullCopy(); //Add best from each pawn foreach (Pawn p in pawns) { Thing t = FindBestMedicineInInventory(p, patient, validatorMed, sufficientQuality, p == healer); if (t == null) { continue; } allMeds.Add(new MedicineEvaluator() { thing = t, pawn = p, rating = MedicineRating(t, sufficientQuality), distance = DistanceTo(p, healer, patient) }); } //Find best allMeds.Sort(); foreach (MedicineEvaluator tryMed in allMeds) { tryMed.DebugLog(); } MedicineEvaluator bestMed = allMeds.LastOrDefault(); List <ThingCount> result = new List <ThingCount>(); if (bestMed.thing != null) { allMeds.RemoveLast(); if (Settings.Get().useCloseMedicine&& bestMed.pawn != null) { bestMed.DebugLog("Best: "); Log.Message($"checking nearby:"); List <MedicineEvaluator> equalMedicines = groundEvaluators.FindAll(eval => eval.rating == bestMed.rating); if (equalMedicines.Count > 0) { MedicineEvaluator closeMed = equalMedicines.MinBy(eval => eval.distance); closeMed.DebugLog("Nearby med on the way there: "); if (closeMed.distance <= minDistance + Settings.Get().distanceToUseEqualOnGround * 2) //*2, there and back { bestMed = closeMed; } } } //Check if any minimal medicine exists if (allMeds.Count == 0 || bestMed.rating == allMeds.MinBy(m => m.rating).rating) { GetMedicineCountToFullyHeal_Patch.__beep_beep_MinimalMedicineAvailable = false; Log.Message($"No minimal medicine available"); } int count = Medicine.GetMedicineCountToFullyHeal(patient); totalCount = count; Log.Message($"Medicine count = {count}"); bestMed.DebugLog("Best Med on " + (bestMed.pawn == null ? "ground" : "hand") + ":"); int usedCount = Mathf.Min(bestMed.thing.stackCount, count); result.Add(new ThingCount(bestMed.thing, usedCount)); count -= usedCount; //Find some more needed nearby if (count > 0) { List <MedicineEvaluator> equalMedicines = allMeds.FindAll(eval => eval.rating == bestMed.rating); equalMedicines.SortBy(eval => DistanceTo(bestMed.pawn ?? bestMed.thing, eval.pawn ?? eval.thing)); Thing droppedMedicine = null; Log.Message($"But needs {count} more"); while (count > 0 && equalMedicines.Count > 0) { MedicineEvaluator closeMed = equalMedicines.First(); equalMedicines.RemoveAt(0); closeMed.DebugLog("More: "); if (DistanceTo(droppedMedicine ?? bestMed.pawn ?? bestMed.thing, closeMed.pawn ?? closeMed.thing) > 8f) //8f as defined in CheckForGetOpportunityDuplicate { break; } usedCount = Mathf.Min(closeMed.thing.stackCount, count); closeMed.DebugLog("Using: ({usedCount})"); result.Add(new ThingCount(closeMed.thing, usedCount)); count -= usedCount; } } } return(result); }
private static bool Prefix(Pawn healer, Pawn patient, ref Thing __result) { if (patient.playerSettings == null || patient.playerSettings.medCare <= MedicalCareCategory.NoMeds || !healer.Faction.IsPlayer) { return(true); } Log.Message(healer + " is tending to " + patient); float sufficientQuality = maxMedicineQuality + 1; // nothing is sufficient! if (Settings.Get().downgradeExcessiveMedicine) { sufficientQuality = CalculateSufficientQuality(healer, patient); Log.Message("Sufficient medicine for best treatment is " + sufficientQuality + "(" + Settings.Get().goodEnoughDowngradeFactor + ")"); } if (Settings.Get().minimalMedicineForNonUrgent) { if (patient.health.hediffSet.hediffs.All(h => !h.TendableNow || !h.IsUrgent())) { sufficientQuality = minMedicineQuality; Log.Message("Sufficient medicine for non-urgent care is " + sufficientQuality); } } //Med Predicate <Thing> validatorMed = t => patient.playerSettings.medCare.AllowsMedicine(t.def); try { ((Action)(() => { MedicalCareCategory pharmacistAdvice = Pharmacist.PharmacistUtility.TendAdvice(patient); validatorMed = t => pharmacistAdvice.AllowsMedicine(t.def); }))(); } catch (Exception) { } //Ground Map map = patient.Map; TraverseParms traverseParams = TraverseParms.For(healer, Danger.Deadly, TraverseMode.ByPawn, false); Predicate <Thing> validator = (Thing t) => validatorMed(t) && map.reachability.CanReach(patient.Position, t, PathEndMode.ClosestTouch, traverseParams) && !t.IsForbidden(healer) && healer.CanReserve(t, FindBestMedicine.maxPawns, 1);//can reserve at least 1 Func <Thing, float> priorityGetter = (Thing t) => MedicineRating(t, sufficientQuality); List <Thing> groundMedicines = patient.Map.listerThings.ThingsInGroup(ThingRequestGroup.Medicine).FindAll(t => validator(t)); //Pawns Predicate <Pawn> validatorHolder = (Pawn p) => map.reachability.CanReach(patient.Position, p, PathEndMode.ClosestTouch, traverseParams); List <Pawn> pawns = healer.Map.mapPawns.SpawnedPawnsInFaction(Faction.OfPlayer).ListFullCopy(); if (!Settings.Get().useDoctorMedicine) { pawns.Remove(healer); } if (!Settings.Get().usePatientMedicine) { pawns.Remove(patient); } if (!Settings.Get().useColonistMedicine) { pawns.RemoveAll(p => p.IsFreeColonist && p != healer && p != patient); } if (!Settings.Get().useAnimalMedicine) { pawns.RemoveAll(p => !p.IsColonist); } int minDistance = DistanceTo(healer, patient); if (!Settings.Get().useOtherEvenIfFar) { pawns.RemoveAll(p => DistanceTo(p, healer, patient) > minDistance + Settings.Get().distanceToUseFromOther * 2); //*2, there and back } pawns.RemoveAll(p => !validatorHolder(p)); //Evaluate them all List <MedicineEvaluator> allMeds = new List <MedicineEvaluator>(); //Add each ground foreach (Thing t in groundMedicines) { allMeds.Add(new MedicineEvaluator() { thing = t, pawn = null, rating = MedicineRating(t, sufficientQuality), distance = DistanceTo(t, healer, patient) }); } List <MedicineEvaluator> groundEvaluators = allMeds.ListFullCopy(); //Add best from each pawn foreach (Pawn p in pawns) { Thing t = FindBestMedicineInInventory(p, patient, validatorMed, sufficientQuality, p == healer); if (t == null) { continue; } allMeds.Add(new MedicineEvaluator() { thing = t, pawn = p, rating = MedicineRating(t, sufficientQuality), distance = DistanceTo(p, healer, patient) }); } //Find best allMeds.Sort(); foreach (MedicineEvaluator tryMed in allMeds) { tryMed.DebugLog(); } MedicineEvaluator bestMed = allMeds.LastOrDefault(); if (bestMed.thing != null) { allMeds.RemoveLast(); if (Settings.Get().useCloseMedicine&& bestMed.pawn != null) { bestMed.DebugLog("Best: "); Log.Message("checking nearby:"); List <MedicineEvaluator> equalMedicines = groundEvaluators.FindAll(eval => eval.rating == bestMed.rating); if (equalMedicines.Count > 0) { MedicineEvaluator closeMed = equalMedicines.MinBy(eval => eval.distance); closeMed.DebugLog("Nearby med on the way there: "); if (closeMed.distance <= minDistance + Settings.Get().distanceToUseEqualOnGround * 2) //*2, there and back { bestMed = closeMed; } } } //Check if any minimal medicine exists if (allMeds.Count == 0 || bestMed.rating == allMeds.MinBy(m => m.rating).rating) { GetMedicineCountToFullyHeal_Patch.__beep_beep_MinimalMedicineAvailable = false; Log.Message("No minimal medicine available"); } int count = Medicine.GetMedicineCountToFullyHeal(patient); JobOnThing_Patch.medCount = count; Log.Message("FindBestMedicine count = " + count); if (bestMed.pawn == null) { bestMed.DebugLog("Best Med on ground:"); __result = bestMed.thing; } else { // because The Toil to get this medicine is FailOnDespawnedNullOrForbidden // And Medicine in inventory is despawned // You can't set the job to use already carried medicine. // Editing the toil would be more difficult. // But we can drop it so the normal job picks it back it ¯\_(ツ)_/¯ bestMed.DebugLog("Best Med on hand: "); //Drop it! int dropCount = Mathf.Min(bestMed.thing.stackCount, count); count -= dropCount; __result = bestMed.thing; // return true; //Todo: mid-job getting new medicine fails since this isn't dropped mid-toil. Sokay since it just fails and restarts. //if healer job is tend maybe? //Find some more needed nearby //bestMed is dropped in Notify_Start, but these aren't tracked there so they are dropped now. //In a very odd case that you right-click assign a tend and a second pawn's medicine is needed, he might drop his entire inventory // That's a vanilla thing though that calls JobOnThing over and over instead of HasJobOnThing if (count > 0) { List <MedicineEvaluator> equalMedicines = allMeds.FindAll(eval => eval.rating == bestMed.rating); equalMedicines.SortBy(eval => DistanceTo(bestMed.pawn, eval.pawn ?? eval.thing)); Thing droppedMedicine = null; Log.Message("But needs " + count + " more"); while (count > 0 && equalMedicines.Count > 0) { MedicineEvaluator closeMed = equalMedicines.First(); equalMedicines.RemoveAt(0); closeMed.DebugLog("More: "); if (DistanceTo(droppedMedicine ?? bestMed.pawn, closeMed.pawn ?? closeMed.thing) > 8f) //8f as defined in CheckForGetOpportunityDuplicate { return(false); } dropCount = Mathf.Min(closeMed.thing.stackCount, count); closeMed.DebugLog("Using: (" + dropCount + ")"); closeMed.pawn?.inventory.innerContainer.TryDrop(closeMed.thing, ThingPlaceMode.Near, dropCount, out droppedMedicine); if (droppedMedicine != null && !droppedMedicine.IsForbidden(healer) && healer.CanReserve(droppedMedicine, maxPawns, count)) { count -= dropCount; } //else return false; } } } //Use it! //Log.Message("using inventory " + medicine); //healer.carryTracker.innerContainer.TryAddOrTransfer(medicine, count); //__result = medicine; } return(__result == null); }