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;
using UnityEngine.Rendering;
using UnityEngine.SceneManagement;
[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("MuckGarlic")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("Adds Vampire Survivors' Garlic as a White powerup: a damage aura around the holder.")]
[assembly: AssemblyFileVersion("0.1.1.0")]
[assembly: AssemblyInformationalVersion("0.1.1+879fc7ac66fec639f2f84b4b7a65d5c61c9ed734")]
[assembly: AssemblyProduct("Garlic")]
[assembly: AssemblyTitle("MuckGarlic")]
[assembly: InternalsVisibleTo("MuckGarlic.Tests")]
[assembly: AssemblyVersion("0.1.1.0")]
namespace Microsoft.CodeAnalysis
{
[CompilerGenerated]
[Embedded]
internal sealed class EmbeddedAttribute : Attribute
{
}
}
namespace System.Runtime.CompilerServices
{
[CompilerGenerated]
[Embedded]
internal sealed class IsReadOnlyAttribute : Attribute
{
}
}
namespace MuckGarlic
{
internal sealed class AuraDisc : MonoBehaviour
{
private static readonly string[] ShaderNames = new string[4] { "Legacy Shaders/Particles/Alpha Blended", "Particles/Alpha Blended", "Sprites/Default", "Legacy Shaders/Particles/Additive" };
private static Mesh _sharedMesh;
private static bool _shaderLogged;
private Material _material;
private bool _built;
private float _flashUntil;
private void Awake()
{
Ensure();
}
public void Ensure()
{
//IL_0085: Unknown result type (might be due to invalid IL or missing references)
//IL_008a: Unknown result type (might be due to invalid IL or missing references)
//IL_0097: Expected O, but got Unknown
if (!_built)
{
_built = true;
if ((Object)(object)_sharedMesh == (Object)null)
{
GameObject obj = GameObject.CreatePrimitive((PrimitiveType)2);
MeshFilter component = obj.GetComponent<MeshFilter>();
_sharedMesh = (((Object)(object)component != (Object)null) ? component.sharedMesh : null);
Object.DestroyImmediate((Object)(object)obj);
}
Shader val = FindShader();
if (!((Object)(object)val == (Object)null) && !((Object)(object)_sharedMesh == (Object)null))
{
((Component)this).gameObject.AddComponent<MeshFilter>().sharedMesh = _sharedMesh;
MeshRenderer obj2 = ((Component)this).gameObject.AddComponent<MeshRenderer>();
_material = new Material(val)
{
hideFlags = (HideFlags)61
};
((Renderer)obj2).sharedMaterial = _material;
((Renderer)obj2).shadowCastingMode = (ShadowCastingMode)0;
((Renderer)obj2).receiveShadows = false;
}
}
}
public void Show(Vector3 playerPos, float radius)
{
//IL_000f: 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_001d: 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_0052: 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_007b: 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)
if (!((Object)(object)_material == (Object)null))
{
float num = playerPos.y - 1f;
RaycastHit val = default(RaycastHit);
if (Physics.Raycast(playerPos, Vector3.down, ref val, 3f, -1, (QueryTriggerInteraction)1))
{
num = ((RaycastHit)(ref val)).point.y + 0.05f;
}
((Component)this).transform.position = new Vector3(playerPos.x, num, playerPos.z);
((Component)this).transform.localScale = new Vector3(radius * 2f, 0.01f, radius * 2f);
((Component)this).gameObject.SetActive(true);
ApplyColor();
}
}
public void Hide()
{
if (((Component)this).gameObject.activeSelf)
{
((Component)this).gameObject.SetActive(false);
}
}
public void Flash()
{
_flashUntil = Time.time + 0.15f;
}
private void Update()
{
ApplyColor();
}
private void OnDestroy()
{
if ((Object)(object)_material != (Object)null)
{
Object.Destroy((Object)(object)_material);
}
}
private void ApplyColor()
{
//IL_0019: 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_0033: Unknown result type (might be due to invalid IL or missing references)
//IL_0066: 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_0089: Unknown result type (might be due to invalid IL or missing references)
if (!((Object)(object)_material == (Object)null))
{
Color value = GarlicPlugin.Cfg.AuraColor.Value;
if (Time.time < _flashUntil)
{
value.a = Mathf.Min(1f, value.a * 2.5f);
}
if (_material.HasProperty("_Color"))
{
_material.SetColor("_Color", value);
}
if (_material.HasProperty("_TintColor"))
{
_material.SetColor("_TintColor", value);
}
_material.color = value;
}
}
private static Shader FindShader()
{
string[] shaderNames = ShaderNames;
foreach (string text in shaderNames)
{
Shader val = Shader.Find(text);
if (!((Object)(object)val == (Object)null))
{
if (!_shaderLogged)
{
_shaderLogged = true;
GarlicPlugin.Log.LogInfo((object)("Garlic aura disc shader: " + text));
}
return val;
}
}
if (!_shaderLogged)
{
_shaderLogged = true;
GarlicPlugin.Log.LogWarning((object)"Garlic found no aura shader. The disc stays hidden and damage still works.");
}
return null;
}
}
internal readonly struct AuraMob
{
public readonly float X;
public readonly float Y;
public readonly float Z;
public readonly bool Alive;
public readonly bool Neutral;
public AuraMob(float x, float y, float z, bool alive, bool neutral)
{
X = x;
Y = y;
Z = z;
Alive = alive;
Neutral = neutral;
}
}
internal static class AuraPlan
{
private readonly struct Candidate
{
public readonly float Sqr;
public readonly int Index;
public Candidate(float sqr, int index)
{
Sqr = sqr;
Index = index;
}
}
public static List<int> Pick(IReadOnlyList<AuraMob> mobs, float ox, float oy, float oz, float radius, int maxTargets, bool hitNeutral)
{
List<int> list = new List<int>();
if (mobs.Count == 0 || !(radius >= 0f))
{
return list;
}
float num = radius * radius;
List<Candidate> list2 = new List<Candidate>(mobs.Count);
for (int i = 0; i < mobs.Count; i++)
{
AuraMob auraMob = mobs[i];
if (auraMob.Alive && (!auraMob.Neutral || hitNeutral))
{
float num2 = auraMob.X - ox;
float num3 = auraMob.Y - oy;
float num4 = auraMob.Z - oz;
float num5 = num2 * num2 + num3 * num3 + num4 * num4;
if (!float.IsNaN(num5) && !(num5 > num))
{
list2.Add(new Candidate(num5, i));
}
}
}
list2.Sort(Compare);
int num6 = ((maxTargets <= 0) ? list2.Count : Math.Min(maxTargets, list2.Count));
for (int j = 0; j < num6; j++)
{
list.Add(list2[j].Index);
}
return list;
}
private static int Compare(Candidate a, Candidate b)
{
float sqr = a.Sqr;
int num = sqr.CompareTo(b.Sqr);
if (num == 0)
{
int index = a.Index;
return index.CompareTo(b.Index);
}
return num;
}
}
internal static class GarlicAura
{
internal static bool IsFiring;
private static readonly List<AuraMob> Snapshot = new List<AuraMob>();
private static readonly List<HitableMob> Hitables = new List<HitableMob>();
private static readonly List<Vector3> Points = new List<Vector3>();
private static readonly List<Collider> ColliderBuffer = new List<Collider>();
private const float PrefilterMargin = 15f;
private static int _errorsLogged;
public static void Pulse(Vector3 origin, int stacks)
{
//IL_0203: 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)
//IL_00a2: 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_0191: Unknown result type (might be due to invalid IL or missing references)
//IL_0197: Unknown result type (might be due to invalid IL or missing references)
//IL_019d: Unknown result type (might be due to invalid IL or missing references)
//IL_00fd: Unknown result type (might be due to invalid IL or missing references)
//IL_00fe: Unknown result type (might be due to invalid IL or missing references)
//IL_0103: 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_0121: Invalid comparison between Unknown and I4
//IL_012d: Unknown result type (might be due to invalid IL or missing references)
//IL_0134: Unknown result type (might be due to invalid IL or missing references)
//IL_013b: Unknown result type (might be due to invalid IL or missing references)
//IL_0169: Unknown result type (might be due to invalid IL or missing references)
GarlicScaling garlicScaling = GarlicPlugin.Cfg.Scaling();
int num = garlicScaling.DamageFor(stacks);
if (num <= 0)
{
return;
}
float num2 = garlicScaling.RadiusFor(stacks);
Snapshot.Clear();
Hitables.Clear();
Points.Clear();
if ((Object)(object)MobManager.Instance == (Object)null || MobManager.Instance.mobs == null)
{
return;
}
float num3 = num2 + 15f;
float num4 = num3 * num3;
foreach (Mob value in MobManager.Instance.mobs.Values)
{
if ((Object)(object)value == (Object)null)
{
continue;
}
Vector3 val = ((Component)value).transform.position - origin;
if (((Vector3)(ref val)).sqrMagnitude > num4)
{
continue;
}
HitableMob component = ((Component)value).GetComponent<HitableMob>();
if (!((Object)(object)component == (Object)null))
{
float defense = (((Object)(object)value.mobType != (Object)null) ? value.mobType.defense : 0f);
if (HostDamage.AfterDefense(num, defense) > 0)
{
Vector3 val2 = ClosestPointOnBody(component, origin);
bool neutral = (Object)(object)value.mobType != (Object)null && (int)value.mobType.behaviour == 0;
Snapshot.Add(new AuraMob(val2.x, val2.y, val2.z, ((Hitable)component).hp > 0, neutral));
Hitables.Add(component);
Points.Add(val2);
}
}
}
List<int> list = AuraPlan.Pick(Snapshot, origin.x, origin.y, origin.z, num2, garlicScaling.MaxTargets, GarlicPlugin.Cfg.HitNeutralMobs.Value);
IsFiring = true;
try
{
foreach (int item in list)
{
HitableMob val3 = Hitables[item];
if ((Object)(object)val3 == (Object)null)
{
continue;
}
try
{
((Hitable)val3).Hit(num, 0f, 0, Points[item], -1);
}
catch (Exception arg)
{
if (_errorsLogged < 5)
{
_errorsLogged++;
GarlicPlugin.Log.LogError((object)$"Garlic hit failed. {arg}");
}
}
}
}
finally
{
IsFiring = false;
}
if (GarlicPlugin.Cfg.VerboseLogging.Value)
{
GarlicPlugin.Log.LogInfo((object)string.Format(CultureInfo.InvariantCulture, "Garlic pulse: {0} stacks, {1} dmg, {2:0.#}m, {3}/{4} hit.", stacks, num, num2, list.Count, Snapshot.Count));
}
}
private static Vector3 ClosestPointOnBody(HitableMob hitable, Vector3 origin)
{
//IL_0010: Unknown result type (might be due to invalid IL or missing references)
//IL_0090: Unknown result type (might be due to invalid IL or missing references)
//IL_0091: Unknown result type (might be due to invalid IL or missing references)
//IL_007e: Unknown result type (might be due to invalid IL or missing references)
//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_0050: 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_0048: Unknown result type (might be due to invalid IL or missing references)
((Component)hitable).GetComponentsInChildren<Collider>(false, ColliderBuffer);
bool flag = false;
Bounds val = default(Bounds);
for (int i = 0; i < ColliderBuffer.Count; i++)
{
Collider val2 = ColliderBuffer[i];
if (!((Object)(object)val2 == (Object)null) && val2.enabled && !val2.isTrigger)
{
if (!flag)
{
val = val2.bounds;
flag = true;
}
else
{
((Bounds)(ref val)).Encapsulate(val2.bounds);
}
}
}
ColliderBuffer.Clear();
if (!flag)
{
return ((Component)hitable).transform.position + Vector3.up;
}
return ((Bounds)(ref val)).ClosestPoint(origin);
}
}
internal sealed class GarlicConfig
{
public readonly ConfigEntry<int> Damage;
public readonly ConfigEntry<int> DamagePerStack;
public readonly ConfigEntry<int> MaxDamage;
public readonly ConfigEntry<float> Radius;
public readonly ConfigEntry<float> RadiusPerStack;
public readonly ConfigEntry<float> MaxRadius;
public readonly ConfigEntry<float> Cooldown;
public readonly ConfigEntry<float> CooldownPerStack;
public readonly ConfigEntry<float> MinCooldown;
public readonly ConfigEntry<int> MaxTargetsPerPulse;
public readonly ConfigEntry<bool> HitNeutralMobs;
public readonly ConfigEntry<bool> ShowAura;
public readonly ConfigEntry<Color> AuraColor;
public readonly ConfigEntry<KeyboardShortcut> GiveGarlicKey;
public readonly ConfigEntry<bool> VerboseLogging;
public GarlicConfig(ConfigFile cfg)
{
//IL_0028: Unknown result type (might be due to invalid IL or missing references)
//IL_0032: Expected O, but got Unknown
//IL_0059: Unknown result type (might be due to invalid IL or missing references)
//IL_0063: Expected O, but got Unknown
//IL_008b: Unknown result type (might be due to invalid IL or missing references)
//IL_0095: Expected O, but got Unknown
//IL_00c4: Unknown result type (might be due to invalid IL or missing references)
//IL_00ce: Expected O, but got Unknown
//IL_00fd: Unknown result type (might be due to invalid IL or missing references)
//IL_0107: Expected O, but got Unknown
//IL_0136: Unknown result type (might be due to invalid IL or missing references)
//IL_0140: Expected O, but got Unknown
//IL_016f: Unknown result type (might be due to invalid IL or missing references)
//IL_0179: Expected O, but got Unknown
//IL_01a8: Unknown result type (might be due to invalid IL or missing references)
//IL_01b2: Expected O, but got Unknown
//IL_01e1: Unknown result type (might be due to invalid IL or missing references)
//IL_01eb: Expected O, but got Unknown
//IL_020f: Unknown result type (might be due to invalid IL or missing references)
//IL_0219: Expected O, but got Unknown
//IL_0236: Unknown result type (might be due to invalid IL or missing references)
//IL_0240: Expected O, but got Unknown
//IL_025d: Unknown result type (might be due to invalid IL or missing references)
//IL_0267: Expected O, but got Unknown
//IL_028c: Unknown result type (might be due to invalid IL or missing references)
//IL_029c: Unknown result type (might be due to invalid IL or missing references)
//IL_02a6: Expected O, but got Unknown
//IL_02b7: Unknown result type (might be due to invalid IL or missing references)
//IL_02c7: Unknown result type (might be due to invalid IL or missing references)
//IL_02d1: Expected O, but got Unknown
//IL_02ee: Unknown result type (might be due to invalid IL or missing references)
//IL_02f8: Expected O, but got Unknown
Damage = cfg.Bind<int>("Garlic", "Damage", 5, new ConfigDescription("Flat damage per pulse with 1 stack, before the target's defense. Vampire Survivors' base damage is 5.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 1000), Array.Empty<object>()));
DamagePerStack = cfg.Bind<int>("Garlic", "DamagePerStack", 2, new ConfigDescription("Extra damage per stack after the first. Guess. +2 reaches Vampire Survivors' +9 by 6 stacks. MaxDamage stops growth after that; without a cap, 8 stacks would be 19 and 20 stacks would be 43.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 1000), Array.Empty<object>()));
MaxDamage = cfg.Bind<int>("Garlic", "MaxDamage", 14, new ConfigDescription("Damage cap. 14 is Vampire Survivors' max (base 5 plus 9). 0 means no cap. A cap below Damage does not shrink 1 stack.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 1000), Array.Empty<object>()));
Radius = cfg.Bind<float>("Garlic", "Radius", 3f, new ConfigDescription("Aura radius in metres with 1 stack. Guess. Vampire Survivors has no metre value.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 30f), Array.Empty<object>()));
RadiusPerStack = cfg.Bind<float>("Garlic", "RadiusPerStack", 0.4f, new ConfigDescription("Extra metres per stack after the first. Guess. About Vampire Survivors' +100% area over 7 level-ups, starting from 3 m.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 10f), Array.Empty<object>()));
MaxRadius = cfg.Bind<float>("Garlic", "MaxRadius", 6f, new ConfigDescription("Radius cap in metres. 6 is twice Radius, matching Vampire Survivors' max-level +100% area. 0 means no cap. A cap below Radius does not shrink 1 stack.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 60f), Array.Empty<object>()));
Cooldown = cfg.Bind<float>("Garlic", "Cooldown", 1.3f, new ConfigDescription("Seconds between pulses with 1 stack. Vampire Survivors' cooldown is 1.3.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 10f), Array.Empty<object>()));
CooldownPerStack = cfg.Bind<float>("Garlic", "CooldownPerStack", 0.05f, new ConfigDescription("Seconds removed from the cooldown per stack after the first. Guess. About Vampire Survivors' -0.3 s over 7 level-ups.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>()));
MinCooldown = cfg.Bind<float>("Garlic", "MinCooldown", 1f, new ConfigDescription("Lowest cooldown in seconds. Vampire Survivors' max level is 1.3 - 0.3. A floor above Cooldown does not slow 1 stack. The code never goes below 0.1 s.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 10f), Array.Empty<object>()));
MaxTargetsPerPulse = cfg.Bind<int>("Garlic", "MaxTargetsPerPulse", 8, new ConfigDescription("Most mobs one pulse may hit. Guess, from the design note of about 8, and a packet budget. 0 means no cap.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 100), Array.Empty<object>()));
HitNeutralMobs = cfg.Bind<bool>("Garlic", "HitNeutralMobs", false, new ConfigDescription("Also damage neutral animals such as cows. Off, matching Ukulele and Will-o'-the-wisp.", (AcceptableValueBase)null, Array.Empty<object>()));
ShowAura = cfg.Bind<bool>("Visuals", "ShowAura", true, new ConfigDescription("Draw the aura disc on the holder's screen.", (AcceptableValueBase)null, Array.Empty<object>()));
AuraColor = cfg.Bind<Color>("Visuals", "AuraColor", new Color(0.45f, 1f, 0.4f, 0.22f), new ConfigDescription("Disc color, including alpha. Guess.", (AcceptableValueBase)null, Array.Empty<object>()));
GiveGarlicKey = cfg.Bind<KeyboardShortcut>("Debug", "GiveGarlicKey", KeyboardShortcut.Empty, new ConfigDescription("Press in-game to give yourself one Garlic stack. Local test helper, for example F8. Empty means disabled.", (AcceptableValueBase)null, Array.Empty<object>()));
VerboseLogging = cfg.Bind<bool>("Debug", "VerboseLogging", false, new ConfigDescription("Log the powerup id table and every pulse.", (AcceptableValueBase)null, Array.Empty<object>()));
Watch<int>(Damage);
Watch<int>(DamagePerStack);
Watch<int>(MaxDamage);
Watch<float>(Radius);
Watch<float>(RadiusPerStack);
Watch<float>(MaxRadius);
Watch<float>(Cooldown);
Watch<float>(CooldownPerStack);
Watch<float>(MinCooldown);
Watch<int>(MaxTargetsPerPulse);
Watch<bool>(HitNeutralMobs);
}
private static void Watch<T>(ConfigEntry<T> entry)
{
entry.SettingChanged += delegate
{
GarlicPowerup.RefreshDescription();
};
}
public GarlicScaling Scaling()
{
return new GarlicScaling(Damage.Value, DamagePerStack.Value, MaxDamage.Value, Radius.Value, RadiusPerStack.Value, MaxRadius.Value, Cooldown.Value, CooldownPerStack.Value, MinCooldown.Value, MaxTargetsPerPulse.Value);
}
public string Summary()
{
return string.Format(CultureInfo.InvariantCulture, "Garlic config: dmg {0}+{1} (max {2}), radius {3}+{4} (max {5}), cooldown {6}-{7} (min {8}), max {9} targets.", Damage.Value, DamagePerStack.Value, MaxDamage.Value, Radius.Value, RadiusPerStack.Value, MaxRadius.Value, Cooldown.Value, CooldownPerStack.Value, MinCooldown.Value, MaxTargetsPerPulse.Value);
}
}
[BepInPlugin("evan.muck.garlic", "Garlic", "0.1.1")]
public class GarlicPlugin : BaseUnityPlugin
{
public const string Guid = "evan.muck.garlic";
public const string Name = "Garlic";
public const string Version = "0.1.1";
internal static ManualLogSource Log;
internal static GarlicConfig Cfg;
private void Awake()
{
//IL_0025: Unknown result type (might be due to invalid IL or missing references)
//IL_002b: Expected O, but got Unknown
Log = ((BaseUnityPlugin)this).Logger;
Cfg = new GarlicConfig(((BaseUnityPlugin)this).Config);
GarlicPowerup.Create();
Harmony val = new Harmony("evan.muck.garlic");
try
{
val.CreateClassProcessor(typeof(RegisterGarlicPatch)).Patch();
}
catch (Exception arg)
{
Log.LogError((object)$"Garlic could not patch ItemManager.InitAllPowerups; the mod is disabled. {arg}");
return;
}
GarlicRunner.Ensure();
Log.LogInfo((object)("Garlic 0.1.1 loaded. " + Cfg.Summary() + " Every player needs this mod and the same set of powerup mods."));
}
}
internal static class GarlicPowerup
{
public const string PowerupName = "Garlic";
private static FieldRef<PowerupInventory, int[]> _counts;
private static bool _countsReady;
private static bool _countsBroken;
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 = "Garlic";
((Object)obj).name = "Garlic";
obj.description = GarlicPlugin.Cfg.Scaling().Description();
obj.tier = (PowerTier)0;
obj.sprite = LoadIcon();
((Object)obj).hideFlags = (HideFlags)32;
Definition = obj;
}
public static void RefreshDescription()
{
if ((Object)(object)Definition != (Object)null)
{
Definition.description = GarlicPlugin.Cfg.Scaling().Description();
}
}
public static int LocalStacks()
{
PowerupInventory instance = PowerupInventory.Instance;
if ((Object)(object)instance == (Object)null || Id < 0)
{
return 0;
}
return StackCount.Read(Counts(instance), Id);
}
public static void GiveOneLocal()
{
PowerupInventory instance = PowerupInventory.Instance;
if ((Object)(object)instance == (Object)null || (Object)(object)PowerupUI.Instance == (Object)null || (Object)(object)PlayerStatus.Instance == (Object)null || Id < 0)
{
GarlicPlugin.Log.LogWarning((object)"Can't give Garlic: not in a game yet, or the powerup isn't registered.");
return;
}
int[] array = Counts(instance);
if (array == null || Id >= array.Length)
{
GarlicPlugin.Log.LogWarning((object)"Can't give Garlic: not in a game yet, or the powerup isn't registered.");
return;
}
array[Id]++;
PowerupUI.Instance.AddPowerup(Id);
PlayerStatus.Instance.UpdateStats();
GarlicPlugin.Log.LogInfo((object)$"Debug: Garlic stacks now {array[Id]} (local only; the host's copy of your powerups is not updated).");
}
private static int[] Counts(PowerupInventory inventory)
{
if (_countsBroken)
{
return null;
}
if (!_countsReady)
{
try
{
_counts = AccessTools.FieldRefAccess<PowerupInventory, int[]>("powerups");
_countsReady = true;
}
catch (Exception arg)
{
_countsBroken = true;
GarlicPlugin.Log.LogError((object)$"Garlic could not read PowerupInventory.powerups; stacks stay at 0. {arg}");
return null;
}
}
try
{
return _counts.Invoke(inventory);
}
catch (Exception arg2)
{
_countsBroken = true;
GarlicPlugin.Log.LogError((object)$"Garlic could not read PowerupInventory.powerups; stacks stay at 0. {arg2}");
return null;
}
}
private static Sprite LoadIcon()
{
//IL_0039: Unknown result type (might be due to invalid IL or missing references)
//IL_003f: Expected O, but got Unknown
//IL_0084: Unknown result type (might be due to invalid IL or missing references)
//IL_0093: Unknown result type (might be due to invalid IL or missing references)
try
{
using Stream stream = typeof(GarlicPowerup).Assembly.GetManifestResourceStream("MuckGarlic.icon.png");
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 = "GarlicIcon";
((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 = "GarlicIcon";
((Object)obj).hideFlags = (HideFlags)32;
return obj;
}
catch (Exception arg)
{
GarlicPlugin.Log.LogError((object)$"Failed to load Garlic icon: {arg}");
return null;
}
}
}
[HarmonyPatch(typeof(ItemManager), "InitAllPowerups")]
internal static class RegisterGarlicPatch
{
[HarmonyPriority(800)]
private static void Prefix(ItemManager __instance)
{
if ((Object)(object)GarlicPowerup.Definition != (Object)null && __instance.powerupsWhite != null)
{
__instance.powerupsWhite = __instance.powerupsWhite.Where((Powerup p) => (Object)(object)p != (Object)(object)GarlicPowerup.Definition).ToArray();
}
}
[HarmonyPriority(600)]
[HarmonyAfter(new string[]
{
"evan.muck.ukulele", "evan.muck.atgmissile", "evan.muck.crowbar", "evan.muck.stickybomb", "evan.muck.tritipdagger", "evan.muck.willothewisp", "evan.muck.devildeal", "evan.muck.shapedglass", "evan.muck.diosbestfriend", "evan.muck.waxquail",
"evan.muck.pennib"
})]
private static void Postfix(ItemManager __instance)
{
try
{
Register(__instance);
}
catch (Exception arg)
{
GarlicPowerup.Id = -1;
GarlicPlugin.Log.LogError((object)$"Garlic could not register; the aura will not run. {arg}");
}
}
private static void Register(ItemManager manager)
{
Powerup definition = GarlicPowerup.Definition;
if ((Object)(object)definition == (Object)null)
{
return;
}
Dictionary<int, Powerup> allPowerups = manager.allPowerups;
Dictionary<string, int> stringToPowerupId = manager.stringToPowerupId;
if (allPowerups == null || stringToPowerupId == null || manager.powerupsWhite == null)
{
GarlicPlugin.Log.LogWarning((object)"Garlic could not register: ItemManager powerup tables are null.");
return;
}
PickupArt.Apply(definition, manager.powerupsWhite);
int value;
bool flag = stringToPowerupId.TryGetValue("Garlic", out value);
Powerup value2;
bool existingIsOurs = flag && allPowerups.TryGetValue(value, out value2) && (Object)(object)value2 == (Object)(object)definition;
int num = PowerupIds.Resolve(allPowerups.Keys, flag, value);
switch (PowerupIds.Decide(flag, existingIsOurs, num, allPowerups.Count))
{
case RegistrationDecision.NameTaken:
GarlicPlugin.Log.LogError((object)"another mod registered a powerup named Garlic; Garlic disabled");
GarlicPowerup.Id = -1;
return;
case RegistrationDecision.IdGap:
GarlicPlugin.Log.LogError((object)$"powerup ids are not contiguous (id {num}, count {allPowerups.Count}); Garlic disabled to avoid an out-of-range pickup");
GarlicPowerup.Id = -1;
return;
case RegistrationDecision.Add:
allPowerups.Add(num, definition);
stringToPowerupId.Add("Garlic", num);
definition.id = num;
break;
case RegistrationDecision.AlreadyOurs:
definition.id = num;
break;
}
if (!manager.powerupsWhite.Contains(definition))
{
manager.powerupsWhite = CollectionExtensions.AddToArray<Powerup>(manager.powerupsWhite, definition);
}
GarlicPowerup.Id = num;
GarlicPlugin.Log.LogInfo((object)$"Registered Garlic as White powerup id {num} ({allPowerups.Count} powerups total, {manager.powerupsWhite.Length} in the White pool).");
if (GarlicPlugin.Cfg.VerboseLogging.Value)
{
GarlicPlugin.Log.LogInfo((object)("Powerup ids: " + string.Join(", ", from kv in allPowerups
orderby kv.Key
select $"{kv.Key}={kv.Value.name}")));
}
}
}
internal sealed class GarlicRunner : MonoBehaviour
{
private static GarlicRunner _instance;
private readonly PulseClock _clock = new PulseClock();
private AuraDisc _disc;
private int _errorsLogged;
public static GarlicRunner Instance
{
get
{
Ensure();
return _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("MuckGarlic")
{
hideFlags = (HideFlags)61
};
Object.DontDestroyOnLoad((Object)val);
_instance = val.AddComponent<GarlicRunner>();
}
}
private void Awake()
{
//IL_0005: Unknown result type (might be due to invalid IL or missing references)
//IL_000b: Expected O, but got Unknown
GameObject val = new GameObject("GarlicAuraDisc");
((Object)val).hideFlags = (HideFlags)61;
val.transform.SetParent(((Component)this).transform, false);
_disc = val.AddComponent<AuraDisc>();
_disc.Ensure();
val.SetActive(false);
}
private void OnEnable()
{
SceneManager.activeSceneChanged += OnSceneChanged;
}
private void OnDisable()
{
SceneManager.activeSceneChanged -= OnSceneChanged;
}
private void OnSceneChanged(Scene previous, Scene next)
{
_clock.Reset();
if ((Object)(object)_disc != (Object)null)
{
_disc.Hide();
}
}
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)
//IL_005a: Unknown result type (might be due to invalid IL or missing references)
try
{
KeyboardShortcut value = GarlicPlugin.Cfg.GiveGarlicKey.Value;
if (((KeyboardShortcut)(ref value)).IsDown())
{
GarlicPowerup.GiveOneLocal();
}
int stacks = GarlicPowerup.LocalStacks();
PlayerMovement movement;
bool active = IsActive(stacks, out movement);
if (_clock.Tick(Time.deltaTime, GarlicPlugin.Cfg.Scaling().CooldownFor(stacks), active))
{
GarlicAura.Pulse(((Component)movement).transform.position, stacks);
_disc.Flash();
}
}
catch (Exception e)
{
LogUpdateError(e);
}
}
private void LateUpdate()
{
//IL_002d: Unknown result type (might be due to invalid IL or missing references)
try
{
int stacks = GarlicPowerup.LocalStacks();
if (IsActive(stacks, out var movement) && GarlicPlugin.Cfg.ShowAura.Value)
{
_disc.Show(((Component)movement).transform.position, GarlicPlugin.Cfg.Scaling().RadiusFor(stacks));
}
else if ((Object)(object)_disc != (Object)null)
{
_disc.Hide();
}
}
catch (Exception e)
{
LogUpdateError(e);
}
}
private static bool IsActive(int stacks, out PlayerMovement movement)
{
movement = PlayerMovement.Instance;
PlayerStatus instance = PlayerStatus.Instance;
if (stacks <= 0 || (Object)(object)movement == (Object)null || (Object)(object)instance == (Object)null || (Object)(object)MobManager.Instance == (Object)null)
{
return false;
}
if (instance.IsPlayerDead())
{
return false;
}
return GarlicPlugin.Cfg.Scaling().DamageFor(stacks) > 0;
}
private void LogUpdateError(Exception e)
{
if (_errorsLogged < 5)
{
_errorsLogged++;
GarlicPlugin.Log.LogError((object)$"Garlic update failed. {e}");
}
}
}
internal readonly struct GarlicScaling
{
public const float AbsoluteMinCooldown = 0.1f;
public readonly int Damage;
public readonly int DamagePerStack;
public readonly int MaxDamage;
public readonly int MaxTargets;
public readonly float Radius;
public readonly float RadiusPerStack;
public readonly float MaxRadius;
public readonly float Cooldown;
public readonly float CooldownPerStack;
public readonly float MinCooldown;
public static GarlicScaling Defaults => new GarlicScaling(5, 2, 14, 3f, 0.4f, 6f, 1.3f, 0.05f, 1f, 8);
public GarlicScaling(int damage, int damagePerStack, int maxDamage, float radius, float radiusPerStack, float maxRadius, float cooldown, float cooldownPerStack, float minCooldown, int maxTargets)
{
Damage = ((damage >= 0) ? damage : 0);
DamagePerStack = ((damagePerStack >= 0) ? damagePerStack : 0);
MaxDamage = ((maxDamage >= 0) ? maxDamage : 0);
MaxTargets = ((maxTargets >= 0) ? maxTargets : 0);
Radius = ((radius >= 0f) ? radius : 0f);
RadiusPerStack = ((radiusPerStack >= 0f) ? radiusPerStack : 0f);
MaxRadius = ((maxRadius >= 0f) ? maxRadius : 0f);
Cooldown = ((cooldown > 0f) ? cooldown : 1.3f);
CooldownPerStack = ((cooldownPerStack >= 0f) ? cooldownPerStack : 0f);
MinCooldown = ((minCooldown > 0f) ? minCooldown : 0.1f);
}
public int DamageFor(int stacks)
{
if (stacks <= 0)
{
return 0;
}
long num = Damage + (long)DamagePerStack * (long)(stacks - 1);
if (num >= int.MaxValue)
{
num = 2147483647L;
}
else if (num <= 0)
{
return 0;
}
if (MaxDamage > 0 && num > Math.Max(MaxDamage, Damage))
{
num = Math.Max(MaxDamage, Damage);
}
return (int)num;
}
public float RadiusFor(int stacks)
{
if (stacks <= 0)
{
return 0f;
}
float num = Radius + RadiusPerStack * (float)(stacks - 1);
if (MaxRadius > 0f)
{
num = Math.Min(num, Math.Max(MaxRadius, Radius));
}
return Math.Max(0f, num);
}
public float CooldownFor(int stacks)
{
if (stacks <= 0)
{
return Cooldown;
}
float val = Math.Max(Math.Min(MinCooldown, Cooldown), Cooldown - CooldownPerStack * (float)(stacks - 1));
return Math.Max(0.1f, val);
}
public string Description()
{
string text = ((MaxTargets == 0) ? "all enemies" : string.Format(CultureInfo.InvariantCulture, "up to {0} enemies", MaxTargets));
string text2 = ((MaxDamage > 0) ? string.Format(CultureInfo.InvariantCulture, ", max {0}", MaxDamage) : "");
string text3 = ((MaxRadius > 0f) ? string.Format(CultureInfo.InvariantCulture, " (max {0:0.#}m)", MaxRadius) : "");
return string.Format(CultureInfo.InvariantCulture, "Every {0:0.##}s (-{1:0.##}s per stack, min {2:0.##}s), deal {3} (+{4} per stack{5}) damage to {6} within {7:0.#}m (+{8:0.#}m per stack){9}.", Cooldown, CooldownPerStack, MinCooldown, Damage, DamagePerStack, text2, text, Radius, RadiusPerStack, text3);
}
}
internal static class HostDamage
{
public static int AfterDefense(int damage, float defense)
{
float num = defense;
if (num < 0f)
{
num = 0f;
}
else if (num > 100f)
{
num = 100f;
}
float num2 = (float)((100.0 - (double)num) / 100.0);
if ((double)num2 < 0.019999999552965164)
{
num2 = 0.02f;
}
return (int)((double)damage * (double)num2);
}
}
internal static class PickupArt
{
public const string ObjResource = "MuckGarlic.pickup.obj";
public const string JsonResource = "MuckGarlic.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("MuckGarlic.pickup.obj"), Read("MuckGarlic.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)
{
GarlicPlugin.Log.LogWarning((object)("Garlic 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
{
GarlicPlugin.Log.LogWarning((object)"No White powerup to borrow a pickup model from; dropped Garlic 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 enum RegistrationDecision
{
Add,
AlreadyOurs,
NameTaken,
IdGap
}
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 RegistrationDecision Decide(bool nameExists, bool existingIsOurs, int id, int countBefore)
{
if (!nameExists)
{
if (id > countBefore)
{
return RegistrationDecision.IdGap;
}
return RegistrationDecision.Add;
}
if (!existingIsOurs)
{
return RegistrationDecision.NameTaken;
}
return RegistrationDecision.AlreadyOurs;
}
}
internal sealed class PulseClock
{
public float Elapsed { get; private set; }
public bool Tick(float dt, float cooldown, bool active)
{
if (!active || !(cooldown > 0f))
{
Elapsed = 0f;
return false;
}
if (!(dt >= 0f))
{
dt = 0f;
}
Elapsed += dt;
if (Elapsed < cooldown)
{
return false;
}
Elapsed -= cooldown;
if (Elapsed > cooldown)
{
Elapsed = 0f;
}
return true;
}
public void Reset()
{
Elapsed = 0f;
}
}
internal static class StackCount
{
public static int Read(int[] counts, int id)
{
if (counts == null || id < 0 || id >= counts.Length)
{
return 0;
}
return Math.Max(0, counts[id]);
}
}
public static class MyPluginInfo
{
public const string PLUGIN_GUID = "MuckGarlic";
public const string PLUGIN_NAME = "Garlic";
public const string PLUGIN_VERSION = "0.1.1";
}
}