Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of Bitencola Cosmetics v1.0.0
BitencolaCosmetics.dll
Decompiled 11 hours agousing 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) { } } }