private static float getCacheElementResult(PawnCapacitiesHandler __instance, PawnCapacityDef capacity) { if (capacity == null) //ADDED { return(0f); } CacheElement2 cacheElement = get_cacheElement(__instance, capacity); //ADDED lock (cacheElement) //ADDED { if (cacheElement.status == CacheStatus.Caching) { Log.Error($"Detected infinite stat recursion when evaluating {capacity}"); return(0f); } if (cacheElement.status == CacheStatus.Uncached) { cacheElement.status = CacheStatus.Caching; try { cacheElement.value = PawnCapacityUtility.CalculateCapacityLevel(pawn(__instance).health.hediffSet, capacity); } finally { cacheElement.status = CacheStatus.Cached; } } } return(cacheElement.value); }
private static string GetCapacityReport(Pawn subject, PawnCapacityDef capacity) { var payload = $"{capacity.GetLabelFor(subject).CapitalizeFirst()}: "; if (!PawnCapacityUtility.BodyCanEverDoCapacity(subject.RaceProps.body, capacity)) { payload += $"{Find.ActiveLanguageWorker.Pluralize(subject.kindDef.race.defName)} "; payload += $" are incapable of {capacity.GetLabelFor(subject)}"; return(payload); } var impactors = new List <PawnCapacityUtility.CapacityImpactor>(); payload += PawnCapacityUtility.CalculateCapacityLevel(subject.health.hediffSet, capacity, impactors) .ToStringPercent(); payload += " | "; if (!impactors.Any()) { return($"{payload}No health conditions"); } var segments = new List <string>(); foreach (var i in impactors) { if (i is PawnCapacityUtility.CapacityImpactorHediff) { segments.Add(i.Readable(subject)); } } foreach (var i in impactors) { if (i is PawnCapacityUtility.CapacityImpactorBodyPartHealth) { segments.Add(i.Readable(subject)); } } foreach (var i in impactors) { if (i is PawnCapacityUtility.CapacityImpactorCapacity) { segments.Add(i.Readable(subject)); } } foreach (var i in impactors) { if (i is PawnCapacityUtility.CapacityImpactorPain) { segments.Add(i.Readable(subject)); } } return($"{payload} | {string.Join(", ", segments.ToArray())}"); }
private static string MyPawnHealthCapacity(Viewer viewer, Pawn pawn, PawnCapacityDef capacityDef) { if (PawnCapacityUtility.BodyCanEverDoCapacity(pawn.RaceProps.body, capacityDef)) { Pair <string, Color> efficiencyLabel = HealthCardUtility.GetEfficiencyLabel(pawn, capacityDef); List <PawnCapacityUtility.CapacityImpactor> impactorList = new List <PawnCapacityUtility.CapacityImpactor>(); float fLevel = PawnCapacityUtility.CalculateCapacityLevel(pawn.health.hediffSet, capacityDef, impactorList); string sLevel = (fLevel * 100.0f).ToString("F0") + "%"; string output = $"@{viewer.username} {pawn.Name.ToStringShort.CapitalizeFirst()}'s {capacityDef.LabelCap}: {efficiencyLabel.First} ({sLevel})"; output += " - " + "AffectedBy".Translate() + " "; if (impactorList.Count > 0) { for (int i = 0; i < impactorList.Count; i++) { if (impactorList[i] is PawnCapacityUtility.CapacityImpactorHediff) { output += impactorList[i].Readable(pawn) + ", "; } } for (int i = 0; i < impactorList.Count; i++) { if (impactorList[i] is PawnCapacityUtility.CapacityImpactorBodyPartHealth) { output += impactorList[i].Readable(pawn) + ", "; } } for (int i = 0; i < impactorList.Count; i++) { if (impactorList[i] is PawnCapacityUtility.CapacityImpactorCapacity) { output += impactorList[i].Readable(pawn) + ", "; } } for (int i = 0; i < impactorList.Count; i++) { if (impactorList[i] is PawnCapacityUtility.CapacityImpactorPain) { output += impactorList[i].Readable(pawn) + ", "; } } output = output.Substring(0, output.Length - 2); } else { output += $"nothing "; } return(output); } else { return($"@{viewer.username} {pawn.Name.ToStringShort.CapitalizeFirst()} Can not have {capacityDef.LabelCap}."); } }
// handle downed zombies ==================================================================== // public static bool Downed(Zombie zombie) { if (zombie.IsDowned() == false) { return(false); } if (ZombieSettings.Values.zombiesDieVeryEasily || zombie.IsSuicideBomber || ZombieSettings.Values.doubleTapRequired == false) { zombie.Kill(null); return(true); } var walkCapacity = PawnCapacityUtility.CalculateCapacityLevel(zombie.health.hediffSet, PawnCapacityDefOf.Moving); var missingBrain = zombie.health.hediffSet.GetBrain() == null; if (walkCapacity < 0.25f || missingBrain) { zombie.Kill(null); return(true); } if (++zombie.healCounter >= Constants.ZOMBIE_HEALING_TICKS) { zombie.healCounter = 0; var injuries = zombie.health.hediffSet.GetHediffs <Hediff_Injury>(); foreach (var injury in injuries) { if (ZombieSettings.Values.zombiesDieVeryEasily) { zombie.Kill(null); return(true); } if (Tools.IsCombatExtendedInstalled()) { injury.Heal(combatExtendedHealAmount); } else { injury.Heal(injury.Severity + 0.5f); } break; } } return(false); }
public static string Postfix(string __result, Pawn pawn, PawnCapacityDef capacity) { // So this might look a bit like dark magic, but what's happening is that the impactors list is populated // in CalculateCapacityLevel. Why isn't it an out parameter? Because it's optional. var impactors = new List <PawnCapacityUtility.CapacityImpactor>(); PawnCapacityUtility.CalculateCapacityLevel(pawn.health.hediffSet, capacity, impactors); var sb = new StringBuilder(); foreach (var impactor in impactors.FindAll(impact => impact is CapacityImpactorPsychic)) { sb.AppendLine($" {impactor.Readable(pawn)}"); } __result += sb; return(__result); }
public static string GetPawnCapacityTip(Pawn pawn, PawnCapacityDef capacity) { List <PawnCapacityUtility.CapacityImpactor> list = new List <PawnCapacityUtility.CapacityImpactor>(); float eff = PawnCapacityUtility.CalculateCapacityLevel(pawn.health.hediffSet, capacity, list); PawnCapacityUtility.CapacityImpactorCapacity capacityImpactorCapacity; list.RemoveAll((PawnCapacityUtility.CapacityImpactor x) => (capacityImpactorCapacity = x as PawnCapacityUtility.CapacityImpactorCapacity) != null && !capacityImpactorCapacity.capacity.CanShowOnPawn(pawn)); StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine(capacity.GetLabelFor(pawn).CapitalizeFirst() + ": " + GetEfficiencyEstimateLabel(eff)); if (list.Count > 0) { stringBuilder.AppendLine(); stringBuilder.AppendLine("AffectedBy".Translate()); for (int i = 0; i < list.Count; i++) { if (list[i] is PawnCapacityUtility.CapacityImpactorHediff) { stringBuilder.AppendLine($" {list[i].Readable(pawn)}"); } } for (int j = 0; j < list.Count; j++) { if (list[j] is PawnCapacityUtility.CapacityImpactorBodyPartHealth) { stringBuilder.AppendLine($" {list[j].Readable(pawn)}"); } } for (int k = 0; k < list.Count; k++) { if (list[k] is PawnCapacityUtility.CapacityImpactorCapacity) { stringBuilder.AppendLine($" {list[k].Readable(pawn)}"); } } for (int l = 0; l < list.Count; l++) { if (list[l] is PawnCapacityUtility.CapacityImpactorPain) { stringBuilder.AppendLine($" {list[l].Readable(pawn)}"); } } } return(stringBuilder.ToString()); }
public static void Patch_HealthCardUtility_GetPawnCapacityTip(ref string __result, Pawn pawn, PawnCapacityDef capacity) { List <PawnCapacityUtility.CapacityImpactor> list = new List <PawnCapacityUtility.CapacityImpactor>(); PawnCapacityUtility.CalculateCapacityLevel(pawn.health.hediffSet, capacity, list); if (list.Count > 0) { StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < list.Count; i++) { if (list[i] is CapacityImpactorRune) { stringBuilder.AppendLine(string.Format(" {0}", list[i].Readable(pawn))); } } __result += stringBuilder.ToString(); } }
public static string GetPawnCapacityTip(Pawn pawn, PawnCapacityDef capacity) { List <PawnCapacityUtility.CapacityImpactor> list = new List <PawnCapacityUtility.CapacityImpactor>(); float num = PawnCapacityUtility.CalculateCapacityLevel(pawn.health.hediffSet, capacity, list); StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine(capacity.LabelCap + ": " + ((float)(num * 100.0)).ToString("F0") + "%"); if (list.Count > 0) { stringBuilder.AppendLine(); stringBuilder.AppendLine("AffectedBy".Translate()); for (int i = 0; i < list.Count; i++) { if (list[i] is PawnCapacityUtility.CapacityImpactorHediff) { stringBuilder.AppendLine(string.Format(" {0}", list[i].Readable(pawn))); } } for (int j = 0; j < list.Count; j++) { if (list[j] is PawnCapacityUtility.CapacityImpactorBodyPartHealth) { stringBuilder.AppendLine(string.Format(" {0}", list[j].Readable(pawn))); } } for (int k = 0; k < list.Count; k++) { if (list[k] is PawnCapacityUtility.CapacityImpactorCapacity) { stringBuilder.AppendLine(string.Format(" {0}", list[k].Readable(pawn))); } } for (int l = 0; l < list.Count; l++) { if (list[l] is PawnCapacityUtility.CapacityImpactorPain) { stringBuilder.AppendLine(string.Format(" {0}", list[l].Readable(pawn))); } } } return(stringBuilder.ToString()); }
public static float PawnQualityPriceFactor(Pawn pawn, StringBuilder explanation = null) { float num = 1f; num *= Mathf.Lerp(0.199999988f, 1f, pawn.health.summaryHealth.SummaryHealthPercent); List <PawnCapacityDef> allDefsListForReading = DefDatabase <PawnCapacityDef> .AllDefsListForReading; for (int i = 0; i < allDefsListForReading.Count; i++) { if (!pawn.health.capacities.CapableOf(allDefsListForReading[i])) { num *= 0.6f; continue; } float t = PawnCapacityUtility.CalculateCapacityLevel(pawn.health.hediffSet, allDefsListForReading[i], null, forTradePrice: true); num *= Mathf.Lerp(0.5f, 1f, t); } if (pawn.skills != null) { num *= AverageSkillCurve.Evaluate((float)pawn.skills.skills.Average((SkillRecord sk) => sk.Level)); } num *= pawn.ageTracker.CurLifeStage.marketValueFactor; if (pawn.story != null && pawn.story.traits != null) { for (int j = 0; j < pawn.story.traits.allTraits.Count; j++) { Trait trait = pawn.story.traits.allTraits[j]; num += trait.CurrentData.marketValueFactorOffset; } } num += pawn.GetStatValue(StatDefOf.PawnBeauty) * 0.2f; if (num < 0.1f) { num = 0.1f; } explanation?.AppendLine("StatsReport_CharacterQuality".Translate() + ": x" + num.ToStringPercent()); return(num); }
private static string HealthCapacityReport([NotNull] Pawn pawn, [NotNull] PawnCapacityDef capacity) { if (!PawnCapacityUtility.BodyCanEverDoCapacity(pawn.RaceProps.body, capacity)) { return("TKUtils.PawnHealth.IncapableOfCapacity".LocalizeKeyed(capacity.GetLabelFor(pawn))); } var impactors = new List <PawnCapacityUtility.CapacityImpactor>(); var segments = new List <string> { ResponseHelper.JoinPair( RichTextHelper.StripTags(capacity.LabelCap), PawnCapacityUtility.CalculateCapacityLevel(pawn.health.hediffSet, capacity, impactors).ToStringPercent() ), impactors.Any() ? ResponseHelper.JoinPair("TKUtils.PawnHealth.AffectedBy".Localize(), GetImpactorsForPawn(pawn, impactors).SectionJoin()) : "NoHealthConditions".Localize().CapitalizeFirst() }; return(segments.GroupedJoin()); }
public static int CapableColonists(Map map) { var colonists = map.mapPawns.FreeHumanlikesSpawnedOfFaction(Faction.OfPlayer); return(colonists.Count(pawn => { if (pawn.Spawned == false || pawn.Downed || pawn.Dead) { return false; } if (pawn.health.HasHediffsNeedingTend(true)) { return false; } if (pawn.equipment.Primary == null) { return false; } if (pawn.InMentalState) { return false; } if (pawn.InContainerEnclosed) { return false; } var walkCapacity = PawnCapacityUtility.CalculateCapacityLevel(pawn.health.hediffSet, PawnCapacityDefOf.Moving); if (walkCapacity < 0.25f) { return false; } return true; })); }
public static bool GetLevel(PawnCapacitiesHandler __instance, ref float __result, PawnCapacityDef capacity) { if (__instance.pawn.health.Dead) { __result = 0f; return(false); } if (__instance.cachedCapacityLevels == null) { __instance.Notify_CapacityLevelsDirty(); } lock (__instance) { CacheElement cacheElement = __instance.cachedCapacityLevels[capacity]; if (cacheElement.status == CacheStatus.Caching) { Log.Error($"Detected infinite stat recursion when evaluating {capacity}"); __result = 0f; return(false); } if (cacheElement.status == CacheStatus.Uncached) { cacheElement.status = CacheStatus.Caching; try { cacheElement.value = PawnCapacityUtility.CalculateCapacityLevel(__instance.pawn.health.hediffSet, capacity); } finally { cacheElement.status = CacheStatus.Cached; } } __result = cacheElement.value; return(false); } }
public static Pair <string, Color> GetEfficiencyLabel(Pawn pawn, PawnCapacityDef activity) { float level = pawn.health.capacities.GetLevel(activity); return(new Pair <string, Color>(PawnCapacityUtility.CalculateCapacityLevel(pawn.health.hediffSet, activity).ToStringPercent(), efficiencyToColor[EfficiencyValueToEstimate(level)])); }
void TickAction() { var fadeOff = Tools.PheromoneFadeoff(); var agitatedFadeoff = fadeOff / 4; var checkSmashableFadeoff = agitatedFadeoff / 2; var zombie = (Zombie)pawn; if (zombie.state == ZombieState.Emerging) { return; } var map = zombie.Map; if (zombie.Dead || zombie.Destroyed) { EndJobWith(JobCondition.InterruptForced); return; } if (zombie.state == ZombieState.ShouldDie) { EndJobWith(JobCondition.InterruptForced); zombie.Kill(null); return; } if (ZombieSettings.Values.zombiesDieVeryEasily) { if (zombie.health.hediffSet.GetHediffs <Hediff_Injury>().Any()) { zombie.Kill(null); return; } } if (zombie.Downed) { if (ZombieSettings.Values.zombiesDieVeryEasily || ZombieSettings.Values.doubleTapRequired == false) { zombie.Kill(null); return; } var walkCapacity = PawnCapacityUtility.CalculateCapacityLevel(zombie.health.hediffSet, PawnCapacityDefOf.Moving); var missingBrain = zombie.health.hediffSet.GetBrain() == null; if (walkCapacity < 0.25f || missingBrain) { zombie.Kill(null); return; } var injuries = zombie.health.hediffSet.GetHediffs <Hediff_Injury>(); foreach (var injury in injuries) { if (ZombieSettings.Values.zombiesDieVeryEasily) { zombie.Kill(null); return; } if (injury.IsOld() == false) { injury.Heal(injury.Severity + 0.5f); break; } } if (zombie.Downed) { return; } } // handling invalid destinations // if (destination.x == 0 && destination.z == 0) { destination = IntVec3.Invalid; } if (zombie.HasValidDestination(destination)) { return; } // if we are near targets then attack them // var enemy = CanAttack(); if (enemy != null) { destination = enemy.Position; zombie.state = ZombieState.Tracking; if (Constants.USE_SOUND) { var info = SoundInfo.InMap(enemy); SoundDef.Named("ZombieHit").PlayOneShot(info); } AttackThing(enemy, JobDefOf.AttackMelee); return; } // eat a downed or dead pawn // if (eatTarget == null) { eatTarget = CanIngest(out eatTargetIsCorpse); if (eatTarget != null) { eatTargetDied = eatTarget.Dead; } } if (eatTarget != null) { if (eatDelayCounter == 0) { if (eatTarget != lastEatTarget) { lastEatTarget = eatTarget; zombie.Drawer.rotator.FaceCell(eatTarget.Position); var zombieLeaner = zombie.Drawer.leaner as ZombieLeaner; if (zombieLeaner != null) { zombieLeaner.extraOffset = (eatTarget.Position.ToVector3() - zombie.Position.ToVector3()) * 0.5f; } Tools.CastThoughtBubble(pawn, Constants.EATING); } CastEatingSound(); } eatDelayCounter++; if (eatDelayCounter <= EatDelay) { return; } eatDelayCounter = 0; var bodyPartRecord = FirstEatablePart(eatTarget); if (bodyPartRecord != null) { var hediff_MissingPart = (Hediff_MissingPart)HediffMaker.MakeHediff(HediffDefOf.MissingBodyPart, eatTarget, bodyPartRecord); hediff_MissingPart.lastInjury = HediffDefOf.Bite; hediff_MissingPart.IsFresh = true; eatTarget.health.AddHediff(hediff_MissingPart, null, null); if (eatTargetIsCorpse == false && eatTargetDied == false && eatTarget.Dead) { Tools.DoWithAllZombies(map, z => { if (z.jobs != null) { var driver = z.jobs.curDriver as JobDriver_Stumble; if (driver != null && driver.eatTarget == eatTarget) { driver.eatTargetDied = true; driver.eatTargetIsCorpse = true; } } }); if (PawnUtility.ShouldSendNotificationAbout(eatTarget) && eatTarget.RaceProps.Humanlike) { Messages.Message("MessageEatenByPredator".Translate(new object[] { eatTarget.LabelShort, zombie.LabelIndefinite() }).CapitalizeFirst(), zombie, MessageSound.Negative); } eatTarget.Strip(); } return; } else { var corpse = map.thingGrid .ThingsListAt(eatTarget.Position) .OfType <Corpse>() .FirstOrDefault(c => c.InnerPawn == eatTarget); if (corpse != null) { corpse.Destroy(DestroyMode.Vanish); } Tools.DoWithAllZombies(map, z => { if (z.jobs != null) { var driver = z.jobs.curDriver as JobDriver_Stumble; if (driver != null && driver.eatTarget == eatTarget) { driver.eatTarget = null; driver.lastEatTarget = null; driver.eatDelayCounter = 0; } } }); } } else { var zombieLeaner = zombie.Drawer.leaner as ZombieLeaner; if (zombieLeaner != null) { zombieLeaner.extraOffset = Vector3.zero; } } var basePos = zombie.Position; // calculate possible moves, sort by pheromone value and take top 3 // then choose the one with the lowest zombie count // also, emit a circle of timestamps when discovering a pheromone // trace so nearby zombies pick it up too (leads to a chain reaction) // var grid = zombie.Map.GetGrid(); var possibleTrackingMoves = new List <IntVec3>(); var currentTicks = Tools.Ticks(); var timeDelta = long.MaxValue; for (int i = 0; i < 8; i++) { var pos = basePos + GenAdj.AdjacentCells[i]; if (currentTicks - grid.Get(pos, false).timestamp < fadeOff && zombie.HasValidDestination(pos)) { possibleTrackingMoves.Add(pos); } } if (possibleTrackingMoves.Count > 0) { possibleTrackingMoves.Sort((p1, p2) => SortByTimestamp(grid, p1, p2)); possibleTrackingMoves = possibleTrackingMoves.Take(Constants.NUMBER_OF_TOP_MOVEMENT_PICKS).ToList(); possibleTrackingMoves = possibleTrackingMoves.OrderBy(p => grid.Get(p, false).zombieCount).ToList(); var nextMove = possibleTrackingMoves.First(); timeDelta = currentTicks - grid.Get(nextMove, false).timestamp; destination = nextMove; if (zombie.state == ZombieState.Wandering) { Tools.ChainReact(zombie.Map, basePos, nextMove); if (timeDelta <= agitatedFadeoff) { CastBrainzThought(); } } zombie.state = ZombieState.Tracking; } if (destination.IsValid == false) { zombie.state = ZombieState.Wandering; } bool checkSmashable = timeDelta >= checkSmashableFadeoff; if (ZombieSettings.Values.smashOnlyWhenAgitated) { checkSmashable &= zombie.state == ZombieState.Tracking; } if (destination.IsValid == false || checkSmashable) { var building = CanSmash(); if (building != null) { destination = building.Position; if (Constants.USE_SOUND) { var info = SoundInfo.InMap(enemy); SoundDef.Named("ZombieHit").PlayOneShot(info); } AttackThing(building, JobDefOf.AttackStatic); return; } } if (destination.IsValid == false) { var hour = GenLocalDate.HourOfDay(Find.VisibleMap); // check for day/night and dust/dawn // var moveTowardsCenter = false; if (map.areaManager.Home[basePos] == false) { if (hour < 12) { hour += 24; } if (hour > Constants.HOUR_START_OF_NIGHT && hour < Constants.HOUR_END_OF_NIGHT) { moveTowardsCenter = true; } else if (hour >= Constants.HOUR_START_OF_DUSK && hour <= Constants.HOUR_START_OF_NIGHT) { moveTowardsCenter = Rand.RangeInclusive(hour, Constants.HOUR_START_OF_NIGHT) == Constants.HOUR_START_OF_NIGHT; } else if (hour >= Constants.HOUR_END_OF_NIGHT && hour <= Constants.HOUR_START_OF_DAWN) { moveTowardsCenter = Rand.RangeInclusive(Constants.HOUR_END_OF_NIGHT, hour) == Constants.HOUR_END_OF_NIGHT; } } var possibleMoves = new List <IntVec3>(); for (int i = 0; i < 8; i++) { var pos = basePos + GenAdj.AdjacentCells[i]; if (zombie.HasValidDestination(pos)) { possibleMoves.Add(pos); } } if (possibleMoves.Count > 0) { // during night, zombies drift towards the colonies center // if (moveTowardsCenter) { var center = zombie.wanderDestination.IsValid ? zombie.wanderDestination : map.Center; possibleMoves.Sort((p1, p2) => SortByDirection(center, p1, p2)); possibleMoves = possibleMoves.Take(Constants.NUMBER_OF_TOP_MOVEMENT_PICKS).ToList(); possibleMoves = possibleMoves.OrderBy(p => grid.Get(p, false).zombieCount).ToList(); destination = possibleMoves.First(); } else { // otherwise they sometimes stand or walk towards a random direction // if (Rand.Chance(Constants.STANDING_STILL_CHANCE)) { var n = possibleMoves.Count(); destination = possibleMoves[Constants.random.Next(n)]; } } } } // if we have a valid destination, go there // if (destination.IsValid) { MoveToCell(destination); } }