Decompiled source of Bitencola Cosmetics v1.0.0

BitencolaCosmetics.dll

Decompiled 11 hours ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using System.Text.RegularExpressions;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using BitencolaCosmetics.Core;
using BitencolaCosmetics.Features;
using HarmonyLib;
using LethalLib.Modules;
using Microsoft.CodeAnalysis;
using Unity.Collections;
using Unity.Netcode;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: IgnoresAccessChecksTo("Assembly-CSharp")]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("BitencolaCosmetics")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+75af055ec911ead866a8d7dd8199d2847769dda6")]
[assembly: AssemblyProduct("BitencolaCosmetics")]
[assembly: AssemblyTitle("BitencolaCosmetics")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace BitencolaCosmetics
{
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInPlugin("Bitencola.Cosmetics", "Bitencola Cosmetics", "1.0.0")]
	public class Plugin : BaseUnityPlugin
	{
		public const string PluginGuid = "Bitencola.Cosmetics";

		public const string PluginName = "Bitencola Cosmetics";

		public const string PluginVersion = "1.0.0";

		public static Plugin Instance { get; private set; }

		public static ManualLogSource Log { get; private set; }

		public static PluginConfig Cfg { get; private set; }

		private void Awake()
		{
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Invalid comparison between Unknown and I4
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			//IL_008f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0099: Expected O, but got Unknown
			Instance = this;
			Log = ((BaseUnityPlugin)this).Logger;
			Cfg = new PluginConfig(((BaseUnityPlugin)this).Config, (int)Application.systemLanguage == 28);
			Log.LogInfo((object)$"Idioma dos textos do mod: {Cfg.Lang} (opção {Cfg.LanguageOption.Value}, Windows em {Application.systemLanguage})");
			try
			{
				JokeSuits.Register();
			}
			catch (Exception arg)
			{
				Log.LogError((object)$"Falha ao registrar as roupas: {arg}");
			}
			PatchEachClass(new Harmony("Bitencola.Cosmetics"));
			Log.LogInfo((object)"Bitencola Cosmetics 1.0.0 carregado");
		}

		private static void PatchEachClass(Harmony harmony)
		{
			Type[] typesFromAssembly = AccessTools.GetTypesFromAssembly(typeof(Plugin).Assembly);
			foreach (Type type in typesFromAssembly)
			{
				try
				{
					harmony.CreateClassProcessor(type).Patch();
				}
				catch (Exception arg)
				{
					Log.LogError((object)$"Falha ao aplicar os patches de {type.Name}: {arg}");
				}
			}
		}
	}
	public class PluginConfig
	{
		public ConfigEntry<LanguageSetting> LanguageOption { get; private set; }

		public Language Lang { get; private set; }

		public ConfigEntry<int> SalePercent { get; private set; }

		public PluginConfig(ConfigFile config, bool systemIsPortuguese)
		{
			bool saveOnConfigSet = config.SaveOnConfigSet;
			config.SaveOnConfigSet = false;
			try
			{
				BindAll(config, systemIsPortuguese);
				SaveSafely(config);
			}
			finally
			{
				config.SaveOnConfigSet = saveOnConfigSet;
			}
		}

		private void BindAll(ConfigFile config, bool systemIsPortuguese)
		{
			LanguageOption = Add(config, ConfigKeys.Language, LanguageSetting.Auto, "Language of the mod's texts in the game and of these descriptions. Auto follows the computer's language: Portuguese for Portuguese, English for everything else. Takes effect the next time the game starts. / Idioma dos textos do mod no jogo e destas descrições. Auto segue o idioma do computador. Vale ao abrir o jogo de novo.");
			Lang = LanguageChoice.Resolve(LanguageOption.Value, systemIsPortuguese);
			SalePercent = Add(config, ConfigKeys.SalePercent, 20, T("Porcentagem do dinheiro da venda de sucata que vira pontos de cosmético. Vale a de quem hospeda.", "Percentage of the scrap sale money that becomes cosmetic points. The host's value is used."), (AcceptableValueBase?)(object)new AcceptableValueRange<int>(0, 100));
		}

		private string T(string portuguese, string english)
		{
			if (Lang != Language.English)
			{
				return portuguese;
			}
			return english;
		}

		private static ConfigEntry<TValue> Add<TValue>(ConfigFile config, ConfigKey key, TValue defaultValue, string description, AcceptableValueBase? acceptable = null)
		{
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Expected O, but got Unknown
			//IL_0025: Expected O, but got Unknown
			return config.Bind<TValue>(new ConfigDefinition(key.Section, key.Key), defaultValue, new ConfigDescription(description, acceptable, Array.Empty<object>()));
		}

		private static void SaveSafely(ConfigFile config)
		{
			try
			{
				config.Save();
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Falha ao gravar o arquivo de config (as opções valem nesta partida): {arg}");
			}
		}
	}
}
namespace BitencolaCosmetics.Features
{
	public static class CosmeticPoints
	{
		public const string SaveKey = "BitencolaCosmeticPoints";

		private const string ChallengeFile = "LCChallengeFile";

		public static int Value { get; private set; }

		public static event Action<int, int, PointsReason>? Changed;

		public static void SetOnHost(int newValue, int delta, PointsReason reason)
		{
			try
			{
				if (newValue < 0)
				{
					newValue = 0;
				}
				if (!PointsNetwork.TryBroadcast(newValue, delta, reason))
				{
					Apply(newValue, delta, reason);
				}
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Falha ao mudar os pontos de cosmético: {arg}");
			}
		}

		internal static void Apply(int newValue, int delta, PointsReason reason)
		{
			Value = newValue;
			if (IsHost())
			{
				Plugin.Log.LogInfo((object)$"Pontos de cosmético: {newValue} ({reason}, {delta})");
			}
			else
			{
				Plugin.Log.LogDebug((object)$"Pontos de cosmético: {newValue} ({reason}, {delta})");
			}
			Action<int, int, PointsReason> changed = CosmeticPoints.Changed;
			if (changed != null)
			{
				Delegate[] invocationList = changed.GetInvocationList();
				foreach (Delegate obj in invocationList)
				{
					try
					{
						((Action<int, int, PointsReason>)obj)(newValue, delta, reason);
					}
					catch (Exception arg)
					{
						Plugin.Log.LogError((object)$"Erro ao tratar a mudança de pontos: {arg}");
					}
				}
			}
			ShowSaleNotice(newValue, delta, reason);
		}

		internal static void Clear()
		{
			if (Value != 0)
			{
				Apply(0, 0, PointsReason.Sync);
			}
		}

		private static void ShowSaleNotice(int total, int delta, PointsReason reason)
		{
			try
			{
				if (reason == PointsReason.Sale && delta > 0 && !((Object)(object)HUDManager.Instance == (Object)null))
				{
					Language lang = Plugin.Cfg.Lang;
					HUDManager.Instance.DisplayTip(Texts.Format(lang, "sale_title"), Texts.Format(lang, "sale_notice", delta.ToString(), total.ToString()), false, false, "LC_Tip1");
				}
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Falha ao mostrar o aviso da venda: {arg}");
			}
		}

		internal static bool IsHost()
		{
			NetworkManager singleton = NetworkManager.Singleton;
			if ((Object)(object)singleton != (Object)null)
			{
				return singleton.IsServer;
			}
			return false;
		}

		internal static bool SavingAllowed()
		{
			GameNetworkManager instance = GameNetworkManager.Instance;
			if ((Object)(object)instance == (Object)null || !instance.isHostingGame)
			{
				return false;
			}
			if (instance.currentSaveFileName == "LCChallengeFile")
			{
				return false;
			}
			StartOfRound instance2 = StartOfRound.Instance;
			if (!((Object)(object)instance2 == (Object)null))
			{
				return !instance2.isChallengeFile;
			}
			return true;
		}
	}
	[HarmonyPatch(typeof(Terminal), "Start")]
	internal static class PointsLoadPatch
	{
		[HarmonyPostfix]
		private static void Postfix()
		{
			try
			{
				if (!CosmeticPoints.IsHost())
				{
					return;
				}
				int num = 0;
				if (CosmeticPoints.SavingAllowed())
				{
					try
					{
						num = ES3.Load<int>("BitencolaCosmeticPoints", GameNetworkManager.Instance.currentSaveFileName, 0);
					}
					catch (Exception ex)
					{
						Plugin.Log.LogWarning((object)("Não consegui ler os pontos do save (começa com 0): " + ex.Message));
					}
				}
				if (num < 0)
				{
					num = 0;
				}
				CosmeticPoints.SetOnHost(num, 0, PointsReason.Sync);
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Falha ao carregar os pontos de cosmético: {arg}");
			}
		}
	}
	[HarmonyPatch(typeof(GameNetworkManager), "SaveGameValues")]
	internal static class PointsSavePatch
	{
		[HarmonyPostfix]
		private static void Postfix()
		{
			try
			{
				if (!CosmeticPoints.SavingAllowed())
				{
					return;
				}
				StartOfRound instance = StartOfRound.Instance;
				if (!((Object)(object)instance == (Object)null))
				{
					RoundManager instance2 = RoundManager.Instance;
					bool generating = (Object)(object)instance2 != (Object)null && instance2.dungeonIsGenerating;
					if (SaveRules.ShouldSavePoints(hosting: true, challenge: false, instance.inShipPhase, instance.beganLoadingNewLevel, generating))
					{
						ES3.Save<int>("BitencolaCosmeticPoints", CosmeticPoints.Value, GameNetworkManager.Instance.currentSaveFileName);
					}
				}
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Falha ao salvar os pontos de cosmético: {arg}");
			}
		}
	}
	[HarmonyPatch(typeof(GameNetworkManager), "ResetSavedGameValues")]
	internal static class PointsResetSavePatch
	{
		[HarmonyPostfix]
		private static void Postfix()
		{
			try
			{
				if (CosmeticPoints.SavingAllowed())
				{
					ES3.Save<int>("BitencolaCosmeticPoints", 0, GameNetworkManager.Instance.currentSaveFileName);
				}
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Falha ao zerar os pontos no save: {arg}");
			}
		}
	}
	[HarmonyPatch(typeof(StartOfRound), "ResetShip")]
	internal static class PointsResetShipPatch
	{
		[HarmonyPostfix]
		private static void Postfix()
		{
			try
			{
				if (CosmeticPoints.IsHost())
				{
					CosmeticPoints.SetOnHost(0, -CosmeticPoints.Value, PointsReason.Reset);
				}
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Falha ao zerar os pontos na demissão: {arg}");
			}
		}
	}
	[HarmonyPatch(typeof(DepositItemsDesk), "SellItemsOnServer")]
	internal static class PointsSalePatch
	{
		[HarmonyPrefix]
		private static void Prefix(out int __state)
		{
			__state = -1;
			try
			{
				if (CosmeticPoints.IsHost())
				{
					Terminal val = Object.FindObjectOfType<Terminal>();
					if ((Object)(object)val != (Object)null)
					{
						__state = val.groupCredits;
					}
				}
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Falha ao guardar os créditos antes da venda: {arg}");
			}
		}

		[HarmonyPostfix]
		private static void Postfix(int __state)
		{
			try
			{
				if (__state < 0 || !CosmeticPoints.IsHost())
				{
					return;
				}
				Terminal val = Object.FindObjectOfType<Terminal>();
				if ((Object)(object)val == (Object)null)
				{
					return;
				}
				int num = val.groupCredits - __state;
				if (num > 0)
				{
					int num2 = PointsRules.EarnedFromSale(num, Plugin.Cfg.SalePercent.Value);
					if (num2 > 0)
					{
						CosmeticPoints.SetOnHost(CosmeticPoints.Value + num2, num2, PointsReason.Sale);
					}
				}
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Falha ao dar os pontos da venda: {arg}");
			}
		}
	}
	[HarmonyPatch(typeof(StartOfRound), "Awake")]
	public static class JokeSuits
	{
		private sealed class Entry
		{
			public JokeSuit Suit;

			public Texture2D Texture;

			public UnlockableItem Item;
		}

		private static readonly List<Entry> Entries = new List<Entry>();

		private static readonly List<UnlockableItem> ItemList = new List<UnlockableItem>();

		public static IReadOnlyList<UnlockableItem> Items => ItemList;

		public static void Register()
		{
			try
			{
				Assembly assembly = typeof(JokeSuits).Assembly;
				foreach (JokeSuit suit in JokeSuitCatalog.Suits)
				{
					try
					{
						RegisterOne(assembly, suit);
					}
					catch (Exception arg)
					{
						Plugin.Log.LogError((object)$"Falha ao registrar a roupa {suit.Name}: {arg}");
					}
				}
			}
			catch (Exception arg2)
			{
				Plugin.Log.LogError((object)$"Falha ao registrar as roupas: {arg2}");
			}
		}

		private static void RegisterOne(Assembly assembly, JokeSuit suit)
		{
			//IL_0087: Unknown result type (might be due to invalid IL or missing references)
			//IL_008d: Expected O, but got Unknown
			//IL_00cd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00de: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e5: 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_0100: Unknown result type (might be due to invalid IL or missing references)
			//IL_0107: Unknown result type (might be due to invalid IL or missing references)
			//IL_010e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0115: Unknown result type (might be due to invalid IL or missing references)
			//IL_011c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0124: Expected O, but got Unknown
			byte[] array;
			using (Stream stream = assembly.GetManifestResourceStream(suit.ResourceName))
			{
				if (stream == null)
				{
					Plugin.Log.LogError((object)("Recurso " + suit.ResourceName + " não encontrado na DLL; roupa " + suit.Name + " ignorada"));
					return;
				}
				using MemoryStream memoryStream = new MemoryStream();
				stream.CopyTo(memoryStream);
				array = memoryStream.ToArray();
			}
			Texture2D val = new Texture2D(2, 2, (TextureFormat)4, true);
			if (!ImageConversion.LoadImage(val, array, false))
			{
				Plugin.Log.LogError((object)("Não foi possível ler a PNG da roupa " + suit.Name));
				return;
			}
			((Object)val).name = suit.Name;
			((Object)val).hideFlags = (HideFlags)32;
			((Texture)val).anisoLevel = 4;
			UnlockableItem val2 = new UnlockableItem
			{
				unlockableName = suit.Name,
				unlockableType = 0,
				alreadyUnlocked = suit.Free,
				alwaysInStock = !suit.Free,
				IsPlaceable = false,
				spawnPrefab = false,
				canBeStored = false,
				maxNumber = 1,
				suitMaterial = null
			};
			Unlockables.RegisterUnlockable(val2, (StoreType)(!suit.Free), (TerminalNode)null, (TerminalNode)null, (TerminalNode)null, suit.Free ? (-1) : 0);
			Entries.Add(new Entry
			{
				Suit = suit,
				Texture = val,
				Item = val2
			});
			ItemList.Add(val2);
		}

		public static bool TryGetPrice(UnlockableItem item, out int pricePoints)
		{
			pricePoints = 0;
			try
			{
				if (item == null)
				{
					return false;
				}
				foreach (Entry entry in Entries)
				{
					if (entry.Item == item && !entry.Suit.Free)
					{
						pricePoints = entry.Suit.PricePoints;
						return true;
					}
				}
				foreach (Entry entry2 in Entries)
				{
					if (!entry2.Suit.Free && entry2.Suit.Name == item.unlockableName)
					{
						pricePoints = entry2.Suit.PricePoints;
						return true;
					}
				}
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Falha em JokeSuits.TryGetPrice: {arg}");
			}
			return false;
		}

		[HarmonyPostfix]
		private static void AwakePostfix(StartOfRound __instance)
		{
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			//IL_006a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0073: Expected O, but got Unknown
			try
			{
				Material suitMaterial = __instance.unlockablesList.unlockables[0].suitMaterial;
				if ((Object)(object)suitMaterial == (Object)null)
				{
					Plugin.Log.LogError((object)"A roupa laranja não tem material; as roupas do mod ficam sem material");
					return;
				}
				foreach (Entry entry in Entries)
				{
					Material val = new Material(suitMaterial)
					{
						name = "BitencolaSuit_" + entry.Suit.Name,
						hideFlags = (HideFlags)32
					};
					val.SetTexture("_BaseColorMap", (Texture)(object)entry.Texture);
					val.SetTexture("_MainTex", (Texture)(object)entry.Texture);
					entry.Item.suitMaterial = val;
				}
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Falha ao montar os materiais das roupas: {arg}");
			}
		}
	}
	public static class PointsNetwork
	{
		[CompilerGenerated]
		private static class <>O
		{
			public static Action <0>__OnClientStarted;

			public static Action<bool> <1>__OnStopped;

			public static Action<ulong> <2>__OnClientConnected;

			public static HandleNamedMessageDelegate <3>__OnMessage;
		}

		public const string MessageName = "Bitencola.Cosmetics.Points";

		private const NetworkDelivery Delivery = (NetworkDelivery)3;

		private static NetworkManager? _subscribedTo;

		private static bool _registered;

		internal static void Attach(NetworkManager nm)
		{
			if (!((Object)(object)nm == (Object)null) && nm != _subscribedTo)
			{
				_subscribedTo = nm;
				nm.OnClientStarted += OnClientStarted;
				nm.OnServerStopped += OnStopped;
				nm.OnClientStopped += OnStopped;
				nm.OnClientConnectedCallback += OnClientConnected;
				Plugin.Log.LogInfo((object)"Rede dos pontos de cosmético ligada ao NetworkManager");
			}
		}

		private static void OnClientStarted()
		{
			//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)
			//IL_004e: Expected O, but got Unknown
			try
			{
				NetworkManager singleton = NetworkManager.Singleton;
				if ((Object)(object)singleton == (Object)null || singleton.CustomMessagingManager == null)
				{
					Plugin.Log.LogWarning((object)"Cliente começou sem o gerenciador de mensagens");
					return;
				}
				CustomMessagingManager customMessagingManager = singleton.CustomMessagingManager;
				object obj = <>O.<3>__OnMessage;
				if (obj == null)
				{
					HandleNamedMessageDelegate val = OnMessage;
					<>O.<3>__OnMessage = val;
					obj = (object)val;
				}
				customMessagingManager.RegisterNamedMessageHandler("Bitencola.Cosmetics.Points", (HandleNamedMessageDelegate)obj);
				_registered = true;
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Falha ao preparar a rede dos pontos: {arg}");
			}
		}

		private static void OnStopped(bool _)
		{
			try
			{
				_registered = false;
				CosmeticPoints.Clear();
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Falha ao desligar a rede dos pontos: {arg}");
			}
		}

		private static void OnClientConnected(ulong clientId)
		{
			try
			{
				NetworkManager singleton = NetworkManager.Singleton;
				if (!((Object)(object)singleton == (Object)null) && singleton.IsServer && clientId != 0L && singleton.CustomMessagingManager != null)
				{
					Send(singleton, CosmeticPoints.Value, 0, PointsReason.Sync, clientId);
				}
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Falha ao mandar o saldo ao jogador {clientId}: {arg}");
			}
		}

		internal static bool TryBroadcast(int points, int delta, PointsReason reason)
		{
			NetworkManager singleton = NetworkManager.Singleton;
			if ((Object)(object)singleton == (Object)null || !singleton.IsServer || !singleton.IsListening || singleton.CustomMessagingManager == null || !_registered)
			{
				return false;
			}
			Send(singleton, points, delta, reason, null);
			return true;
		}

		private unsafe static void Send(NetworkManager nm, int points, int delta, PointsReason reason, ulong? clientId)
		{
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_0066: 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)
			byte[] array = PointsCodec.Encode(points, delta, reason);
			FastBufferWriter val = default(FastBufferWriter);
			((FastBufferWriter)(ref val))..ctor(array.Length + 8, (Allocator)2, -1);
			try
			{
				int num = array.Length;
				((FastBufferWriter)(ref val)).WriteValueSafe<int>(ref num, default(ForPrimitives));
				((FastBufferWriter)(ref val)).WriteBytesSafe(array, -1, 0);
				if (clientId.HasValue)
				{
					nm.CustomMessagingManager.SendNamedMessage("Bitencola.Cosmetics.Points", clientId.Value, val, (NetworkDelivery)3);
				}
				else
				{
					nm.CustomMessagingManager.SendNamedMessageToAll("Bitencola.Cosmetics.Points", val, (NetworkDelivery)3);
				}
			}
			finally
			{
				((IDisposable)(*(FastBufferWriter*)(&val))/*cast due to .constrained prefix*/).Dispose();
			}
		}

		private static void OnMessage(ulong sender, FastBufferReader reader)
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if (sender == 0L)
				{
					var (newValue, delta, reason) = PointsCodec.Decode(ReadPayload(reader));
					CosmeticPoints.Apply(newValue, delta, reason);
				}
			}
			catch (InvalidDataException ex)
			{
				Plugin.Log.LogWarning((object)("Mensagem de pontos inválida do host (outra versão do mod?): " + ex.Message));
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Erro ao tratar a mensagem de pontos: {arg}");
			}
		}

		private static byte[] ReadPayload(FastBufferReader reader)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				int num = default(int);
				((FastBufferReader)(ref reader)).ReadValueSafe<int>(ref num, default(ForPrimitives));
				if (num < 0 || num > ((FastBufferReader)(ref reader)).Length - ((FastBufferReader)(ref reader)).Position)
				{
					throw new InvalidDataException("Tamanho inválido: " + num);
				}
				byte[] result = new byte[num];
				if (num > 0)
				{
					((FastBufferReader)(ref reader)).ReadBytesSafe(ref result, num, 0);
				}
				return result;
			}
			catch (OverflowException innerException)
			{
				throw new InvalidDataException("Mensagem cortada.", innerException);
			}
		}
	}
	[HarmonyPatch(typeof(GameNetworkManager), "Start")]
	internal static class PointsNetworkStartPatch
	{
		[HarmonyPostfix]
		private static void Postfix(GameNetworkManager __instance)
		{
			try
			{
				NetworkManager component = ((Component)__instance).GetComponent<NetworkManager>();
				if ((Object)(object)component == (Object)null)
				{
					Plugin.Log.LogError((object)"GameNetworkManager sem NetworkManager: os pontos não vão funcionar em rede");
				}
				else
				{
					PointsNetwork.Attach(component);
				}
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Falha ao ligar a rede dos pontos: {arg}");
			}
		}
	}
	internal static class PurchaseGuardState
	{
		private static readonly FieldInfo? ExecStage = AccessTools.Field(typeof(NetworkBehaviour), "__rpc_exec_stage");

		private const int ExecuteStage = 1;

		private const int SendStage = 0;

		private static bool _missingLogged;

		public static bool IsHostExecution(NetworkBehaviour instance)
		{
			if (ExecStage == null)
			{
				if (!_missingLogged)
				{
					_missingLogged = true;
					Plugin.Log.LogError((object)"Campo __rpc_exec_stage não encontrado: a conferência de pontos na compra está desligada.");
				}
				return false;
			}
			NetworkManager singleton = NetworkManager.Singleton;
			if ((Object)(object)singleton == (Object)null || !singleton.IsServer)
			{
				return false;
			}
			return Convert.ToInt32(ExecStage.GetValue(instance)) == 1;
		}

		public static void ResetStage(NetworkBehaviour instance)
		{
			if (!(ExecStage == null))
			{
				ExecStage.SetValue(instance, Enum.ToObject(ExecStage.FieldType, 0));
			}
		}
	}
	[HarmonyPatch(typeof(StartOfRound), "BuyShipUnlockableServerRpc")]
	internal static class PurchaseRefusePatch
	{
		[HarmonyPrefix]
		private static bool Prefix(StartOfRound __instance, int unlockableID)
		{
			try
			{
				if (!PurchaseGuardState.IsHostExecution((NetworkBehaviour)(object)__instance))
				{
					return true;
				}
				int price;
				bool flag = TerminalCosmetics.TryGetPointPrice(unlockableID, out price);
				if (!flag)
				{
					return true;
				}
				List<UnlockableItem> list = __instance.unlockablesList?.unlockables;
				if (list == null || unlockableID < 0 || unlockableID >= list.Count)
				{
					return true;
				}
				UnlockableItem val = list[unlockableID];
				bool alreadyUnlocked = val.hasBeenUnlockedByPlayer || val.alreadyUnlocked;
				if (!PurchaseRules.ShouldRefuse(flag, price, alreadyUnlocked, CosmeticPoints.Value))
				{
					return true;
				}
				try
				{
					PurchaseGuardState.ResetStage((NetworkBehaviour)(object)__instance);
					Terminal val2 = Object.FindObjectOfType<Terminal>();
					if ((Object)(object)val2 != (Object)null)
					{
						__instance.BuyShipUnlockableClientRpc(val2.groupCredits, -1);
					}
					else
					{
						Plugin.Log.LogError((object)"Terminal não encontrado ao recusar a compra; os créditos não foram reenviados.");
					}
					Plugin.Log.LogInfo((object)$"Compra de {val.unlockableName} recusada: faltam pontos ({CosmeticPoints.Value}/{price})");
				}
				catch (Exception arg)
				{
					Plugin.Log.LogError((object)$"Falha ao recusar a compra por falta de pontos (a compra continua recusada): {arg}");
				}
				return false;
			}
			catch (Exception arg2)
			{
				Plugin.Log.LogError((object)$"Falha ao conferir os pontos da compra (a compra segue): {arg2}");
				return true;
			}
		}
	}
	[HarmonyPatch(typeof(StartOfRound), "UnlockShipObject")]
	internal static class PurchaseChargePatch
	{
		[HarmonyPrefix]
		private static void Prefix(StartOfRound __instance, int unlockableID, out bool __state)
		{
			__state = true;
			try
			{
				List<UnlockableItem> list = __instance.unlockablesList?.unlockables;
				if (list != null && unlockableID >= 0 && unlockableID < list.Count)
				{
					UnlockableItem val = list[unlockableID];
					__state = val.hasBeenUnlockedByPlayer || val.alreadyUnlocked;
				}
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Falha ao guardar o estado do cosmético antes de liberar: {arg}");
			}
		}

		[HarmonyPostfix]
		private static void Postfix(StartOfRound __instance, int unlockableID, bool __state)
		{
			try
			{
				if (!CosmeticPoints.IsHost())
				{
					return;
				}
				int price;
				bool flag = TerminalCosmetics.TryGetPointPrice(unlockableID, out price);
				if (!flag)
				{
					return;
				}
				List<UnlockableItem> list = __instance.unlockablesList?.unlockables;
				if (list != null && unlockableID >= 0 && unlockableID < list.Count)
				{
					bool hasBeenUnlockedByPlayer = list[unlockableID].hasBeenUnlockedByPlayer;
					if (PurchaseRules.ShouldCharge(flag, price, __state, hasBeenUnlockedByPlayer))
					{
						CosmeticPoints.SetOnHost(PointsRules.Spend(CosmeticPoints.Value, price), -price, PointsReason.Purchase);
					}
				}
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Falha ao descontar os pontos da compra: {arg}");
			}
		}
	}
	public static class TerminalCosmetics
	{
		private sealed class ByReference<T> : IEqualityComparer<T> where T : class
		{
			public bool Equals(T? x, T? y)
			{
				return x == y;
			}

			public int GetHashCode(T obj)
			{
				return RuntimeHelpers.GetHashCode(obj);
			}
		}

		private static readonly ByReference<TerminalNode> NodeComparer = new ByReference<TerminalNode>();

		private static Dictionary<int, int> _priceById = new Dictionary<int, int>();

		private static Dictionary<string, int> _priceByName = new Dictionary<string, int>(StringComparer.Ordinal);

		private static readonly Dictionary<TerminalNode, int> OriginalCost = new Dictionary<TerminalNode, int>(NodeComparer);

		private static readonly HashSet<TerminalNode> Zeroed = new HashSet<TerminalNode>(NodeComparer);

		private static int _lastCount = -1;

		private const string ObjectPrefix = "BitencolaCosmetics_";

		private static readonly string[] PointsWords = new string[2] { "points", "pontos" };

		private static readonly Dictionary<string, TerminalKeyword> OwnKeywords = new Dictionary<string, TerminalKeyword>(StringComparer.Ordinal);

		private static TerminalNode? _pointsNode;

		private static TerminalNode? _notEnoughNode;

		public static bool TryGetPointPrice(int unlockableId, out int price)
		{
			return _priceById.TryGetValue(unlockableId, out price);
		}

		private static bool IsCosmetic(UnlockableItem item)
		{
			return CosmeticRules.IsCosmetic(item.unlockableType, item.alreadyUnlocked, item.alwaysInStock, (Object)(object)item.shopSelectionNode != (Object)null);
		}

		internal static void Rebuild(Terminal terminal, string when)
		{
			StartOfRound instance = StartOfRound.Instance;
			if ((Object)(object)terminal == (Object)null || (Object)(object)instance == (Object)null || (Object)(object)instance.unlockablesList == (Object)null)
			{
				return;
			}
			List<UnlockableItem> unlockables = instance.unlockablesList.unlockables;
			if (unlockables == null)
			{
				return;
			}
			PruneDestroyed();
			Dictionary<int, List<TerminalNode>> dictionary = new Dictionary<int, List<TerminalNode>>();
			HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
			foreach (TerminalNode item in ReachableNodes(terminal, unlockables))
			{
				int shipUnlockableID = item.shipUnlockableID;
				if (shipUnlockableID < 0 || shipUnlockableID >= unlockables.Count || unlockables[shipUnlockableID] == null)
				{
					continue;
				}
				if (!IsCosmetic(unlockables[shipUnlockableID]))
				{
					AddName(hashSet, item.creatureName);
					continue;
				}
				if (!OriginalCost.ContainsKey(item))
				{
					OriginalCost[item] = item.itemCost;
				}
				if (!dictionary.TryGetValue(shipUnlockableID, out var value))
				{
					value = (dictionary[shipUnlockableID] = new List<TerminalNode>());
				}
				value.Add(item);
			}
			foreach (UnlockableItem item2 in unlockables)
			{
				if (item2 != null && !IsCosmetic(item2))
				{
					AddName(hashSet, item2.unlockableName);
				}
			}
			if (terminal.buyableItemsList != null)
			{
				Item[] buyableItemsList = terminal.buyableItemsList;
				foreach (Item val in buyableItemsList)
				{
					if ((Object)(object)val != (Object)null)
					{
						AddName(hashSet, val.itemName);
					}
				}
			}
			if (terminal.buyableVehicles != null)
			{
				BuyableVehicle[] buyableVehicles = terminal.buyableVehicles;
				foreach (BuyableVehicle val2 in buyableVehicles)
				{
					if (val2 != null)
					{
						AddName(hashSet, val2.vehicleDisplayName);
					}
				}
			}
			Dictionary<int, int> dictionary2 = new Dictionary<int, int>();
			Dictionary<string, int> dictionary3 = new Dictionary<string, int>(StringComparer.Ordinal);
			foreach (KeyValuePair<int, List<TerminalNode>> item3 in dictionary)
			{
				UnlockableItem val3 = unlockables[item3.Key];
				int num = PointPrice(val3, item3.Value);
				dictionary2[item3.Key] = num;
				AddPrice(dictionary3, hashSet, val3.unlockableName, num);
				foreach (TerminalNode item4 in item3.Value)
				{
					AddPrice(dictionary3, hashSet, item4.creatureName, num);
				}
			}
			_priceById = dictionary2;
			_priceByName = dictionary3;
			HashSet<TerminalNode> hashSet2 = new HashSet<TerminalNode>(NodeComparer);
			foreach (List<TerminalNode> value2 in dictionary.Values)
			{
				foreach (TerminalNode item5 in value2)
				{
					item5.itemCost = 0;
					hashSet2.Add(item5);
					Zeroed.Add(item5);
				}
			}
			RestoreDropped(hashSet2);
			if (dictionary2.Count != _lastCount)
			{
				_lastCount = dictionary2.Count;
				Plugin.Log.LogInfo((object)$"Terminal: {dictionary2.Count} cosméticos custam pontos em vez de dinheiro ({when})");
			}
		}

		private static int PointPrice(UnlockableItem item, List<TerminalNode> nodes)
		{
			TerminalNode shopSelectionNode = item.shopSelectionNode;
			int num;
			int pricePoints;
			if ((Object)(object)shopSelectionNode != (Object)null && OriginalCost.TryGetValue(shopSelectionNode, out var value))
			{
				num = value;
			}
			else if (JokeSuits.TryGetPrice(item, out pricePoints))
			{
				num = pricePoints;
			}
			else
			{
				TerminalNode key = nodes[0];
				foreach (TerminalNode node in nodes)
				{
					if (!node.buyUnlockable)
					{
						key = node;
						break;
					}
				}
				num = (OriginalCost.TryGetValue(key, out var value2) ? value2 : 0);
			}
			if (num >= 0)
			{
				return num;
			}
			return 0;
		}

		private static List<TerminalNode> ReachableNodes(Terminal terminal, List<UnlockableItem> unlockables)
		{
			HashSet<TerminalNode> seen = new HashSet<TerminalNode>(NodeComparer);
			Stack<TerminalNode> pending = new Stack<TerminalNode>();
			List<TerminalNode> list = new List<TerminalNode>();
			TerminalKeyword[] array = (((Object)(object)terminal.terminalNodes != (Object)null) ? terminal.terminalNodes.allKeywords : null);
			if (array != null)
			{
				TerminalKeyword[] array2 = array;
				foreach (TerminalKeyword val in array2)
				{
					if ((Object)(object)val == (Object)null || val.compatibleNouns == null)
					{
						continue;
					}
					CompatibleNoun[] compatibleNouns = val.compatibleNouns;
					foreach (CompatibleNoun val2 in compatibleNouns)
					{
						if (val2 != null)
						{
							Visit(val2.result);
						}
					}
				}
			}
			if (terminal.ShipDecorSelection != null)
			{
				foreach (TerminalNode item in terminal.ShipDecorSelection)
				{
					Visit(item);
				}
			}
			foreach (UnlockableItem unlockable in unlockables)
			{
				if (unlockable != null)
				{
					Visit(unlockable.shopSelectionNode);
				}
			}
			while (pending.Count > 0)
			{
				TerminalNode val3 = pending.Pop();
				list.Add(val3);
				if (val3.terminalOptions == null)
				{
					continue;
				}
				CompatibleNoun[] compatibleNouns = val3.terminalOptions;
				foreach (CompatibleNoun val4 in compatibleNouns)
				{
					if (val4 != null)
					{
						Visit(val4.result);
					}
				}
			}
			return list;
			void Visit(TerminalNode? node)
			{
				if ((Object)(object)node != (Object)null && seen.Add(node))
				{
					pending.Push(node);
				}
			}
		}

		private static void AddName(HashSet<string> names, string? name)
		{
			if (!string.IsNullOrWhiteSpace(name))
			{
				names.Add(name.Trim());
			}
		}

		private static void AddPrice(Dictionary<string, int> byName, HashSet<string> moneyNames, string? name, int price)
		{
			if (!string.IsNullOrWhiteSpace(name))
			{
				string text = name.Trim();
				if (moneyNames.Contains(text))
				{
					Plugin.Log.LogWarning((object)("O cosmético \"" + text + "\" tem o nome de algo que custa dinheiro; a linha dele na loja fica sem o preço em pontos"));
				}
				else if (!byName.ContainsKey(text))
				{
					byName[text] = price;
				}
			}
		}

		private static void RestoreDropped(HashSet<TerminalNode> current)
		{
			if (Zeroed.Count == current.Count)
			{
				return;
			}
			List<TerminalNode> list = new List<TerminalNode>();
			foreach (TerminalNode item in Zeroed)
			{
				if (!current.Contains(item))
				{
					list.Add(item);
				}
			}
			foreach (TerminalNode item2 in list)
			{
				Zeroed.Remove(item2);
				if (OriginalCost.TryGetValue(item2, out var value))
				{
					item2.itemCost = value;
				}
			}
		}

		private static void PruneDestroyed()
		{
			List<TerminalNode> list = new List<TerminalNode>();
			foreach (TerminalNode key in OriginalCost.Keys)
			{
				if ((Object)(object)key == (Object)null)
				{
					list.Add(key);
				}
			}
			foreach (TerminalNode item in list)
			{
				OriginalCost.Remove(item);
				Zeroed.Remove(item);
			}
		}

		internal static string ProcessText(string text, TerminalNode? node)
		{
			Language lang = Plugin.Cfg.Lang;
			int value = CosmeticPoints.Value;
			if (text.IndexOf("Price:", StringComparison.Ordinal) >= 0)
			{
				text = StoreText.ShowPointPrices(text, _priceByName, lang);
				if (IsStorePage(node))
				{
					text = StoreText.AddPointsHeader(text, value, lang);
				}
			}
			if ((Object)(object)node != (Object)null && !node.buyUnlockable && _priceById.TryGetValue(node.shipUnlockableID, out var value2))
			{
				text = StoreText.ShowConfirmCost(text, value2, lang);
			}
			return StoreText.FillPointsMarker(text, value);
		}

		private static bool IsStorePage(TerminalNode? node)
		{
			if ((Object)(object)node == (Object)null || string.IsNullOrEmpty(node.displayText))
			{
				return false;
			}
			string displayText = node.displayText;
			if (displayText.IndexOf("[buyableItemsList]", StringComparison.Ordinal) < 0)
			{
				return displayText.IndexOf("[unlockablesSelectionList]", StringComparison.Ordinal) >= 0;
			}
			return true;
		}

		internal static bool LacksPoints(TerminalNode? node, out int price)
		{
			price = 0;
			if ((Object)(object)node == (Object)null)
			{
				return false;
			}
			int shipUnlockableID = node.shipUnlockableID;
			if (!_priceById.TryGetValue(shipUnlockableID, out price))
			{
				return false;
			}
			StartOfRound instance = StartOfRound.Instance;
			if ((Object)(object)instance == (Object)null || (Object)(object)instance.unlockablesList == (Object)null || instance.unlockablesList.unlockables == null)
			{
				return false;
			}
			List<UnlockableItem> unlockables = instance.unlockablesList.unlockables;
			if (shipUnlockableID < 0 || shipUnlockableID >= unlockables.Count)
			{
				return false;
			}
			UnlockableItem val = unlockables[shipUnlockableID];
			if (val == null || val.hasBeenUnlockedByPlayer || val.alreadyUnlocked || val.inStorage)
			{
				return false;
			}
			return !PointsRules.CanAfford(CosmeticPoints.Value, price);
		}

		internal static TerminalNode NotEnoughNode(int price)
		{
			TerminalNode val = _notEnoughNode;
			if ((Object)(object)val == (Object)null)
			{
				val = ScriptableObject.CreateInstance<TerminalNode>();
				((Object)val).name = "BitencolaCosmetics_NotEnoughPoints";
				((Object)val).hideFlags = (HideFlags)32;
				val.clearPreviousText = true;
				val.maxCharactersToType = 25;
				_notEnoughNode = val;
			}
			val.displayText = Texts.Format(Plugin.Cfg.Lang, "not_enough", price.ToString(CultureInfo.InvariantCulture), CosmeticPoints.Value.ToString(CultureInfo.InvariantCulture)) + "\n\n";
			return val;
		}

		internal static void AddPointsCommands(Terminal terminal)
		{
			if ((Object)(object)terminal == (Object)null || (Object)(object)terminal.terminalNodes == (Object)null)
			{
				return;
			}
			TerminalNode val = _pointsNode;
			if ((Object)(object)val == (Object)null)
			{
				val = ScriptableObject.CreateInstance<TerminalNode>();
				((Object)val).name = "BitencolaCosmetics_PointsCommand";
				((Object)val).hideFlags = (HideFlags)32;
				val.clearPreviousText = true;
				val.maxCharactersToType = 25;
				_pointsNode = val;
			}
			val.displayText = Texts.Format(Plugin.Cfg.Lang, "points_command", "[bitencolaPoints]", Plugin.Cfg.SalePercent.Value.ToString(CultureInfo.InvariantCulture)) + "\n\n";
			List<TerminalKeyword> list = new List<TerminalKeyword>((IEnumerable<TerminalKeyword>)(((object)terminal.terminalNodes.allKeywords) ?? ((object)new TerminalKeyword[0])));
			bool flag = false;
			string[] pointsWords = PointsWords;
			foreach (string word in pointsWords)
			{
				TerminalKeyword val2 = list.Find((TerminalKeyword k) => (Object)(object)k != (Object)null && k.word == word);
				OwnKeywords.TryGetValue(word, out TerminalKeyword value);
				if ((Object)(object)val2 != (Object)null)
				{
					if ((Object)(object)value != (Object)null && val2 == value)
					{
						val2.specialKeywordResult = val;
					}
					else
					{
						Plugin.Log.LogWarning((object)("O terminal já tem a palavra \"" + word + "\"; o comando do saldo não vai usá-la"));
					}
					continue;
				}
				if ((Object)(object)value == (Object)null)
				{
					value = ScriptableObject.CreateInstance<TerminalKeyword>();
					((Object)value).name = "BitencolaCosmetics_Keyword_" + word;
					((Object)value).hideFlags = (HideFlags)32;
					value.word = word;
					value.isVerb = false;
					value.compatibleNouns = (CompatibleNoun[])(object)new CompatibleNoun[0];
					OwnKeywords[word] = value;
				}
				value.specialKeywordResult = val;
				list.Add(value);
				flag = true;
			}
			if (flag)
			{
				terminal.terminalNodes.allKeywords = list.ToArray();
			}
		}
	}
	[HarmonyPatch(typeof(Terminal), "Awake")]
	internal static class PointsCommandPatch
	{
		[HarmonyPostfix]
		private static void Postfix(Terminal __instance)
		{
			try
			{
				TerminalCosmetics.AddPointsCommands(__instance);
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Falha ao criar o comando do saldo no terminal: {arg}");
			}
		}
	}
	[HarmonyPatch(typeof(Terminal), "Start")]
	internal static class PointPricesStartPatch
	{
		[HarmonyPostfix]
		private static void Postfix(Terminal __instance)
		{
			try
			{
				TerminalCosmetics.Rebuild(__instance, "início");
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Falha ao montar os preços em pontos: {arg}");
			}
		}
	}
	[HarmonyPatch(typeof(Terminal), "RotateShipDecorSelection")]
	internal static class PointPricesRotatePatch
	{
		[HarmonyPostfix]
		private static void Postfix(Terminal __instance)
		{
			try
			{
				TerminalCosmetics.Rebuild(__instance, "rodízio");
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Falha ao conferir os preços em pontos no rodízio: {arg}");
			}
		}
	}
	[HarmonyPatch(typeof(Terminal), "TextPostProcess")]
	internal static class PointPricesTextPatch
	{
		[HarmonyPostfix]
		private static void Postfix(TerminalNode node, ref string __result)
		{
			try
			{
				if (!string.IsNullOrEmpty(__result))
				{
					__result = TerminalCosmetics.ProcessText(__result, node);
				}
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Falha ao mostrar os preços em pontos no terminal: {arg}");
			}
		}
	}
	[HarmonyPatch(typeof(Terminal), "LoadNewNodeIfAffordable")]
	internal static class PointPricesAffordPatch
	{
		[HarmonyPrefix]
		private static bool Prefix(Terminal __instance, TerminalNode node)
		{
			int price;
			try
			{
				if (!TerminalCosmetics.LacksPoints(node, out price))
				{
					return true;
				}
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Falha ao conferir os pontos da compra: {arg}");
				return true;
			}
			try
			{
				Plugin.Log.LogInfo((object)$"Terminal: faltam pontos para o desbloqueável {node.shipUnlockableID} (custa {price}, tem {CosmeticPoints.Value})");
				__instance.LoadNewNode(TerminalCosmetics.NotEnoughNode(price));
			}
			catch (Exception arg2)
			{
				Plugin.Log.LogError((object)$"Falha ao mostrar o aviso de pontos insuficientes: {arg2}");
			}
			return false;
		}
	}
}
namespace BitencolaCosmetics.Core
{
	public sealed class ConfigKey : IEquatable<ConfigKey>
	{
		public string Section { get; }

		public string Key { get; }

		public ConfigKey(string section, string key)
		{
			Section = section;
			Key = key;
		}

		public bool Equals(ConfigKey? other)
		{
			if (other != null && string.Equals(Section, other.Section, StringComparison.Ordinal))
			{
				return string.Equals(Key, other.Key, StringComparison.Ordinal);
			}
			return false;
		}

		public override bool Equals(object? obj)
		{
			return Equals(obj as ConfigKey);
		}

		public override int GetHashCode()
		{
			return StringComparer.Ordinal.GetHashCode(Section) * 31 + StringComparer.Ordinal.GetHashCode(Key);
		}

		public override string ToString()
		{
			return "[" + Section + "] " + Key;
		}
	}
	public static class ConfigKeys
	{
		public const string General = "0. General";

		public const string Points = "1. Points";

		public static readonly ConfigKey Language = new ConfigKey("0. General", "Language");

		public static readonly ConfigKey SalePercent = new ConfigKey("1. Points", "Points from sales (%)");

		public static IReadOnlyList<string> Sections { get; } = new string[2] { "0. General", "1. Points" };

		public static IReadOnlyList<ConfigKey> All { get; } = new ConfigKey[2] { Language, SalePercent };
	}
	public static class CosmeticRules
	{
		public static bool IsCosmetic(int unlockableType, bool alreadyUnlocked, bool alwaysInStock, bool hasShopNode)
		{
			switch (unlockableType)
			{
			case 0:
				return !alreadyUnlocked;
			case 1:
				if (!alreadyUnlocked && hasShopNode)
				{
					return !alwaysInStock;
				}
				return false;
			default:
				return false;
			}
		}
	}
	public sealed class JokeSuit
	{
		public string Name { get; }

		public string ResourceName { get; }

		public int PricePoints { get; }

		public bool Free { get; }

		public JokeSuit(string name, int pricePoints, bool free)
		{
			Name = name;
			ResourceName = "BitencolaCosmetics.Suits." + name + ".png";
			PricePoints = pricePoints;
			Free = free;
		}
	}
	public static class JokeSuitCatalog
	{
		public const int DefaultPricePoints = 100;

		private static readonly JokeSuit[] Items = new JokeSuit[1]
		{
			new JokeSuit("Tele Pizza", 0, free: true)
		};

		public static IReadOnlyList<JokeSuit> Suits { get; } = Array.AsReadOnly(Items);
	}
	public enum Language
	{
		Portuguese,
		English
	}
	public enum LanguageSetting
	{
		Auto,
		English,
		Portugues
	}
	public static class LanguageChoice
	{
		public static Language Resolve(LanguageSetting setting, bool systemIsPortuguese)
		{
			switch (setting)
			{
			case LanguageSetting.English:
				return Language.English;
			case LanguageSetting.Portugues:
				return Language.Portuguese;
			default:
				if (!systemIsPortuguese)
				{
					return Language.English;
				}
				return Language.Portuguese;
			}
		}
	}
	public enum PointsReason : byte
	{
		Sync,
		Sale,
		Purchase,
		Reset
	}
	public static class PointsCodec
	{
		private const byte Version = 1;

		private const int Size = 10;

		public static byte[] Encode(int points, int delta, PointsReason reason)
		{
			byte[] obj = new byte[10] { 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
			Write(obj, 1, points);
			Write(obj, 5, delta);
			obj[9] = (byte)reason;
			return obj;
		}

		public static (int points, int delta, PointsReason reason) Decode(byte[] data)
		{
			if (data == null || data.Length != 10)
			{
				throw new InvalidDataException("Tamanho inválido.");
			}
			if (data[0] != 1)
			{
				throw new InvalidDataException("Versão desconhecida.");
			}
			int num = Read(data, 1);
			int item = Read(data, 5);
			byte b = data[9];
			if (num < 0)
			{
				throw new InvalidDataException("Pontos negativos.");
			}
			if (b > 3)
			{
				throw new InvalidDataException("Motivo desconhecido.");
			}
			return (points: num, delta: item, reason: (PointsReason)b);
		}

		private static void Write(byte[] data, int offset, int value)
		{
			data[offset] = (byte)value;
			data[offset + 1] = (byte)(value >> 8);
			data[offset + 2] = (byte)(value >> 16);
			data[offset + 3] = (byte)(value >> 24);
		}

		private static int Read(byte[] data, int offset)
		{
			return data[offset] | (data[offset + 1] << 8) | (data[offset + 2] << 16) | (data[offset + 3] << 24);
		}
	}
	public static class PointsRules
	{
		public static int EarnedFromSale(int creditsGained, int percent)
		{
			if (creditsGained <= 0)
			{
				return 0;
			}
			if (percent <= 0)
			{
				return 0;
			}
			if (percent > 100)
			{
				percent = 100;
			}
			return (int)((long)creditsGained * (long)percent / 100);
		}

		public static bool CanAfford(int points, int price)
		{
			return points >= price;
		}

		public static int Spend(int points, int price)
		{
			if (price < 0)
			{
				price = 0;
			}
			long num = (long)points - (long)price;
			if (num >= 0)
			{
				return (int)num;
			}
			return 0;
		}
	}
	public static class PurchaseRules
	{
		public static bool ShouldRefuse(bool isCosmetic, int price, bool alreadyUnlocked, int points)
		{
			if (!isCosmetic || price <= 0 || alreadyUnlocked)
			{
				return false;
			}
			return !PointsRules.CanAfford(points, price);
		}

		public static bool ShouldCharge(bool isCosmetic, int price, bool wasUnlocked, bool isUnlockedNow)
		{
			return isCosmetic && price > 0 && !wasUnlocked && isUnlockedNow;
		}
	}
	public static class SaveRules
	{
		public static bool ShouldSavePoints(bool hosting, bool challenge, bool inShipPhase, bool loadingLevel, bool generating)
		{
			if (!hosting || challenge)
			{
				return false;
			}
			if (inShipPhase && !loadingLevel)
			{
				return !generating;
			}
			return false;
		}
	}
	public static class StoreText
	{
		public const string PointsMarker = "[bitencolaPoints]";

		private const string PriceLabel = "Price:";

		private static readonly Regex DollarPrice = new Regex("^(\\s*)\\$\\d+", RegexOptions.CultureInvariant);

		private static readonly Regex ConfirmCost = new Regex("(Total cost of items?:\\s*)\\$0\\b", RegexOptions.CultureInvariant);

		public static string ShowPointPrices(string text, IReadOnlyDictionary<string, int> pointPricesByName, Language lang)
		{
			if (string.IsNullOrEmpty(text))
			{
				return text ?? "";
			}
			if (pointPricesByName == null || pointPricesByName.Count == 0)
			{
				return text;
			}
			if (text.IndexOf("Price:", StringComparison.Ordinal) < 0)
			{
				return text;
			}
			string[] array = text.Split('\n');
			bool flag = false;
			for (int i = 0; i < array.Length; i++)
			{
				string text2 = array[i];
				int num = text2.IndexOf("Price:", StringComparison.Ordinal);
				if (num < 0)
				{
					continue;
				}
				string text3 = NameBefore(text2, num);
				if (text3.Length != 0 && pointPricesByName.TryGetValue(text3, out var value))
				{
					int num2 = num + "Price:".Length;
					Match match = DollarPrice.Match(text2, num2, text2.Length - num2);
					if (match.Success)
					{
						array[i] = text2.Substring(0, num2) + match.Groups[1].Value + PriceText(value, lang) + text2.Substring(num2 + match.Length);
						flag = true;
					}
				}
			}
			if (!flag)
			{
				return text;
			}
			return string.Join("\n", array);
		}

		public static string AddPointsHeader(string text, int points, Language lang)
		{
			if (string.IsNullOrEmpty(text))
			{
				return text ?? "";
			}
			string[] array = text.Split('\n');
			int num = Array.FindIndex(array, (string l) => l.IndexOf("Price:", StringComparison.Ordinal) >= 0);
			if (num < 0)
			{
				return text;
			}
			string text2 = Texts.Format(lang, "points_header", points.ToString(CultureInfo.InvariantCulture));
			string prefix = Texts.Format(lang, "points_header", "");
			int num2 = Array.FindIndex(array, (string l) => l.StartsWith(prefix, StringComparison.Ordinal));
			if (num2 >= 0)
			{
				array[num2] = text2;
				return string.Join("\n", array);
			}
			List<string> list = new List<string>(array);
			list.Insert(num, text2);
			return string.Join("\n", list);
		}

		public static string ShowConfirmCost(string text, int pricePoints, Language lang)
		{
			if (string.IsNullOrEmpty(text))
			{
				return text ?? "";
			}
			string price = PriceText(pricePoints, lang);
			return ConfirmCost.Replace(text, (Match m) => m.Groups[1].Value + price);
		}

		public static string FillPointsMarker(string text, int points)
		{
			if (string.IsNullOrEmpty(text))
			{
				return text ?? "";
			}
			if (text.IndexOf("[bitencolaPoints]", StringComparison.Ordinal) < 0)
			{
				return text;
			}
			return text.Replace("[bitencolaPoints]", points.ToString(CultureInfo.InvariantCulture));
		}

		private static string PriceText(int points, Language lang)
		{
			return Texts.Format(lang, "points_price", points.ToString(CultureInfo.InvariantCulture));
		}

		private static string NameBefore(string line, int label)
		{
			string text = line.Substring(0, label).Trim();
			if (text.EndsWith("//", StringComparison.Ordinal))
			{
				text = text.Substring(0, text.Length - 2).TrimEnd();
			}
			if (text.StartsWith("* ", StringComparison.Ordinal))
			{
				text = text.Substring(2).TrimStart();
			}
			return text;
		}
	}
	public static class Texts
	{
		private static readonly Dictionary<string, (string pt, string en)> Table = new Dictionary<string, (string, string)>
		{
			["points_header"] = ("Pontos de cosmético: {0}", "Cosmetic points: {0}"),
			["points_price"] = ("{0} pts", "{0} pts"),
			["points_command"] = ("Você tem {0} pontos de cosmético.\nVenda sucata na Companhia para ganhar mais ({1}% do valor).", "You have {0} cosmetic points.\nSell scrap at the Company to earn more ({1}% of the value)."),
			["not_enough"] = ("Pontos de cosmético insuficientes.\nCusta {0} pts e a tripulação tem {1}.", "Not enough cosmetic points.\nIt costs {0} pts and the crew has {1}."),
			["sale_notice"] = ("+{0} pontos de cosmético (total {1})", "+{0} cosmetic points (total {1})"),
			["sale_title"] = ("Pontos de cosmético", "Cosmetic points")
		};

		public static IReadOnlyCollection<string> Keys => Table.Keys;

		public static string Format(Language lang, string key, params string[] args)
		{
			if (key == null || !Table.TryGetValue(key, out (string, string) value))
			{
				return key ?? "";
			}
			string text;
			if (lang == Language.Portuguese)
			{
				(text, _) = value;
			}
			else
			{
				text = value.Item2;
			}
			string text2 = text;
			if (args == null)
			{
				args = new string[0];
			}
			StringBuilder stringBuilder = new StringBuilder(text2.Length + 16);
			int num = 0;
			while (num < text2.Length)
			{
				char c = text2[num];
				if (c == '{')
				{
					int i = num + 1;
					int num2 = 0;
					for (; i < text2.Length && text2[i] >= '0' && text2[i] <= '9'; i++)
					{
						if (num2 >= 10000)
						{
							break;
						}
						num2 = num2 * 10 + (text2[i] - 48);
					}
					if (i > num + 1 && i < text2.Length && text2[i] == '}')
					{
						if (num2 < args.Length)
						{
							stringBuilder.Append(args[num2] ?? "");
						}
						else
						{
							stringBuilder.Append(text2, num, i - num + 1);
						}
						num = i + 1;
						continue;
					}
				}
				stringBuilder.Append(c);
				num++;
			}
			return stringBuilder.ToString();
		}
	}
}
namespace System.Runtime.CompilerServices
{
	[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
	internal sealed class IgnoresAccessChecksToAttribute : Attribute
	{
		public IgnoresAccessChecksToAttribute(string assemblyName)
		{
		}
	}
}