using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Text.Json; using BloodLabMod.Core; using MelonLoader; using Microsoft.CodeAnalysis; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: MelonInfo(typeof(BloodLabMod.Core.BloodLabMod), "BloodLabMod", "1.0.0", "fxleons")] [assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")] [assembly: AssemblyCompany("BloodLabMod")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+5e16ae1fe7ffb66e8ea5b08c010a9eba18f1c329")] [assembly: AssemblyProduct("BloodLabMod")] [assembly: AssemblyTitle("BloodLabMod")] [assembly: AssemblyVersion("1.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [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 BloodLabMod.Core { public class BloodDroplet : MonoBehaviour { private Rigidbody rb; public float Volume { get; private set; } public Vector3 LastNormal { get; private set; } public Vector3 Position => transform.position; public bool IsSettled { get; private set; } public void Initialize(Vector3 initialVelocity, float volume) { if (rb == null) { rb = gameObject.GetComponent(); } if (rb == null) { rb = gameObject.AddComponent(); } rb.useGravity = true; rb.velocity = initialVelocity; rb.mass = Mathf.Clamp(volume * 0.1f, 0.01f, 5f); Volume = Mathf.Clamp(volume, 0.001f, 1f); IsSettled = false; LastNormal = Vector3.up; } public void Simulate(float dt) { if (rb != null && !IsSettled && rb.velocity.sqrMagnitude < 0.01f && transform.position.y < 10f) { IsSettled = true; if (Physics.Raycast(transform.position, -Vector3.up, out var hit, 0.5f)) { LastNormal = hit.normal; } rb.isKinematic = true; } } private void OnCollisionEnter(Collision collision) { if (collision.contacts.Length != 0) { LastNormal = collision.contacts[0].normal; } if (rb != null) { rb.velocity = rb.velocity * 0.3f + collision.contacts[0].normal * -0.2f; } } } public class BloodLabMod : MelonMod { private static bool initialized; public override void OnApplicationStart() { if (!initialized) { initialized = true; MelonLogger.Msg("BloodLabMod initializing..."); ConfigManager.Load(); PoolManager.Initialize(); WoundManager.Initialize(); DropletManager.Initialize(); PuddleManager.Initialize(); DecalManager.Initialize(); WeaponBloodManager.Initialize(); PlayerBloodManager.Initialize(); HookManager.Initialize(); } } public override void OnUpdate() { WoundManager.UpdateAll(Time.deltaTime); DropletManager.UpdateAll(Time.deltaTime); PuddleManager.UpdateAll(Time.deltaTime); WeaponBloodManager.UpdateAll(Time.deltaTime); PlayerBloodManager.UpdateAll(Time.deltaTime); } } public class BloodPuddle { private Vector3 normal; private float volume; private GameObject decalGO; private float age; public Vector3 Position { get; private set; } public bool IsExpired => age > ConfigManager.Settings.DecalLifetime * 2f; public BloodPuddle(Vector3 position, Vector3 normal, float initialVolume) { Position = position; this.normal = normal; volume = initialVolume; age = 0f; SpawnDecal(); } private void SpawnDecal() { GameObject gameObject = Resources.Load("BloodPrefabs/BloodPuddle"); if (gameObject != null) { decalGO = GameObject.Instantiate(gameObject); decalGO.transform.position = Position + normal * 0.01f; decalGO.transform.rotation = Quaternion.LookRotation(normal); UpdateVisual(); } } public void AddVolume(float v) { volume += v; UpdateVisual(); } public void Update(float dt) { age += dt; if (decalGO != null) { Renderer component = decalGO.GetComponent(); if (component != null && component.material != null) { Color bloodColor = ConfigManager.Settings.BloodColor; float t = Mathf.Clamp01(age * ConfigManager.Settings.BloodDryingSpeed); component.material.color = Color.Lerp(bloodColor, Color.black, t); } } } private void UpdateVisual() { if (decalGO != null) { float num = Mathf.Clamp(0.1f + volume * 0.5f, 0.1f, 5f); decalGO.transform.localScale = new Vector3(num, 1f, num); } } public void Destroy() { if (decalGO != null) { GameObject.Destroy(decalGO); } } } public class BloodWound { private float bleedIntensity; private float lifeTimer; private float maxLife; public Transform Parent { get; private set; } public Vector3 LocalPosition { get; private set; } public Vector3 Normal { get; private set; } public float Damage { get; private set; } public string BodyPart { get; private set; } public bool IsFinished { get { if (bleedIntensity <= 0f) { return lifeTimer > maxLife; } return false; } } public BloodWound(Transform parent, Vector3 localPos, Vector3 normal, float damage, string bodyPart) { Parent = parent; LocalPosition = localPos; Normal = normal; Damage = damage; BodyPart = bodyPart; bleedIntensity = Mathf.Clamp01(damage * 0.1f); maxLife = Mathf.Max(30f, damage * 10f) * ConfigManager.Settings.BleedingDurationMultiplier; lifeTimer = 0f; DropletManager.SpawnGore(Parent.TransformPoint(LocalPosition), Normal, Mathf.Clamp01(bleedIntensity)); } public void Update(float dt) { lifeTimer += dt; if (bleedIntensity > 0f) { float amount = bleedIntensity * dt * (1f - lifeTimer / maxLife); DropletManager.SpawnDrip(Parent.TransformPoint(LocalPosition), Normal, amount); bleedIntensity = Mathf.Max(0f, bleedIntensity - dt * 0.01f); } } } public static class BodyPartIdentifier { public static string Identify(Transform root, Transform hitTransform) { if (hitTransform == null) { return "unknown"; } string text = hitTransform.name.ToLower(); if (text.Contains("head") || text.Contains("skull")) { return "head"; } if (text.Contains("spine") || text.Contains("chest") || text.Contains("torso")) { return "torso"; } if (text.Contains("arm") || text.Contains("hand") || text.Contains("shoulder")) { return "arm"; } if (text.Contains("leg") || text.Contains("thigh") || text.Contains("foot")) { return "leg"; } return "limb"; } } public static class ConfigManager { public class Config { public float BloodMultiplier = 1f; public float PlayerBloodMultiplier = 1f; public float NPCBloodMultiplier = 1f; public float BleedingDurationMultiplier = 1f; public int MaxDroplets = 500; public int MaxPuddles = 200; public bool PerformanceMode; public float DecalLifetime = 300f; public Color BloodColor = new Color(0.6f, 0.05f, 0.03f, 1f); public float DripFrequency = 1f; public float BloodDryingSpeed = 0.01f; } public static Config Settings = new Config(); private static string configPath = Path.Combine(Application.dataPath, "BloodLabModConfig.json"); public static void Load() { if (File.Exists(configPath)) { try { Settings = JsonUtility.FromJson(File.ReadAllText(configPath)) ?? new Config(); return; } catch { Settings = new Config(); return; } } Save(); } public static void Save() { try { string contents = JsonUtility.ToJson(Settings, pretty: true); File.WriteAllText(configPath, contents); } catch { } } } public struct DamageInfo { public Transform Target; public Vector3 HitPoint; public Vector3 HitNormal; public float Damage; public string BodyPart; public GameObject Source; } public static class DecalManager { public static void Initialize() { } public static void SpawnWallSplat(Vector3 pos, Vector3 normal, float size, float intensity) { GameObject gameObject = Resources.Load("BloodPrefabs/BloodDecal"); if (gameObject != null) { GameObject gameObject2 = GameObject.Instantiate(gameObject); gameObject2.transform.position = pos + normal * 0.01f; gameObject2.transform.rotation = Quaternion.LookRotation(normal); gameObject2.transform.localScale = Vector3.one * Mathf.Clamp(size * intensity, 0.2f, 6f); Renderer component = gameObject2.GetComponent(); if (component != null && component.material != null) { component.material.color = ConfigManager.Settings.BloodColor; } GameObject.Destroy(gameObject2, ConfigManager.Settings.DecalLifetime); } } } public static class DropletManager { private static List droplets = new List(); public static void Initialize() { } public static void SpawnGore(Vector3 pos, Vector3 normal, float intensity) { int num = Mathf.CeilToInt(Mathf.Clamp(5f * intensity * ConfigManager.Settings.BloodMultiplier, 1f, 50f)); for (int i = 0; i < num; i++) { SpawnDroplet(pos + UnityEngine.Random.insideUnitSphere * 0.05f, UnityEngine.Random.onUnitSphere * 0.5f + -normal * 1f, intensity); } } public static void SpawnDrip(Vector3 pos, Vector3 normal, float amount) { if (droplets.Count <= ConfigManager.Settings.MaxDroplets && !(amount <= 0.0001f)) { SpawnDroplet(pos + normal * 0.01f, -normal * UnityEngine.Random.Range(0.1f, 0.3f), amount); } } private static void SpawnDroplet(Vector3 pos, Vector3 velocity, float volume) { GameObject gameObject = PoolManager.Get("BloodPrefabs/BloodDroplet"); if (gameObject != null) { gameObject.transform.position = pos; gameObject.transform.rotation = UnityEngine.Random.rotation; gameObject.SetActive(v: true); BloodDroplet bloodDroplet = gameObject.GetComponent(); if (bloodDroplet == null) { bloodDroplet = gameObject.AddComponent(); } bloodDroplet.Initialize(velocity, volume); droplets.Add(bloodDroplet); } } public static void UpdateAll(float dt) { for (int num = droplets.Count - 1; num >= 0; num--) { BloodDroplet bloodDroplet = droplets[num]; bloodDroplet.Simulate(dt); if (bloodDroplet.IsSettled) { droplets.RemoveAt(num); PuddleManager.SeedPuddle(bloodDroplet.Position, bloodDroplet.Volume, bloodDroplet.LastNormal); PoolManager.Release("BloodPrefabs/BloodDroplet", bloodDroplet.gameObject); } } } } public class ExampleDamageReceiver : MonoBehaviour { public Transform target; public void SimulateHit(Vector3 worldPos, Vector3 normal, float damage) { if (target != null) { string bodyPart = BodyPartIdentifier.Identify(target, transform); HookManager.OnEntityHit(target, worldPos, normal, damage, bodyPart, gameObject); } } } public static class HookManager { public static void Initialize() { } public static void OnEntityHit(Transform entity, Vector3 hitWorldPos, Vector3 hitNormal, float damage, string bodyPart, GameObject hitSource) { Vector3 localPos = entity.InverseTransformPoint(hitWorldPos); WoundManager.CreateWound(entity, localPos, hitNormal, damage, bodyPart); DropletManager.SpawnGore(hitWorldPos, hitNormal, damage * ConfigManager.Settings.BloodMultiplier); if (hitSource == null) { DecalManager.SpawnWallSplat(hitWorldPos, hitNormal, 1f + damage * 0.5f, Mathf.Clamp01(damage * 0.2f)); } if (hitSource != null) { WeaponBloodManager.AddBloodToWeapon(hitSource.transform, damage * 0.1f); } } } public static class PlayerBloodManager { private static Dictionary> playerWounds = new Dictionary>(); public static void Initialize() { } public static void RegisterPlayer(Transform player) { if (!playerWounds.ContainsKey(player)) { playerWounds[player] = new List(); } } public static void AddWound(Transform player, Vector3 localPos, Vector3 normal, float damage, string bodyPart) { BloodWound item = WoundManager.CreateWound(player, localPos, normal, damage, bodyPart); if (!playerWounds.ContainsKey(player)) { playerWounds[player] = new List(); } playerWounds[player].Add(item); } public static void UpdateAll(float dt) { } } public static class PoolManager { private static Dictionary> pools = new Dictionary>(); private static GameObject root; public static void Initialize() { root = new GameObject("BloodLab_Pools"); UnityEngine.Object.DontDestroyOnLoad(root); } public static GameObject Get(string resourcePath, int initialSize = 10) { if (!pools.ContainsKey(resourcePath)) { pools[resourcePath] = new Queue(); } Queue queue = pools[resourcePath]; if (queue.Count == 0) { GameObject gameObject = Resources.Load(resourcePath); if (gameObject == null) { return null; } GameObject gameObject2 = UnityEngine.Object.Instantiate(gameObject, root.transform); gameObject2.SetActive(v: false); return gameObject2; } return queue.Dequeue(); } public static void Release(string resourcePath, GameObject obj) { if (obj != null) { obj.SetActive(v: false); if (!pools.ContainsKey(resourcePath)) { pools[resourcePath] = new Queue(); } pools[resourcePath].Enqueue(obj); obj.transform.SetParent(root.transform, worldPositionStays: false); } } } public static class PuddleManager { private static List puddles = new List(); public static void Initialize() { } public static void SeedPuddle(Vector3 worldPos, float volume, Vector3 normal) { float num = 1f; BloodPuddle bloodPuddle = null; float num2 = float.MaxValue; foreach (BloodPuddle puddle in puddles) { float num3 = Vector3.Distance(puddle.Position, worldPos); if (num3 < num2 && num3 < num) { num2 = num3; bloodPuddle = puddle; } } if (bloodPuddle != null) { bloodPuddle.AddVolume(volume); } else if (puddles.Count <= ConfigManager.Settings.MaxPuddles) { BloodPuddle item = new BloodPuddle(worldPos, normal, volume); puddles.Add(item); } } public static void UpdateAll(float dt) { for (int num = puddles.Count - 1; num >= 0; num--) { BloodPuddle bloodPuddle = puddles[num]; bloodPuddle.Update(dt); if (bloodPuddle.IsExpired) { bloodPuddle.Destroy(); puddles.RemoveAt(num); } } } } public static class WeaponBloodManager { private static Dictionary weaponBlood = new Dictionary(); public static void Initialize() { } public static void AddBloodToWeapon(Transform weapon, float amount) { if (!weaponBlood.ContainsKey(weapon)) { weaponBlood[weapon] = 0f; } weaponBlood[weapon] += amount; } public static void UpdateAll(float dt) { foreach (Transform item in new List(weaponBlood.Keys)) { weaponBlood[item] = Mathf.Max(0f, weaponBlood[item] - dt * 0.001f * ConfigManager.Settings.BloodDryingSpeed); } } } public static class WoundManager { private static List wounds = new List(); public static void Initialize() { } public static BloodWound CreateWound(Transform parent, Vector3 localPos, Vector3 normal, float damage, string bodyPart) { BloodWound bloodWound = new BloodWound(parent, localPos, normal, damage, bodyPart); wounds.Add(bloodWound); return bloodWound; } public static void UpdateAll(float dt) { for (int num = wounds.Count - 1; num >= 0; num--) { BloodWound bloodWound = wounds[num]; bloodWound.Update(dt); if (bloodWound.IsFinished) { wounds.RemoveAt(num); } } } public static IEnumerable GetWoundsFor(Transform t) { foreach (BloodWound wound in wounds) { if (wound.Parent == t) { yield return wound; } } } } } namespace UnityEngine { public struct Vector3 { public float x; public float y; public float z; public static Vector3 zero => new Vector3(0f, 0f, 0f); public static Vector3 one => new Vector3(1f, 1f, 1f); public static Vector3 up => new Vector3(0f, 1f, 0f); public float sqrMagnitude => x * x + y * y + z * z; public Vector3(float x, float y, float z) { this.x = x; this.y = y; this.z = z; } public static Vector3 operator -(Vector3 a) { return new Vector3(0f - a.x, 0f - a.y, 0f - a.z); } public static Vector3 operator +(Vector3 a, Vector3 b) { return new Vector3(a.x + b.x, a.y + b.y, a.z + b.z); } public static Vector3 operator -(Vector3 a, Vector3 b) { return new Vector3(a.x - b.x, a.y - b.y, a.z - b.z); } public static Vector3 operator *(Vector3 a, float s) { return new Vector3(a.x * s, a.y * s, a.z * s); } public static float Distance(Vector3 a, Vector3 b) { float num = a.x - b.x; float num2 = a.y - b.y; float num3 = a.z - b.z; return (float)Math.Sqrt(num * num + num2 * num2 + num3 * num3); } } [StructLayout(LayoutKind.Sequential, Size = 1)] public struct Quaternion { public static Quaternion identity => default(Quaternion); public static Quaternion LookRotation(Vector3 v) { return identity; } } public class Object { public string name = "Object"; public static void DontDestroyOnLoad(Object obj) { } public static GameObject Instantiate(GameObject prefab, Transform parent) { return GameObject.Instantiate(prefab, parent); } } public static class Application { public static string dataPath => Directory.GetCurrentDirectory(); } public static class ObjectEx { public static void DontDestroyOnLoad(Object obj) { } public static GameObject Instantiate(GameObject prefab, Transform parent) { return GameObject.Instantiate(prefab, parent); } } public class Component : Object { public Transform transform = new Transform(); public GameObject gameObject = new GameObject(); } public class GameObject : Object { public Transform transform = new Transform(); public bool activeSelf = true; private List components = new List(); public GameObject() { transform = new Transform(); transform.parent = null; transform.gameObject = this; } public GameObject(string name) { base.name = name; transform = new Transform(); transform.gameObject = this; } public void SetActive(bool v) { activeSelf = v; } public T GetComponent() where T : Component { foreach (Component component in components) { if (component as T != null) { return (T)component; } } return null; } public T AddComponent() where T : Component, new() { T val = new T { gameObject = this, transform = transform }; components.Add(val); return val; } public static T Instantiate(T prefab) where T : GameObject { return (T)Activator.CreateInstance(typeof(T)); } public static GameObject Instantiate(GameObject prefab) { return new GameObject(prefab?.name ?? "GO"); } public new static GameObject Instantiate(GameObject prefab, Transform parent) { return new GameObject(prefab?.name ?? "GO") { transform = { parent = parent } }; } public static void Destroy(GameObject go) { } public static void Destroy(GameObject go, float t) { } } public class Transform : Object { public Vector3 position = Vector3.zero; public Quaternion rotation = Quaternion.identity; public Transform parent; public Vector3 localScale = new Vector3(1f, 1f, 1f); public GameObject gameObject; public Vector3 TransformPoint(Vector3 p) { return position + p; } public Vector3 InverseTransformPoint(Vector3 p) { return new Vector3(p.x - position.x, p.y - position.y, p.z - position.z); } public void SetParent(Transform p, bool worldPositionStays = true) { parent = p; } } public class MonoBehaviour : Component { } public class Rigidbody : Component { public bool useGravity; public bool isKinematic; public Vector3 velocity; public float mass; } public class Renderer : Component { public Material material = new Material(); } public class Material { public Color color = new Color(1f, 0f, 0f, 1f); } public struct Color { public float r; public float g; public float b; public float a; public static Color black => new Color(0f, 0f, 0f, 1f); public Color(float r, float g, float b, float a) { this.r = r; this.g = g; this.b = b; this.a = a; } public static Color Lerp(Color a, Color b, float t) { return new Color(a.r * (1f - t) + b.r * t, a.g * (1f - t) + b.g * t, a.b * (1f - t) + b.b * t, a.a * (1f - t) + b.a * t); } } public static class Resources { public static T Load(string path) where T : class { return null; } } public static class Time { public static float deltaTime => 0.016f; } public static class Random { private static readonly System.Random rnd = new System.Random(0); public static Vector3 insideUnitSphere => new Vector3((float)(rnd.NextDouble() - 0.5), (float)(rnd.NextDouble() - 0.5), (float)(rnd.NextDouble() - 0.5)); public static Vector3 onUnitSphere => insideUnitSphere; public static Quaternion rotation => Quaternion.identity; public static float Range(float a, float b) { return a + (float)rnd.NextDouble() * (b - a); } } public static class Mathf { public static int CeilToInt(float v) { return (int)Math.Ceiling(v); } public static float Clamp01(float v) { if (!(v < 0f)) { if (!(v > 1f)) { return v; } return 1f; } return 0f; } public static float Clamp(float v, float a, float b) { if (!(v < a)) { if (!(v > b)) { return v; } return b; } return a; } public static float Max(float a, float b) { if (!(a > b)) { return b; } return a; } public static float Lerp(float a, float b, float t) { return a + (b - a) * t; } } public class Collision { public ContactPoint[] contacts = new ContactPoint[0]; } public struct ContactPoint { public Vector3 normal; } public struct RaycastHit { public Vector3 point; public Vector3 normal; } public static class Physics { public static bool Raycast(Vector3 origin, Vector3 dir, out RaycastHit hit, float max) { hit = default(RaycastHit); return false; } } public static class JsonUtility { public static T FromJson(string json) { return JsonSerializer.Deserialize(json); } public static string ToJson(T obj, bool pretty = false) { return JsonSerializer.Serialize(obj); } } } namespace MelonLoader { [AttributeUsage(AttributeTargets.Assembly)] public sealed class MelonInfoAttribute : Attribute { public MelonInfoAttribute(Type t, string name, string version, string author) { } } public class MelonMod { public virtual void OnApplicationStart() { } public virtual void OnUpdate() { } } public static class MelonLogger { public static void Msg(string s) { Console.WriteLine(s); } } }