using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
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(".NETStandard,Version=v2.0", FrameworkDisplayName = ".NET Standard 2.0")]
[assembly: AssemblyCompany("MuckCrowbar")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("Adds Risk of Rain 2's Crowbar (bonus damage to healthy enemies) to Muck as a White powerup.")]
[assembly: AssemblyFileVersion("0.2.1.0")]
[assembly: AssemblyInformationalVersion("0.2.1+879fc7ac66fec639f2f84b4b7a65d5c61c9ed734")]
[assembly: AssemblyProduct("Crowbar")]
[assembly: AssemblyTitle("MuckCrowbar")]
[assembly: InternalsVisibleTo("MuckCrowbar.Tests")]
[assembly: AssemblyVersion("0.2.1.0")]
namespace Microsoft.CodeAnalysis
{
[CompilerGenerated]
[Embedded]
internal sealed class EmbeddedAttribute : Attribute
{
}
}
namespace System.Runtime.CompilerServices
{
[CompilerGenerated]
[Embedded]
internal sealed class IsReadOnlyAttribute : Attribute
{
}
internal static class IsExternalInit
{
}
}
namespace MuckCrowbar
{
internal sealed class CrowbarConfig
{
public readonly ConfigEntry<int> BonusPercentPerStack;
public readonly ConfigEntry<int> HealthThresholdPercent;
public readonly ConfigEntry<KeyboardShortcut> GiveCrowbarKey;
public readonly ConfigEntry<bool> VerboseLogging;
internal static int CardRefreshCount;
internal static string LastCardText;
public CrowbarConfig(ConfigFile cfg)
{
//IL_0031: Unknown result type (might be due to invalid IL or missing references)
//IL_003b: Expected O, but got Unknown
//IL_0068: Unknown result type (might be due to invalid IL or missing references)
//IL_0072: Expected O, but got Unknown
//IL_0083: Unknown result type (might be due to invalid IL or missing references)
BonusPercentPerStack = cfg.Bind<int>("Crowbar", "BonusPercentPerStack", CrowbarRules.Wiki.BonusPercentPerStack, new ConfigDescription("Extra damage per stack, in percent of the hit, on hits that qualify. 75 is 1.75x with 1 stack and 2.5x with 2. The result rounds to a whole number, halves to even, before the boss cap and the target's armor. Not synced. Use the same value on every machine.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 1000), Array.Empty<object>()));
HealthThresholdPercent = cfg.Bind<int>("Crowbar", "HealthThresholdPercent", CrowbarRules.Wiki.HealthThresholdPercent, new ConfigDescription("A hit qualifies while the mob's health is strictly above this percent of its max. Uses the health your game shows, which can lag the host by about one hit. Not synced.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 99), Array.Empty<object>()));
GiveCrowbarKey = cfg.Bind<KeyboardShortcut>("Debug", "GiveCrowbarKey", KeyboardShortcut.Empty, "Press in-game to give yourself one Crowbar stack (local test helper). Empty = disabled.");
VerboseLogging = cfg.Bind<bool>("Debug", "VerboseLogging", false, "Logs every boosted hit.");
cfg.SettingChanged += OnSettingChanged;
}
public CrowbarRules Rules()
{
return new CrowbarRules(BonusPercentPerStack.Value, HealthThresholdPercent.Value);
}
internal static bool UpdatesCard(string section, string key)
{
if (section == "Crowbar")
{
if (!(key == "BonusPercentPerStack"))
{
return key == "HealthThresholdPercent";
}
return true;
}
return false;
}
private void OnSettingChanged(object sender, SettingChangedEventArgs args)
{
ConfigDefinition definition = args.ChangedSetting.Definition;
if (UpdatesCard(definition.Section, definition.Key))
{
string text = (LastCardText = Rules().Description());
CardRefreshCount++;
CrowbarPowerup.ApplyDescription(text);
}
}
}
[HarmonyPatch(typeof(HitableMob), "Hit", new Type[]
{
typeof(int),
typeof(float),
typeof(int),
typeof(Vector3),
typeof(int)
})]
internal static class CrowbarHitPatch
{
private static bool _reportedFailure;
internal static Func<int> LocalStacks = CrowbarPowerup.LocalStacks;
private const int NotBoosted = int.MinValue;
private static int _beforeBonus = int.MinValue;
[HarmonyPriority(0)]
private static void Prefix(HitableMob __instance, ref int damage, int hitWeaponType)
{
_beforeBonus = int.MinValue;
try
{
CrowbarHit hit = new CrowbarHit
{
Damage = damage,
WeaponType = hitWeaponType,
TargetHp = ((Hitable)__instance).hp,
TargetMaxHp = ((Hitable)__instance).maxHp,
Stacks = LocalStacks()
};
int num = CrowbarPlugin.Cfg.Rules().DamageFor(hit);
if (num != damage)
{
if (CrowbarPlugin.Cfg.VerboseLogging.Value)
{
CrowbarPlugin.Log.LogInfo((object)$"Crowbar: {damage} -> {num} ({hit.Stacks} stacks, target {hit.TargetHp}/{hit.TargetMaxHp} hp)");
}
_beforeBonus = damage;
damage = num;
}
}
catch (Exception arg)
{
if (!_reportedFailure)
{
_reportedFailure = true;
CrowbarPlugin.Log.LogError((object)$"Crowbar on-hit failed (further failures are not logged): {arg}");
}
}
}
[HarmonyPriority(800)]
[HarmonyBefore(new string[] { "evan.muck.ukulele", "evan.muck.atgmissile", "evan.muck.stickybomb", "evan.muck.tritipdagger" })]
private static void Postfix(ref int damage)
{
int beforeBonus = _beforeBonus;
_beforeBonus = int.MinValue;
if (beforeBonus != int.MinValue)
{
damage = CrowbarRules.ProcDamage(beforeBonus, damage);
}
}
}
[BepInPlugin("evan.muck.crowbar", "Crowbar", "0.2.1")]
public class CrowbarPlugin : BaseUnityPlugin
{
public const string Guid = "evan.muck.crowbar";
public const string Name = "Crowbar";
public const string Version = "0.2.1";
internal static ManualLogSource Log;
internal static CrowbarConfig Cfg;
private void Awake()
{
//IL_008c: Unknown result type (might be due to invalid IL or missing references)
//IL_0092: Expected O, but got Unknown
Log = ((BaseUnityPlugin)this).Logger;
Cfg = new CrowbarConfig(((BaseUnityPlugin)this).Config);
CrowbarPowerup.Create();
CrowbarRunner.Ensure();
Log.LogInfo((object)(string.Format("{0} {1} config: +{2}% per stack above {3}% health. ", "Crowbar", "0.2.1", Cfg.BonusPercentPerStack.Value, Cfg.HealthThresholdPercent.Value) + "The config file is per player and is not synced. Use the same numbers on every machine."));
Harmony harmony = new Harmony("evan.muck.crowbar");
if (!TryPatch(harmony, typeof(CrowbarHitPatch)))
{
Log.LogError((object)"Crowbar hit patch failed. The item will not be added to the drop pool.");
}
else if (!TryPatch(harmony, typeof(RegisterCrowbarPatch)))
{
Log.LogError((object)"Crowbar registration patch failed. Hits can still be boosted, but the item will not drop.");
}
else
{
Log.LogInfo((object)"Crowbar 0.2.1 loaded. Every player in a lobby needs this mod (same version).");
}
}
private static bool TryPatch(Harmony harmony, Type patch)
{
try
{
harmony.CreateClassProcessor(patch).Patch();
return true;
}
catch (Exception arg)
{
Log.LogError((object)$"Crowbar failed to patch {patch.Name}: {arg}");
return false;
}
}
}
internal static class CrowbarPowerup
{
public const string PowerupName = "Crowbar";
private static readonly FieldRef<PowerupInventory, int[]> PowerupCounts = AccessTools.FieldRefAccess<PowerupInventory, int[]>("powerups");
public static Powerup Definition { get; private set; }
public static int Id { get; internal set; } = -1;
public static void Create()
{
//IL_0035: Unknown result type (might be due to invalid IL or missing references)
Powerup obj = ScriptableObject.CreateInstance<Powerup>();
obj.name = "Crowbar";
((Object)obj).name = "Crowbar";
obj.description = CrowbarPlugin.Cfg.Rules().Description();
obj.tier = (PowerTier)0;
obj.sprite = LoadIcon();
((Object)obj).hideFlags = (HideFlags)32;
Definition = obj;
}
public static void ApplyDescription(string text)
{
if ((Object)(object)Definition != (Object)null)
{
Definition.description = text;
}
}
public static int LocalStacks()
{
PowerupInventory instance = PowerupInventory.Instance;
if ((Object)(object)instance == (Object)null || Id < 0)
{
return 0;
}
int[] array = PowerupCounts.Invoke(instance);
if (array == null || Id >= array.Length)
{
return 0;
}
return array[Id];
}
public static void GiveOneLocal()
{
PowerupInventory instance = PowerupInventory.Instance;
int[] array = (((Object)(object)instance != (Object)null) ? PowerupCounts.Invoke(instance) : null);
if (array == null || Id < 0 || Id >= array.Length)
{
CrowbarPlugin.Log.LogWarning((object)"Can't give Crowbar: not in a game yet, or the powerup isn't registered.");
return;
}
if ((Object)(object)PowerupUI.Instance == (Object)null || (Object)(object)PlayerStatus.Instance == (Object)null)
{
CrowbarPlugin.Log.LogWarning((object)"Can't give Crowbar: the powerup UI or player status is not ready.");
return;
}
array[Id]++;
PowerupUI.Instance.AddPowerup(Id);
PlayerStatus.Instance.UpdateStats();
CrowbarPlugin.Log.LogInfo((object)$"Debug: Crowbar stacks now {array[Id]} (local only; the host's copy of your powerups is not updated).");
}
private static Sprite LoadIcon()
{
//IL_004b: Unknown result type (might be due to invalid IL or missing references)
//IL_0051: Expected O, but got Unknown
//IL_0096: 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)
try
{
Assembly assembly = typeof(CrowbarPowerup).Assembly;
string name = assembly.GetName().Name + ".icon.png";
using Stream stream = assembly.GetManifestResourceStream(name);
if (stream == null)
{
throw new FileNotFoundException("embedded icon.png missing");
}
using MemoryStream memoryStream = new MemoryStream();
stream.CopyTo(memoryStream);
Texture2D val = new Texture2D(2, 2, (TextureFormat)4, false);
if (!ImageConversion.LoadImage(val, memoryStream.ToArray()))
{
throw new InvalidDataException("icon.png failed to decode");
}
((Object)val).name = "CrowbarIcon";
((Object)val).hideFlags = (HideFlags)32;
Sprite obj = Sprite.Create(val, new Rect(0f, 0f, (float)((Texture)val).width, (float)((Texture)val).height), new Vector2(0.5f, 0.5f), 100f);
((Object)obj).name = "CrowbarIcon";
((Object)obj).hideFlags = (HideFlags)32;
return obj;
}
catch (Exception arg)
{
CrowbarPlugin.Log.LogError((object)$"Failed to load Crowbar icon: {arg}");
return null;
}
}
}
[HarmonyPatch(typeof(ItemManager), "InitAllPowerups")]
internal static class RegisterCrowbarPatch
{
[HarmonyPriority(600)]
[HarmonyAfter(new string[] { "evan.muck.ukulele", "evan.muck.atgmissile" })]
private static void Postfix(ItemManager __instance)
{
Powerup definition = CrowbarPowerup.Definition;
if ((Object)(object)definition == (Object)null)
{
return;
}
Dictionary<int, Powerup> allPowerups = __instance.allPowerups;
Dictionary<string, int> stringToPowerupId = __instance.stringToPowerupId;
int value;
bool flag = stringToPowerupId.TryGetValue("Crowbar", out value);
Powerup value2;
bool existingIsOurs = flag && allPowerups.TryGetValue(value, out value2) && (Object)(object)value2 == (Object)(object)definition;
PowerupClaim powerupClaim = PowerupClaim.Resolve(flag, existingIsOurs, value, allPowerups.Keys);
if (!powerupClaim.Claimed)
{
CrowbarPlugin.Log.LogError((object)$"The name Crowbar is already powerup id {value}. This mod will not add another pool entry.");
return;
}
PickupArt.Apply(definition, __instance.powerupsWhite);
if (!flag)
{
allPowerups.Add(powerupClaim.Id, definition);
stringToPowerupId.Add("Crowbar", powerupClaim.Id);
}
definition.id = powerupClaim.Id;
if (!__instance.powerupsWhite.Contains(definition))
{
__instance.powerupsWhite = CollectionExtensions.AddToArray<Powerup>(__instance.powerupsWhite, definition);
}
CrowbarPowerup.Id = powerupClaim.Id;
CrowbarPlugin.Log.LogInfo((object)$"Registered Crowbar as White powerup id {powerupClaim.Id} ({allPowerups.Count} total, {__instance.powerupsWhite.Length} in the White pool).");
}
}
internal readonly struct CrowbarHit
{
public int Damage { get; init; }
public int WeaponType { get; init; }
public int TargetHp { get; init; }
public int TargetMaxHp { get; init; }
public int Stacks { get; init; }
}
internal readonly struct CrowbarRules
{
public const int MaxBonusPercentPerStack = 1000;
public const int MaxHealthThresholdPercent = 99;
public static readonly CrowbarRules Wiki = new CrowbarRules(75, 90);
public readonly int BonusPercentPerStack;
public readonly int HealthThresholdPercent;
public CrowbarRules(int bonusPercentPerStack, int healthThresholdPercent)
{
BonusPercentPerStack = Math.Max(0, Math.Min(1000, bonusPercentPerStack));
HealthThresholdPercent = Math.Max(0, Math.Min(99, healthThresholdPercent));
}
public int DamageFor(CrowbarHit hit)
{
if (hit.WeaponType < 0 || hit.Damage <= 0 || hit.Stacks <= 0 || hit.TargetMaxHp <= 0)
{
return hit.Damage;
}
if ((long)hit.TargetHp * 100L <= (long)hit.TargetMaxHp * (long)HealthThresholdPercent)
{
return hit.Damage;
}
decimal num = Math.Round((decimal)hit.Damage * (100m + (decimal)BonusPercentPerStack * (decimal)hit.Stacks) / 100m, MidpointRounding.ToEven);
if (num >= 2147483647m)
{
return int.MaxValue;
}
return (int)num;
}
public static int ProcDamage(int beforeBonus, int afterBody)
{
return Math.Min(beforeBonus, afterBody);
}
public string Description()
{
return $"Deal +{BonusPercentPerStack}% (+{BonusPercentPerStack}% per stack) damage to enemies above {HealthThresholdPercent}% health.";
}
}
internal sealed class CrowbarRunner : MonoBehaviour
{
private static CrowbarRunner _instance;
public static void Ensure()
{
//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_0020: Unknown result type (might be due to invalid IL or missing references)
//IL_0026: Expected O, but got Unknown
if (!((Object)(object)_instance != (Object)null))
{
GameObject val = new GameObject("MuckCrowbar")
{
hideFlags = (HideFlags)61
};
Object.DontDestroyOnLoad((Object)val);
_instance = val.AddComponent<CrowbarRunner>();
}
}
private void Update()
{
//IL_000a: Unknown result type (might be due to invalid IL or missing references)
//IL_000f: Unknown result type (might be due to invalid IL or missing references)
KeyboardShortcut value = CrowbarPlugin.Cfg.GiveCrowbarKey.Value;
if (((KeyboardShortcut)(ref value)).IsDown())
{
CrowbarPowerup.GiveOneLocal();
}
}
}
internal static class PickupArt
{
public const string ObjResource = "MuckCrowbar.pickup.obj";
public const string JsonResource = "MuckCrowbar.pickup.json";
public static void Apply(Powerup powerup, Powerup[] pool)
{
if ((Object)(object)powerup == (Object)null || (Object)(object)powerup.mesh != (Object)null)
{
return;
}
Powerup val = Donor(pool, powerup);
try
{
PickupMesh data = PickupMesh.From(PickupModel.Parse(Read("MuckCrowbar.pickup.obj"), Read("MuckCrowbar.pickup.json")));
Material val2 = (((Object)(object)val != (Object)null) ? val.material : null);
if ((Object)(object)val2 == (Object)null)
{
val2 = FallbackShader();
}
bool vertexColors = (Object)(object)val2 == (Object)null || !val2.HasProperty("_MainTex");
powerup.mesh = ToMesh(data, powerup.name, vertexColors);
powerup.material = ToMaterial(data, val2, powerup.name);
}
catch (Exception ex)
{
CrowbarPlugin.Log.LogWarning((object)("Crowbar pickup model was not applied (" + ex.Message + ")."));
if ((Object)(object)val != (Object)null && (Object)(object)val.mesh != (Object)null)
{
powerup.mesh = val.mesh;
powerup.material = val.material;
}
else
{
CrowbarPlugin.Log.LogWarning((object)"No White powerup to borrow a pickup model from; dropped Crowbars will be invisible.");
}
}
}
private static Powerup Donor(Powerup[] pool, Powerup self)
{
if (pool == null)
{
return null;
}
Powerup val = null;
foreach (Powerup val2 in pool)
{
if (!((Object)(object)val2 == (Object)null) && val2 != self)
{
if ((Object)(object)val2.material != (Object)null)
{
return val2;
}
if ((Object)(object)val == (Object)null && (Object)(object)val2.mesh != (Object)null)
{
val = val2;
}
}
}
return val;
}
private static string Read(string name)
{
using Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(name);
if (stream == null)
{
throw new FileNotFoundException(name);
}
using StreamReader streamReader = new StreamReader(stream);
return streamReader.ReadToEnd();
}
private static Mesh ToMesh(PickupMesh data, string itemName, bool vertexColors)
{
//IL_0000: Unknown result type (might be due to invalid IL or missing references)
//IL_0005: 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_001f: Expected O, but got Unknown
//IL_0097: Unknown result type (might be due to invalid IL or missing references)
//IL_00d8: Unknown result type (might be due to invalid IL or missing references)
//IL_00f1: Unknown result type (might be due to invalid IL or missing references)
//IL_0127: Unknown result type (might be due to invalid IL or missing references)
//IL_0100: Unknown result type (might be due to invalid IL or missing references)
Mesh val = new Mesh
{
name = itemName + " pickup",
hideFlags = (HideFlags)32
};
List<Vector3> list = new List<Vector3>(data.Positions.Length);
List<Vector3> list2 = new List<Vector3>(data.Normals.Length);
List<Vector2> list3 = new List<Vector2>(data.U.Length);
List<Color> list4 = new List<Color>(data.Positions.Length);
for (int i = 0; i < data.Positions.Length; i++)
{
list.Add(new Vector3(data.Positions[i].X, data.Positions[i].Y, data.Positions[i].Z));
list2.Add(new Vector3(data.Normals[i].X, data.Normals[i].Y, data.Normals[i].Z));
list3.Add(new Vector2(data.U[i], 0.5f));
list4.Add((Color)(vertexColors ? new Color(data.R[i], data.G[i], data.B[i], 1f) : Color.white));
}
val.SetVertices(list);
val.SetNormals(list2);
val.SetUVs(0, list3);
val.SetColors(list4);
val.SetTriangles(data.Triangles, 0);
val.RecalculateBounds();
return val;
}
private static Material FallbackShader()
{
//IL_002b: Unknown result type (might be due to invalid IL or missing references)
//IL_0031: Expected O, but got Unknown
Shader val = Shader.Find("Standard");
if ((Object)(object)val == (Object)null)
{
val = Shader.Find("Diffuse");
}
if (!((Object)(object)val != (Object)null))
{
return null;
}
return new Material(val);
}
private static Material ToMaterial(PickupMesh data, Material source, string itemName)
{
//IL_0075: Unknown result type (might be due to invalid IL or missing references)
//IL_007a: 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)
//IL_0092: 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_00a2: Expected O, but got Unknown
//IL_0026: 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_003c: 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_005e: Unknown result type (might be due to invalid IL or missing references)
//IL_0067: Expected O, but got Unknown
//IL_00d1: Unknown result type (might be due to invalid IL or missing references)
//IL_00f6: Unknown result type (might be due to invalid IL or missing references)
//IL_00fb: Unknown result type (might be due to invalid IL or missing references)
//IL_010c: Unknown result type (might be due to invalid IL or missing references)
//IL_0115: Expected O, but got Unknown
//IL_011d: Unknown result type (might be due to invalid IL or missing references)
//IL_0128: Unknown result type (might be due to invalid IL or missing references)
//IL_0140: Unknown result type (might be due to invalid IL or missing references)
if ((Object)(object)source == (Object)null)
{
return null;
}
if (!source.HasProperty("_MainTex"))
{
MaterialSpec materialSpec = data.Palette[0];
return new Material(source)
{
name = itemName + " pickup",
color = new Color(materialSpec.R, materialSpec.G, materialSpec.B, 1f),
hideFlags = (HideFlags)32
};
}
Texture2D val = new Texture2D(data.Palette.Count, 1, (TextureFormat)4, false)
{
name = itemName + " palette",
filterMode = (FilterMode)0,
wrapMode = (TextureWrapMode)1,
hideFlags = (HideFlags)32
};
for (int i = 0; i < data.Palette.Count; i++)
{
MaterialSpec materialSpec2 = data.Palette[i];
val.SetPixel(i, 0, new Color(materialSpec2.R, materialSpec2.G, materialSpec2.B, 1f));
}
val.Apply(false, true);
Material val2 = new Material(source)
{
name = itemName + " pickup",
hideFlags = (HideFlags)32
};
val2.mainTexture = (Texture)(object)val;
val2.mainTextureScale = Vector2.one;
val2.mainTextureOffset = Vector2.zero;
if (val2.HasProperty("_Color"))
{
val2.color = Color.white;
}
return val2;
}
}
internal readonly struct Vec3
{
public readonly float X;
public readonly float Y;
public readonly float Z;
public Vec3(float x, float y, float z)
{
X = x;
Y = y;
Z = z;
}
public static Vec3 operator -(Vec3 a, Vec3 b)
{
return new Vec3(a.X - b.X, a.Y - b.Y, a.Z - b.Z);
}
}
internal readonly struct MaterialSpec
{
public readonly float R;
public readonly float G;
public readonly float B;
public readonly float Emission;
public MaterialSpec(float r, float g, float b, float emission)
{
R = r;
G = g;
B = b;
Emission = emission;
}
}
internal sealed class SubMesh
{
public readonly string Material;
public readonly int[] Triangles;
public SubMesh(string material, int[] triangles)
{
Material = material;
Triangles = triangles;
}
}
internal sealed class PickupModel
{
private sealed class ObjObject
{
public readonly List<Vec3> Positions = new List<Vec3>();
public readonly List<Vec3> Normals = new List<Vec3>();
public readonly List<string> MaterialOrder = new List<string>();
public readonly Dictionary<string, List<int>> Triangles = new Dictionary<string, List<int>>();
}
public readonly string Name;
public readonly Vec3[] Positions;
public readonly Vec3[] Normals;
public readonly IReadOnlyList<SubMesh> SubMeshes;
public readonly IReadOnlyDictionary<string, MaterialSpec> Materials;
public int TriangleCount => Positions.Length / 3;
public PickupModel(string name, Vec3[] positions, Vec3[] normals, IReadOnlyList<SubMesh> subMeshes, IReadOnlyDictionary<string, MaterialSpec> materials)
{
Name = name;
Positions = positions;
Normals = normals;
SubMeshes = subMeshes;
Materials = materials;
}
public static PickupModel Parse(string objText, string json)
{
if (!(JsonReader.Parse(json) is Dictionary<string, object> obj))
{
throw new FormatException("pickup json: top level is not an object.");
}
string text = Str(obj, "name");
Dictionary<string, MaterialSpec> dictionary = ReadMaterials(Obj(obj, "materials"));
Dictionary<string, ObjObject> dictionary2 = ReadObj(objText, text);
if (dictionary2.Count != 1)
{
throw new FormatException($"{text}: a pickup OBJ needs exactly one object, it has {dictionary2.Count}.");
}
ObjObject objObject = null;
foreach (ObjObject value in dictionary2.Values)
{
objObject = value;
}
List<SubMesh> list = new List<SubMesh>();
foreach (string item in objObject.MaterialOrder)
{
if (!dictionary.ContainsKey(item))
{
throw new FormatException(text + ": uses undefined material '" + item + "'.");
}
list.Add(new SubMesh(item, objObject.Triangles[item].ToArray()));
}
PickupModel pickupModel = new PickupModel(text, objObject.Positions.ToArray(), objObject.Normals.ToArray(), list, dictionary);
int num = (int)Num(obj, "tris");
if (pickupModel.TriangleCount != num)
{
throw new FormatException($"{text}: OBJ has {pickupModel.TriangleCount} triangles, the json says {num}.");
}
return pickupModel;
}
private static Dictionary<string, ObjObject> ReadObj(string text, string name)
{
if (text == null)
{
throw new FormatException(name + ": OBJ text is null.");
}
List<Vec3> list = new List<Vec3>();
List<Vec3> list2 = new List<Vec3>();
Dictionary<string, ObjObject> dictionary = new Dictionary<string, ObjObject>();
ObjObject objObject = null;
string text2 = null;
string[] array = text.Split(new char[1] { '\n' });
for (int i = 0; i < array.Length; i++)
{
string[] array2 = array[i].Trim().Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries);
if (array2.Length == 0)
{
continue;
}
switch (array2[0])
{
case "v":
{
Vec3 vec2 = ReadVec(array2, name, i);
list.Add(new Vec3(0f - vec2.X, vec2.Y, vec2.Z));
break;
}
case "vn":
{
Vec3 vec = ReadVec(array2, name, i);
list2.Add(new Vec3(0f - vec.X, vec.Y, vec.Z));
break;
}
case "o":
{
string text3 = string.Join(" ", array2, 1, array2.Length - 1);
if (dictionary.ContainsKey(text3))
{
throw new FormatException(name + ": OBJ object " + text3 + " appears twice.");
}
objObject = new ObjObject();
dictionary.Add(text3, objObject);
text2 = null;
break;
}
case "usemtl":
text2 = ((array2.Length > 1) ? array2[1] : null);
break;
case "f":
if (objObject == null || text2 == null)
{
throw new FormatException($"{name}: OBJ line {i + 1} has a face outside an object or material.");
}
if (array2.Length != 4)
{
throw new FormatException($"{name}: OBJ line {i + 1} is not a triangle.");
}
AddTriangle(objObject, text2, list, list2, array2, name, i);
break;
}
}
return dictionary;
}
private static void AddTriangle(ObjObject obj, string material, List<Vec3> vertices, List<Vec3> normals, string[] tokens, string name, int line)
{
if (!obj.Triangles.TryGetValue(material, out var value))
{
value = new List<int>();
obj.Triangles.Add(material, value);
obj.MaterialOrder.Add(material);
}
int[] array = new int[3] { 1, 3, 2 };
foreach (int num in array)
{
string[] array2 = tokens[num].Split(new char[1] { '/' });
Vec3 item = vertices[Index(array2[0], vertices.Count, name, line)];
if (array2.Length < 3 || array2[2].Length == 0)
{
throw new FormatException($"{name}: OBJ line {line + 1} has a corner without a normal.");
}
value.Add(obj.Positions.Count);
obj.Positions.Add(item);
obj.Normals.Add(normals[Index(array2[2], normals.Count, name, line)]);
}
}
private static int Index(string raw, int count, string name, int line)
{
if (!int.TryParse(raw, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result) || result == 0)
{
throw new FormatException($"{name}: OBJ line {line + 1} has a bad index '{raw}'.");
}
int num = ((result > 0) ? (result - 1) : (count + result));
if (num < 0 || num >= count)
{
throw new FormatException($"{name}: OBJ line {line + 1} index {raw} is out of range.");
}
return num;
}
private static Vec3 ReadVec(string[] tokens, string name, int line)
{
if (tokens.Length < 4)
{
throw new FormatException($"{name}: OBJ line {line + 1} needs three numbers.");
}
return new Vec3(Float(tokens[1], name, line), Float(tokens[2], name, line), Float(tokens[3], name, line));
}
private static float Float(string raw, string name, int line)
{
if (!float.TryParse(raw, NumberStyles.Float, CultureInfo.InvariantCulture, out var result))
{
throw new FormatException($"{name}: OBJ line {line + 1} has a bad number '{raw}'.");
}
return result;
}
private static Dictionary<string, MaterialSpec> ReadMaterials(Dictionary<string, object> raw)
{
Dictionary<string, MaterialSpec> dictionary = new Dictionary<string, MaterialSpec>();
foreach (KeyValuePair<string, object> item in raw)
{
if (!(item.Value is Dictionary<string, object> obj))
{
throw new FormatException("pickup json: material " + item.Key + " is not an object.");
}
Vec3 vec = ReadVec3(obj, "rgb");
dictionary.Add(item.Key, new MaterialSpec(vec.X, vec.Y, vec.Z, (float)Num(obj, "emission")));
}
return dictionary;
}
private static Vec3 ReadVec3(Dictionary<string, object> obj, string key)
{
List<object> list = Arr(obj, key);
if (list.Count != 3 || !(list[0] is double num) || !(list[1] is double num2) || !(list[2] is double num3))
{
throw new FormatException("pickup json: '" + key + "' is not three numbers.");
}
return new Vec3((float)num, (float)num2, (float)num3);
}
private static object Field(Dictionary<string, object> obj, string key)
{
if (!obj.TryGetValue(key, out var value))
{
throw new FormatException("pickup json: missing '" + key + "'.");
}
return value;
}
private static string Str(Dictionary<string, object> obj, string key)
{
return (Field(obj, key) as string) ?? throw new FormatException("pickup json: '" + key + "' is not a string.");
}
private static double Num(Dictionary<string, object> obj, string key)
{
object obj2 = Field(obj, key);
if (obj2 is double)
{
return (double)obj2;
}
throw new FormatException("pickup json: '" + key + "' is not a number.");
}
private static List<object> Arr(Dictionary<string, object> obj, string key)
{
return (Field(obj, key) as List<object>) ?? throw new FormatException("pickup json: '" + key + "' is not an array.");
}
private static Dictionary<string, object> Obj(Dictionary<string, object> obj, string key)
{
return (Field(obj, key) as Dictionary<string, object>) ?? throw new FormatException("pickup json: '" + key + "' is not an object.");
}
}
internal sealed class PickupMesh
{
public readonly Vec3[] Positions;
public readonly Vec3[] Normals;
public readonly float[] U;
public readonly float[] R;
public readonly float[] G;
public readonly float[] B;
public readonly int[] Triangles;
public readonly IReadOnlyList<MaterialSpec> Palette;
private PickupMesh(Vec3[] positions, Vec3[] normals, float[] u, float[] r, float[] g, float[] b, int[] triangles, IReadOnlyList<MaterialSpec> palette)
{
Positions = positions;
Normals = normals;
U = u;
R = r;
G = g;
B = b;
Triangles = triangles;
Palette = palette;
}
public static PickupMesh From(PickupModel model)
{
Vec3 vec = new Vec3(float.MaxValue, float.MaxValue, float.MaxValue);
Vec3 vec2 = new Vec3(float.MinValue, float.MinValue, float.MinValue);
Vec3[] positions = model.Positions;
for (int i = 0; i < positions.Length; i++)
{
Vec3 vec3 = positions[i];
vec = new Vec3(Math.Min(vec.X, vec3.X), Math.Min(vec.Y, vec3.Y), Math.Min(vec.Z, vec3.Z));
vec2 = new Vec3(Math.Max(vec2.X, vec3.X), Math.Max(vec2.Y, vec3.Y), Math.Max(vec2.Z, vec3.Z));
}
Vec3 vec4 = new Vec3((vec.X + vec2.X) / 2f, (vec.Y + vec2.Y) / 2f, (vec.Z + vec2.Z) / 2f);
int count = model.SubMeshes.Count;
List<Vec3> list = new List<Vec3>(model.Positions.Length);
List<Vec3> list2 = new List<Vec3>(model.Positions.Length);
List<float> list3 = new List<float>(model.Positions.Length);
List<float> list4 = new List<float>(model.Positions.Length);
List<float> list5 = new List<float>(model.Positions.Length);
List<float> list6 = new List<float>(model.Positions.Length);
List<MaterialSpec> list7 = new List<MaterialSpec>(count);
for (int j = 0; j < count; j++)
{
SubMesh subMesh = model.SubMeshes[j];
MaterialSpec item = model.Materials[subMesh.Material];
list7.Add(item);
float item2 = ((float)j + 0.5f) / (float)count;
int[] triangles = subMesh.Triangles;
foreach (int num in triangles)
{
list.Add(model.Positions[num] - vec4);
list2.Add(model.Normals[num]);
list3.Add(item2);
list4.Add(item.R);
list5.Add(item.G);
list6.Add(item.B);
}
}
int[] array = new int[list.Count];
for (int k = 0; k < array.Length; k++)
{
array[k] = k;
}
return new PickupMesh(list.ToArray(), list2.ToArray(), list3.ToArray(), list4.ToArray(), list5.ToArray(), list6.ToArray(), array, list7);
}
}
internal sealed class JsonReader
{
private readonly string text;
private int pos;
private JsonReader(string text)
{
this.text = text;
}
public static object Parse(string text)
{
if (text == null)
{
throw new FormatException("JSON text is null.");
}
JsonReader jsonReader = new JsonReader(text);
object result = jsonReader.ReadValue();
jsonReader.SkipWhitespace();
if (jsonReader.pos != text.Length)
{
throw jsonReader.Error("trailing characters");
}
return result;
}
private object ReadValue()
{
SkipWhitespace();
if (pos >= text.Length)
{
throw Error("unexpected end");
}
char c = text[pos];
switch (c)
{
case '{':
return ReadObject();
case '[':
return ReadArray();
case '"':
return ReadString();
case 't':
Expect("true");
return true;
case 'f':
Expect("false");
return false;
case 'n':
Expect("null");
return null;
default:
switch (c)
{
case '-':
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
return ReadNumber();
default:
throw Error($"unexpected '{c}'");
}
}
}
private Dictionary<string, object> ReadObject()
{
Dictionary<string, object> dictionary = new Dictionary<string, object>();
pos++;
SkipWhitespace();
if (Peek() == '}')
{
pos++;
return dictionary;
}
while (true)
{
SkipWhitespace();
if (Peek() != '"')
{
throw Error("expected a key");
}
string key = ReadString();
SkipWhitespace();
if (Peek() != ':')
{
break;
}
pos++;
dictionary[key] = ReadValue();
SkipWhitespace();
char c = Peek();
pos++;
switch (c)
{
case ',':
break;
case '}':
return dictionary;
default:
throw Error("expected ',' or '}'");
}
}
throw Error("expected ':'");
}
private List<object> ReadArray()
{
List<object> list = new List<object>();
pos++;
SkipWhitespace();
if (Peek() == ']')
{
pos++;
return list;
}
while (true)
{
list.Add(ReadValue());
SkipWhitespace();
char c = Peek();
pos++;
switch (c)
{
case ',':
break;
case ']':
return list;
default:
throw Error("expected ',' or ']'");
}
}
}
private string ReadString()
{
pos++;
StringBuilder stringBuilder = new StringBuilder();
while (pos < text.Length)
{
char c = text[pos++];
switch (c)
{
case '"':
return stringBuilder.ToString();
default:
stringBuilder.Append(c);
break;
case '\\':
{
if (pos >= text.Length)
{
throw Error("unterminated escape");
}
char c2 = text[pos++];
switch (c2)
{
case '"':
stringBuilder.Append('"');
break;
case '\\':
stringBuilder.Append('\\');
break;
case '/':
stringBuilder.Append('/');
break;
case 'b':
stringBuilder.Append('\b');
break;
case 'f':
stringBuilder.Append('\f');
break;
case 'n':
stringBuilder.Append('\n');
break;
case 'r':
stringBuilder.Append('\r');
break;
case 't':
stringBuilder.Append('\t');
break;
case 'u':
if (pos + 4 > text.Length)
{
throw Error("short \\u escape");
}
stringBuilder.Append((char)int.Parse(text.Substring(pos, 4), NumberStyles.HexNumber, CultureInfo.InvariantCulture));
pos += 4;
break;
default:
throw Error($"bad escape '\\{c2}'");
}
break;
}
}
}
throw Error("unterminated string");
}
private double ReadNumber()
{
int num = pos;
while (pos < this.text.Length && "+-0123456789.eE".IndexOf(this.text[pos]) >= 0)
{
pos++;
}
string text = this.text.Substring(num, pos - num);
if (!double.TryParse(text, NumberStyles.Float, CultureInfo.InvariantCulture, out var result))
{
throw Error("bad number '" + text + "'");
}
return result;
}
private void Expect(string word)
{
if (string.CompareOrdinal(text, pos, word, 0, word.Length) != 0)
{
throw Error("expected '" + word + "'");
}
pos += word.Length;
}
private char Peek()
{
if (pos >= text.Length)
{
return '\0';
}
return text[pos];
}
private void SkipWhitespace()
{
while (pos < text.Length && char.IsWhiteSpace(text[pos]))
{
pos++;
}
}
private FormatException Error(string what)
{
return new FormatException($"JSON: {what} at offset {pos}.");
}
}
internal readonly struct PowerupClaim
{
public readonly bool Claimed;
public readonly int Id;
private PowerupClaim(bool claimed, int id)
{
Claimed = claimed;
Id = id;
}
public static PowerupClaim Resolve(bool nameExists, bool existingIsOurs, int existingId, IEnumerable<int> existingIds)
{
if (nameExists && !existingIsOurs)
{
return new PowerupClaim(claimed: false, 0);
}
return new PowerupClaim(claimed: true, PowerupIds.Resolve(existingIds, nameExists, existingId));
}
}
internal static class PowerupIds
{
public static int Resolve(IEnumerable<int> existingIds, bool nameExists, int existingId)
{
if (nameExists)
{
return existingId;
}
int num = 0;
bool flag = false;
foreach (int existingId2 in existingIds)
{
if (!flag || existingId2 > num)
{
num = existingId2;
flag = true;
}
}
if (!flag)
{
return 0;
}
return num + 1;
}
}
public static class MyPluginInfo
{
public const string PLUGIN_GUID = "MuckCrowbar";
public const string PLUGIN_NAME = "Crowbar";
public const string PLUGIN_VERSION = "0.2.1";
}
}