Menu

Show posts

This section allows you to view all posts made by this member. Note that you can only see posts made in areas you currently have access to.

Show posts Menu

Messages - Currently_Fortifying

#1
Sorry I didn't get back to you sooner, and I wasn't being clear enough.

It only gives an error on startup, explosive weapons that have FMR removed work perfectly fine in game.
Hence the reason I said it needed to be changed to forcedMissRadius >= 0f != CausesExplosion. Although, this should be changed to forcedMissRadius >=0f, so that FMR can be applied to non Explosive weapons without an error.

Given it doesn't prevent the loading of the mod, and it doesn't prevent weapons from firing without error, the error isn't an error. It's a warning displayed as an error, that's erroneously returned by inappropriate boundary check.

Also, yes I've checked to make sure there's no unintended side effects from adding or removing FMR. The relevant code for FMR calculations is in Verse.Verb_LaunchProjectile.TryCastShot()
In it, it has this check before calculating FMR, if (this.verbProps.forcedMissRadius > 0.5f). Which means erroneous values are already check for, and the check in Verse.VerbProperties.ConfigErrors, is entirely pointless. You could literally have a negative FMR and the relevant code would still function without issue.

It's not something that needs immediate fixing, and I can make a harmony patch for this issue so that people won't complain about my mod giving them an error. Still though, this config error needs to be eventually removed, because it is pointless, or it needs to be changed so that it only throws a warning when FMR is negative.
#2
Relevant thread:
https://ludeon.com/forums/index.php?topic=51389.0

Basically if you remove forcedMissRadius from explosive weapons via xml patch it creates the following error on startup:
(Config error in Gun_TripleRocket: verb 0: has incorrect forcedMiss settings; explosive projectiles and only explosive projectiles should have forced miss enabled)

Homez found the offending bug in Verse.VerbProperties.ConfigErrors
The relevant code is:
(if (LaunchesProjectile && defaultProjectile != null && forcedMissRadius > 0f != CausesExplosion)
{
yield return "has incorrect forcedMiss settings; explosive projectiles and only explosive projectiles should have forced miss enabled";
})


The issue of course is that forcedMissRadius needs to be greater than 0 if the weapon is an explosive based weapon. Which if you remove FRM from an explosive weapon, this sets FRM to it's default value of 0, float has a default value of 0 in c#.

The solution is to change forcedMissRadius > 0f != CausesExplosion to forcedMissRadius >= 0f != CausesExplosion.
#3
Thank you, I'll see about making a prefix patch and just preventing the original function from running, because transpilers scare me.
I was worried for a moment as a c++ developer when I read through and noticed forcedMissRadius didn't have a default value. Then I realized this is c#.

I'll also make a bug report given the fix is as easy as changing this.forcedMissRadius > 0f != this.CausesExplosion to
this.forcedMissRadius >= 0f != this.CausesExplosion. Since it's only a problem when the miss radius is negative
Thanks again for rooting around in the source for me.
#4
If forcedMissRadius is removed via xml patch, I.E.
<Operation Class="PatchOperationRemove">
   <xpath>Defs/ThingDef[defName="Gun_DoomsdayRocket"]/verbs/li/forcedMissRadius</xpath>
</Operation>
The user will get an error on startup. The error is:

Config error in Gun_TripleRocket: verb 0: has incorrect forcedMiss settings; explosive projectiles and only explosive projectiles should have forced miss enabled
Verse.Log:Error(String, Boolean)
Verse.DefDatabase`1:ErrorCheckAllDefs()
System.Reflection.MonoMethod:InternalInvoke(Object, Object[], Exception&)
System.Reflection.MonoMethod:Invoke(Object, BindingFlags, Binder, Object[], CultureInfo)
System.Reflection.MethodBase:Invoke(Object, Object[])
Verse.GenGeneric:InvokeStaticMethodOnGenericType(Type, Type, String)
Verse.PlayDataLoader:DoPlayLoad()
Verse.PlayDataLoader:LoadAllPlayData(Boolean)
Verse.<>c:<Start>b__6_1()
Verse.LongEventHandler:RunEventFromAnotherThread(Action)
Verse.<>c:<UpdateCurrentAsynchronousEvent>b__27_0()
System.Threading.ThreadHelper:ThreadStart_Context(Object)
System.Threading.ExecutionContext:RunInternal(ExecutionContext, ContextCallback, Object, Boolean)
System.Threading.ExecutionContext:Run(ExecutionContext, ContextCallback, Object, Boolean)
System.Threading.ExecutionContext:Run(ExecutionContext, ContextCallback, Object)
System.Threading.ThreadHelper:ThreadStart()


Anyone know how to fix this? It doesn't prevent the game from being ran, and as far as I can tell FMR is removed after test firing weapons that use it.
#5
I'm pretty sure this has been reported before, but I couldn't find any topics on the matter.

The number of hives that spawn in an infestation scale with the players wealth. This isn't an issue early on as any given room the infestation spawns in will accommodate all of the spawned hives. Later on however, the number of hives that spawn will be so numerous that any given room cannot fit all of them in it. So the hives will overflow into nearby areas. Normally another viable room is picked and the hives spawn there. However, every so often locations that are not viable are picked instead. I've seen hives spawn in areas that are at -20 F, and I've seen them spawn in broad daylight.

To replicate this, make a 5x5 room deep in a mountain, make sure it's a viable temperature. Have 3 tile thick walls around the room, and then make a 1 tile wide tunnel around the walls, make sure the tunnel is below the minimum spawning temperature.

Also before I forget, infestations can spawn in the middle of a river if that river runs underneath overhead mountain. Not sure if it's intended, but it is annoying .
#6
Help / Re: Sharp to blunt bugfix
March 26, 2020, 05:16:33 AM
ArmorUtility
(namespace Verse
{
// Token: 0x0200022B RID: 555
public static class ArmorUtility
{
// Token: 0x06000F4E RID: 3918 RVA: 0x000580B4 File Offset: 0x000562B4
public static float GetPostArmorDamage(Pawn pawn, float amount, float armorPenetration, BodyPartRecord part, ref DamageDef damageDef, out bool deflectedByMetalArmor, out bool diminishedByMetalArmor)
{
deflectedByMetalArmor = false;
diminishedByMetalArmor = false;
if (damageDef.armorCategory == null)
{
return amount;
}
StatDef armorRatingStat = damageDef.armorCategory.armorRatingStat;
if (pawn.apparel != null)
{
List<Apparel> wornApparel = pawn.apparel.WornApparel;
for (int i = wornApparel.Count - 1; i >= 0; i--)
{
Apparel apparel = wornApparel[i];
if (apparel.def.apparel.CoversBodyPart(part))
{
float num = amount;
bool flag;
ArmorUtility.ApplyArmor(ref amount, armorPenetration, apparel.GetStatValue(armorRatingStat, true), apparel, ref damageDef, pawn, out flag);
if (amount < 0.001f)
{
deflectedByMetalArmor = flag;
return 0f;
}
if (amount < num && flag)
{
diminishedByMetalArmor = true;
}
}
}
}
float num2 = amount;
bool flag2;
ArmorUtility.ApplyArmor(ref amount, armorPenetration, pawn.GetStatValue(armorRatingStat, true), null, ref damageDef, pawn, out flag2);
if (amount < 0.001f)
{
deflectedByMetalArmor = flag2;
return 0f;
}
if (amount < num2 && flag2)
{
diminishedByMetalArmor = true;
}
return amount;
}

// Token: 0x06000F4F RID: 3919 RVA: 0x000581A0 File Offset: 0x000563A0
private static void ApplyArmor(ref float damAmount, float armorPenetration, float armorRating, Thing armorThing, ref DamageDef damageDef, Pawn pawn, out bool metalArmor)
{
if (armorThing != null)
{
metalArmor = (armorThing.def.apparel.useDeflectMetalEffect || (armorThing.Stuff != null && armorThing.Stuff.IsMetal));
}
else
{
metalArmor = pawn.RaceProps.IsMechanoid;
}
if (armorThing != null)
{
float f = damAmount * 0.25f;
armorThing.TakeDamage(new DamageInfo(damageDef, (float)GenMath.RoundRandom(f), 0f, -1f, null, null, null, DamageInfo.SourceCategory.ThingOrUnknown, null));
}
float num = Mathf.Max(armorRating - armorPenetration, 0f);
float value = Rand.Value;
float num2 = num * 0.5f;
float num3 = num;
if (value < num2)
{
damAmount = 0f;
return;
}
if (value < num3)
{
damAmount = (float)GenMath.RoundRandom(damAmount / 2f);
if (damageDef.armorCategory == DamageArmorCategoryDefOf.Sharp)
{
damageDef = DamageDefOf.Blunt;
}
}
}

// Token: 0x04000B59 RID: 2905
public const float MaxArmorRating = 2f;

// Token: 0x04000B5A RID: 2906
public const float DeflectThresholdFactor = 0.5f;
}
}
)
#7
Help / Re: Sharp to blunt bugfix
March 26, 2020, 05:15:37 AM
DamageWorker_Blunt
(namespace Verse
{
// Token: 0x02000224 RID: 548
public class DamageWorker_Blunt : DamageWorker_AddInjury
{
// Token: 0x06000F39 RID: 3897 RVA: 0x000574AA File Offset: 0x000556AA
protected override BodyPartRecord ChooseHitPart(DamageInfo dinfo, Pawn pawn)
{
return pawn.health.hediffSet.GetRandomNotMissingPart(dinfo.Def, dinfo.Height, BodyPartDepth.Outside, null);
}

// Token: 0x06000F3A RID: 3898 RVA: 0x000574D4 File Offset: 0x000556D4
protected override void ApplySpecialEffectsToPart(Pawn pawn, float totalDamage, DamageInfo dinfo, DamageWorker.DamageResult result)
{
bool flag = Rand.Chance(this.def.bluntInnerHitChance);
float num = flag ? this.def.bluntInnerHitDamageFractionToConvert.RandomInRange : 0f;
float num2 = totalDamage * (1f - num);
DamageInfo lastInfo = dinfo;
for (;;)
{
num2 -= base.FinalizeAndAddInjury(pawn, num2, lastInfo, result);
if (!pawn.health.hediffSet.PartIsMissing(lastInfo.HitPart) || num2 <= 1f)
{
break;
}
BodyPartRecord parent = lastInfo.HitPart.parent;
if (parent == null)
{
break;
}
lastInfo.SetHitPart(parent);
}
if (flag && !lastInfo.HitPart.def.IsSolid(lastInfo.HitPart, pawn.health.hediffSet.hediffs) && lastInfo.HitPart.depth == BodyPartDepth.Outside)
{
BodyPartRecord hitPart;
if ((from x in pawn.health.hediffSet.GetNotMissingParts(BodyPartHeight.Undefined, BodyPartDepth.Undefined, null, null)
where x.parent == lastInfo.HitPart && x.def.IsSolid(x, pawn.health.hediffSet.hediffs) && x.depth == BodyPartDepth.Inside
select x).TryRandomElementByWeight((BodyPartRecord x) => x.coverageAbs, out hitPart))
{
DamageInfo lastInfo2 = lastInfo;
lastInfo2.SetHitPart(hitPart);
float totalDamage2 = totalDamage * num + totalDamage * this.def.bluntInnerHitDamageFractionToAdd.RandomInRange;
base.FinalizeAndAddInjury(pawn, totalDamage2, lastInfo2, result);
}
}
if (!pawn.Dead)
{
SimpleCurve simpleCurve = null;
if (lastInfo.HitPart.parent == null)
{
simpleCurve = this.def.bluntStunChancePerDamagePctOfCorePartToBodyCurve;
}
else
{
foreach (BodyPartRecord lhs in pawn.RaceProps.body.GetPartsWithTag(BodyPartTagDefOf.ConsciousnessSource))
{
if (this.InSameBranch(lhs, lastInfo.HitPart))
{
simpleCurve = this.def.bluntStunChancePerDamagePctOfCorePartToHeadCurve;
break;
}
}
}
if (simpleCurve != null)
{
float x2 = totalDamage / pawn.def.race.body.corePart.def.GetMaxHealth(pawn);
if (Rand.Chance(simpleCurve.Evaluate(x2)))
{
DamageInfo dinfo2 = dinfo;
dinfo2.Def = DamageDefOf.Stun;
dinfo2.SetAmount((float)this.def.bluntStunDuration.SecondsToTicks() / 30f);
pawn.TakeDamage(dinfo2);
}
}
}
}

// Token: 0x06000F3B RID: 3899 RVA: 0x000577A0 File Offset: 0x000559A0
[DebugOutput]
public static void StunChances()
{
Func<ThingDef, float, bool, string> bluntBodyStunChance = delegate(ThingDef d, float dam, bool onHead)
{
SimpleCurve simpleCurve = onHead ? DamageDefOf.Blunt.bluntStunChancePerDamagePctOfCorePartToHeadCurve : DamageDefOf.Blunt.bluntStunChancePerDamagePctOfCorePartToBodyCurve;
Pawn pawn = PawnGenerator.GeneratePawn(new PawnGenerationRequest(d.race.AnyPawnKind, Find.FactionManager.FirstFactionOfDef(d.race.AnyPawnKind.defaultFactionType), PawnGenerationContext.NonPlayer, -1, true, false, false, false, true, false, 1f, false, true, true, true, false, false, false, false, 0f, null, 1f, null, null, null, null, null, null, null, null, null, null, null, null));
float x = dam / d.race.body.corePart.def.GetMaxHealth(pawn);
Find.WorldPawns.PassToWorld(pawn, PawnDiscardDecideMode.Discard);
return Mathf.Clamp01(simpleCurve.Evaluate(x)).ToStringPercent();
};
List<TableDataGetter<ThingDef>> list = new List<TableDataGetter<ThingDef>>();
list.Add(new TableDataGetter<ThingDef>("defName", (ThingDef d) => d.defName));
list.Add(new TableDataGetter<ThingDef>("body size", (ThingDef d) => d.race.baseBodySize.ToString("F2")));
list.Add(new TableDataGetter<ThingDef>("health scale", (ThingDef d) => d.race.baseHealthScale.ToString("F2")));
list.Add(new TableDataGetter<ThingDef>("body size\n* health scale", (ThingDef d) => (d.race.baseHealthScale * d.race.baseBodySize).ToString("F2")));
list.Add(new TableDataGetter<ThingDef>("core part\nhealth", delegate(ThingDef d)
{
Pawn pawn = PawnGenerator.GeneratePawn(new PawnGenerationRequest(d.race.AnyPawnKind, Find.FactionManager.FirstFactionOfDef(d.race.AnyPawnKind.defaultFactionType), PawnGenerationContext.NonPlayer, -1, true, false, false, false, true, false, 1f, false, true, true, true, false, false, false, false, 0f, null, 1f, null, null, null, null, null, null, null, null, null, null, null, null));
float maxHealth = d.race.body.corePart.def.GetMaxHealth(pawn);
Find.WorldPawns.PassToWorld(pawn, PawnDiscardDecideMode.Discard);
return maxHealth;
}));
list.Add(new TableDataGetter<ThingDef>("stun\nchance\nbody\n5", (ThingDef d) => bluntBodyStunChance(d, 5f, false)));
list.Add(new TableDataGetter<ThingDef>("stun\nchance\nbody\n10", (ThingDef d) => bluntBodyStunChance(d, 10f, false)));
list.Add(new TableDataGetter<ThingDef>("stun\nchance\nbody\n15", (ThingDef d) => bluntBodyStunChance(d, 15f, false)));
list.Add(new TableDataGetter<ThingDef>("stun\nchance\nbody\n20", (ThingDef d) => bluntBodyStunChance(d, 20f, false)));
list.Add(new TableDataGetter<ThingDef>("stun\nchance\nhead\n5", (ThingDef d) => bluntBodyStunChance(d, 5f, true)));
list.Add(new TableDataGetter<ThingDef>("stun\nchance\nhead\n10", (ThingDef d) => bluntBodyStunChance(d, 10f, true)));
list.Add(new TableDataGetter<ThingDef>("stun\nchance\nhead\n15", (ThingDef d) => bluntBodyStunChance(d, 15f, true)));
list.Add(new TableDataGetter<ThingDef>("stun\nchance\nhead\n20", (ThingDef d) => bluntBodyStunChance(d, 20f, true)));
DebugTables.MakeTablesDialog<ThingDef>(from d in DefDatabase<ThingDef>.AllDefs
where d.category == ThingCategory.Pawn
select d, list.ToArray());
}

// Token: 0x06000F3C RID: 3900 RVA: 0x000579E0 File Offset: 0x00055BE0
private bool InSameBranch(BodyPartRecord lhs, BodyPartRecord rhs)
{
while (lhs.parent != null)
{
if (lhs.parent.parent == null)
{
break;
}
lhs = lhs.parent;
}
while (rhs.parent != null && rhs.parent.parent != null)
{
rhs = rhs.parent;
}
return lhs == rhs;
}
}
}
)
#8
Help / Re: Sharp to blunt bugfix
March 26, 2020, 05:14:17 AM
Actually, I'll just go ahead and post the source.
DamageWorker_AddInjury
(namespace Verse
{
// Token: 0x02000222 RID: 546
public class DamageWorker_AddInjury : DamageWorker
{
// Token: 0x06000F27 RID: 3879 RVA: 0x00056A98 File Offset: 0x00054C98
public override DamageWorker.DamageResult Apply(DamageInfo dinfo, Thing thing)
{
Pawn pawn = thing as Pawn;
if (pawn == null)
{
return base.Apply(dinfo, thing);
}
return this.ApplyToPawn(dinfo, pawn);
}

// Token: 0x06000F28 RID: 3880 RVA: 0x00056AC0 File Offset: 0x00054CC0
private DamageWorker.DamageResult ApplyToPawn(DamageInfo dinfo, Pawn pawn)
{
DamageWorker.DamageResult damageResult = new DamageWorker.DamageResult();
if (dinfo.Amount <= 0f)
{
return damageResult;
}
if (!DebugSettings.enablePlayerDamage && pawn.Faction == Faction.OfPlayer)
{
return damageResult;
}
Map mapHeld = pawn.MapHeld;
bool spawnedOrAnyParentSpawned = pawn.SpawnedOrAnyParentSpawned;
if (dinfo.AllowDamagePropagation && dinfo.Amount >= (float)dinfo.Def.minDamageToFragment)
{
int num = Rand.RangeInclusive(2, 4);
for (int i = 0; i < num; i++)
{
DamageInfo dinfo2 = dinfo;
dinfo2.SetAmount(dinfo.Amount / (float)num);
this.ApplyDamageToPart(dinfo2, pawn, damageResult);
}
}
else
{
this.ApplyDamageToPart(dinfo, pawn, damageResult);
this.ApplySmallPawnDamagePropagation(dinfo, pawn, damageResult);
}
if (damageResult.wounded)
{
DamageWorker_AddInjury.PlayWoundedVoiceSound(dinfo, pawn);
pawn.Drawer.Notify_DamageApplied(dinfo);
EffecterDef damageEffecter = pawn.RaceProps.FleshType.damageEffecter;
if (damageEffecter != null)
{
if (pawn.health.woundedEffecter != null && pawn.health.woundedEffecter.def != damageEffecter)
{
pawn.health.woundedEffecter.Cleanup();
}
pawn.health.woundedEffecter = damageEffecter.Spawn();
pawn.health.woundedEffecter.Trigger(pawn, dinfo.Instigator ?? pawn);
}
}
if (damageResult.headshot && pawn.Spawned)
{
MoteMaker.ThrowText(new Vector3((float)pawn.Position.x + 1f, (float)pawn.Position.y, (float)pawn.Position.z + 1f), pawn.Map, "Headshot".Translate(), Color.white, -1f);
if (dinfo.Instigator != null)
{
Pawn pawn2 = dinfo.Instigator as Pawn;
if (pawn2 != null)
{
pawn2.records.Increment(RecordDefOf.Headshots);
}
}
}
if ((damageResult.deflected || damageResult.diminished) && spawnedOrAnyParentSpawned)
{
EffecterDef effecterDef;
if (damageResult.deflected)
{
if (damageResult.deflectedByMetalArmor && dinfo.Def.canUseDeflectMetalEffect)
{
if (dinfo.Def == DamageDefOf.Bullet)
{
effecterDef = EffecterDefOf.Deflect_Metal_Bullet;
}
else
{
effecterDef = EffecterDefOf.Deflect_Metal;
}
}
else if (dinfo.Def == DamageDefOf.Bullet)
{
effecterDef = EffecterDefOf.Deflect_General_Bullet;
}
else
{
effecterDef = EffecterDefOf.Deflect_General;
}
}
else if (damageResult.diminishedByMetalArmor)
{
effecterDef = EffecterDefOf.DamageDiminished_Metal;
}
else
{
effecterDef = EffecterDefOf.DamageDiminished_General;
}
if (pawn.health.deflectionEffecter == null || pawn.health.deflectionEffecter.def != effecterDef)
{
if (pawn.health.deflectionEffecter != null)
{
pawn.health.deflectionEffecter.Cleanup();
pawn.health.deflectionEffecter = null;
}
pawn.health.deflectionEffecter = effecterDef.Spawn();
}
pawn.health.deflectionEffecter.Trigger(pawn, dinfo.Instigator ?? pawn);
if (damageResult.deflected)
{
pawn.Drawer.Notify_DamageDeflected(dinfo);
}
}
if (!damageResult.deflected && spawnedOrAnyParentSpawned)
{
ImpactSoundUtility.PlayImpactSound(pawn, dinfo.Def.impactSoundType, mapHeld);
}
return damageResult;
}

// Token: 0x06000F29 RID: 3881 RVA: 0x00056DF0 File Offset: 0x00054FF0
private void CheckApplySpreadDamage(DamageInfo dinfo, Thing t)
{
if (dinfo.Def == DamageDefOf.Flame && !t.FlammableNow)
{
return;
}
if (Rand.Chance(0.5f))
{
dinfo.SetAmount((float)Mathf.CeilToInt(dinfo.Amount * Rand.Range(0.35f, 0.7f)));
t.TakeDamage(dinfo);
}
}

// Token: 0x06000F2A RID: 3882 RVA: 0x00056E4C File Offset: 0x0005504C
private void ApplySmallPawnDamagePropagation(DamageInfo dinfo, Pawn pawn, DamageWorker.DamageResult result)
{
if (!dinfo.AllowDamagePropagation)
{
return;
}
if (result.LastHitPart != null && dinfo.Def.harmsHealth && result.LastHitPart != pawn.RaceProps.body.corePart && result.LastHitPart.parent != null && pawn.health.hediffSet.GetPartHealth(result.LastHitPart.parent) > 0f && result.LastHitPart.parent.coverageAbs > 0f && dinfo.Amount >= 10f && pawn.HealthScale <= 0.5001f)
{
DamageInfo dinfo2 = dinfo;
dinfo2.SetHitPart(result.LastHitPart.parent);
this.ApplyDamageToPart(dinfo2, pawn, result);
}
}

// Token: 0x06000F2B RID: 3883 RVA: 0x00056F18 File Offset: 0x00055118
private void ApplyDamageToPart(DamageInfo dinfo, Pawn pawn, DamageWorker.DamageResult result)
{
BodyPartRecord exactPartFromDamageInfo = this.GetExactPartFromDamageInfo(dinfo, pawn);
if (exactPartFromDamageInfo == null)
{
return;
}
dinfo.SetHitPart(exactPartFromDamageInfo);
float num = dinfo.Amount;
bool flag = !dinfo.InstantPermanentInjury;
bool deflectedByMetalArmor = false;
if (flag)
{
DamageDef def = dinfo.Def;
bool diminishedByMetalArmor;
num = ArmorUtility.GetPostArmorDamage(pawn, num, dinfo.ArmorPenetrationInt, dinfo.HitPart, ref def, out deflectedByMetalArmor, out diminishedByMetalArmor);
dinfo.Def = def;
if (num < dinfo.Amount)
{
result.diminished = true;
result.diminishedByMetalArmor = diminishedByMetalArmor;
}
}
if (num <= 0f)
{
result.AddPart(pawn, dinfo.HitPart);
result.deflected = true;
result.deflectedByMetalArmor = deflectedByMetalArmor;
return;
}
if (DamageWorker_AddInjury.IsHeadshot(dinfo, pawn))
{
result.headshot = true;
}
if (dinfo.InstantPermanentInjury && (HealthUtility.GetHediffDefFromDamage(dinfo.Def, pawn, dinfo.HitPart).CompPropsFor(typeof(HediffComp_GetsPermanent)) == null || dinfo.HitPart.def.permanentInjuryChanceFactor == 0f || pawn.health.hediffSet.PartOrAnyAncestorHasDirectlyAddedParts(dinfo.HitPart)))
{
return;
}
if (!dinfo.AllowDamagePropagation)
{
this.FinalizeAndAddInjury(pawn, num, dinfo, result);
return;
}
this.ApplySpecialEffectsToPart(pawn, num, dinfo, result);
}

// Token: 0x06000F2C RID: 3884 RVA: 0x0005704A File Offset: 0x0005524A
protected virtual void ApplySpecialEffectsToPart(Pawn pawn, float totalDamage, DamageInfo dinfo, DamageWorker.DamageResult result)
{
totalDamage = this.ReduceDamageToPreserveOutsideParts(totalDamage, dinfo, pawn);
this.FinalizeAndAddInjury(pawn, totalDamage, dinfo, result);
this.CheckDuplicateDamageToOuterParts(dinfo, pawn, totalDamage, result);
}

// Token: 0x06000F2D RID: 3885 RVA: 0x00057070 File Offset: 0x00055270
protected float FinalizeAndAddInjury(Pawn pawn, float totalDamage, DamageInfo dinfo, DamageWorker.DamageResult result)
{
if (pawn.health.hediffSet.PartIsMissing(dinfo.HitPart))
{
return 0f;
}
if (dinfo.Def.ExternalViolenceFor(pawn))
{
totalDamage *= pawn.GetStatValue(StatDefOf.IncomingDamageFactor, true);
}
HediffDef hediffDefFromDamage = HealthUtility.GetHediffDefFromDamage(dinfo.Def, pawn, dinfo.HitPart);
Hediff_Injury hediff_Injury = (Hediff_Injury)HediffMaker.MakeHediff(hediffDefFromDamage, pawn, null);
hediff_Injury.Part = dinfo.HitPart;
hediff_Injury.source = dinfo.Weapon;
hediff_Injury.sourceBodyPartGroup = dinfo.WeaponBodyPartGroup;
hediff_Injury.sourceHediffDef = dinfo.WeaponLinkedHediff;
hediff_Injury.Severity = totalDamage;
if (dinfo.InstantPermanentInjury)
{
HediffComp_GetsPermanent hediffComp_GetsPermanent = hediff_Injury.TryGetComp<HediffComp_GetsPermanent>();
if (hediffComp_GetsPermanent != null)
{
hediffComp_GetsPermanent.IsPermanent = true;
}
else
{
Log.Error(string.Concat(new object[]
{
"Tried to create instant permanent injury on Hediff without a GetsPermanent comp: ",
hediffDefFromDamage,
" on ",
pawn
}), false);
}
}
return this.FinalizeAndAddInjury(pawn, hediff_Injury, dinfo, result);
}

// Token: 0x06000F2E RID: 3886 RVA: 0x0005716C File Offset: 0x0005536C
protected float FinalizeAndAddInjury(Pawn pawn, Hediff_Injury injury, DamageInfo dinfo, DamageWorker.DamageResult result)
{
HediffComp_GetsPermanent hediffComp_GetsPermanent = injury.TryGetComp<HediffComp_GetsPermanent>();
if (hediffComp_GetsPermanent != null)
{
hediffComp_GetsPermanent.PreFinalizeInjury();
}
pawn.health.AddHediff(injury, null, new DamageInfo?(dinfo), result);
float num = Mathf.Min(injury.Severity, pawn.health.hediffSet.GetPartHealth(injury.Part));
result.totalDamageDealt += num;
result.wounded = true;
result.AddPart(pawn, injury.Part);
result.AddHediff(injury);
return num;
}

// Token: 0x06000F2F RID: 3887 RVA: 0x000571F0 File Offset: 0x000553F0
private void CheckDuplicateDamageToOuterParts(DamageInfo dinfo, Pawn pawn, float totalDamage, DamageWorker.DamageResult result)
{
if (!dinfo.AllowDamagePropagation)
{
return;
}
if (dinfo.Def.harmAllLayersUntilOutside && dinfo.HitPart.depth == BodyPartDepth.Inside)
{
BodyPartRecord parent = dinfo.HitPart.parent;
do
{
if (pawn.health.hediffSet.GetPartHealth(parent) != 0f && parent.coverageAbs > 0f)
{
Hediff_Injury hediff_Injury = (Hediff_Injury)HediffMaker.MakeHediff(HealthUtility.GetHediffDefFromDamage(dinfo.Def, pawn, parent), pawn, null);
hediff_Injury.Part = parent;
hediff_Injury.source = dinfo.Weapon;
hediff_Injury.sourceBodyPartGroup = dinfo.WeaponBodyPartGroup;
hediff_Injury.Severity = totalDamage;
if (hediff_Injury.Severity <= 0f)
{
hediff_Injury.Severity = 1f;
}
this.FinalizeAndAddInjury(pawn, hediff_Injury, dinfo, result);
}
if (parent.depth == BodyPartDepth.Outside)
{
break;
}
parent = parent.parent;
}
while (parent != null);
}
}

// Token: 0x06000F30 RID: 3888 RVA: 0x000572D9 File Offset: 0x000554D9
private static bool IsHeadshot(DamageInfo dinfo, Pawn pawn)
{
return !dinfo.InstantPermanentInjury && dinfo.HitPart.groups.Contains(BodyPartGroupDefOf.FullHead) && dinfo.Def == DamageDefOf.Bullet;
}

// Token: 0x06000F31 RID: 3889 RVA: 0x00057310 File Offset: 0x00055510
private BodyPartRecord GetExactPartFromDamageInfo(DamageInfo dinfo, Pawn pawn)
{
if (dinfo.HitPart == null)
{
BodyPartRecord bodyPartRecord = this.ChooseHitPart(dinfo, pawn);
if (bodyPartRecord == null)
{
Log.Warning("ChooseHitPart returned null (any part).", false);
}
return bodyPartRecord;
}
if (!pawn.health.hediffSet.GetNotMissingParts(BodyPartHeight.Undefined, BodyPartDepth.Undefined, null, null).Any((BodyPartRecord x) => x == dinfo.HitPart))
{
return null;
}
return dinfo.HitPart;
}

// Token: 0x06000F32 RID: 3890 RVA: 0x00057387 File Offset: 0x00055587
protected virtual BodyPartRecord ChooseHitPart(DamageInfo dinfo, Pawn pawn)
{
return pawn.health.hediffSet.GetRandomNotMissingPart(dinfo.Def, dinfo.Height, dinfo.Depth, null);
}

// Token: 0x06000F33 RID: 3891 RVA: 0x000573B0 File Offset: 0x000555B0
private static void PlayWoundedVoiceSound(DamageInfo dinfo, Pawn pawn)
{
if (pawn.Dead)
{
return;
}
if (dinfo.InstantPermanentInjury)
{
return;
}
if (!pawn.SpawnedOrAnyParentSpawned)
{
return;
}
if (dinfo.Def.ExternalViolenceFor(pawn))
{
LifeStageUtility.PlayNearestLifestageSound(pawn, (LifeStageAge ls) => ls.soundWounded, 1f);
}
}

// Token: 0x06000F34 RID: 3892 RVA: 0x00057414 File Offset: 0x00055614
protected float ReduceDamageToPreserveOutsideParts(float postArmorDamage, DamageInfo dinfo, Pawn pawn)
{
if (!DamageWorker_AddInjury.ShouldReduceDamageToPreservePart(dinfo.HitPart))
{
return postArmorDamage;
}
float partHealth = pawn.health.hediffSet.GetPartHealth(dinfo.HitPart);
if (postArmorDamage < partHealth)
{
return postArmorDamage;
}
float maxHealth = dinfo.HitPart.def.GetMaxHealth(pawn);
float f = (postArmorDamage - partHealth) / maxHealth;
if (Rand.Chance(this.def.overkillPctToDestroyPart.InverseLerpThroughRange(f)))
{
return postArmorDamage;
}
return postArmorDamage = partHealth - 1f;
}

// Token: 0x06000F35 RID: 3893 RVA: 0x0005748C File Offset: 0x0005568C
public static bool ShouldReduceDamageToPreservePart(BodyPartRecord bodyPart)
{
return bodyPart.depth == BodyPartDepth.Outside && !bodyPart.IsCorePart;
}
}
}
)
#9
Help / Re: Sharp to blunt bugfix
March 26, 2020, 03:40:54 AM
In ArmorUtility.ApplyArmor, it occurs at the very end of the function.

ArmorUtility.GetPostArmorDamage is called in DamageWorker_AddInjury.ApplyDamageToPart.
.ApplyArmor is called by .GetPostArmorDamage.

Do you want me to post the relevant parts from the decompiled source code?
#10
Help / Sharp to blunt bugfix
March 25, 2020, 06:46:06 PM
Currently whenever sharp is converted to blunt, via ArmorUtility.ApplyArmor, DamageWorker_Blunt.ApplySpecialEffects is not called.
The reason of course is that the calculations for sharp damage, and its conversion to blunt, take place in the base class DamageWorker_AddInjury, which means the derived class, DamageWorker_Blunt, is never created and as such DamageWorker_AddInjury.ApplySpecialEffects is called.

The intended goal is to make a patch that checks to see if the damage type has been converted to blunt, and then uses DamageWorker_Blunt.ApplySpecialEffects.
The reason I want to use Blunt's special Effects is that I want other peoples' changes to Blunt's special effects to still work. Likewise I wanted to make a mod later down the line that overhauls how blunt damage effects internal parts.

I've been knocking my head against this problem in my free time for a few days now, and haven't made any progress.
As far as I can tell, tanspilers aren't an option as I need to call Blunt's code.
I would post my code, but I would prefer to hear what you guys think and start fresh.

Any help is appreciated.
I'm going to be away for several hours so I probably won't respond for a while.
#11
Just a heads up, all information below is from RimWorlds decompiled source.

To elaborate, when a bullet hits an armored pawn, there's a rng role to determine if the damage deflects harmlessly, is diminished and turned into blunt damage, or the damage is unaffected.

In the event of a diminished role, the damage is halved and converted into blunt, however DamageWorker_Blunt is not called, meaning the shot will deal full blunt damage to internal organs.
For the moderators, blunt damage is supposed to have a rng role to deal less damage to internal organs, and DamageWorker_Blunt is responsible for calculating the reduction in damage.

The relevant issue in the code is in class verse.ArmorUtility.ApplyArmor:
if (value < num3)
         {
            damAmount = (float)GenMath.RoundRandom(damAmount / 2f);
            if (damageDef.armorCategory == DamageArmorCategoryDefOf.Sharp)
            {
               damageDef = DamageDefOf.Blunt;
            }
         }

As can be seen, the damage is directly converted to blunt meaning DamageWorker_Blunt is never used.

This is testable in game by having 1 pawn shoot an armored pawn until you get a blunt shot that penetrates to an interior organ. The damage between the outer skin layer and the internal organ will be the same.

If this is an intended feature please let me know so I can create a mod changing the behavior.
If this will be fixed, but will be patched at a later date, i.e. months from now, please let me know so I can create a mod as a temporary fix.