using System;
using System.Collections;
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.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("MuckAtgMissile")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("Adds Risk of Rain 2's AtG Missile Mk. 1 (a chance on hit to fire a missile) to Muck as a Blue powerup.")]
[assembly: AssemblyFileVersion("0.1.2.0")]
[assembly: AssemblyInformationalVersion("0.1.2+879fc7ac66fec639f2f84b4b7a65d5c61c9ed734")]
[assembly: AssemblyProduct("AtG Missile Mk. 1")]
[assembly: AssemblyTitle("MuckAtgMissile")]
[assembly: InternalsVisibleTo("MuckAtgMissile.Tests")]
[assembly: AssemblyVersion("0.1.2.0")]
namespace Microsoft.CodeAnalysis
{
[CompilerGenerated]
[Embedded]
internal sealed class EmbeddedAttribute : Attribute
{
}
}
namespace System.Runtime.CompilerServices
{
[CompilerGenerated]
[Embedded]
internal sealed class IsReadOnlyAttribute : Attribute
{
}
}
namespace MuckAtgMissile
{
internal sealed class AtgConfig
{
public readonly ConfigEntry<float> ProcChance;
public readonly ConfigEntry<float> DamagePerStack;
public readonly ConfigEntry<float> FlightSeconds;
public readonly ConfigEntry<bool> HitNeutral;
public readonly ConfigEntry<bool> ShowMissile;
public readonly ConfigEntry<bool> VerboseLogging;
public AtgConfig(ConfigFile cfg)
{
//IL_0037: Unknown result type (might be due to invalid IL or missing references)
//IL_0041: Expected O, but got Unknown
//IL_0071: Unknown result type (might be due to invalid IL or missing references)
//IL_007b: Expected O, but got Unknown
//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
//IL_00b5: Expected O, but got Unknown
MissileRules defaults = MissileRules.Defaults;
ProcChance = cfg.Bind<float>("AtG Missile Mk. 1", "ProcChance", defaults.Chance, new ConfigDescription("Chance (0-1) that a melee, arrow, or thrown-rock hit fires one missile. Does not scale with stacks. 1 always fires. Muck has no proc coefficient, so this stays flat. Not synced to other players.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>()));
DamagePerStack = cfg.Bind<float>("AtG Missile Mk. 1", "DamagePerStack", defaults.DamagePerStack, new ConfigDescription("TOTAL damage as a multiple of the triggering hit, per stack. 0 disables the missile. 3 is 300% at 1 stack and 600% at 2. Half rounds to even, then at least 1, and never above int.MaxValue. No second crit. Not synced to other players.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 20f), Array.Empty<object>()));
FlightSeconds = cfg.Bind<float>("AtG Missile Mk. 1", "FlightSeconds", defaults.FlightSeconds, new ConfigDescription("Seconds from launch to impact. Not from the RoR2 wiki. The target is locked at launch. A dead target deals nothing. 0 impacts on the first resolution.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 5f), Array.Empty<object>()));
HitNeutral = cfg.Bind<bool>("AtG Missile Mk. 1", "HitNeutral", defaults.HitNeutral, "If true, a hit on a neutral animal can launch a missile at that animal. Default off.");
ShowMissile = cfg.Bind<bool>("AtG Missile Mk. 1", "ShowMissile", true, "Draw the homing sphere on the attacker's machine. Off still waits out the flight before damage.");
VerboseLogging = cfg.Bind<bool>("AtG Missile Mk. 1", "VerboseLogging", false, "Log each launch, impact, and fizzle.");
}
public MissileRules Rules()
{
return new MissileRules(ProcChance.Value, DamagePerStack.Value, FlightSeconds.Value, HitNeutral.Value);
}
}
[BepInPlugin("evan.muck.atgmissile", "AtG Missile Mk. 1", "0.1.2")]
public class AtgPlugin : BaseUnityPlugin
{
public const string Guid = "evan.muck.atgmissile";
public const string Name = "AtG Missile Mk. 1";
public const string Version = "0.1.2";
internal static ManualLogSource Log;
internal static AtgConfig Cfg;
private void Awake()
{
//IL_0025: Unknown result type (might be due to invalid IL or missing references)
//IL_002a: Unknown result type (might be due to invalid IL or missing references)
//IL_003f: Expected O, but got Unknown
//IL_0053: Expected O, but got Unknown
Log = ((BaseUnityPlugin)this).Logger;
Cfg = new AtgConfig(((BaseUnityPlugin)this).Config);
AtgPowerup.Create();
Harmony val = new Harmony("evan.muck.atgmissile");
TryPatch(val, typeof(RegisterAtgPatch), "powerup registration");
TryPatch(val, typeof(AtgHitPatch), "on-hit");
Log.LogInfo((object)"AtG Missile Mk. 1 0.1.2 loaded. Every player needs the same set of powerup mods. Ids are positions, so a different set swaps items.");
}
private static void TryPatch(Harmony harmony, Type type, string what)
{
try
{
harmony.PatchAll(type);
}
catch (Exception arg)
{
Log.LogError((object)$"Failed to patch {what}. That part of AtG Missile stays off. {arg}");
}
}
}
internal static class AtgPowerup
{
public const string PowerupName = "AtG Missile Mk. 1";
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_0032: Unknown result type (might be due to invalid IL or missing references)
Powerup obj = ScriptableObject.CreateInstance<Powerup>();
obj.name = "AtG Missile Mk. 1";
((Object)obj).name = "AtG Missile Mk. 1";
obj.description = Missile.CardText(AtgPlugin.Cfg.Rules());
obj.tier = (PowerTier)1;
obj.sprite = LoadIcon();
((Object)obj).hideFlags = (HideFlags)32;
Definition = obj;
}
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];
}
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(AtgPowerup).Assembly.GetManifestResourceStream("MuckAtgMissile.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 = "AtgMissileIcon";
((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 = "AtgMissileIcon";
((Object)obj).hideFlags = (HideFlags)32;
return obj;
}
catch (Exception arg)
{
AtgPlugin.Log.LogError((object)$"Failed to load AtG Missile icon: {arg}");
return null;
}
}
}
[HarmonyPatch(typeof(ItemManager), "InitAllPowerups")]
internal static class RegisterAtgPatch
{
[HarmonyPriority(600)]
[HarmonyAfter(new string[] { "evan.muck.ukulele" })]
private static void Postfix(ItemManager __instance)
{
Powerup definition = AtgPowerup.Definition;
if ((Object)(object)definition == (Object)null)
{
return;
}
Dictionary<int, Powerup> allPowerups = __instance.allPowerups;
Dictionary<string, int> stringToPowerupId = __instance.stringToPowerupId;
PickupArt.Apply(definition, __instance.powerupsBlue);
int value;
bool flag = stringToPowerupId.TryGetValue("AtG Missile Mk. 1", out value);
int num = PowerupIds.Resolve(allPowerups.Keys, flag, value);
if (!flag)
{
allPowerups.Add(num, definition);
stringToPowerupId.Add("AtG Missile Mk. 1", num);
definition.id = num;
}
if (__instance.powerupsBlue == null)
{
__instance.powerupsBlue = (Powerup[])(object)new Powerup[1] { definition };
}
else if (!__instance.powerupsBlue.Contains(definition))
{
__instance.powerupsBlue = CollectionExtensions.AddToArray<Powerup>(__instance.powerupsBlue, definition);
}
AtgPowerup.Id = num;
List<KeyValuePair<int, string>> list = new List<KeyValuePair<int, string>>(allPowerups.Count);
foreach (KeyValuePair<int, Powerup> item in allPowerups)
{
list.Add(new KeyValuePair<int, string>(item.Key, ((Object)(object)item.Value != (Object)null) ? item.Value.name : ""));
}
AtgPlugin.Log.LogInfo((object)($"Registered AtG Missile Mk. 1 as Blue powerup id {num} ({__instance.powerupsBlue.Length} in the Blue pool). " + "Every player needs the same set of powerup mods. Fingerprint: " + PowerupIds.Fingerprint(list)));
}
}
internal static class AtgBodies
{
private static readonly List<Collider> ColliderBuffer = new List<Collider>();
internal static BodySample? Sample(HitableMob struck)
{
if ((Object)(object)struck == (Object)null)
{
return null;
}
Dictionary<int, Mob> dictionary = Mobs();
if (dictionary == null)
{
return null;
}
foreach (KeyValuePair<int, Mob> item in dictionary)
{
Mob value = item.Value;
if (!((Object)(object)value == (Object)null))
{
HitableMob component = ((Component)value).GetComponent<HitableMob>();
if (component == struck)
{
return SampleOf(item.Key, value, component);
}
}
}
return null;
}
internal static BodySample? Sample(int id)
{
Dictionary<int, Mob> dictionary = Mobs();
if (dictionary == null || !dictionary.TryGetValue(id, out var value) || (Object)(object)value == (Object)null)
{
return null;
}
HitableMob component = ((Component)value).GetComponent<HitableMob>();
if ((Object)(object)component == (Object)null)
{
return null;
}
return SampleOf(id, value, component);
}
internal static HitableMob Find(int id)
{
Dictionary<int, Mob> dictionary = Mobs();
if (dictionary == null || !dictionary.TryGetValue(id, out var value) || (Object)(object)value == (Object)null)
{
return null;
}
return ((Component)value).GetComponent<HitableMob>();
}
private static Dictionary<int, Mob> Mobs()
{
if (!((Object)(object)MobManager.Instance != (Object)null))
{
return null;
}
return MobManager.Instance.mobs;
}
private static BodySample SampleOf(int id, Mob mob, HitableMob hitable)
{
//IL_0014: Unknown result type (might be due to invalid IL or missing references)
//IL_001a: Invalid comparison between Unknown and I4
bool neutral = (Object)(object)mob.mobType != (Object)null && (int)mob.mobType.behaviour == 0;
return new BodySample(id, AimOf(hitable), ((Hitable)hitable).hp > 0, neutral);
}
private static AimPoint AimOf(HitableMob mob)
{
//IL_0006: Unknown result type (might be due to invalid IL or missing references)
//IL_000b: Unknown result type (might be due to invalid IL or missing references)
//IL_0010: Unknown result type (might be due to invalid IL or missing references)
//IL_0015: 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_0080: Unknown result type (might be due to invalid IL or missing references)
//IL_0086: Unknown result type (might be due to invalid IL or missing references)
//IL_0051: Unknown result type (might be due to invalid IL or missing references)
//IL_0056: Unknown result type (might be due to invalid IL or missing references)
//IL_0059: Unknown result type (might be due to invalid IL or missing references)
//IL_005e: Unknown result type (might be due to invalid IL or missing references)
Vector3 val = ((Component)mob).transform.position + Vector3.up;
((Component)mob).GetComponentsInChildren<Collider>(false, ColliderBuffer);
foreach (Collider item in ColliderBuffer)
{
if ((Object)(object)item != (Object)null && item.enabled && !item.isTrigger)
{
Bounds bounds = item.bounds;
val = ((Bounds)(ref bounds)).center;
break;
}
}
return new AimPoint(val.x, val.y, val.z);
}
}
[HarmonyPatch(typeof(HitableMob), "Hit")]
internal static class AtgHitPatch
{
private static void Postfix(HitableMob __instance, int __0, int __4)
{
try
{
int num = AtgPowerup.LocalStacks();
MissileRules rules = AtgPlugin.Cfg.Rules();
if (Missile.HoldBeforeSample(__4, __0, AtgFlight.IsDelivering, num, rules.DamagePerStack).HasValue)
{
return;
}
BodySample? struck = AtgBodies.Sample(__instance);
if (Missile.TryLaunch(new HitReport(__0, __4, struck.HasValue, AtgFlight.IsDelivering, num, struck.HasValue ? struck.Value.Id : 0), rules, struck, () => Random.value) is Missile.Fired fired)
{
if (AtgPlugin.Cfg.VerboseLogging.Value)
{
AtgPlugin.Log.LogInfo((object)$"AtG Missile launched at mob {fired.Shot.TargetId} from a {__0} hit with {num} stacks.");
}
AtgFlight.Begin(fired.Shot);
}
}
catch (Exception arg)
{
AtgPlugin.Log.LogError((object)$"AtG Missile on-hit failed: {arg}");
}
}
}
internal sealed class AtgFlight : MonoBehaviour
{
private static AtgFlight _host;
private static Material _sphereMaterial;
private static readonly List<GameObject> Spheres = new List<GameObject>();
internal static bool IsDelivering { get; private set; }
internal static void Begin(Missile shot)
{
//IL_0016: Unknown result type (might be due to invalid IL or missing references)
//IL_001b: Unknown result type (might be due to invalid IL or missing references)
if (shot != null)
{
if ((Object)(object)_host == (Object)null)
{
_host = new GameObject("MuckAtgMissile")
{
hideFlags = (HideFlags)61
}.AddComponent<AtgFlight>();
}
((MonoBehaviour)_host).StartCoroutine(_host.Fly(shot));
}
}
private void OnEnable()
{
SceneManager.activeSceneChanged += OnSceneChanged;
}
private void OnDisable()
{
SceneManager.activeSceneChanged -= OnSceneChanged;
}
private void OnDestroy()
{
ReleaseAllSpheres();
if ((Object)(object)_host == (Object)(object)this)
{
_host = null;
}
}
private void OnSceneChanged(Scene previous, Scene next)
{
((MonoBehaviour)this).StopAllCoroutines();
ReleaseAllSpheres();
}
private IEnumerator Fly(Missile shot)
{
bool show = AtgPlugin.Cfg.ShowMissile.Value;
Vector3? start = null;
GameObject sphere = null;
bool first = true;
try
{
Missile.Step step;
while (true)
{
float deltaSeconds = (first ? 0f : Time.deltaTime);
first = false;
step = shot.Advance(AtgBodies.Sample(shot.TargetId), deltaSeconds);
if (!(step is Missile.Steer steer))
{
break;
}
Vector3 val = ToVector(steer.At);
start.GetValueOrDefault();
if (!start.HasValue)
{
Vector3 value = val + Vector3.up * 1.5f;
start = value;
}
if (show)
{
if ((Object)(object)sphere == (Object)null)
{
sphere = CreateSphere();
}
sphere.transform.position = Vector3.Lerp(start.Value, val, steer.Along);
}
yield return null;
}
if (!(step is Missile.Impact impact))
{
if (!(step is Missile.Fizzle fizzle))
{
if (!(step is Missile.Idle))
{
throw new InvalidOperationException($"Unhandled missile step: {step}");
}
}
else if (AtgPlugin.Cfg.VerboseLogging.Value)
{
AtgPlugin.Log.LogInfo((object)$"AtG Missile fizzled: mob {fizzle.TargetId} died or despawned before impact.");
}
}
else
{
Deliver(impact);
}
}
finally
{
ReleaseSphere(sphere);
}
}
private static void Deliver(Missile.Impact impact)
{
//IL_005f: Unknown result type (might be due to invalid IL or missing references)
HitableMob val = AtgBodies.Find(impact.TargetId);
if ((Object)(object)val == (Object)null)
{
if (AtgPlugin.Cfg.VerboseLogging.Value)
{
AtgPlugin.Log.LogInfo((object)$"AtG Missile impact skipped: mob {impact.TargetId} is gone.");
}
return;
}
IsDelivering = true;
try
{
((Hitable)val).Hit(impact.Damage, 0f, 0, ToVector(impact.At), -1);
}
finally
{
IsDelivering = false;
}
if (AtgPlugin.Cfg.VerboseLogging.Value)
{
AtgPlugin.Log.LogInfo((object)$"AtG Missile hit mob {impact.TargetId} for {impact.Damage}.");
}
}
private static GameObject CreateSphere()
{
//IL_0023: Unknown result type (might be due to invalid IL or missing references)
//IL_002d: Unknown result type (might be due to invalid IL or missing references)
//IL_0051: Unknown result type (might be due to invalid IL or missing references)
//IL_0056: Unknown result type (might be due to invalid IL or missing references)
//IL_006b: Unknown result type (might be due to invalid IL or missing references)
//IL_007a: Expected O, but got Unknown
GameObject val = GameObject.CreatePrimitive((PrimitiveType)0);
((Object)val).name = "AtG Missile";
Object.DestroyImmediate((Object)(object)val.GetComponent<Collider>());
val.transform.localScale = Vector3.one * 0.35f;
Renderer component = val.GetComponent<Renderer>();
if ((Object)(object)_sphereMaterial == (Object)null)
{
_sphereMaterial = new Material(component.sharedMaterial)
{
color = new Color(1f, 0.5f, 0.15f, 1f)
};
((Object)_sphereMaterial).hideFlags = (HideFlags)32;
}
component.sharedMaterial = _sphereMaterial;
Spheres.Add(val);
return val;
}
private static void ReleaseSphere(GameObject sphere)
{
if (!((Object)(object)sphere == (Object)null))
{
Spheres.Remove(sphere);
Object.Destroy((Object)(object)sphere);
}
}
private static void ReleaseAllSpheres()
{
for (int num = Spheres.Count - 1; num >= 0; num--)
{
ReleaseSphere(Spheres[num]);
}
}
private static Vector3 ToVector(AimPoint at)
{
//IL_0012: Unknown result type (might be due to invalid IL or missing references)
return new Vector3(at.X, at.Y, at.Z);
}
}
internal readonly struct AimPoint
{
public readonly float X;
public readonly float Y;
public readonly float Z;
public AimPoint(float x, float y, float z)
{
X = x;
Y = y;
Z = z;
}
}
internal readonly struct BodySample
{
public readonly int Id;
public readonly AimPoint At;
public readonly bool Alive;
public readonly bool Neutral;
public BodySample(int id, AimPoint at, bool alive, bool neutral)
{
Id = id;
At = at;
Alive = alive;
Neutral = neutral;
}
}
internal readonly struct HitReport
{
public readonly int Damage;
public readonly int WeaponType;
public readonly bool HasStruckBody;
public readonly bool DeliveryInProgress;
public readonly int Stacks;
public readonly int StruckId;
public HitReport(int damage, int weaponType, bool hasStruckBody, bool deliveryInProgress, int stacks, int struckId)
{
Damage = damage;
WeaponType = weaponType;
HasStruckBody = hasStruckBody;
DeliveryInProgress = deliveryInProgress;
Stacks = stacks;
StruckId = struckId;
}
}
internal readonly struct MissileRules
{
internal static readonly MissileRules Defaults = new MissileRules(0.1f, 3f, 0.4f, hitNeutral: false);
public readonly float Chance;
public readonly float DamagePerStack;
public readonly float FlightSeconds;
public readonly bool HitNeutral;
public MissileRules(float chance, float damagePerStack, float flightSeconds, bool hitNeutral)
{
Chance = chance;
DamagePerStack = damagePerStack;
FlightSeconds = ((flightSeconds < 0f) ? 0f : flightSeconds);
HitNeutral = hitNeutral;
}
}
internal enum HoldReason
{
DeliveryInProgress,
NonWeaponHit,
NoDamage,
NoStruckBody,
NoStacks,
DamageDisabled,
StruckNotEligible,
ChanceMiss
}
internal sealed class Missile
{
private enum Phase
{
Airborne,
Impact,
Fizzle
}
internal abstract class Launch
{
private protected Launch()
{
}
}
internal sealed class Fired : Launch
{
internal Missile Shot { get; }
internal Fired(Missile shot)
{
Shot = shot;
}
}
internal sealed class Held : Launch
{
internal HoldReason Reason { get; }
internal Held(HoldReason reason)
{
Reason = reason;
}
}
internal abstract class Step
{
private protected Step()
{
}
}
internal sealed class Steer : Step
{
internal int TargetId { get; }
internal AimPoint At { get; }
internal float Along { get; }
internal Steer(int targetId, AimPoint at, float along)
{
TargetId = targetId;
At = at;
Along = along;
}
}
internal sealed class Impact : Step
{
internal int TargetId { get; }
internal AimPoint At { get; }
internal int Damage { get; }
internal Impact(int targetId, AimPoint at, int damage)
{
TargetId = targetId;
At = at;
Damage = damage;
}
}
internal sealed class Fizzle : Step
{
internal int TargetId { get; }
internal Fizzle(int targetId)
{
TargetId = targetId;
}
}
internal sealed class Idle : Step
{
internal static readonly Idle Instance = new Idle();
private Idle()
{
}
}
private readonly int _targetId;
private readonly int _damage;
private readonly float _flightSeconds;
private float _elapsed;
private Phase _phase;
internal int TargetId => _targetId;
private Missile(int targetId, int damage, float flightSeconds)
{
_targetId = targetId;
_damage = damage;
_flightSeconds = flightSeconds;
_elapsed = 0f;
_phase = Phase.Airborne;
}
internal static HoldReason? HoldBeforeSample(int weaponType, int damage, bool deliveryInProgress, int stacks, float damagePerStack)
{
if (deliveryInProgress)
{
return HoldReason.DeliveryInProgress;
}
if (weaponType < 0)
{
return HoldReason.NonWeaponHit;
}
if (damage <= 0)
{
return HoldReason.NoDamage;
}
if (stacks <= 0)
{
return HoldReason.NoStacks;
}
if (damagePerStack <= 0f)
{
return HoldReason.DamageDisabled;
}
return null;
}
internal static Launch TryLaunch(HitReport hit, MissileRules rules, BodySample? struck, Func<float> nextRoll)
{
HoldReason? holdReason = HoldBeforeSample(hit.WeaponType, hit.Damage, hit.DeliveryInProgress, hit.Stacks, rules.DamagePerStack);
if (holdReason.HasValue)
{
return new Held(holdReason.Value);
}
if (!hit.HasStruckBody)
{
return new Held(HoldReason.NoStruckBody);
}
if (!struck.HasValue || struck.Value.Id != hit.StruckId || !struck.Value.Alive || (struck.Value.Neutral && !rules.HitNeutral))
{
return new Held(HoldReason.StruckNotEligible);
}
if (rules.Chance < 1f && !(nextRoll() < rules.Chance))
{
return new Held(HoldReason.ChanceMiss);
}
return new Fired(new Missile(hit.StruckId, Scale(hit.Damage, hit.Stacks, rules.DamagePerStack), rules.FlightSeconds));
}
internal Step Advance(BodySample? body, float deltaSeconds)
{
if (_phase != Phase.Airborne)
{
return Idle.Instance;
}
if (deltaSeconds > 0f)
{
_elapsed += deltaSeconds;
}
if (!body.HasValue || body.Value.Id != _targetId || !body.Value.Alive)
{
_phase = Phase.Fizzle;
return new Fizzle(_targetId);
}
if (_elapsed < _flightSeconds)
{
return new Steer(_targetId, body.Value.At, _elapsed / _flightSeconds);
}
_phase = Phase.Impact;
return new Impact(_targetId, body.Value.At, _damage);
}
internal static string CardText(MissileRules rules)
{
int num = RoundHalfEven(rules.Chance * 100f);
int num2 = RoundHalfEven(rules.DamagePerStack * 100f);
return $"{num}% chance on hit to fire a missile that deals {num2}% (+{num2}% per stack) TOTAL damage.";
}
private static int Scale(int triggerDamage, int stacks, float damagePerStack)
{
double num = (double)triggerDamage * (double)damagePerStack * (double)stacks;
if (double.IsNaN(num) || num < 1.0)
{
return 1;
}
if (double.IsInfinity(num) || num >= 2147483647.0)
{
return int.MaxValue;
}
int num2 = (int)Math.Round(num, MidpointRounding.ToEven);
if (num2 >= 1)
{
return num2;
}
return 1;
}
private static int RoundHalfEven(float value)
{
return (int)Math.Round(value, MidpointRounding.ToEven);
}
}
internal static class PowerupIds
{
public static string Fingerprint(IEnumerable<KeyValuePair<int, string>> entries)
{
List<KeyValuePair<int, string>> list = new List<KeyValuePair<int, string>>();
if (entries != null)
{
foreach (KeyValuePair<int, string> entry in entries)
{
list.Add(entry);
}
}
list.Sort((KeyValuePair<int, string> a, KeyValuePair<int, string> b) => a.Key.CompareTo(b.Key));
if (list.Count == 0)
{
return "";
}
string[] array = new string[list.Count];
for (int num = 0; num < list.Count; num++)
{
array[num] = list[num].Key + "=" + list[num].Value;
}
return string.Join(", ", array);
}
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;
}
}
internal static class PickupArt
{
public const string ObjResource = "MuckAtgMissile.pickup.obj";
public const string JsonResource = "MuckAtgMissile.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("MuckAtgMissile.pickup.obj"), Read("MuckAtgMissile.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)
{
AtgPlugin.Log.LogWarning((object)("AtG Missile 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
{
AtgPlugin.Log.LogWarning((object)"No Blue powerup to borrow a pickup model from. Dropped missiles 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}.");
}
}
public static class MyPluginInfo
{
public const string PLUGIN_GUID = "MuckAtgMissile";
public const string PLUGIN_NAME = "AtG Missile Mk. 1";
public const string PLUGIN_VERSION = "0.1.2";
}
}