Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of Carturs HD Blood v1.1.2
plugins/CarturHDBlood.dll
Decompiled a week ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyCompany("CarturHDBlood")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.1.2.0")] [assembly: AssemblyInformationalVersion("1.1.2+c0784f4a44cc9b99ca32c07a05a959e1744e037d")] [assembly: AssemblyProduct("CarturHDBlood")] [assembly: AssemblyTitle("CarturHDBlood")] [assembly: AssemblyVersion("1.1.2.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace CarturHDBlood { internal struct GroundPreset { public float MinChance; public float ChanceMultiplier; public float SizeMultiplier; public float LifetimeMultiplier; public int MaxDecals; public static GroundPreset From(float g) { g = Mathf.Clamp(g, 0f, 3f); if (g <= 0f) { return new GroundPreset { MinChance = 0f, ChanceMultiplier = 0f, SizeMultiplier = 1f, LifetimeMultiplier = 1f, MaxDecals = 0 }; } return new GroundPreset { MinChance = ((g <= 1f) ? (10f * g) : Mathf.Lerp(10f, 100f, (g - 1f) * 0.5f)), ChanceMultiplier = g, SizeMultiplier = Mathf.Max(0.25f, 1f + (g - 1f) * 0.3f), LifetimeMultiplier = Mathf.Max(1f, g), MaxDecals = ((!(g <= 1f)) ? Mathf.RoundToInt(Mathf.Lerp(0f, 600f, (g - 1f) * 0.5f)) : 0) }; } public static GroundPreset Current() { return From(BloodPreset.Current().Ground); } } internal enum BloodAmount { Low, Normal, High, Extreme, Custom } internal struct BloodPreset { public float Ground; public float Hit; public float Death; public float Wet; public float Opacity; public float PoolSize; public int PoolCount; public float DropletSize; public float DropletSpread; public float DropletCount; public float CloudSize; public bool Stretch; public float HitThreshold; public static BloodPreset For(BloodAmount level) { return level switch { BloodAmount.Low => new BloodPreset { Ground = 0.15f, Hit = 0.1f, Death = 0.25f, Wet = 0.3f, Opacity = 0.4f, PoolSize = 2.5f, PoolCount = 1, DropletSize = 0.02f, DropletSpread = 0.4f, DropletCount = 0.75f, CloudSize = 0.85f, Stretch = false, HitThreshold = 0.1f }, BloodAmount.High => new BloodPreset { Ground = 0.6f, Hit = 0.4f, Death = 1f, Wet = 0.38f, Opacity = 0.6f, PoolSize = 4.5f, PoolCount = 3, DropletSize = 0.02f, DropletSpread = 0.7f, DropletCount = 2f, CloudSize = 1f, Stretch = false, HitThreshold = 0.01f }, BloodAmount.Extreme => new BloodPreset { Ground = 1.2f, Hit = 0.8f, Death = 2f, Wet = 0.4f, Opacity = 0.8f, PoolSize = 6f, PoolCount = 6, DropletSize = 0.02f, DropletSpread = 0.85f, DropletCount = 4f, CloudSize = 1.25f, Stretch = false, HitThreshold = 0f }, _ => new BloodPreset { Ground = 0.3f, Hit = 0.2f, Death = 0.5f, Wet = 0.4f, Opacity = 0.51f, PoolSize = 3.2f, PoolCount = 4, DropletSize = 0.02f, DropletSpread = 0.6f, DropletCount = 1.5f, CloudSize = 0.92f, Stretch = false, HitThreshold = 0.02f }, }; } public static BloodPreset Current() { BloodAmount value = Plugin.BloodLevel.Value; if (value != BloodAmount.Custom) { return For(value); } return new BloodPreset { Ground = Plugin.GroundBlood.Value, Hit = Plugin.HitBlood.Value, Death = Plugin.DeathBlood.Value, Wet = Plugin.Wetness.Value, Opacity = Plugin.GroundOpacity.Value, PoolSize = Plugin.PoolSize.Value, PoolCount = Plugin.PoolCount.Value, DropletSize = DebugTuning.DropletSize.Value, DropletSpread = DebugTuning.DropletSpread.Value, DropletCount = DebugTuning.DropletCount.Value, CloudSize = DebugTuning.CloudSize.Value, Stretch = DebugTuning.StretchParticles.Value, HitThreshold = Plugin.HitEffectThreshold.Value }; } } internal static class BloodSkin { private static readonly HashSet<int> SkinnedMaterials = new HashSet<int>(); private static readonly List<Material> Owned = new List<Material>(); private static Texture2D _groundLarge; private static Texture2D _groundLargeAlt; private static Texture2D _groundSmall; private static Texture2D _splash; private static Texture2D _gloss; private static Texture2D _droplet; private static Texture2D _groundLargeNormal; private static Texture2D _groundLargeAltNormal; private static Texture2D _groundSmallNormal; private static bool _loadAttempted; private static readonly string[] KnownDecalMaterials = new string[3] { "splat_decal_blend", "seeker_blood_splat", "SeekerQueen_decals" }; private static string[] _materialPrefixes; private static Material _bloodSplashMaterial; private static Texture _originalMainTex; private static Material _vanillaDecalMaterial; private static string[] _excludePrefixes; private static Material _bloodDropMaterial; private static readonly Dictionary<int, Material> SmallGroundMaterials = new Dictionary<int, Material>(); private static readonly Dictionary<int, Material> AltGroundMaterials = new Dictionary<int, Material>(); private const int CommonDecalBurst = 5; private const float BoarHitMin = 1f; private const float BoarHitMax = 3f; private const float PileLifetime = 0.25f; private const float SpreadLifetime = 0.8f; private const float SpreadGravity = 0.5f; private const float MaxReach = 20f; private static void Own(Material mat) { if ((Object)(object)mat != (Object)null && !Owned.Contains(mat)) { Owned.Add(mat); } } internal static void RefreshLookSettings() { int num = 0; foreach (Material item in Owned) { if (!((Object)(object)item == (Object)null)) { ApplyWetness(item); ApplyGroundFade(item); num++; } } if (num > 0) { Plugin.Log.LogInfo((object)$"Re-applied look settings to {num} blood material(s)."); } } public static void PreSkin(ZNetScene scene) { if ((Object)(object)scene == (Object)null || !Plugin.ModEnabled.Value || (!Plugin.ReplaceTexture.Value && !Plugin.FixGreenSplash.Value)) { return; } int num = 0; int num2 = 0; foreach (GameObject prefab in scene.m_prefabs) { if ((Object)(object)prefab == (Object)null) { continue; } int num3 = 0; ParticleDecal[] componentsInChildren = prefab.GetComponentsInChildren<ParticleDecal>(true); foreach (ParticleDecal val in componentsInChildren) { if ((Object)(object)val == (Object)null || (Object)(object)val.m_decalSystem == (Object)null) { continue; } Material val2 = MaterialOf(val.m_decalSystem); if ((Object)(object)val2 == (Object)null || !IsBloodMaterial(val2)) { continue; } num3++; if (Plugin.ReplaceTexture.Value) { try { SkinMaterial(val2); num++; } catch (Exception ex) { Plugin.Log.LogWarning((object)("Pre-skin failed on " + ((Object)val2).name + ": " + ex.Message)); } } } if (!Plugin.FixGreenSplash.Value) { continue; } bool flag = num3 > 0; ParticleSystemRenderer[] componentsInChildren2 = prefab.GetComponentsInChildren<ParticleSystemRenderer>(true); foreach (ParticleSystemRenderer val3 in componentsInChildren2) { if ((Object)(object)val3 == (Object)null) { continue; } Material sharedMaterial = ((Renderer)val3).sharedMaterial; if ((Object)(object)sharedMaterial == (Object)null || !flag || !IsSlimeSplashMaterial(sharedMaterial)) { continue; } try { if (ReplaceSlimeSplash(val3)) { num2++; } } catch (Exception ex2) { Plugin.Log.LogWarning((object)("Splash fix failed on " + ((Object)sharedMaterial).name + ": " + ex2.Message)); } } } Plugin.Log.LogInfo((object)($"Pre-skinned {num} decal material(s) and re-coloured " + $"{num2} green splash renderer(s) at world load.")); } public static void Apply(ParticleDecal decal) { //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)decal == (Object)null || !Plugin.ModEnabled.Value) { return; } ParticleSystem decalSystem = decal.m_decalSystem; if ((Object)(object)decalSystem == (Object)null) { return; } Material val = MaterialOf(decalSystem); if ((Object)(object)val == (Object)null || !IsBloodMaterial(val)) { return; } if (IsExcludedEffect(decal)) { RestoreVanilla(decalSystem, val); return; } try { if (Plugin.ReplaceTexture.Value) { SkinMaterial(val); } MainModule main = decalSystem.main; float textureSize = AuthoredSize(((MainModule)(ref main)).startSize); LevelHitMark(decal, decalSystem); TuneDecalSystem(decalSystem, val, textureSize); TuneChance(decal); LevelHitDensity(decal); NormaliseSpread(decal); CloudGraft.Apply(decal); if (IsDeathEffect(decal) && DebugTuning.DeathPoolEnabled()) { Pooling.SpawnPool(decal); } ExtendEffectLifetime(decal); SprayAim.Apply(decal); DebugTuning.Apply(decal); } catch (Exception ex) { Plugin.Log.LogWarning((object)("Failed applying blood skin: " + ex.Message)); } } internal static bool IsBloodMaterial(Material mat) { if (((Object)mat).name == null) { return false; } if (_materialPrefixes == null) { _materialPrefixes = ParseList(Plugin.DecalMaterials.Value); } if (!MatchesAny(((Object)mat).name, _materialPrefixes)) { return MatchesAny(((Object)mat).name, KnownDecalMaterials); } return true; } private static bool IsSlimeSplashMaterial(Material mat) { if (((Object)mat).name != null) { return ((Object)mat).name.StartsWith("slime", StringComparison.OrdinalIgnoreCase); } return false; } internal static bool ApplyDropletTexture(ParticleSystemRenderer r) { //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Expected O, but got Unknown LoadTextures(); if ((Object)(object)_droplet == (Object)null || (Object)(object)r == (Object)null) { return false; } Material sharedMaterial = ((Renderer)r).sharedMaterial; if ((Object)(object)sharedMaterial == (Object)null || (Object)(object)sharedMaterial == (Object)(object)_bloodDropMaterial) { return false; } if ((Object)(object)_bloodDropMaterial == (Object)null) { _bloodDropMaterial = new Material(sharedMaterial) { name = "CarturBloodDroplet" }; if (!_bloodDropMaterial.HasProperty("_MainTex")) { Plugin.Log.LogWarning((object)("\"" + ((Object)sharedMaterial).name + "\" has no _MainTex, so a droplet texture cannot be assigned to it. Droplets stay untextured; nothing else is affected.")); _bloodDropMaterial = null; return false; } _bloodDropMaterial.SetTexture("_MainTex", (Texture)(object)_droplet); ApplyWetness(_bloodDropMaterial); Plugin.Log.LogInfo((object)("Cloned \"" + ((Object)sharedMaterial).name + "\" -> \"CarturBloodDroplet\" and gave it a droplet texture (vanilla leaves these untextured).")); } Own(_bloodDropMaterial); ((Renderer)r).sharedMaterial = _bloodDropMaterial; return true; } private static bool ReplaceSlimeSplash(ParticleSystemRenderer r) { //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Expected O, but got Unknown //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) Material sharedMaterial = ((Renderer)r).sharedMaterial; if ((Object)(object)sharedMaterial == (Object)null || (Object)(object)sharedMaterial == (Object)(object)_bloodSplashMaterial) { return false; } if ((Object)(object)_bloodSplashMaterial == (Object)null) { _bloodSplashMaterial = new Material(sharedMaterial) { name = "CarturBloodSplash" }; if (_bloodSplashMaterial.HasProperty("_Color")) { _bloodSplashMaterial.SetColor("_Color", Color.white); } if (_bloodSplashMaterial.HasProperty("_EmissionColor")) { _bloodSplashMaterial.SetColor("_EmissionColor", Color.black); } if ((Object)(object)_splash != (Object)null && Plugin.ReplaceSplashTexture.Value && _bloodSplashMaterial.HasProperty("_MainTex")) { _bloodSplashMaterial.SetTexture("_MainTex", (Texture)(object)_splash); } ApplyWetness(_bloodSplashMaterial); Plugin.Log.LogInfo((object)("Cloned \"" + ((Object)sharedMaterial).name + "\" -> \"CarturBloodSplash\" (green splash inside blood effects; the original is left alone so slimes are unaffected).")); } Own(_bloodSplashMaterial); ((Renderer)r).sharedMaterial = _bloodSplashMaterial; return true; } private static void SkinMaterial(Material mat) { LoadTextures(); Texture2D val = _groundLarge ?? _groundLargeAlt ?? _groundSmall; if ((Object)(object)val == (Object)null) { return; } Texture val2 = (mat.HasProperty("_MainTex") ? mat.GetTexture("_MainTex") : null); if ((Object)(object)val2 == (Object)(object)val && SkinnedMaterials.Contains(((Object)mat).GetInstanceID())) { Own(mat); ApplyWetness(mat); ApplyGroundFade(mat); ClearForeignNormalSlots(mat); SetNormal(mat, _groundLargeNormal); return; } SkinnedMaterials.Add(((Object)mat).GetInstanceID()); Own(mat); if ((Object)(object)_originalMainTex == (Object)null && (Object)(object)val2 != (Object)null) { _originalMainTex = val2; } mat.SetTexture("_MainTex", (Texture)(object)val); SetNormal(mat, _groundLargeNormal); ApplyWetness(mat); ApplyGroundFade(mat); Plugin.Log.LogInfo((object)($"Skinned \"{((Object)mat).name}\" id={((Object)mat).GetInstanceID()}: " + "_MainTex \"" + (((Object)(object)val2 == (Object)null) ? "none" : ((Object)val2).name) + "\" -> \"" + ((Object)val).name + "\" " + $"{((Texture)val).width}x{((Texture)val).height}")); Texture val3 = (mat.HasProperty("_MainTex") ? mat.GetTexture("_MainTex") : null); if ((Object)(object)val3 != (Object)(object)val) { Plugin.Log.LogWarning((object)("\"" + ((Object)mat).name + "\" still holds \"" + (((Object)(object)val3 == (Object)null) ? "none" : ((Object)val3).name) + "\" immediately after skinning - something else is writing to it.")); } } private static void AssignGroundVariant(ParticleSystem ps, Material shared, float authoredSize) { ParticleSystemRenderer component = ((Component)ps).GetComponent<ParticleSystemRenderer>(); if ((Object)(object)component == (Object)null) { return; } if (authoredSize < Plugin.SmallDecalSize.Value) { Material val = GroundClone(SmallGroundMaterials, shared, _groundSmall, _groundSmallNormal, "CarturBloodGroundSmall"); if ((Object)(object)val != (Object)null) { ((Renderer)component).sharedMaterial = val; } } else if (!(Random.value < 0.5f)) { Material val2 = GroundClone(AltGroundMaterials, shared, _groundLargeAlt, _groundLargeAltNormal, "CarturBloodGroundAlt"); if ((Object)(object)val2 != (Object)null) { ((Renderer)component).sharedMaterial = val2; } } } private static Material GroundClone(Dictionary<int, Material> cache, Material shared, Texture2D tex, Texture2D normal, string name) { //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Expected O, but got Unknown if ((Object)(object)shared == (Object)null || (Object)(object)tex == (Object)null) { return null; } int instanceID = ((Object)shared).GetInstanceID(); if (cache.TryGetValue(instanceID, out var value) && (Object)(object)value != (Object)null) { return value; } try { Material val = new Material(shared) { name = name }; if (!val.HasProperty("_MainTex")) { return null; } Own(val); val.SetTexture("_MainTex", (Texture)(object)tex); ApplyWetness(val); ApplyGroundFade(val); SetNormal(val, normal); cache[instanceID] = val; Plugin.Log.LogInfo((object)("Built \"" + name + "\" from \"" + ((Object)shared).name + "\" with \"" + ((Object)tex).name + "\" " + $"{((Texture)tex).width}x{((Texture)tex).height}.")); return val; } catch (Exception ex) { Plugin.Log.LogWarning((object)("Could not build " + name + ": " + ex.Message)); return null; } } private static void ClearForeignNormalSlots(Material mat) { if (!((Object)(object)mat == (Object)null) && mat.HasProperty("_NormalTex") && mat.HasProperty("_BumpMap")) { mat.SetTexture("_BumpMap", (Texture)null); mat.DisableKeyword("_NORMALMAP"); } } private static void SetNormal(Material mat, Texture2D normal) { if (!((Object)(object)normal == (Object)null) && !((Object)(object)mat == (Object)null)) { if (mat.HasProperty("_NormalTex")) { mat.SetTexture("_NormalTex", (Texture)(object)normal); } else if (mat.HasProperty("_BumpMap")) { mat.SetTexture("_BumpMap", (Texture)(object)normal); mat.EnableKeyword("_NORMALMAP"); } } } internal static void ApplyGroundFade(Material mat) { if (!((Object)(object)mat == (Object)null) && Plugin.GroundFade.Value && mat.HasProperty("_Mode") && !(Mathf.Abs(mat.GetFloat("_Mode") - 2f) < 0.01f)) { mat.SetFloat("_Mode", 2f); if (mat.HasProperty("_SrcBlend")) { mat.SetFloat("_SrcBlend", 5f); } if (mat.HasProperty("_DstBlend")) { mat.SetFloat("_DstBlend", 10f); } if (mat.HasProperty("_ZWrite")) { mat.SetFloat("_ZWrite", 0f); } mat.DisableKeyword("_ALPHATEST_ON"); mat.EnableKeyword("_ALPHABLEND_ON"); mat.DisableKeyword("_ALPHAPREMULTIPLY_ON"); mat.renderQueue = 3000; Plugin.Log.LogInfo((object)("\"" + ((Object)mat).name + "\": Cutout -> Fade, so ground blood can fade out instead of being cut away and popping.")); } } internal static void ApplyWetness(Material mat) { //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)mat == (Object)null) { return; } float num = Mathf.Clamp01(BloodPreset.Current().Wet); if (mat.HasProperty("_Glossiness")) { mat.SetFloat("_Glossiness", num); } if (mat.HasProperty("_GlossMapScale")) { mat.SetFloat("_GlossMapScale", num); } if (mat.HasProperty("_Color")) { Color color = mat.GetColor("_Color"); if (color.r < 0.999f || color.g < 0.999f || color.b < 0.999f) { mat.SetColor("_Color", new Color(1f, 1f, 1f, color.a)); } } if (mat.HasProperty("_LightingEnabled")) { mat.SetFloat("_LightingEnabled", Plugin.GroundLighting.Value ? 1f : 0f); } if (mat.HasProperty("_GlossyReflections")) { bool value = Plugin.Reflections.Value; mat.SetFloat("_GlossyReflections", value ? 1f : 0f); if (value) { mat.DisableKeyword("_GLOSSYREFLECTIONS_OFF"); } else { mat.EnableKeyword("_GLOSSYREFLECTIONS_OFF"); } } if (Plugin.WetnessMap.Value && (Object)(object)_gloss != (Object)null && mat.HasProperty("_MetallicGlossMap")) { mat.SetTexture("_MetallicGlossMap", (Texture)(object)_gloss); mat.EnableKeyword("_METALLICGLOSSMAP"); } } private static void LevelHitMark(ParticleDecal decal, ParticleSystem decalSystem) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)decalSystem == (Object)null || IsDeathEffect(decal)) { return; } try { MainModule main = decalSystem.main; if (!(AuthoredSize(((MainModule)(ref main)).startSize) >= 2f)) { ((MainModule)(ref main)).startSize = new MinMaxCurve(1f, 3f); } } catch (Exception ex) { Plugin.Log.LogWarning((object)("Hit mark levelling failed: " + ex.Message)); } } private static float MaxOf(MinMaxCurve c) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Invalid comparison between Unknown and I4 ParticleSystemCurveMode mode = ((MinMaxCurve)(ref c)).mode; if ((int)mode != 0) { if ((int)mode == 3) { return ((MinMaxCurve)(ref c)).constantMax; } return ((MinMaxCurve)(ref c)).curveMultiplier; } return ((MinMaxCurve)(ref c)).constant; } private static void NormaliseSpread(ParticleDecal decal) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) ParticleSystem component = ((Component)decal).GetComponent<ParticleSystem>(); if ((Object)(object)component == (Object)null) { return; } try { MainModule main = component.main; float num = AuthoredSize(((MainModule)(ref main)).startLifetime); if (num <= 0f) { return; } if (num < 0.25f) { ((MainModule)(ref main)).startLifetime = new MinMaxCurve(0.8f); ((MainModule)(ref main)).gravityModifier = new MinMaxCurve(0.5f); return; } float num2 = MaxOf(((MainModule)(ref main)).startSpeed); if (!(num2 <= 0.01f)) { float num3 = num2 * num; if (num3 > 20f) { ((MainModule)(ref main)).startLifetime = Scale(((MainModule)(ref main)).startLifetime, 20f / num3); } } } catch (Exception ex) { Plugin.Log.LogWarning((object)("Death spread normalisation failed: " + ex.Message)); } } private static void LevelHitDensity(ParticleDecal decal) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) ParticleSystem component = ((Component)decal).GetComponent<ParticleSystem>(); if ((Object)(object)component == (Object)null) { return; } try { EmissionModule emission = component.emission; if (!((EmissionModule)(ref emission)).enabled) { return; } for (int i = 0; i < ((EmissionModule)(ref emission)).burstCount; i++) { Burst burst = ((EmissionModule)(ref emission)).GetBurst(i); MinMaxCurve count = ((Burst)(ref burst)).count; float constantMax = ((MinMaxCurve)(ref count)).constantMax; if (!(constantMax <= 0f) && !(constantMax >= 5f)) { ((Burst)(ref burst)).count = new MinMaxCurve(5f); ((EmissionModule)(ref emission)).SetBurst(i, burst); } } } catch (Exception ex) { Plugin.Log.LogWarning((object)("Hit density levelling failed: " + ex.Message)); } } private static void TuneDecalSystem(ParticleSystem ps, Material shared, float textureSize) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) GroundPreset groundPreset = GroundPreset.Current(); MainModule main = ps.main; ((MainModule)(ref main)).startSize = Realism.JitterSize(((MainModule)(ref main)).startSize); if (Math.Abs(groundPreset.SizeMultiplier - 1f) > 0.001f) { ((MainModule)(ref main)).startSize = Scale(((MainModule)(ref main)).startSize, groundPreset.SizeMultiplier); } if (Math.Abs(groundPreset.LifetimeMultiplier - 1f) > 0.001f) { ((MainModule)(ref main)).startLifetime = Scale(((MainModule)(ref main)).startLifetime, groundPreset.LifetimeMultiplier); } if (groundPreset.MaxDecals > 0 && ((MainModule)(ref main)).maxParticles < groundPreset.MaxDecals) { ((MainModule)(ref main)).maxParticles = groundPreset.MaxDecals; } FixWashedOutColor(ref main); DeepenRed(ref main); Realism.AgeDecal(ps); Realism.GrowDecal(ps); if (Plugin.ReplaceTexture.Value) { AssignGroundVariant(ps, shared, textureSize); } } internal static float AuthoredSize(MinMaxCurve c) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Invalid comparison between Unknown and I4 ParticleSystemCurveMode mode = ((MinMaxCurve)(ref c)).mode; if ((int)mode != 0) { if ((int)mode == 3) { return (((MinMaxCurve)(ref c)).constantMin + ((MinMaxCurve)(ref c)).constantMax) * 0.5f; } return ((MinMaxCurve)(ref c)).curveMultiplier; } return ((MinMaxCurve)(ref c)).constant; } private static void FixWashedOutColor(ref MainModule main) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Invalid comparison between Unknown and I4 //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) float value = Plugin.ColorFallbackSaturation.Value; if (value <= 0f) { return; } float num = LiveProbe.Saturation(((MainModule)(ref main)).startColor); if (num < 0f || num >= value) { return; } Color blood = ParseColor(Plugin.BloodColor.Value); MinMaxGradient startColor = ((MainModule)(ref main)).startColor; ParticleSystemGradientMode mode = ((MinMaxGradient)(ref startColor)).mode; if ((int)mode != 0) { if ((int)mode == 2) { ((MainModule)(ref main)).startColor = new MinMaxGradient(Recolor(((MinMaxGradient)(ref startColor)).colorMin, blood), Recolor(((MinMaxGradient)(ref startColor)).colorMax, blood)); } } else { ((MainModule)(ref main)).startColor = new MinMaxGradient(Recolor(((MinMaxGradient)(ref startColor)).color, blood)); } } private static void DeepenRed(ref MainModule main) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Invalid comparison between Unknown and I4 //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) float num = Mathf.Clamp01(Plugin.BloodTint.Value); if (num <= 0.001f) { return; } Color blood = ParseColor(Plugin.BloodColor.Value); MinMaxGradient startColor = ((MainModule)(ref main)).startColor; ParticleSystemGradientMode mode = ((MinMaxGradient)(ref startColor)).mode; if ((int)mode != 0) { if ((int)mode == 2 && IsRedBlood(((MinMaxGradient)(ref startColor)).colorMin) && IsRedBlood(((MinMaxGradient)(ref startColor)).colorMax)) { ((MainModule)(ref main)).startColor = new MinMaxGradient(Deepen(((MinMaxGradient)(ref startColor)).colorMin, blood, num), Deepen(((MinMaxGradient)(ref startColor)).colorMax, blood, num)); } } else if (IsRedBlood(((MinMaxGradient)(ref startColor)).color)) { ((MainModule)(ref main)).startColor = new MinMaxGradient(Deepen(((MinMaxGradient)(ref startColor)).color, blood, num)); } } private static bool IsRedBlood(Color c) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) if (c.r > 0.15f && c.g < c.r * 0.4f) { return c.b < c.r * 0.4f; } return false; } private static Color Deepen(Color original, Color blood, float tint) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) Color result = Color.Lerp(original, blood, tint); result.a = original.a; return result; } private static Color Recolor(Color original, Color blood) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) float num = Mathf.Max(original.r, Mathf.Max(original.g, original.b)); if (num <= 0f) { num = 1f; } return new Color(blood.r * num, blood.g * num, blood.b * num, original.a); } private static Color ParseColor(string hex) { //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) Color result = default(Color); if (!string.IsNullOrEmpty(hex) && ColorUtility.TryParseHtmlString(hex.Trim(), ref result)) { return result; } return new Color(0.55f, 0.02f, 0.02f, 1f); } private static void TuneChance(ParticleDecal decal) { GroundPreset groundPreset = GroundPreset.Current(); float num = decal.m_chance * groundPreset.ChanceMultiplier; if (num < groundPreset.MinChance) { num = groundPreset.MinChance; } decal.m_chance = Mathf.Clamp(num, 0f, 100f); } private static bool IsDeathEffect(ParticleDecal decal) { try { Transform root = ((Component)decal).transform.root; return (((Object)(object)root == (Object)null) ? string.Empty : ((Object)root).name).IndexOf("death", StringComparison.OrdinalIgnoreCase) >= 0; } catch { return false; } } private static bool IsExcludedEffect(ParticleDecal decal) { if (_excludePrefixes == null) { _excludePrefixes = ParseList(Plugin.ExcludeEffects.Value); } if (_excludePrefixes.Length == 0) { return false; } try { Transform root = ((Component)decal).transform.root; string text = (((Object)(object)root == (Object)null) ? string.Empty : ((Object)root).name); string[] excludePrefixes = _excludePrefixes; foreach (string value in excludePrefixes) { if (text.IndexOf(value, StringComparison.OrdinalIgnoreCase) >= 0) { return true; } } } catch { } return false; } private static void RestoreVanilla(ParticleSystem decalSystem, Material template) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Expected O, but got Unknown if ((Object)(object)_originalMainTex == (Object)null) { return; } try { if ((Object)(object)_vanillaDecalMaterial == (Object)null) { _vanillaDecalMaterial = new Material(template) { name = "CarturVanillaDecal" }; _vanillaDecalMaterial.SetTexture("_MainTex", _originalMainTex); Plugin.Log.LogInfo((object)("Built \"CarturVanillaDecal\" from \"" + ((Object)_originalMainTex).name + "\" for effects that borrow the blood material for non-blood (frost breath, acid spit, egg goo).")); } ParticleSystemRenderer component = ((Component)decalSystem).GetComponent<ParticleSystemRenderer>(); if ((Object)(object)component != (Object)null) { ((Renderer)component).sharedMaterial = _vanillaDecalMaterial; } } catch (Exception ex) { Plugin.Log.LogWarning((object)("Could not restore vanilla decal: " + ex.Message)); } } internal static MinMaxCurve Scale(MinMaxCurve c, float mul) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Invalid comparison between Unknown and I4 //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) ParticleSystemCurveMode mode = ((MinMaxCurve)(ref c)).mode; if ((int)mode != 0) { if ((int)mode == 3) { return new MinMaxCurve(((MinMaxCurve)(ref c)).constantMin * mul, ((MinMaxCurve)(ref c)).constantMax * mul); } ((MinMaxCurve)(ref c)).curveMultiplier = ((MinMaxCurve)(ref c)).curveMultiplier * mul; return c; } return new MinMaxCurve(((MinMaxCurve)(ref c)).constant * mul); } private static Material MaterialOf(ParticleSystem ps) { ParticleSystemRenderer component = ((Component)ps).GetComponent<ParticleSystemRenderer>(); if (!((Object)(object)component == (Object)null)) { return ((Renderer)component).sharedMaterial; } return null; } private static string[] ParseList(string raw) { List<string> list = new List<string>(); string[] array = (raw ?? string.Empty).Split(new char[1] { ',' }); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (text.Length > 0) { list.Add(text); } } return list.ToArray(); } private static bool MatchesAny(string name, string[] prefixes) { foreach (string value in prefixes) { if (name.StartsWith(value, StringComparison.OrdinalIgnoreCase)) { return true; } } return false; } private static void LoadTextures() { if (!_loadAttempted) { _loadAttempted = true; Texture2D obj = Load("splatter_spray_1024_rgba.png", "CarturBloodGroundLargeSpray", mipmap: true); _groundLargeAlt = Load("splatter_mist_1024_rgba.png", "CarturBloodGroundLargeAlt", mipmap: true); _groundSmall = Load("splatter_impact_512_rgba.png", "CarturBloodGroundSmall", mipmap: true); Solidify(obj); Solidify(_groundLargeAlt); Solidify(_groundSmall); _groundLarge = Composite(obj, _groundSmall, "CarturBloodGroundLarge"); _groundLargeAlt = Composite(obj, _groundLargeAlt, "CarturBloodGroundLargeAlt"); _splash = Load("splash_1024_rgba.png", "CarturBloodSplashTex", mipmap: true); _droplet = Load("droplet_256_rgba.png", "CarturBloodDropletTex", mipmap: true, optional: true); _gloss = Load("splatter_spray_1024_gloss.png", "CarturBloodGlossTex", mipmap: true, optional: true, linear: true); if (Plugin.GroundNormalMap.Value) { _groundLargeNormal = Load("splatter_spray_1024_normal.png", "CarturBloodGroundLargeN", mipmap: true, optional: false, linear: true); _groundLargeAltNormal = Load("splatter_mist_1024_normal.png", "CarturBloodGroundLargeAltN", mipmap: true, optional: false, linear: true); _groundSmallNormal = Load("splatter_impact_512_normal.png", "CarturBloodGroundSmallN", mipmap: true, optional: false, linear: true); } } } private static void Solidify(Texture2D tex) { float opacity = BloodPreset.Current().Opacity; if ((Object)(object)tex == (Object)null || Mathf.Abs(opacity - 1f) < 0.001f) { return; } try { Color[] pixels = tex.GetPixels(); for (int i = 0; i < pixels.Length; i++) { pixels[i].a = Mathf.Clamp01(pixels[i].a * opacity); } tex.SetPixels(pixels); tex.Apply(true); } catch (Exception ex) { Plugin.Log.LogWarning((object)("Could not rewrite \"" + ((Object)tex).name + "\" to straight alpha: " + ex.Message)); } } private static Texture2D Composite(Texture2D basePattern, Texture2D overlay, string texName) { //IL_00fa: Unknown result type (might be due to invalid IL or missing references) //IL_00ff: Unknown result type (might be due to invalid IL or missing references) //IL_0106: Unknown result type (might be due to invalid IL or missing references) //IL_010d: Unknown result type (might be due to invalid IL or missing references) //IL_0114: Unknown result type (might be due to invalid IL or missing references) //IL_011b: Unknown result type (might be due to invalid IL or missing references) //IL_0124: Expected O, but got Unknown //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)basePattern == (Object)null || (Object)(object)overlay == (Object)null) { return basePattern; } try { int width = ((Texture)basePattern).width; int height = ((Texture)basePattern).height; Color[] pixels = basePattern.GetPixels(); Color[] array = (Color[])(object)new Color[pixels.Length]; for (int i = 0; i < height; i++) { float num = ((float)i + 0.5f) / (float)height; for (int j = 0; j < width; j++) { Color val = pixels[i * width + j]; Color pixelBilinear = overlay.GetPixelBilinear(((float)j + 0.5f) / (float)width, num); float num2 = 1f - (1f - val.a) * (1f - pixelBilinear.a); Color val2 = ((val.a >= pixelBilinear.a) ? val : pixelBilinear); array[i * width + j] = new Color(val2.r, val2.g, val2.b, num2); } } Texture2D val3 = new Texture2D(width, height, (TextureFormat)4, true) { name = texName, wrapMode = (TextureWrapMode)1, filterMode = (FilterMode)1 }; val3.SetPixels(array); val3.Apply(true); return val3; } catch (Exception ex) { Plugin.Log.LogWarning((object)("Could not composite \"" + texName + "\": " + ex.Message + ". Using \"" + ((Object)basePattern).name + "\" alone.")); return basePattern; } } private static void ExtendEffectLifetime(ParticleDecal decal) { //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Invalid comparison between Unknown and I4 //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) try { Transform root = ((Component)decal).transform.root; if ((Object)(object)root == (Object)null) { return; } TimedDestruction componentInChildren = ((Component)root).GetComponentInChildren<TimedDestruction>(true); if ((Object)(object)componentInChildren == (Object)null) { return; } float num = 0f; ParticleSystem[] componentsInChildren = ((Component)root).GetComponentsInChildren<ParticleSystem>(true); foreach (ParticleSystem val in componentsInChildren) { if (!((Object)(object)val == (Object)null)) { MainModule main = val.main; MinMaxCurve startLifetime = ((MainModule)(ref main)).startLifetime; float num2; if ((int)((MinMaxCurve)(ref startLifetime)).mode != 3) { startLifetime = ((MainModule)(ref main)).startLifetime; num2 = ((MinMaxCurve)(ref startLifetime)).constant; } else { startLifetime = ((MainModule)(ref main)).startLifetime; num2 = ((MinMaxCurve)(ref startLifetime)).constantMax; } float num3 = num2; num = Mathf.Max(num, ((MainModule)(ref main)).duration + num3); } } num += 1f; if (componentInChildren.m_timeout < num) { componentInChildren.m_timeout = num; } } catch (Exception ex) { Plugin.Log.LogWarning((object)("Could not extend effect lifetime: " + ex.Message)); } } private static Texture2D Load(string fileName, string texName, bool mipmap, bool optional = false, bool linear = false) { //IL_0135: Unknown result type (might be due to invalid IL or missing references) //IL_013a: Unknown result type (might be due to invalid IL or missing references) //IL_0141: Unknown result type (might be due to invalid IL or missing references) //IL_0148: Unknown result type (might be due to invalid IL or missing references) //IL_0150: Expected O, but got Unknown byte[] array = null; try { string text = Path.Combine(Plugin.ConfigDir ?? ".", "carturblood_" + fileName); if (File.Exists(text)) { array = File.ReadAllBytes(text); Plugin.Log.LogInfo((object)("Using override texture " + text)); } } catch (Exception ex) { Plugin.Log.LogWarning((object)("Could not read override texture: " + ex.Message)); } if (array == null) { try { using Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("CarturHDBlood." + fileName); if (stream == null) { if (optional) { Plugin.Log.LogInfo((object)("No " + fileName + " yet - drop one in BepInEx/config as carturblood_" + fileName + " to use it.")); } else { Plugin.Log.LogError((object)("Embedded texture missing: " + fileName)); } return null; } array = new byte[stream.Length]; stream.Read(array, 0, array.Length); } catch (Exception ex2) { Plugin.Log.LogError((object)("Could not read embedded texture " + fileName + ": " + ex2.Message)); return null; } } Texture2D val = new Texture2D(2, 2, (TextureFormat)4, mipmap, linear) { name = texName, wrapMode = (TextureWrapMode)1, filterMode = (FilterMode)1 }; if (!LoadImageViaReflection(val, array)) { Object.Destroy((Object)(object)val); return null; } return val; } private static bool LoadImageViaReflection(Texture2D tex, byte[] data) { try { Type type = Type.GetType("UnityEngine.ImageConversion, UnityEngine.ImageConversionModule") ?? Type.GetType("UnityEngine.ImageConversion, UnityEngine"); if (type != null) { MethodInfo method = type.GetMethod("LoadImage", BindingFlags.Static | BindingFlags.Public, null, new Type[3] { typeof(Texture2D), typeof(byte[]), typeof(bool) }, null); if (method != null) { return (bool)method.Invoke(null, new object[3] { tex, data, false }); } } MethodInfo method2 = typeof(Texture2D).GetMethod("LoadImage", BindingFlags.Instance | BindingFlags.Public, null, new Type[1] { typeof(byte[]) }, null); if (method2 != null) { return (bool)method2.Invoke(tex, new object[1] { data }); } Plugin.Log.LogError((object)"No usable LoadImage overload found."); return false; } catch (Exception ex) { Plugin.Log.LogError((object)("LoadImage failed: " + ex.Message)); return false; } } } internal static class CloudGraft { private const string SourceEffect = "vfx_greydwarf_death"; private const string GraftPrefix = "CarturBloodCloud_"; private static List<ParticleSystem> _source; private static float _sourceDecalSize = 1f; private static readonly HashSet<int> Grafted = new HashSet<int>(); private static readonly HashSet<string> _strippedTypes = new HashSet<string>(); private static bool _strippedChanged; private static readonly Dictionary<int, Material> NeutralMaterials = new Dictionary<int, Material>(); public static void Cache(ZNetScene scene) { //IL_0125: Unknown result type (might be due to invalid IL or missing references) //IL_012a: Unknown result type (might be due to invalid IL or missing references) _source = null; Grafted.Clear(); if ((Object)(object)scene == (Object)null || !Plugin.ModEnabled.Value) { return; } BloodPreset bloodPreset = BloodPreset.Current(); float num = Mathf.Max(0f, bloodPreset.Hit); float num2 = Mathf.Max(0f, bloodPreset.Death); if (num <= 0f && num2 <= 0f) { Plugin.Log.LogInfo((object)"Cloud graft off (both scales are 0)."); return; } GameObject val = null; foreach (GameObject prefab in scene.m_prefabs) { if ((Object)(object)prefab != (Object)null && ((Object)prefab).name == "vfx_greydwarf_death") { val = prefab; break; } } if ((Object)(object)val == (Object)null) { Plugin.Log.LogWarning((object)"Cloud graft: \"vfx_greydwarf_death\" not found, so there is nothing to copy from. Nothing else is affected. If the game renamed it, point SourceEffect at any prefab carrying blood_cloud systems."); return; } List<ParticleSystem> list = new List<ParticleSystem>(); ParticleSystemRenderer[] componentsInChildren = val.GetComponentsInChildren<ParticleSystemRenderer>(true); foreach (ParticleSystemRenderer val2 in componentsInChildren) { if ((Object)(object)val2 == (Object)null || !IsCloud(((Renderer)val2).sharedMaterial)) { continue; } ParticleSystem component = ((Component)val2).GetComponent<ParticleSystem>(); if ((Object)(object)component != (Object)null) { EmissionModule emission = component.emission; if (((EmissionModule)(ref emission)).enabled) { list.Add(component); } } } if (list.Count == 0) { Plugin.Log.LogWarning((object)"Cloud graft: \"vfx_greydwarf_death\" has no live blood_cloud systems. Nothing copied."); return; } _source = list; Plugin.Log.LogInfo((object)(string.Format("Cloud graft armed: {0} system(s) from \"{1}\", ", list.Count, "vfx_greydwarf_death") + $"hits at {num:0.##}x, deaths at {num2:0.##}x. Applied per spawned effect.")); } public static void Apply(ParticleDecal decal) { //IL_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_00de: Unknown result type (might be due to invalid IL or missing references) //IL_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_00eb: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Unknown result type (might be due to invalid IL or missing references) //IL_012a: Unknown result type (might be due to invalid IL or missing references) if (_source == null || (Object)(object)decal == (Object)null || (Object)(object)decal.m_decalSystem == (Object)null) { return; } try { Transform root = ((Component)decal).transform.root; if ((Object)(object)root == (Object)null) { return; } string name = ((Object)root).name; bool flag = name.IndexOf("hit", StringComparison.OrdinalIgnoreCase) >= 0; bool flag2 = name.IndexOf("death", StringComparison.OrdinalIgnoreCase) >= 0; if (!flag && !flag2) { return; } BloodPreset bloodPreset = BloodPreset.Current(); float num = Mathf.Max(0f, flag ? bloodPreset.Hit : bloodPreset.Death); if (num <= 0f || !Grafted.Add(((Object)((Component)root).gameObject).GetInstanceID()) || HasCloud(root)) { return; } MainModule main = decal.m_decalSystem.main; MinMaxGradient startColor = ((MainModule)(ref main)).startColor; main = decal.m_decalSystem.main; float sizeScale = Mathf.Clamp(BloodSkin.AuthoredSize(((MainModule)(ref main)).startSize) / _sourceDecalSize, 0.4f, 2.5f); foreach (ParticleSystem item in _source) { Graft(item, root, num, sizeScale, startColor); } } catch (Exception ex) { Plugin.Log.LogWarning((object)("Cloud graft failed: " + ex.Message)); } } private static float ReadDecalSize(GameObject prefab, float fallback) { //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) float num = 0f; int num2 = 0; ParticleDecal[] componentsInChildren = prefab.GetComponentsInChildren<ParticleDecal>(true); foreach (ParticleDecal val in componentsInChildren) { if (!((Object)(object)val == (Object)null) && !((Object)(object)val.m_decalSystem == (Object)null)) { ParticleSystemRenderer component = ((Component)val.m_decalSystem).GetComponent<ParticleSystemRenderer>(); if (!((Object)(object)component == (Object)null) && !((Object)(object)((Renderer)component).sharedMaterial == (Object)null) && BloodSkin.IsBloodMaterial(((Renderer)component).sharedMaterial)) { float num3 = num; MainModule main = val.m_decalSystem.main; num = num3 + BloodSkin.AuthoredSize(((MainModule)(ref main)).startSize); num2++; } } } if (num2 != 0) { return Mathf.Max(0.01f, num / (float)num2); } return fallback; } private static bool IsCloud(Material mat) { if ((Object)(object)mat != (Object)null && ((Object)mat).name != null) { return ((Object)mat).name.StartsWith("blood_cloud", StringComparison.OrdinalIgnoreCase); } return false; } private static bool HasCloud(Transform root) { //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) ParticleSystemRenderer[] componentsInChildren = ((Component)root).GetComponentsInChildren<ParticleSystemRenderer>(true); foreach (ParticleSystemRenderer val in componentsInChildren) { if ((Object)(object)val == (Object)null || !IsCloud(((Renderer)val).sharedMaterial) || ((Object)val).name.StartsWith("CarturBloodCloud_", StringComparison.Ordinal)) { continue; } ParticleSystem component = ((Component)val).GetComponent<ParticleSystem>(); if ((Object)(object)component != (Object)null) { EmissionModule emission = component.emission; if (((EmissionModule)(ref emission)).enabled) { return true; } } } return false; } private static void Graft(ParticleSystem source, Transform parent, float scale, float sizeScale, MinMaxGradient tint) { //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00d7: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_011e: Unknown result type (might be due to invalid IL or missing references) //IL_0123: Unknown result type (might be due to invalid IL or missing references) //IL_012e: Unknown result type (might be due to invalid IL or missing references) //IL_0133: Unknown result type (might be due to invalid IL or missing references) //IL_0139: Unknown result type (might be due to invalid IL or missing references) //IL_013f: Unknown result type (might be due to invalid IL or missing references) //IL_014d: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)source == (Object)null) { return; } GameObject val = Object.Instantiate<GameObject>(((Component)source).gameObject); ((Object)val).name = "CarturBloodCloud_" + ((Object)((Component)source).gameObject).name; ParticleSystem component = val.GetComponent<ParticleSystem>(); if ((Object)(object)component == (Object)null) { Object.Destroy((Object)(object)val); return; } component.Stop(true, (ParticleSystemStopBehavior)0); StripToParticlesOnly(val); val.transform.SetParent(parent, false); val.transform.localPosition = Vector3.zero; val.transform.localRotation = ((Component)source).transform.localRotation; val.transform.localScale = ((Component)source).transform.localScale; MainModule main = component.main; ((MainModule)(ref main)).startColor = tint; if (Math.Abs(sizeScale - 1f) > 0.001f) { ((MainModule)(ref main)).startSize = BloodSkin.Scale(((MainModule)(ref main)).startSize, sizeScale); } ColorOverLifetimeModule colorOverLifetime = component.colorOverLifetime; ((ColorOverLifetimeModule)(ref colorOverLifetime)).enabled = false; ParticleSystemRenderer component2 = val.GetComponent<ParticleSystemRenderer>(); if ((Object)(object)component2 != (Object)null) { ((Renderer)component2).sharedMaterial = NeutralCloudMaterial(((Renderer)component2).sharedMaterial); } if (Math.Abs(scale - 1f) > 0.001f) { EmissionModule emission = component.emission; for (int i = 0; i < ((EmissionModule)(ref emission)).burstCount; i++) { Burst burst = ((EmissionModule)(ref emission)).GetBurst(i); ((Burst)(ref burst)).count = BloodSkin.Scale(((Burst)(ref burst)).count, scale); ((EmissionModule)(ref emission)).SetBurst(i, burst); } if (scale > 1f) { int num = Mathf.CeilToInt((float)((MainModule)(ref main)).maxParticles * scale); if (((MainModule)(ref main)).maxParticles < num) { ((MainModule)(ref main)).maxParticles = num; } } } Ballistics(component); component.Play(true); } private static void Ballistics(ParticleSystem ps) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) MainModule main = ps.main; MinMaxCurve gravityModifier = ((MainModule)(ref main)).gravityModifier; if (Mathf.Abs(((MinMaxCurve)(ref gravityModifier)).constant) < 0.001f) { gravityModifier = ((MainModule)(ref main)).gravityModifier; if (Mathf.Abs(((MinMaxCurve)(ref gravityModifier)).constantMax) < 0.001f) { ((MainModule)(ref main)).gravityModifier = new MinMaxCurve(0.6f, 1.1f); } } LimitVelocityOverLifetimeModule limitVelocityOverLifetime = ps.limitVelocityOverLifetime; ((LimitVelocityOverLifetimeModule)(ref limitVelocityOverLifetime)).enabled = true; ((LimitVelocityOverLifetimeModule)(ref limitVelocityOverLifetime)).dampen = 0f; ((LimitVelocityOverLifetimeModule)(ref limitVelocityOverLifetime)).drag = new MinMaxCurve(0.06f); ((LimitVelocityOverLifetimeModule)(ref limitVelocityOverLifetime)).multiplyDragByParticleSize = true; ((LimitVelocityOverLifetimeModule)(ref limitVelocityOverLifetime)).multiplyDragByParticleVelocity = true; } private static void StripToParticlesOnly(GameObject copy) { Transform[] componentsInChildren = copy.GetComponentsInChildren<Transform>(true); foreach (Transform val in componentsInChildren) { if ((Object)(object)val == (Object)null) { continue; } Component[] components = ((Component)val).GetComponents<Component>(); foreach (Component val2 in components) { if (!((Object)(object)val2 == (Object)null) && !(val2 is Transform) && !(val2 is ParticleSystem) && !(val2 is ParticleSystemRenderer)) { Behaviour val3 = (Behaviour)(object)((val2 is Behaviour) ? val2 : null); if ((Object)(object)val3 != (Object)null) { val3.enabled = false; } if (_strippedTypes.Add(((object)val2).GetType().Name)) { _strippedChanged = true; } Object.Destroy((Object)(object)val2); } } } if (_strippedChanged) { _strippedChanged = false; Plugin.Log.LogInfo((object)("Cloud graft disables and removes these components from its copies: " + string.Join(", ", new List<string>(_strippedTypes).ToArray()))); } } private static Material NeutralCloudMaterial(Material source) { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Expected O, but got Unknown //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)source == (Object)null) { return null; } int instanceID = ((Object)source).GetInstanceID(); if (NeutralMaterials.TryGetValue(instanceID, out var value) && (Object)(object)value != (Object)null) { return value; } try { Material val = new Material(source) { name = "CarturBloodCloudNeutral" }; if (val.HasProperty("_Color")) { val.SetColor("_Color", Color.white); } if (val.HasProperty("_EmissionColor")) { val.SetColor("_EmissionColor", Color.black); } NeutralMaterials[instanceID] = val; Plugin.Log.LogInfo((object)("Cloned \"" + ((Object)source).name + "\" -> \"CarturBloodCloudNeutral\" (_Color white, _EmissionColor black) so grafted clouds take the creature's own blood colour. The original is left alone, so effects with their own clouds are unaffected.")); return val; } catch (Exception ex) { Plugin.Log.LogWarning((object)("Could not neutralise cloud material: " + ex.Message)); return source; } } } internal sealed class ConfigurationManagerAttributes { public bool? Browsable; public bool? IsAdvanced; public int? Order; } internal static class CreatureSize { private static float _height; internal const float ReferenceHeight = 1.8f; internal const float MinScale = 0.35f; internal const float MaxScale = 4f; private static readonly HashSet<string> Logged = new HashSet<string>(); public static void Clear() { Logged.Clear(); } internal static void Record(Character character) { if ((Object)(object)character == (Object)null) { _height = 0f; return; } try { _height = character.GetHeight(); string text = ((Object)character).name ?? "?"; if (Logged.Add(text)) { Plugin.Log.LogInfo((object)($"Creature size: {text} height {_height:0.##}m " + $"-> pool scale {Scale():0.##}")); } } catch { _height = 0f; } } internal static float Scale() { return ScaleFor(_height); } internal static float ScaleFor(float height) { if (height <= 0.01f) { return 1f; } return Mathf.Clamp(height / 1.8f, 0.35f, 4f); } } [HarmonyPatch(typeof(Character), "OnDeath")] internal static class Patch_Character_OnDeath_Size { private static void Prefix(Character __instance) { CreatureSize.Record(__instance); } } internal static class DebugTuning { private enum Kind { Other, Droplet, Cloud, Splash, Ground } internal static ConfigEntry<bool> DebugMenu; internal static ConfigEntry<bool> ShowDroplets; internal static ConfigEntry<bool> DropletTexture; internal static ConfigEntry<float> DropletSize; internal static ConfigEntry<float> DropletSpread; internal static ConfigEntry<float> DropletCount; internal static ConfigEntry<bool> BigDropsSlowFaster; internal static ConfigEntry<bool> ShowCloud; internal static ConfigEntry<float> CloudSize; internal static ConfigEntry<bool> ShowSplash; internal static ConfigEntry<float> SplashSize; internal static ConfigEntry<bool> ShowGroundDecals; internal static ConfigEntry<float> GroundDecalSize; internal static ConfigEntry<bool> ShowDeathPool; internal static ConfigEntry<float> DeathPoolSize; internal static ConfigEntry<float> DeathPoolCount; internal static ConfigEntry<bool> LogShaderProperties; internal static ConfigEntry<bool> StretchParticles; internal static ConfigEntry<float> StretchAmount; internal static ConfigEntry<float> Gravity; internal static ConfigEntry<float> Drag; internal static ConfigEntry<float> Speed; private const string Section = "4 - Debug"; private static readonly HashSet<int> Tuned = new HashSet<int>(); private static readonly HashSet<int> Dumped = new HashSet<int>(); private static ConfigEntry<T> BindDebug<T>(ConfigFile cfg, string section, string key, T def, ConfigDescription desc) { //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Expected O, but got Unknown ConfigurationManagerAttributes configurationManagerAttributes = new ConfigurationManagerAttributes { IsAdvanced = true }; ConfigDescription val = ((desc == null) ? new ConfigDescription("", (AcceptableValueBase)null, new object[1] { configurationManagerAttributes }) : new ConfigDescription(desc.Description, desc.AcceptableValues, new object[1] { configurationManagerAttributes })); return cfg.Bind<T>(section, key, def, val); } public static void Bind(ConfigFile cfg) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Expected O, but got Unknown //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Expected O, but got Unknown //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Expected O, but got Unknown //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Expected O, but got Unknown //IL_00d3: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Expected O, but got Unknown //IL_010b: Unknown result type (might be due to invalid IL or missing references) //IL_0115: Expected O, but got Unknown //IL_0131: Unknown result type (might be due to invalid IL or missing references) //IL_013b: Expected O, but got Unknown //IL_0169: Unknown result type (might be due to invalid IL or missing references) //IL_0173: Expected O, but got Unknown //IL_018f: Unknown result type (might be due to invalid IL or missing references) //IL_0199: Expected O, but got Unknown //IL_01c7: Unknown result type (might be due to invalid IL or missing references) //IL_01d1: Expected O, but got Unknown //IL_01ed: Unknown result type (might be due to invalid IL or missing references) //IL_01f7: Expected O, but got Unknown //IL_0225: Unknown result type (might be due to invalid IL or missing references) //IL_022f: Expected O, but got Unknown //IL_024b: Unknown result type (might be due to invalid IL or missing references) //IL_0255: Expected O, but got Unknown //IL_0283: Unknown result type (might be due to invalid IL or missing references) //IL_028d: Expected O, but got Unknown //IL_02bb: Unknown result type (might be due to invalid IL or missing references) //IL_02c5: Expected O, but got Unknown //IL_02e1: Unknown result type (might be due to invalid IL or missing references) //IL_02eb: Expected O, but got Unknown //IL_0307: Unknown result type (might be due to invalid IL or missing references) //IL_0311: Expected O, but got Unknown //IL_033f: Unknown result type (might be due to invalid IL or missing references) //IL_0349: Expected O, but got Unknown //IL_0377: Unknown result type (might be due to invalid IL or missing references) //IL_0381: Expected O, but got Unknown //IL_03af: Unknown result type (might be due to invalid IL or missing references) //IL_03b9: Expected O, but got Unknown //IL_03d5: Unknown result type (might be due to invalid IL or missing references) //IL_03df: Expected O, but got Unknown //IL_040d: Unknown result type (might be due to invalid IL or missing references) //IL_0417: Expected O, but got Unknown DebugMenu = BindDebug(cfg, "4 - Debug", "DebugMenu", def: false, new ConfigDescription("Master switch for everything below. Off means this whole section is ignored and the mod behaves exactly as shipped.\nThese settings apply to the NEXT hit or death - no restart needed - because they are applied to each effect as it spawns. Settings in the other sections mostly are not: those change shared materials once at world load.", (AcceptableValueBase)null, Array.Empty<object>())); ShowDroplets = BindDebug(cfg, "4 - Debug", "ShowDroplets", def: true, new ConfigDescription("The 200 small blood_drop particles. Turn off to see what the rest of the effect contributes on its own.\nNote these carry NO texture in vanilla - they are flat shaded quads.", (AcceptableValueBase)null, Array.Empty<object>())); DropletTexture = BindDebug(cfg, "4 - Debug", "DropletTexture", def: true, new ConfigDescription("Gives the droplets a texture. Vanilla has none on them at all - they are flat shaded quads.\nNeeds a file at BepInEx/config/carturblood_droplet_256_rgba.png. With no file present this does nothing and says so once in the log.\nVisible at vanilla size in melee - a droplet is 28 screen pixels across at 1.5 m on a 1080p screen. It is only across a field that it shrinks to a dot.", (AcceptableValueBase)null, Array.Empty<object>())); DropletSize = BindDebug(cfg, "4 - Debug", "DropletSize", 0.15f, new ConfigDescription("Multiplies droplet size. Vanilla is 0.05 world units, and since a world unit is a metre that is a 50 mm droplet - a golf ball. Real spatter thrown off a blade is 2-4 mm, so the default here is well below vanilla.\nScreen sizes are quoted at 1.5 m on a 1080p screen, which is where you stand when swinging something. Halve them at 3 m, multiply by 1.33 at 1440p.\n 0.08 4 mm, 2 px faint dot; near the floor of visible\n 0.15 8 mm, 4 px DEFAULT. a clear dot - the texture does not read here\n 0.25 13 mm, 7 px the droplet shape starts to read\n 0.40 20 mm, 11 px texture clearly readable, still well under vanilla\n 1.00 50 mm, 28 px vanilla\nBelow about 0.1 a droplet is under two pixels and stops contributing. Above about 0.5 the count has to come down or the screen fills with red.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.02f, 10f), Array.Empty<object>())); DropletSpread = BindDebug(cfg, "4 - Debug", "DropletSpread", 0.6f, new ConfigDescription("Spread of droplet sizes within one burst, as a fraction either side of DropletSize. 0.6 means 0.4x to 1.6x.\nVanilla authors startSize as a flat 0.05 - every one of the 200 droplets is exactly the same size, which is the most artificial thing about them. Real spatter is a distribution: a few heavy drops among many fine ones.\nThe useful ceiling moves with DropletSize, because the bottom of the range has to stay above one screen pixel or those particles are spent on nothing. At the 0.15 default the smallest droplet hits one pixel at a spread of about 0.75, so:\n DropletSize 0.15 -> spread up to 0.75\n DropletSize 0.25 -> spread up to 0.85\n DropletSize 0.40 -> the full 0.9 is usable\nGoing past it is not harmful, it just buys nothing - the small half of the burst becomes invisible.\nUniformly distributed, so it is an even mix rather than the long tail real spatter has. An even mix is already a large improvement on none.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 0.9f), Array.Empty<object>())); DropletCount = BindDebug(cfg, "4 - Debug", "DropletCount", 1f, new ConfigDescription("Multiplies how many droplets are emitted. 1 is vanilla's 200, 5 is 1000.\nCount and size pull against each other, and small-and-many is the pairing that reads as spatter: real blood throws hundreds of small marks, not a handful of large ones. Turning size DOWN is what buys room to turn this UP.\nGround decals are held steady automatically. The airborne droplets are also the ground-mark emitter, so raising this would otherwise multiply the mess on the floor by the same amount; the per-collision chance is divided to match.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 8f), Array.Empty<object>())); ShowCloud = BindDebug(cfg, "4 - Debug", "ShowCloud", def: true, new ConfigDescription("The blood_cloud burst - both the systems this mod grafts on and any the effect already had. Turn off to judge the droplets and splash alone.", (AcceptableValueBase)null, Array.Empty<object>())); CloudSize = BindDebug(cfg, "4 - Debug", "CloudSize", 1f, new ConfigDescription("Multiplies cloud particle size. Vanilla is 0.5-1.5 world units.\nSmaller reads as spray, larger reads as mist.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 3f), Array.Empty<object>())); ShowSplash = BindDebug(cfg, "4 - Debug", "ShowSplash", def: true, new ConfigDescription("The flat splash quad (slime_splash material, tinted per creature).", (AcceptableValueBase)null, Array.Empty<object>())); SplashSize = BindDebug(cfg, "4 - Debug", "SplashSize", 1f, new ConfigDescription("Multiplies splash size.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 4f), Array.Empty<object>())); ShowGroundDecals = BindDebug(cfg, "4 - Debug", "ShowGroundDecals", def: true, new ConfigDescription("The blood left on the floor. Turn off to judge the airborne blood on its own - the ground marks are what dominate a screenshot a second after the kill, and they make it hard to see what the spray itself is doing.", (AcceptableValueBase)null, Array.Empty<object>())); GroundDecalSize = BindDebug(cfg, "4 - Debug", "GroundDecalSize", 1f, new ConfigDescription("Multiplies ground mark size, on top of GroundBlood in section 1. This one is live on the next hit; GroundBlood is applied to shared materials at world load and needs a reload to change.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 4f), Array.Empty<object>())); ShowDeathPool = BindDebug(cfg, "4 - Debug", "ShowDeathPool", def: true, new ConfigDescription("The pool that spreads under a corpse. Off skips it entirely, leaving the ordinary death decals.", (AcceptableValueBase)null, Array.Empty<object>())); DeathPoolSize = BindDebug(cfg, "4 - Debug", "DeathPoolSize", 1f, new ConfigDescription("Multiplies pool size, on top of PoolSize in section 2.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 4f), Array.Empty<object>())); DeathPoolCount = BindDebug(cfg, "4 - Debug", "DeathPoolCount", 1f, new ConfigDescription("Multiplies how many overlapping pools are laid down, on top of PoolCount in section 2. The result is still clamped to 1-8 by the pool code itself.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 4f), Array.Empty<object>())); LogShaderProperties = BindDebug(cfg, "4 - Debug", "LogShaderProperties", def: false, new ConfigDescription("Writes every shader property on each blood material to the BepInEx log, once per material, on the next hit. Then switch it back off.\nThis is how you find out what a material can actually be told to do - which gloss, smoothness or specular property exists on Custom/ParticleDecal, and what it is currently set to. Guessing a property name costs nothing at compile time and silently does nothing at runtime, which is the expensive way to find out you were wrong.", (AcceptableValueBase)null, Array.Empty<object>())); StretchParticles = BindDebug(cfg, "4 - Debug", "StretchParticles", def: false, new ConfigDescription("Draws droplets and cloud particles STRETCHED along their own direction of travel instead of as camera-facing squares.\nThis is the single biggest difference between grain floating in the air and blood spray: a fast particle becomes a streak, a slow one stays a blob, so the burst shows its own motion. Vanilla draws every particle as the same square whether it is flying or hanging still.", (AcceptableValueBase)null, Array.Empty<object>())); StretchAmount = BindDebug(cfg, "4 - Debug", "StretchAmount", 0.012f, new ConfigDescription("How far a particle stretches, in metres per metre-per-second of its speed. Only used when StretchParticles is on.\nThis is not a taste dial - it has a correct order of magnitude. Droplets travel 1-6 m/s and are about 17 mm across at the default size, so a streak two or three times a droplet's own length needs roughly 0.012. The old default of 2 drew TWELVE METRE streaks and turned a kill into a firework.\n 0.004 barely elongated\n 0.012 default; reads as a droplet in flight\n 0.05 long tracer streaks\nCloud particles are far bigger than droplets, so no single value flatters both. This one is scaled for the droplets.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 0.2f), Array.Empty<object>())); Gravity = BindDebug(cfg, "4 - Debug", "Gravity", 0.85f, new ConfigDescription("Downward pull on airborne blood. 0 makes particles fly in a straight line until they expire, which is the vanilla behaviour and the most artificial thing about it. The mod already applies 0.6-1.1; this is that value, exposed.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 4f), Array.Empty<object>())); Drag = BindDebug(cfg, "4 - Debug", "Drag", 0.06f, new ConfigDescription("Air resistance, scaled by particle size AND speed - so fine mist slows and hangs while heavy droplets carry. Higher values stop the burst sooner.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 0.5f), Array.Empty<object>())); BigDropsSlowFaster = BindDebug(cfg, "4 - Debug", "BigDropsSlowFaster", def: false, new ConfigDescription("Scales Drag by particle size. Only has a visible effect once DropletSpread is above 0, because until then every droplet is the same size.\nREAD THIS BEFORE TURNING IT ON. Unity applies MORE drag to LARGER particles, so this makes heavy drops slow down first and fine mist carry furthest. That is backwards from real air resistance, where a heavy droplet punches through and atomised mist is stopped almost immediately.\nLeft off, all droplets get the same drag, which is neither right nor backwards. Unity has no option for the physically correct direction in this module, so off is the honest default. On is available because backwards can still look good - long fine streaks with heavy drops falling short is a recognisable stylised look, it is just not what blood does.", (AcceptableValueBase)null, Array.Empty<object>())); Speed = BindDebug(cfg, "4 - Debug", "Speed", 1f, new ConfigDescription("Multiplies how fast particles leave the wound. Higher throws blood further and, with StretchParticles on, makes longer streaks.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 5f), Array.Empty<object>())); } public static void Reset() { Tuned.Clear(); } public static void Apply(ParticleDecal decal) { //IL_017a: Unknown result type (might be due to invalid IL or missing references) //IL_017f: Unknown result type (might be due to invalid IL or missing references) //IL_0199: Unknown result type (might be due to invalid IL or missing references) //IL_01a0: Unknown result type (might be due to invalid IL or missing references) //IL_01ec: Unknown result type (might be due to invalid IL or missing references) //IL_01fb: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)decal == (Object)null || DebugMenu == null) { return; } Transform root = ((Component)decal).transform.root; if ((Object)(object)root == (Object)null || !Tuned.Add(((Object)root).GetInstanceID())) { return; } bool value = DebugMenu.Value; BloodPreset bloodPreset = BloodPreset.Current(); ParticleSystemRenderer[] componentsInChildren = ((Component)root).GetComponentsInChildren<ParticleSystemRenderer>(true); foreach (ParticleSystemRenderer val in componentsInChildren) { if ((Object)(object)val == (Object)null) { continue; } ParticleSystem component = ((Component)val).GetComponent<ParticleSystem>(); if ((Object)(object)component == (Object)null) { continue; } Kind kind = Classify(((Renderer)val).sharedMaterial); if (kind == Kind.Other) { continue; } if (value && LogShaderProperties.Value) { DumpProperties(((Renderer)val).sharedMaterial, kind); } BloodSkin.ApplyWetness(((Renderer)val).sharedMaterial); bool flag; float num; switch (kind) { case Kind.Droplet: flag = !value || ShowDroplets.Value; num = bloodPreset.DropletSize; break; case Kind.Cloud: flag = !value || ShowCloud.Value; num = bloodPreset.CloudSize; break; case Kind.Ground: flag = !value || ShowGroundDecals.Value; num = (value ? GroundDecalSize.Value : 1f); break; default: flag = !value || ShowSplash.Value; num = (value ? SplashSize.Value : 1f); break; } if (!flag) { component.Stop(true, (ParticleSystemStopBehavior)0); continue; } MainModule main = component.main; if (Math.Abs(num - 1f) > 0.001f) { ((MainModule)(ref main)).startSize = BloodSkin.Scale(((MainModule)(ref main)).startSize, num); } if (kind == Kind.Droplet && bloodPreset.DropletSpread > 0.001f) { Spread(ref main, bloodPreset.DropletSpread); } if (value && Math.Abs(Speed.Value - 1f) > 0.001f) { ((MainModule)(ref main)).startSpeed = BloodSkin.Scale(((MainModule)(ref main)).startSpeed, Speed.Value); } if (kind == Kind.Droplet || kind == Kind.Cloud) { if (value) { Ballistics(component); } SetStretch(val, value ? StretchParticles.Value : bloodPreset.Stretch); } if (kind == Kind.Droplet) { if (Math.Abs(bloodPreset.DropletCount - 1f) > 0.001f) { ScaleEmission(component, bloodPreset.DropletCount, ((Object)(object)component == (Object)(object)decal.m_decalSystem) ? decal : null); } if (DropletTexture.Value) { BloodSkin.ApplyDropletTexture(val); } } } } private static void Ballistics(ParticleSystem ps) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) MainModule main = ps.main; ((MainModule)(ref main)).gravityModifier = new MinMaxCurve(Gravity.Value * 0.7f, Gravity.Value * 1.3f); LimitVelocityOverLifetimeModule limitVelocityOverLifetime = ps.limitVelocityOverLifetime; if (Drag.Value <= 0f) { ((LimitVelocityOverLifetimeModule)(ref limitVelocityOverLifetime)).enabled = false; return; } ((LimitVelocityOverLifetimeModule)(ref limitVelocityOverLifetime)).enabled = true; ((LimitVelocityOverLifetimeModule)(ref limitVelocityOverLifetime)).dampen = 0f; ((LimitVelocityOverLifetimeModule)(ref limitVelocityOverLifetime)).drag = new MinMaxCurve(Drag.Value); ((LimitVelocityOverLifetimeModule)(ref limitVelocityOverLifetime)).multiplyDragByParticleSize = BigDropsSlowFaster.Value; ((LimitVelocityOverLifetimeModule)(ref limitVelocityOverLifetime)).multiplyDragByParticleVelocity = true; } private static void Spread(ref MainModule main, float spread) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) float num = BloodSkin.AuthoredSize(((MainModule)(ref main)).startSize); if (!(num <= 0f)) { ((MainModule)(ref main)).startSize = new MinMaxCurve(num * Mathf.Max(0.01f, 1f - spread), num * (1f + spread)); } } private static void SetStretch(ParticleSystemRenderer r, bool stretch) { if (stretch) { r.renderMode = (ParticleSystemRenderMode)1; r.velocityScale = StretchAmount.Value; r.lengthScale = 1f; } else { r.renderMode = (ParticleSystemRenderMode)0; } } private static void ScaleEmission(ParticleSystem ps, float mul, ParticleDecal groundSource) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) EmissionModule emission = ps.emission; for (int i = 0; i < ((EmissionModule)(ref emission)).burstCount; i++) { Burst burst = ((EmissionModule)(ref emission)).GetBurst(i); ((Burst)(ref burst)).count = BloodSkin.Scale(((Burst)(ref burst)).count, mul); ((EmissionModule)(ref emission)).SetBurst(i, burst); } ((EmissionModule)(ref emission)).rateOverTime = BloodSkin.Scale(((EmissionModule)(ref emission)).rateOverTime, mul); MainModule main = ps.main; if (mul > 1f) { int num = Mathf.CeilToInt((float)((MainModule)(ref main)).maxParticles * mul); if (((MainModule)(ref main)).maxParticles < num) { ((MainModule)(ref main)).maxParticles = num; } } if ((Object)(object)groundSource != (Object)null && mul > 0.01f) { groundSource.m_chance = Mathf.Clamp(groundSource.m_chance / mul, 0f, 100f); } } private static void DumpProperties(Material mat, Kind kind) { //IL_017c: Unknown result type (might be due to invalid IL or missing references) //IL_0181: Unknown result type (might be due to invalid IL or missing references) //IL_019a: Unknown result type (might be due to invalid IL or missing references) //IL_019f: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)mat == (Object)null || (Object)(object)mat.shader == (Object)null || !Dumped.Add(((Object)mat).GetInstanceID())) { return; } try { Type? typeFromHandle = typeof(Shader); MethodInfo method = typeFromHandle.GetMethod("GetPropertyCount", BindingFlags.Static | BindingFlags.Public); MethodInfo method2 = typeFromHandle.GetMethod("GetPropertyName", BindingFlags.Static | BindingFlags.Public); MethodInfo method3 = typeFromHandle.GetMethod("GetPropertyType", BindingFlags.Static | BindingFlags.Public); if (method == null || method2 == null || method3 == null) { Plugin.Log.LogWarning((object)"Shader.GetPropertyCount is not available in this Unity version, so the property list cannot be read. Nothing else is affected."); return; } StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine($"--- {kind} material \"{((Object)mat).name}\" shader \"{((Object)mat.shader).name}\" ---"); int num = (int)method.Invoke(null, new object[1] { mat.shader }); for (int i = 0; i < num; i++) { string text = (string)method2.Invoke(null, new object[2] { mat.shader, i }); string text2 = method3.Invoke(null, new object[2] { mat.shader, i }).ToString(); string arg; switch (text2) { case "Color": arg = ((object)mat.GetColor(text)/*cast due to .constrained prefix*/).ToString(); break; case "Vector": arg = ((object)mat.GetVector(text)/*cast due to .constrained prefix*/).ToString(); break; case "Float": case "Range": arg = mat.GetFloat(text).ToString("0.###"); break; case "Texture": { Texture texture = mat.GetTexture(text); arg = (((Object)(object)texture == (Object)null) ? "(none)" : $"{((Object)texture).name} {texture.width}x{texture.height}"); break; } default: arg = "?"; break; } stringBuilder.AppendLine($" {text,-24} {text2,-8} = {arg}"); } stringBuilder.AppendLine(" keywords: " + string.Join(" ", mat.shaderKeywords)); Plugin.Log.LogInfo((object)stringBuilder.ToString()); } catch (Exception ex) { Plugin.Log.LogWarning((object)("Could not list shader properties: " + ex.Message)); } } private static Kind Classify(Material mat) { if ((Object)(object)mat == (Object)null || ((Object)mat).name == null) { return Kind.Other; } string name = ((Object)mat).name; if (Is(name, "blood_drop") || Is(name, "CarturBloodDroplet")) { return Kind.Droplet; } if (Is(name, "blood_cloud")) { return Kind.Cloud; } if (Is(name, "slime") || Is(name, "CarturBloodSplash")) { return Kind.Splash; } if (Is(name, "blood_splat") || Is(name, "splat_decal_blend") || Is(name, "CarturBloodGround")) { return Kind.Ground; } return Kind.Other; } private static bool Is(string name, string prefix) { return name.StartsWith(prefix, StringComparison.OrdinalIgnoreCase); } internal static bool DeathPoolEnabled() { if (DebugMenu != null && DebugMenu.Value) { return ShowDeathPool.Value; } return true; } internal static float PoolSizeMultiplier() { if (DebugMenu == null || !DebugMenu.Value) { return 1f; } return DeathPoolSize.Value; } internal static float PoolCountMultiplier() { if (DebugMenu == null || !DebugMenu.Value) { return 1f; } return DeathPoolCount.Value; } } [HarmonyPatch(typeof(Character), "ApplyDamage")] internal static class Patch_Character_ApplyDamage { private static HitData _lastBlocked; internal static void MarkBlocked(HitData hit) { _lastBlocked = hit; } private static void Postfix(Character __instance, HitData hit, bool triggerEffects) { //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)__instance == (Object)null || hit == null || !Plugin.ModEnabled.Value || !triggerEffects || hit == _lastBlocked) { return; } float hitThreshold = BloodPreset.Current().HitThreshold; if (hitThreshold >= 0.1f) { return; } try { if (((DamageTypes)(ref hit.m_damage)).GetTotalPhysicalDamage() <= 0f) { return; } float maxHealth = __instance.GetMaxHealth(); if (!(maxHealth <= 0f)) { float totalDamage = hit.GetTotalDamage(); if (!(totalDamage > maxHealth * 0.1f) && !(totalDamage <= maxHealth * hitThreshold)) { __instance.m_hitEffects.Create(hit.m_point, Quaternion.identity, ((Component)__instance).transform, 1f, -1, default(ZDOID)); } } } catch (Exception ex) { Plugin.Log.LogWarning((object)("Hit-threshold effect failed: " + ex.Message)); } } } [HarmonyPatch(typeof(Humanoid), "BlockAttack", new Type[] { typeof(HitData), typeof(Character) })] internal static class Patch_Humanoid_BlockAttack { private static void Postfix(HitData hit, bool __result) { if (__result && hit != null) { Patch_Character_ApplyDamage.MarkBlocked(hit); } } } internal static class LiveProbe { internal static float Saturation(MinMaxGradient g) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Invalid comparison between Unknown and I4 //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) try { ParticleSystemGradientMode mode = ((MinMaxGradient)(ref g)).mode; Color val; if ((int)mode != 0) { if ((int)mode != 2) { return -1f; } val = ((MinMaxGradient)(ref g)).colorMin; } else { val = ((MinMaxGradient)(ref g)).color; } float num = Mathf.Max(val.r, Mathf.Max(val.g, val.b)); float num2 = Mathf.Min(val.r, Mathf.Min(val.g, val.b)); return (num <= 0f) ? 0f : ((num - num2) / num); } catch { return -1f; } } } [HarmonyPatch(typeof(ParticleDecal), "Awake")] internal static class Patch_ParticleDecal_Awake { private static void Postfix(ParticleDecal __instance) { BloodSkin.Apply(__instance); } } [BepInPlugin("com.jekkle.valheim.carturhdblood", "Cartur's HD Blood", "1.1.2")] public class Plugin : BaseUnityPlugin { public const string Guid = "com.jekkle.valheim.carturhdblood"; public const string Version = "1.1.2"; internal static Plugin Instance; internal static ManualLogSource Log; internal static string ConfigDir; internal static ConfigEntry<bool> ModEnabled; internal static ConfigEntry<BloodAmount> BloodLevel; internal static ConfigEntry<float> HitEffectThreshold; internal static ConfigEntry<float> GroundBlood; internal static ConfigEntry<bool> ReplaceTexture; internal static ConfigEntry<bool> ReplaceSplashTexture; internal static ConfigEntry<string> DecalMaterials; internal static ConfigEntry<string> ExcludeEffects; internal static ConfigEntry<float> SmallDecalSize; internal static ConfigEntry<bool> GroundNormalMap; internal static ConfigEntry<float> Wetness; internal static ConfigEntry<bool> WetnessMap; internal static ConfigEntry<bool> Reflections; internal static ConfigEntry<bool> GroundLighting; internal static ConfigEntry<bool> GroundFade; internal static ConfigEntry<float> FadeStart; internal static ConfigEntry<float> GroundOpacity; internal static ConfigEntry<bool> DirectionalSpray; internal static ConfigEntry<float> SprayAngle; internal static ConfigEntry<float> SprayLift; internal static ConfigEntry<float> SprayRadius; internal static ConfigEntry<float> HitBlood; internal static ConfigEntry<float> DeathBlood; internal static ConfigEntry<bool> FixGreenSplash; internal static ConfigEntry<float> ColorFallbackSaturation; internal static ConfigEntry<string> BloodColor; internal static ConfigEntry<float> BloodTint; internal static ConfigEntry<bool> DecalAging; internal static ConfigEntry<float> DryDarkening; internal static ConfigEntry<bool> DecalGrowIn; internal static ConfigEntry<float> SizeJitter; internal static ConfigEntry<bool> PoolOnDeath; internal static ConfigEntry<float> PoolSize; internal static ConfigEntry<float> PoolLifetime; internal static ConfigEntry<int> PoolCount; private Harmony _harmony; private void WatchForManualEdits() { ConfigEntryBase[] array = (ConfigEntryBase[])(object)new ConfigEntryBase[13] { (ConfigEntryBase)GroundBlood, (ConfigEntryBase)HitBlood, (ConfigEntryBase)DeathBlood, (ConfigEntryBase)Wetness, (ConfigEntryBase)GroundOpacity, (ConfigEntryB