using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Text; using System.Text.RegularExpressions; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using ExitGames.Client.Photon; using HarmonyLib; using Photon.Pun; using Photon.Realtime; using TMPro; using UnityEngine; using UnityEngine.Events; using UnityEngine.InputSystem; using UnityEngine.InputSystem.Controls; using UnityEngine.SceneManagement; using UnityEngine.UI; using Zorro.Core; using Zorro.Core.Serizalization; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: AssemblyTitle("Craft PEAK")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("Craft PEAK")] [assembly: AssemblyCopyright("Copyright © 2026")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("1380deb8-d6dd-4eef-8491-f716b480ac63")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyVersion("1.0.0.0")] namespace CraftPeak; [BepInPlugin("com.sappheiros.crafting.campfire", "Craft PEAK Campfire Materials", "1.1.5")] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] public sealed class CampfireGate : BaseUnityPlugin, IOnEventCallback { private sealed class IngredientLocation { public Player Player; public Character Character; public ItemSlot Slot; public bool IsBackpackInternal; public byte ExternalSlotId; public int BackpackSlotIndex; public ushort ItemId; public int AvailableCount; } private sealed class IngredientPlan { public readonly List FireWood = new List(); public readonly List Stone = new List(); public readonly List Torch = new List(); } private struct ConsumedSelectedSlot { public int ActorNumber; public int SlotId; } public const string PluginGuid = "com.sappheiros.crafting.campfire"; public const string PluginName = "Craft PEAK Campfire Materials"; public const string PluginVersion = "1.1.5"; public const ushort FireWoodItemId = 28; public const ushort StoneItemId = 72; public const ushort TorchItemId = 109; private const int DefaultRequiredFireWoodCount = 1; private const int DefaultRequiredStoneCount = 1; private const int DefaultRequiredTorchCount = 1; private const bool DefaultRequireEveryoneInRange = true; private const float DefaultEveryoneInRangeDistance = 15f; private const float DefaultMaximumRequesterDistance = 4f; private const int MaximumConfigMaterialCount = 20; private const float MinimumConfigDistance = 1f; private const float MaximumConfigDistance = 50f; private static readonly MethodInfo OriginalLightRpcMethod = AccessTools.Method(typeof(Campfire), "Light_Rpc", new Type[2] { typeof(bool), typeof(float) }, (Type[])null); private static ConfigEntry requiredFireWoodCountConfig; private static ConfigEntry requiredStoneCountConfig; private static ConfigEntry requiredTorchCountConfig; private static ConfigEntry requireEveryoneInRangeConfig; private static ConfigEntry everyoneInRangeDistanceConfig; private static ConfigEntry maximumRequesterDistanceConfig; private const byte IgniteRequestEventCode = 170; private const byte IgniteResultEventCode = 171; private const byte ConsumedSelectedSlotEventCode = 172; private readonly HashSet committedCampfireViewIds = new HashSet(); public static int RequiredFireWoodCount { get; private set; } = 1; public static int RequiredStoneCount { get; private set; } = 1; public static int RequiredTorchCount { get; private set; } = 1; public static bool RequireEveryoneInRange { get; private set; } = true; public static float EveryoneInRangeDistance { get; private set; } = 15f; public static float MaximumRequesterDistance { get; private set; } = 4f; internal static CampfireGate Instance { get; private set; } internal static ManualLogSource ModLogger { get; private set; } internal static bool Enabled { get; private set; } private void Awake() { Instance = this; ModLogger = ((BaseUnityPlugin)this).Logger; BindCampfireConfig(); Enabled = true; SceneManager.sceneLoaded += HandleSceneLoaded; LogCurrentCampfireConditions("Loaded"); ((BaseUnityPlugin)this).Logger.LogInfo((object)("Campfire Photon event codes=" + (byte)170 + "-" + (byte)172 + " (Photon-safe range)")); } private void BindCampfireConfig() { //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Expected O, but got Unknown //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Expected O, but got Unknown //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Expected O, but got Unknown //IL_00e5: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Expected O, but got Unknown //IL_0122: Unknown result type (might be due to invalid IL or missing references) //IL_012c: Expected O, but got Unknown requiredFireWoodCountConfig = ((BaseUnityPlugin)this).Config.Bind("01. 캠프파이어 재료 조건", "나뭇가지 요구 수량", 1, new ConfigDescription("모닥불 하나를 점화할 때 소비할 나뭇가지 수량입니다. 0으로 설정하면 나뭇가지를 요구하거나 소비하지 않습니다.", (AcceptableValueBase)(object)new AcceptableValueRange(0, 20), Array.Empty())); requiredStoneCountConfig = ((BaseUnityPlugin)this).Config.Bind("01. 캠프파이어 재료 조건", "돌 요구 수량", 1, new ConfigDescription("모닥불 하나를 점화할 때 소비할 돌 수량입니다. 0으로 설정하면 돌을 요구하거나 소비하지 않습니다.", (AcceptableValueBase)(object)new AcceptableValueRange(0, 20), Array.Empty())); requiredTorchCountConfig = ((BaseUnityPlugin)this).Config.Bind("01. 캠프파이어 재료 조건", "횃불 요구 수량", 1, new ConfigDescription("모닥불 하나를 점화할 때 소비할 횃불 수량입니다. 0으로 설정하면 횃불을 요구하거나 소비하지 않습니다.", (AcceptableValueBase)(object)new AcceptableValueRange(0, 20), Array.Empty())); requireEveryoneInRangeConfig = ((BaseUnityPlugin)this).Config.Bind("02. 캠프파이어 집결 조건", "모든 생존 플레이어 집결 필요", true, "활성화하면 기존 PEAK처럼 모든 생존 플레이어가 모닥불 근처에 모여야 점화할 수 있습니다."); everyoneInRangeDistanceConfig = ((BaseUnityPlugin)this).Config.Bind("02. 캠프파이어 집결 조건", "집결 판정 거리", 15f, new ConfigDescription("모든 생존 플레이어 집결 조건에 사용하는 모닥불 중심 거리입니다.", (AcceptableValueBase)(object)new AcceptableValueRange(1f, 50f), Array.Empty())); maximumRequesterDistanceConfig = ((BaseUnityPlugin)this).Config.Bind("02. 캠프파이어 집결 조건", "점화 요청 허용 거리", 4f, new ConfigDescription("상호작용한 플레이어가 호스트 검증 시 모닥불에서 떨어질 수 있는 최대 거리입니다.", (AcceptableValueBase)(object)new AcceptableValueRange(1f, 15f), Array.Empty())); requiredFireWoodCountConfig.SettingChanged += HandleCampfireConfigChanged; requiredStoneCountConfig.SettingChanged += HandleCampfireConfigChanged; requiredTorchCountConfig.SettingChanged += HandleCampfireConfigChanged; requireEveryoneInRangeConfig.SettingChanged += HandleCampfireConfigChanged; everyoneInRangeDistanceConfig.SettingChanged += HandleCampfireConfigChanged; maximumRequesterDistanceConfig.SettingChanged += HandleCampfireConfigChanged; ApplyCampfireConfigValues(); } private static void HandleCampfireConfigChanged(object sender, EventArgs eventArgs) { ApplyCampfireConfigValues(); LogCurrentCampfireConditions("Config changed"); } private static void ApplyCampfireConfigValues() { RequiredFireWoodCount = ((requiredFireWoodCountConfig == null) ? 1 : Mathf.Clamp(requiredFireWoodCountConfig.Value, 0, 20)); RequiredStoneCount = ((requiredStoneCountConfig == null) ? 1 : Mathf.Clamp(requiredStoneCountConfig.Value, 0, 20)); RequiredTorchCount = ((requiredTorchCountConfig == null) ? 1 : Mathf.Clamp(requiredTorchCountConfig.Value, 0, 20)); RequireEveryoneInRange = requireEveryoneInRangeConfig == null || requireEveryoneInRangeConfig.Value; EveryoneInRangeDistance = ((everyoneInRangeDistanceConfig != null) ? Mathf.Clamp(everyoneInRangeDistanceConfig.Value, 1f, 50f) : 15f); MaximumRequesterDistance = ((maximumRequesterDistanceConfig != null) ? Mathf.Clamp(maximumRequesterDistanceConfig.Value, 1f, 15f) : 4f); } private static void LogCurrentCampfireConditions(string reason) { if (ModLogger != null) { ModLogger.LogInfo((object)("Craft PEAK Campfire Materials 1.1.5 conditions applied. Reason=" + reason + " | FireWood(" + (ushort)28 + ") x" + RequiredFireWoodCount + " | Stone(" + (ushort)72 + ") x" + RequiredStoneCount + " | Torch(" + (ushort)109 + ") x" + RequiredTorchCount + " | RequireEveryone=" + RequireEveryoneInRange + " | EveryoneRange=" + EveryoneInRangeDistance + " | RequesterRange=" + MaximumRequesterDistance + ". Host settings are authoritative.")); } } private void OnEnable() { PhotonNetwork.AddCallbackTarget((object)this); } private void OnDisable() { PhotonNetwork.RemoveCallbackTarget((object)this); } private void OnDestroy() { Enabled = false; SceneManager.sceneLoaded -= HandleSceneLoaded; UnbindCampfireConfigEvents(); committedCampfireViewIds.Clear(); if ((Object)(object)Instance == (Object)(object)this) { Instance = null; } ModLogger = null; } private static void UnbindCampfireConfigEvents() { if (requiredFireWoodCountConfig != null) { requiredFireWoodCountConfig.SettingChanged -= HandleCampfireConfigChanged; } if (requiredStoneCountConfig != null) { requiredStoneCountConfig.SettingChanged -= HandleCampfireConfigChanged; } if (requiredTorchCountConfig != null) { requiredTorchCountConfig.SettingChanged -= HandleCampfireConfigChanged; } if (requireEveryoneInRangeConfig != null) { requireEveryoneInRangeConfig.SettingChanged -= HandleCampfireConfigChanged; } if (everyoneInRangeDistanceConfig != null) { everyoneInRangeDistanceConfig.SettingChanged -= HandleCampfireConfigChanged; } if (maximumRequesterDistanceConfig != null) { maximumRequesterDistanceConfig.SettingChanged -= HandleCampfireConfigChanged; } requiredFireWoodCountConfig = null; requiredStoneCountConfig = null; requiredTorchCountConfig = null; requireEveryoneInRangeConfig = null; everyoneInRangeDistanceConfig = null; maximumRequesterDistanceConfig = null; } private void HandleSceneLoaded(Scene scene, LoadSceneMode loadSceneMode) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) committedCampfireViewIds.Clear(); if (IsExcludedScene(scene)) { ((BaseUnityPlugin)this).Logger.LogInfo((object)("Campfire material gate disabled in scene: " + ((Scene)(ref scene)).name)); } else { ((BaseUnityPlugin)this).Logger.LogInfo((object)("Campfire material gate enabled in scene: " + ((Scene)(ref scene)).name)); } } internal static bool IsGameplayActive() { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) if (!Enabled) { return false; } Scene activeScene = SceneManager.GetActiveScene(); if (IsExcludedScene(activeScene)) { return false; } return (Object)(object)Object.FindAnyObjectByType() != (Object)null; } internal static bool IsManagedCampfire(Campfire campfire) { if (!IsGameplayActive() || (Object)(object)campfire == (Object)null) { return false; } try { return (Object)(object)MapHandler.CurrentCampfire == (Object)(object)campfire; } catch (Exception) { return false; } } private static bool IsExcludedScene(Scene scene) { if (!((Scene)(ref scene)).IsValid() || !((Scene)(ref scene)).isLoaded) { return true; } return string.Equals(((Scene)(ref scene)).name, "Airport", StringComparison.OrdinalIgnoreCase) || string.Equals(((Scene)(ref scene)).name, "Title", StringComparison.OrdinalIgnoreCase) || string.Equals(((Scene)(ref scene)).name, "Pretitle", StringComparison.OrdinalIgnoreCase); } internal void RequestIgnition(Campfire campfire, Character interactor) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Invalid comparison between Unknown and I4 //IL_0198: Unknown result type (might be due to invalid IL or missing references) //IL_019d: Unknown result type (might be due to invalid IL or missing references) //IL_019f: Unknown result type (might be due to invalid IL or missing references) //IL_01a5: Expected O, but got Unknown //IL_01ac: Unknown result type (might be due to invalid IL or missing references) if (!IsManagedCampfire(campfire) || campfire.Lit || (int)campfire.state > 0) { return; } if (!PhotonNetwork.InRoom) { NotifyLocalPlayer("모닥불 점화 실패: 네트워크 방에 연결되어 있지 않습니다."); return; } if ((Object)(object)interactor == (Object)null || (Object)(object)((MonoBehaviourPun)interactor).photonView == (Object)null || !interactor.IsLocal) { NotifyLocalPlayer("모닥불 점화 실패: 상호작용 플레이어를 확인할 수 없습니다."); return; } PhotonView component = ((Component)campfire).GetComponent(); if ((Object)(object)component == (Object)null || component.ViewID <= 0) { NotifyLocalPlayer("모닥불 점화 실패: 모닥불 네트워크 정보를 찾지 못했습니다."); return; } int num = ((((MonoBehaviourPun)interactor).photonView.Owner != null) ? ((MonoBehaviourPun)interactor).photonView.Owner.ActorNumber : (-1)); if (num <= 0) { NotifyLocalPlayer("모닥불 점화 실패: 플레이어 네트워크 번호를 찾지 못했습니다."); return; } object[] array = new object[1] { component.ViewID }; ((BaseUnityPlugin)this).Logger.LogInfo((object)("[CampfireIgnitionDiag] Request prepared. Actor=" + num + " | CampfireViewID=" + component.ViewID + " | IsMaster=" + PhotonNetwork.IsMasterClient + " | EventCode=" + (byte)170)); if (PhotonNetwork.IsMasterClient) { ProcessIgniteRequestOnHost(num, array); return; } RaiseEventOptions val = new RaiseEventOptions { Receivers = (ReceiverGroup)2 }; if (!PhotonNetwork.RaiseEvent((byte)170, (object)array, val, SendOptions.SendReliable)) { NotifyLocalPlayer("모닥불 점화 요청 전송에 실패했습니다."); } else { ((BaseUnityPlugin)this).Logger.LogInfo((object)("Campfire ignite request sent to host. Actor=" + num + " | CampfireViewID=" + component.ViewID)); } } public void OnEvent(EventData photonEvent) { if (photonEvent == null) { return; } if (photonEvent.Code == 170) { if (PhotonNetwork.IsMasterClient) { ProcessIgniteRequestOnHost(photonEvent.Sender, photonEvent.CustomData as object[]); } } else if (photonEvent.Code == 171) { HandleIgniteResult(photonEvent.CustomData as object[]); } else if (photonEvent.Code == 172) { HandleConsumedSelectedSlots(photonEvent.CustomData as object[]); } } private void ProcessIgniteRequestOnHost(int requesterActorNumber, object[] requestData) { //IL_0156: Unknown result type (might be due to invalid IL or missing references) //IL_015c: Invalid comparison between Unknown and I4 //IL_0241: Unknown result type (might be due to invalid IL or missing references) //IL_0248: Unknown result type (might be due to invalid IL or missing references) if (!PhotonNetwork.IsMasterClient) { return; } ((BaseUnityPlugin)this).Logger.LogInfo((object)("[CampfireIgnitionDiag] Host received ignition request. Actor=" + requesterActorNumber + " | PayloadLength=" + ((requestData != null) ? requestData.Length : 0))); if (!IsGameplayActive()) { SendIgniteResult(requesterActorNumber, success: false, "현재 씬에서는 모닥불 재료 기능이 작동하지 않습니다."); return; } if (requestData == null || requestData.Length < 1) { SendIgniteResult(requesterActorNumber, success: false, "잘못된 모닥불 점화 요청입니다."); return; } int num; try { num = Convert.ToInt32(requestData[0]); } catch (Exception) { SendIgniteResult(requesterActorNumber, success: false, "모닥불 네트워크 정보를 해석하지 못했습니다."); return; } if (num <= 0) { SendIgniteResult(requesterActorNumber, success: false, "잘못된 모닥불 네트워크 번호입니다."); return; } if (committedCampfireViewIds.Contains(num)) { SendIgniteResult(requesterActorNumber, success: false, "이 모닥불은 이미 점화 처리 중이거나 켜져 있습니다."); return; } PhotonView val = PhotonView.Find(num); if ((Object)(object)val == (Object)null) { SendIgniteResult(requesterActorNumber, success: false, "모닥불을 찾을 수 없습니다."); return; } Campfire component = ((Component)val).GetComponent(); if (!IsManagedCampfire(component)) { SendIgniteResult(requesterActorNumber, success: false, "이 모닥불은 재료 조건 적용 대상이 아닙니다."); return; } if (component.Lit || (int)component.state > 0) { SendIgniteResult(requesterActorNumber, success: false, "이 모닥불은 이미 켜졌거나 사용할 수 없습니다."); return; } ((BaseUnityPlugin)this).Logger.LogInfo((object)("[CampfireIgnitionDiag] Campfire target validated. ViewID=" + num + " | Lit=" + component.Lit + " | State=" + ((object)Unsafe.As(ref component.state)/*cast due to .constrained prefix*/).ToString())); Player player = PlayerHandler.GetPlayer(requesterActorNumber); Character val2 = (((Object)(object)player != (Object)null) ? player.character : null); if ((Object)(object)player == (Object)null || (Object)(object)val2 == (Object)null || (Object)(object)val2.data == (Object)null || val2.data.dead) { SendIgniteResult(requesterActorNumber, success: false, "점화를 요청한 플레이어를 확인할 수 없습니다."); return; } float num2 = Vector3.Distance(component.Center(), val2.Center); if (num2 > MaximumRequesterDistance) { SendIgniteResult(requesterActorNumber, success: false, "모닥불에서 너무 멀리 떨어져 있습니다."); return; } ((BaseUnityPlugin)this).Logger.LogInfo((object)("[CampfireIgnitionDiag] Requester distance validated. Actor=" + requesterActorNumber + " | Distance=" + num2.ToString("0.00") + " | Maximum=" + MaximumRequesterDistance.ToString("0.00"))); string value = default(string); if (RequireEveryoneInRange && !component.EveryoneInRange(ref value, EveryoneInRangeDistance)) { string message = (string.IsNullOrEmpty(value) ? "모든 생존 플레이어가 모닥불 근처에 모여야 합니다." : StripRichTextForNotification(value)); SendIgniteResult(requesterActorNumber, success: false, message); return; } ((BaseUnityPlugin)this).Logger.LogInfo((object)("[CampfireIgnitionDiag] Player range condition passed. RequireEveryone=" + RequireEveryoneInRange + " | EveryoneRange=" + EveryoneInRangeDistance.ToString("0.00"))); if (!TryCreateIngredientPlan(out var plan, out var missingMessage)) { SendIgniteResult(requesterActorNumber, success: false, missingMessage); ((BaseUnityPlugin)this).Logger.LogInfo((object)("Campfire ignite denied: missing materials. Actor=" + requesterActorNumber + " | CampfireViewID=" + num + " | " + BuildMaterialCountLog())); return; } ((BaseUnityPlugin)this).Logger.LogInfo((object)("[CampfireIgnitionDiag] Material condition passed. " + BuildMaterialCountLog())); committedCampfireViewIds.Add(num); if (!TryConsumeIngredientPlan(plan, out var consumedSelectedSlots)) { committedCampfireViewIds.Remove(num); SendIgniteResult(requesterActorNumber, success: false, "재료 소비 중 오류가 발생했습니다. 재료는 소비되지 않았습니다."); return; } BroadcastConsumedSelectedSlots(consumedSelectedSlots); ((BaseUnityPlugin)this).Logger.LogInfo((object)("[CampfireIgnitionDiag] All conditions passed. Calling Light_Rpc(true, 0f). Actor=" + requesterActorNumber + " | CampfireViewID=" + num + " | BeforeLit=" + component.Lit + " | BeforeState=" + ((object)Unsafe.As(ref component.state)/*cast due to .constrained prefix*/).ToString())); try { val.RPC("Light_Rpc", (RpcTarget)0, new object[2] { true, 0f }); } catch (Exception ex2) { committedCampfireViewIds.Remove(num); ((BaseUnityPlugin)this).Logger.LogError((object)("[CampfireIgnitionDiag] Photon Light_Rpc(true, 0f) threw an exception. CampfireViewID=" + num + " | Exception=" + ex2)); SendIgniteResult(requesterActorNumber, success: false, "모닥불 점화 RPC 호출 중 예외가 발생했습니다. 점화 잠금을 해제했습니다."); return; } ((MonoBehaviour)this).StartCoroutine(VerifyCorrectArgumentRpcResult(requesterActorNumber, num, component)); } private IEnumerator VerifyCorrectArgumentRpcResult(int requesterActorNumber, int campfireViewId, Campfire campfire) { yield return (object)new WaitForSecondsRealtime(0.25f); bool succeeded = IsCampfireActuallyLit(campfire); ((BaseUnityPlugin)this).Logger.LogInfo((object)("[CampfireIgnitionDiag] Light_Rpc(true, 0f) verification. Actor=" + requesterActorNumber + " | CampfireViewID=" + campfireViewId + " | Lit=" + ((Object)(object)campfire != (Object)null && campfire.Lit) + " | State=" + (((Object)(object)campfire != (Object)null) ? ((object)Unsafe.As(ref campfire.state)/*cast due to .constrained prefix*/).ToString() : "") + " | Success=" + succeeded)); if (succeeded) { SendIgniteResult(requesterActorNumber, success: true, "모닥불 점화 성공\n" + BuildConsumedMaterialMessage()); ((BaseUnityPlugin)this).Logger.LogInfo((object)("Campfire ignition verified with correct RPC arguments. Actor=" + requesterActorNumber + " | CampfireViewID=" + campfireViewId + " | updateSegment=True | burningFor=0 | Consumed: FireWood=" + RequiredFireWoodCount + ", Stone=" + RequiredStoneCount + ", Torch=" + RequiredTorchCount + ".")); } else { committedCampfireViewIds.Remove(campfireViewId); SendIgniteResult(requesterActorNumber, success: false, "Light_Rpc(true, 0f)를 호출했지만 모닥불 상태가 변경되지 않았습니다. 점화 잠금을 해제했습니다."); ((BaseUnityPlugin)this).Logger.LogError((object)("[CampfireIgnitionDiag] Correct-argument Light_Rpc did not activate campfire. Actor=" + requesterActorNumber + " | CampfireViewID=" + campfireViewId + " | LockReleased=True")); } } private static bool IsCampfireActuallyLit(Campfire campfire) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Invalid comparison between Unknown and I4 if ((Object)(object)campfire == (Object)null) { return false; } return campfire.Lit || (int)campfire.state > 0; } private static bool TryCreateIngredientPlan(out IngredientPlan plan, out string missingMessage) { plan = new IngredientPlan(); List locations = CollectAllIngredientLocations(); int num = CountAvailableUnits(locations, 28); int num2 = CountAvailableUnits(locations, 72); int num3 = CountAvailableUnits(locations, 109); if (num < RequiredFireWoodCount || num2 < RequiredStoneCount || num3 < RequiredTorchCount) { missingMessage = "모닥불 점화 재료가 부족합니다.\n" + BuildMaterialProgressText(num, num2, num3); return false; } bool flag = TryAppendIngredientUnits(locations, 28, RequiredFireWoodCount, plan.FireWood); bool flag2 = TryAppendIngredientUnits(locations, 72, RequiredStoneCount, plan.Stone); bool flag3 = TryAppendIngredientUnits(locations, 109, RequiredTorchCount, plan.Torch); if (!flag || !flag2 || !flag3) { missingMessage = "재료 목록을 구성하는 동안 인벤토리가 변경되었습니다. 다시 시도하세요."; return false; } missingMessage = string.Empty; return true; } private static bool TryAppendIngredientUnits(List locations, ushort itemId, int requiredCount, List destination) { if (destination == null) { return false; } destination.Clear(); if (requiredCount <= 0) { return true; } List list = new List(); for (int i = 0; i < locations.Count; i++) { IngredientLocation ingredientLocation = locations[i]; if (ingredientLocation != null && ingredientLocation.ItemId == itemId && ingredientLocation.AvailableCount > 0) { list.Add(ingredientLocation); } } list.Sort(CompareIngredientLocations); int num = requiredCount; for (int j = 0; j < list.Count; j++) { if (num <= 0) { break; } IngredientLocation ingredientLocation2 = list[j]; int num2 = Mathf.Min(ingredientLocation2.AvailableCount, num); for (int k = 0; k < num2; k++) { destination.Add(ingredientLocation2); } num -= num2; } return num <= 0; } private static int CompareIngredientLocations(IngredientLocation left, IngredientLocation right) { int num = GetConsumptionPriority(left).CompareTo(GetConsumptionPriority(right)); if (num != 0) { return num; } int actorNumber = GetActorNumber(left); int actorNumber2 = GetActorNumber(right); int num2 = actorNumber.CompareTo(actorNumber2); if (num2 != 0) { return num2; } if (left.IsBackpackInternal != right.IsBackpackInternal) { return (!left.IsBackpackInternal) ? 1 : (-1); } if (left.IsBackpackInternal) { return left.BackpackSlotIndex.CompareTo(right.BackpackSlotIndex); } return left.ExternalSlotId.CompareTo(right.ExternalSlotId); } private static int GetActorNumber(IngredientLocation location) { if (location == null || (Object)(object)location.Character == (Object)null || (Object)(object)((MonoBehaviourPun)location.Character).photonView == (Object)null || ((MonoBehaviourPun)location.Character).photonView.Owner == null) { return int.MaxValue; } return ((MonoBehaviourPun)location.Character).photonView.Owner.ActorNumber; } private static List CollectAllIngredientLocations() { List list = new List(); List allPlayerCharacters = PlayerHandler.GetAllPlayerCharacters(); for (int i = 0; i < allPlayerCharacters.Count; i++) { Character val = allPlayerCharacters[i]; if ((Object)(object)val == (Object)null || (Object)(object)val.player == (Object)null || (Object)(object)((MonoBehaviourPun)val).photonView == (Object)null || ((MonoBehaviourPun)val).photonView.Owner == null || ((MonoBehaviourPun)val).photonView.Owner.IsInactive) { continue; } Player player = val.player; ItemSlot[] itemSlots = player.itemSlots; if (itemSlots != null) { for (int j = 0; j < itemSlots.Length; j++) { AddIngredientLocation(list, player, val, itemSlots[j], isBackpackInternal: false, (byte)j, -1); } } AddIngredientLocation(list, player, val, player.tempFullSlot, isBackpackInternal: false, 250, -1); BackpackData val2 = null; if (player.backpackSlot != null && !((ItemSlot)player.backpackSlot).IsEmpty() && ((ItemSlot)player.backpackSlot).data != null && ((ItemSlot)player.backpackSlot).data.TryGetDataEntry((DataEntryKey)7, ref val2) && val2 != null && val2.itemSlots != null) { for (int k = 0; k < val2.itemSlots.Length; k++) { AddIngredientLocation(list, player, val, val2.itemSlots[k], isBackpackInternal: true, byte.MaxValue, k); } } } return list; } private static void AddIngredientLocation(List locations, Player player, Character character, ItemSlot slot, bool isBackpackInternal, byte externalSlotId, int backpackSlotIndex) { if (locations != null && !((Object)(object)player == (Object)null) && !((Object)(object)character == (Object)null) && slot != null && !slot.IsEmpty() && !((Object)(object)slot.prefab == (Object)null)) { ushort itemID = slot.prefab.itemID; if (itemID == 28 || itemID == 72 || itemID == 109) { locations.Add(new IngredientLocation { Player = player, Character = character, Slot = slot, IsBackpackInternal = isBackpackInternal, ExternalSlotId = externalSlotId, BackpackSlotIndex = backpackSlotIndex, ItemId = itemID, AvailableCount = GetLocationAvailableCount(player, slot, isBackpackInternal, externalSlotId) }); } } } private static int GetLocationAvailableCount(Player player, ItemSlot slot, bool isBackpackInternal, byte externalSlotId) { if (slot == null || slot.IsEmpty()) { return 0; } if (isBackpackInternal) { return 1; } int stackCount = InventoryStack.GetStackCount(player, externalSlotId); return Mathf.Max(1, stackCount); } private static int GetConsumptionPriority(IngredientLocation location) { if (location == null) { return int.MaxValue; } if (location.IsBackpackInternal) { return 0; } if (!IsCurrentlySelected(location)) { return 1; } return 2; } private static bool IsCurrentlySelected(IngredientLocation location) { //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) if (location == null || location.IsBackpackInternal || (Object)(object)location.Character == (Object)null || location.Character.refs == null || (Object)(object)location.Character.refs.items == (Object)null) { return false; } Optionable currentSelectedSlot = location.Character.refs.items.currentSelectedSlot; return currentSelectedSlot.IsSome && currentSelectedSlot.Value == location.ExternalSlotId; } private static int CountAvailableUnits(List locations, ushort itemId) { int num = 0; for (int i = 0; i < locations.Count; i++) { IngredientLocation ingredientLocation = locations[i]; if (ingredientLocation != null && ingredientLocation.ItemId == itemId) { num += Mathf.Max(0, ingredientLocation.AvailableCount); } } return num; } private static bool TryConsumeIngredientPlan(IngredientPlan plan, out List consumedSelectedSlots) { consumedSelectedSlots = new List(); if (plan == null || !ValidatePlannedUnits(plan.FireWood, 28, RequiredFireWoodCount) || !ValidatePlannedUnits(plan.Stone, 72, RequiredStoneCount) || !ValidatePlannedUnits(plan.Torch, 109, RequiredTorchCount)) { return false; } List list = new List(); list.AddRange(plan.FireWood); list.AddRange(plan.Stone); list.AddRange(plan.Torch); HashSet hashSet = new HashSet(); HashSet hashSet2 = new HashSet(); HashSet hashSet3 = new HashSet(); for (int i = 0; i < list.Count; i++) { IngredientLocation ingredientLocation = list[i]; if (!IsLocationStillValid(ingredientLocation, ingredientLocation.ItemId)) { return false; } if (IsCurrentlySelected(ingredientLocation) && (Object)(object)((MonoBehaviourPun)ingredientLocation.Character).photonView != (Object)null && ((MonoBehaviourPun)ingredientLocation.Character).photonView.Owner != null) { int actorNumber = ((MonoBehaviourPun)ingredientLocation.Character).photonView.Owner.ActorNumber; string item = actorNumber + ":" + ingredientLocation.ExternalSlotId; if (hashSet3.Add(item)) { consumedSelectedSlots.Add(new ConsumedSelectedSlot { ActorNumber = actorNumber, SlotId = ingredientLocation.ExternalSlotId }); } } ingredientLocation.Slot.EmptyOut(); hashSet.Add(ingredientLocation.Player); if (ingredientLocation.IsBackpackInternal) { hashSet2.Add(ingredientLocation.Character); } } foreach (Player item2 in hashSet) { SyncPlayerInventoryFromHost(item2); } foreach (Character item3 in hashSet2) { RefreshBackpackVisuals(item3); } RefreshAllCarryWeights(hashSet); return true; } private static bool ValidatePlannedUnits(List plannedUnits, ushort expectedItemId, int expectedCount) { if (expectedCount <= 0) { return plannedUnits != null && plannedUnits.Count == 0; } if (plannedUnits == null || plannedUnits.Count != expectedCount) { return false; } Dictionary dictionary = new Dictionary(); for (int i = 0; i < plannedUnits.Count; i++) { IngredientLocation ingredientLocation = plannedUnits[i]; if (!IsLocationStillValid(ingredientLocation, expectedItemId)) { return false; } if (!dictionary.ContainsKey(ingredientLocation)) { dictionary[ingredientLocation] = 0; } dictionary[ingredientLocation]++; } foreach (KeyValuePair item in dictionary) { int locationAvailableCount = GetLocationAvailableCount(item.Key.Player, item.Key.Slot, item.Key.IsBackpackInternal, item.Key.ExternalSlotId); if (locationAvailableCount < item.Value) { return false; } } return true; } private static bool IsLocationStillValid(IngredientLocation location, ushort expectedItemId) { return location != null && (Object)(object)location.Player != (Object)null && (Object)(object)location.Character != (Object)null && location.Slot != null && !location.Slot.IsEmpty() && (Object)(object)location.Slot.prefab != (Object)null && location.Slot.prefab.itemID == expectedItemId; } private static void SyncPlayerInventoryFromHost(Player player) { //IL_0059: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player == (Object)null || !PhotonNetwork.IsMasterClient) { return; } PhotonView component = ((Component)player).GetComponent(); if (!((Object)(object)component == (Object)null)) { InventorySyncData val = default(InventorySyncData); ((InventorySyncData)(ref val))..ctor(player.itemSlots, player.backpackSlot, player.tempFullSlot); component.RPC("SyncInventoryRPC", (RpcTarget)1, new object[2] { IBinarySerializable.ToManagedArray(val), false }); if (player.itemsChangedAction != null) { player.itemsChangedAction(player.itemSlots); } } } private static void RefreshBackpackVisuals(Character character) { if (!((Object)(object)character == (Object)null) && PhotonNetwork.IsMasterClient) { CharacterBackpackHandler component = ((Component)character).GetComponent(); if (!((Object)(object)component == (Object)null) && !((Object)(object)component.backpackVisuals == (Object)null)) { ((BackpackVisuals)component.backpackVisuals).RefreshVisuals(); } } } private static void RefreshAllCarryWeights(HashSet touchedPlayers) { if (touchedPlayers == null) { return; } foreach (Player touchedPlayer in touchedPlayers) { if ((Object)(object)touchedPlayer == (Object)null || (Object)(object)touchedPlayer.character == (Object)null || touchedPlayer.character.refs == null || (Object)(object)touchedPlayer.character.refs.items == (Object)null) { continue; } touchedPlayer.character.refs.items.RefreshAllCharacterCarryWeight(); break; } } private void BroadcastConsumedSelectedSlots(List consumedSlots) { //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Expected O, but got Unknown //IL_0095: Unknown result type (might be due to invalid IL or missing references) if (consumedSlots != null && consumedSlots.Count != 0) { object[] array = new object[1 + consumedSlots.Count * 2]; array[0] = consumedSlots.Count; for (int i = 0; i < consumedSlots.Count; i++) { array[1 + i * 2] = consumedSlots[i].ActorNumber; array[2 + i * 2] = consumedSlots[i].SlotId; } RaiseEventOptions val = new RaiseEventOptions { Receivers = (ReceiverGroup)1 }; PhotonNetwork.RaiseEvent((byte)172, (object)array, val, SendOptions.SendReliable); } } private static void HandleConsumedSelectedSlots(object[] payload) { if (payload == null || payload.Length < 1 || PhotonNetwork.LocalPlayer == null) { return; } int num; try { num = Convert.ToInt32(payload[0]); } catch (Exception) { return; } int actorNumber = PhotonNetwork.LocalPlayer.ActorNumber; for (int i = 0; i < num; i++) { int num2 = 1 + i * 2; int num3 = 2 + i * 2; if (num3 >= payload.Length) { break; } int num4; int consumedSlotId; try { num4 = Convert.ToInt32(payload[num2]); consumedSlotId = Convert.ToInt32(payload[num3]); } catch (Exception) { continue; } if (num4 == actorNumber) { UnequipConsumedLocalSlot(consumedSlotId); } } } private static void UnequipConsumedLocalSlot(int consumedSlotId) { //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) Character localCharacter = Character.localCharacter; if (!((Object)(object)localCharacter == (Object)null) && localCharacter.refs != null && !((Object)(object)localCharacter.refs.items == (Object)null)) { Optionable currentSelectedSlot = localCharacter.refs.items.currentSelectedSlot; if (!currentSelectedSlot.IsNone && currentSelectedSlot.Value == (byte)consumedSlotId) { localCharacter.refs.items.EquipSlot(Optionable.None); } } } private void SendIgniteResult(int targetActorNumber, bool success, string message) { //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Expected O, but got Unknown //IL_0062: Unknown result type (might be due to invalid IL or missing references) object[] array = new object[2] { success, message ?? string.Empty }; if (PhotonNetwork.LocalPlayer != null && PhotonNetwork.LocalPlayer.ActorNumber == targetActorNumber) { HandleIgniteResult(array); return; } RaiseEventOptions val = new RaiseEventOptions(); val.TargetActors = new int[1] { targetActorNumber }; RaiseEventOptions val2 = val; PhotonNetwork.RaiseEvent((byte)171, (object)array, val2, SendOptions.SendReliable); } private static void HandleIgniteResult(object[] resultData) { if (resultData == null || resultData.Length < 2) { return; } bool flag; string text; try { flag = Convert.ToBoolean(resultData[0]); text = resultData[1] as string; } catch (Exception) { return; } if (string.IsNullOrEmpty(text)) { text = (flag ? "모닥불을 점화했습니다." : "모닥불을 점화하지 못했습니다."); } NotifyLocalPlayer(text); if (ModLogger != null) { ModLogger.LogInfo((object)("[CampfireIgnitionDiag] Result received. Success=" + flag + " | Message=" + text.Replace("\n", " | "))); } if (ModLogger != null) { if (flag) { ModLogger.LogInfo((object)text.Replace("\n", " | ")); } else { ModLogger.LogWarning((object)text.Replace("\n", " | ")); } } } internal static void NotifyLocalPlayer(string message) { if (!string.IsNullOrEmpty(message)) { UI_Notifications val = Object.FindAnyObjectByType(); if ((Object)(object)val != (Object)null) { val.AddNotification(message); } else if (ModLogger != null) { ModLogger.LogInfo((object)("Notification UI not found. Message=" + message.Replace("\n", " | "))); } } } internal static string BuildRequirementPrompt() { List locations = CollectAllIngredientLocations(); int num = CountAvailableUnits(locations, 28); int num2 = CountAvailableUnits(locations, 72); int num3 = CountAvailableUnits(locations, 109); string text = ((num >= RequiredFireWoodCount && num2 >= RequiredStoneCount && num3 >= RequiredTorchCount) ? "#79E081" : "#FF8A80"); return "\n필요: " + BuildMaterialProgressText(num, num2, num3) + ""; } private static string BuildMaterialProgressText(int fireWoodCount, int stoneCount, int torchCount) { List list = new List(); if (RequiredFireWoodCount > 0) { list.Add("나뭇가지 " + Mathf.Min(fireWoodCount, RequiredFireWoodCount) + "/" + RequiredFireWoodCount); } if (RequiredStoneCount > 0) { list.Add("돌 " + Mathf.Min(stoneCount, RequiredStoneCount) + "/" + RequiredStoneCount); } if (RequiredTorchCount > 0) { list.Add("횃불 " + Mathf.Min(torchCount, RequiredTorchCount) + "/" + RequiredTorchCount); } if (list.Count == 0) { return "재료 없음"; } return string.Join(" | ", list.ToArray()); } private static string BuildConsumedMaterialMessage() { List list = new List(); if (RequiredFireWoodCount > 0) { list.Add("나뭇가지 " + RequiredFireWoodCount + "개"); } if (RequiredStoneCount > 0) { list.Add("돌 " + RequiredStoneCount + "개"); } if (RequiredTorchCount > 0) { list.Add("횃불 " + RequiredTorchCount + "개"); } if (list.Count == 0) { return "재료를 소비하지 않았습니다."; } return string.Join(", ", list.ToArray()) + "를 소비했습니다."; } private static int CountGroupIngredient(ushort itemId) { List locations = CollectAllIngredientLocations(); return CountAvailableUnits(locations, itemId); } private static string BuildMaterialCountLog() { List locations = CollectAllIngredientLocations(); return "FireWood=" + CountAvailableUnits(locations, 28) + ", Stone=" + CountAvailableUnits(locations, 72) + ", Torch=" + CountAvailableUnits(locations, 109) + " | Required=[" + RequiredFireWoodCount + ", " + RequiredStoneCount + ", " + RequiredTorchCount + "]"; } private static string DescribeLocation(IngredientLocation location) { if (location == null || (Object)(object)location.Character == (Object)null || (Object)(object)((MonoBehaviourPun)location.Character).photonView == (Object)null || ((MonoBehaviourPun)location.Character).photonView.Owner == null) { return ""; } string nickName = ((MonoBehaviourPun)location.Character).photonView.Owner.NickName; if (location.IsBackpackInternal) { return nickName + ":Backpack[" + location.BackpackSlotIndex + "]"; } return nickName + ":Inventory[" + location.ExternalSlotId + "]"; } private static string StripRichTextForNotification(string value) { if (string.IsNullOrEmpty(value)) { return string.Empty; } string text = value; for (int i = 0; i < 32; i++) { int num = text.IndexOf('<'); if (num < 0) { break; } int num2 = text.IndexOf('>', num); if (num2 < 0) { break; } text = text.Remove(num, num2 - num + 1); } return text.Trim(); } } [HarmonyPatch(typeof(Campfire), "Interact_CastFinished")] internal static class CampfireInteractCastFinishedPatch { [HarmonyPrefix] private static bool Prefix(Campfire __instance, Character interactor) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Invalid comparison between Unknown and I4 if (!CampfireGate.IsManagedCampfire(__instance)) { return true; } if (__instance.Lit || (int)__instance.state > 0) { return true; } if ((Object)(object)CampfireGate.Instance != (Object)null) { CampfireGate.Instance.RequestIgnition(__instance, interactor); } return false; } } [HarmonyPatch(typeof(Campfire), "GetInteractionText")] internal static class CampfireGetInteractionTextPatch { [HarmonyPostfix] private static void Postfix(Campfire __instance, ref string __result) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Invalid comparison between Unknown and I4 if (CampfireGate.IsManagedCampfire(__instance) && !__instance.Lit && (int)__instance.state <= 0) { __result += CampfireGate.BuildRequirementPrompt(); } } } [BepInPlugin("com.sappheiros.crafting.shop", "Craft PEAK Unified Hub", "2.13.1")] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] public sealed class CraftHub : BaseUnityPlugin, IOnEventCallback, IInRoomCallbacks { internal enum HubTab { Description, Upgrade, Craft, Sell, Parts, Developer } internal enum HubLanguage { English, Korean, Chinese, Japanese, French } private sealed class UiTranslationEntry { public readonly string Source; public readonly string Korean; public readonly string English; public readonly string Chinese; public readonly string Japanese; public readonly string French; public UiTranslationEntry(string source, string korean, string english, string chinese, string japanese, string french) { Source = source ?? string.Empty; Korean = korean ?? string.Empty; English = english ?? string.Empty; Chinese = chinese ?? string.Empty; Japanese = japanese ?? string.Empty; French = french ?? string.Empty; } public string Get(HubLanguage language) { return language switch { HubLanguage.Korean => Korean, HubLanguage.Chinese => Chinese, HubLanguage.Japanese => Japanese, HubLanguage.French => French, _ => English, }; } } internal enum PendingRequest { None, Sell, Craft, Upgrade, Parts } internal enum RecipeTier { Basic, Standard, Advanced, Special, Masterwork } internal enum CraftUiCategory { Climbing, Food, Heal, Revive, Essential } internal sealed class IngredientCost { public ushort ItemId; public int Count; public IngredientCost(ushort itemId, int count) { ItemId = itemId; Count = Mathf.Max(1, count); } } internal sealed class CraftRecipe { public ushort OutputItemId; public Item OutputPrefab; public string DisplayName; public string Category; public RecipeTier Tier; public int RequiredResourceLevel; public int MoneyCost; public readonly List Ingredients = new List(); } private sealed class CampfireBuildRecipe { public int Stage; public string Name; public int RequiredResourceLevel; public int MoneyCost; public readonly List Ingredients = new List(); public CampfireBuildRecipe(int stage, string name, int requiredResourceLevel, int moneyCost, params IngredientCost[] ingredients) { Stage = Mathf.Clamp(stage, 1, 4); Name = name ?? string.Empty; RequiredResourceLevel = Mathf.Clamp(requiredResourceLevel, 0, 4); MoneyCost = Mathf.Max(0, moneyCost); if (ingredients != null) { Ingredients.AddRange(ingredients); } } } private sealed class PlanePartRecipe { public int Index; public string Name; public string Route; public int RequiredResourceLevel; public int MoneyCost; public readonly List Ingredients = new List(); public PlanePartRecipe(int index, string name, string route, int requiredResourceLevel, int moneyCost, params IngredientCost[] ingredients) { Index = index; Name = name ?? string.Empty; Route = route ?? string.Empty; RequiredResourceLevel = Mathf.Clamp(requiredResourceLevel, 0, 4); MoneyCost = Mathf.Max(0, moneyCost); if (ingredients != null) { Ingredients.AddRange(ingredients); } } } internal enum UpgradeKind { ResourceGrade, StackCapacity, CampfireEfficiency, DoubleYield, SellValue } private sealed class UpgradeFormulaConfig { public ConfigEntry BaseCost; public ConfigEntry CostGrowth; } private sealed class UpgradeState { public int Protocol; public int Revision; public int OwnerActor; public string RunId; public int ResourceLevel; public int StackLevel; public int CampfireLevel; public int YieldMultiplier; public int SellMultiplier; public int BaseStackCount; public int[] BaseCampfireMaterials; public UpgradeState Clone() { return new UpgradeState { Protocol = Protocol, Revision = Revision, OwnerActor = OwnerActor, RunId = (RunId ?? string.Empty), ResourceLevel = ResourceLevel, StackLevel = StackLevel, CampfireLevel = CampfireLevel, YieldMultiplier = YieldMultiplier, SellMultiplier = SellMultiplier, BaseStackCount = BaseStackCount, BaseCampfireMaterials = CloneIntArray(BaseCampfireMaterials) }; } public static UpgradeState CreateDefault() { UpgradeState upgradeState = new UpgradeState(); upgradeState.Protocol = 1; upgradeState.Revision = 0; upgradeState.OwnerActor = 0; upgradeState.RunId = string.Empty; upgradeState.ResourceLevel = 0; upgradeState.StackLevel = 0; upgradeState.CampfireLevel = 0; upgradeState.YieldMultiplier = 1; upgradeState.SellMultiplier = 1; upgradeState.BaseStackCount = 10; upgradeState.BaseCampfireMaterials = new int[3] { 1, 1, 1 }; return upgradeState; } } private sealed class IngredientLocation { public Player Player; public Character Character; public ItemSlot Slot; public bool IsBackpackInternal; public byte ExternalSlotId; public int BackpackSlotIndex; public ushort ItemId; public int AvailableCount; } private sealed class PlannedIngredientUnit { public IngredientLocation Location; public ushort ItemId; } private sealed class CraftConsumptionPlan { public readonly List Units = new List(); } private struct ConsumedSelectedSlot { public int ActorNumber; public byte SlotId; } private sealed class PendingSaleTransaction { public int TransactionId; public int ActorNumber; public byte SlotId; public ushort ItemId; public string ItemGuid; public string ItemName; public int SalePrice; public double CreatedAt; } private sealed class LocalPendingSale { public int TransactionId; public byte SlotId; public ushort ItemId; public string ItemGuid; } internal struct PickupBonusState { public bool Eligible; public Player Player; public ushort ItemId; public int CountBefore; } private sealed class PoolRequest { public readonly List Pool; public readonly int Count; public PoolRequest(List pool, int count) { Pool = pool ?? new List(); Count = Mathf.Max(1, count); } } private sealed class LiteTabView { private static readonly Color Normal = new Color(0.18f, 0.19f, 0.22f, 1f); private static readonly Color Selected = new Color(0.82f, 0.65f, 0.26f, 1f); private readonly HubTab tab; private readonly Button button; private readonly Image image; private readonly TextMeshProUGUI label; public LiteTabView(HubTab value, Button tabButton, Image tabImage, TextMeshProUGUI tabLabel) { tab = value; button = tabButton; image = tabImage; label = tabLabel; } public void Refresh(HubTab selectedTab) { //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) SetTextIfChanged(label, GetHubTabDisplayName(tab)); if ((Object)(object)image != (Object)null) { Color val = ((tab == selectedTab) ? Selected : Normal); if (((Graphic)image).color != val) { ((Graphic)image).color = val; } } SetInteractableIfChanged(button, tab != selectedTab); } } private sealed class LiteCraftCategoryView { private static readonly Color Normal = new Color(0.18f, 0.19f, 0.22f, 1f); private static readonly Color Selected = new Color(0.82f, 0.65f, 0.26f, 1f); private readonly CraftUiCategory category; private readonly Button button; private readonly Image image; private readonly TextMeshProUGUI label; public LiteCraftCategoryView(CraftUiCategory value, Button categoryButton, Image categoryImage, TextMeshProUGUI categoryLabel) { category = value; button = categoryButton; image = categoryImage; label = categoryLabel; } public void Refresh(bool visible, CraftUiCategory selectedCategory) { //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) SetTextIfChanged(label, GetCraftUiCategoryName(category)); SetActiveIfChanged(((Object)(object)button != (Object)null) ? ((Component)button).gameObject : null, visible); if (!visible) { return; } if ((Object)(object)image != (Object)null) { Color val = ((category == selectedCategory) ? Selected : Normal); if (((Graphic)image).color != val) { ((Graphic)image).color = val; } } SetInteractableIfChanged(button, category != selectedCategory); } } private sealed class LiteLanguageView { private static readonly Color Normal = new Color(0.18f, 0.19f, 0.22f, 1f); private static readonly Color Selected = new Color(0.82f, 0.65f, 0.26f, 1f); private readonly HubLanguage language; private readonly Button button; private readonly Image image; public LiteLanguageView(HubLanguage value, Button languageButton, Image languageImage) { language = value; button = languageButton; image = languageImage; } public void Refresh(HubLanguage selectedLanguage) { //IL_0022: 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_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)image != (Object)null) { Color val = ((language == selectedLanguage) ? Selected : Normal); if (((Graphic)image).color != val) { ((Graphic)image).color = val; } } SetInteractableIfChanged(button, language != selectedLanguage); } } private sealed class LiteRowView { private static readonly Color Normal = new Color(0.14f, 0.15f, 0.18f, 1f); private static readonly Color Selected = new Color(0.82f, 0.65f, 0.26f, 1f); private readonly Button button; private readonly Image image; private readonly TextMeshProUGUI label; private readonly RawImage icon; public LiteRowView(Button rowButton, Image rowImage, TextMeshProUGUI rowLabel, RawImage rowIcon) { button = rowButton; image = rowImage; label = rowLabel; icon = rowIcon; } public void Refresh(bool active, bool selected, string text, Texture iconTexture = null, bool showIcon = false) { //IL_00be: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Unknown result type (might be due to invalid IL or missing references) SetActiveIfChanged(((Component)button).gameObject, active); if (!active) { if ((Object)(object)icon != (Object)null) { SetActiveIfChanged(((Component)icon).gameObject, active: false); } return; } if ((Object)(object)icon != (Object)null) { bool active2 = showIcon && (Object)(object)iconTexture != (Object)null; if ((Object)(object)icon.texture != (Object)(object)iconTexture) { icon.texture = iconTexture; } SetActiveIfChanged(((Component)icon).gameObject, active2); } if ((Object)(object)image != (Object)null) { Color val = (selected ? Selected : Normal); if (((Graphic)image).color != val) { ((Graphic)image).color = val; } } SetTextIfChanged(label, text); SetInteractableIfChanged(button, interactable: true); } } private sealed class CraftHubWindow : MenuWindow { private CraftHub owner; private List tabs = new List(); private List craftCategoryTabs = new List(); private List languages = new List(); private List rows = new List(); private TextMeshProUGUI title; private TextMeshProUGUI balance; private TextMeshProUGUI languageTitle; private TextMeshProUGUI help; private TextMeshProUGUI explanation; private GameObject detailPanel; private TextMeshProUGUI detail; private TextMeshProUGUI status; private TextMeshProUGUI actionLabel; private TextMeshProUGUI page; private TextMeshProUGUI previousLabel; private TextMeshProUGUI nextLabel; private TextMeshProUGUI closeLabel; private Button action; private Button previous; private Button next; private Button close; public override bool closeOnPause => true; public override bool closeOnUICancel => true; public override bool blocksPlayerInput => true; public override bool showCursorWhileOpen => true; public override Selectable objectToSelectOnOpen => (Selectable)(object)close; public void Initialize(CraftHub hub) { owner = hub; } public void SetReferences(List tabViews, List craftCategoryViews, List languageViews, List rowViews, TextMeshProUGUI titleText, TextMeshProUGUI balanceText, TextMeshProUGUI languageTitleText, TextMeshProUGUI helpText, TextMeshProUGUI explanationText, GameObject detailPanelObject, TextMeshProUGUI detailText, TextMeshProUGUI statusText, Button actionButton, TextMeshProUGUI actionButtonLabel, TextMeshProUGUI pageText, Button previousButton, TextMeshProUGUI previousButtonLabel, Button nextButton, TextMeshProUGUI nextButtonLabel, Button closeButton, TextMeshProUGUI closeButtonLabel) { tabs = tabViews ?? new List(); craftCategoryTabs = craftCategoryViews ?? new List(); languages = languageViews ?? new List(); rows = rowViews ?? new List(); title = titleText; balance = balanceText; languageTitle = languageTitleText; help = helpText; explanation = explanationText; detailPanel = detailPanelObject; detail = detailText; status = statusText; action = actionButton; actionLabel = actionButtonLabel; page = pageText; previous = previousButton; previousLabel = previousButtonLabel; next = nextButton; nextLabel = nextButtonLabel; close = closeButton; closeLabel = closeButtonLabel; } protected override void OnOpen() { RefreshContents(); } public void RefreshContents() { if (!((Object)(object)owner == (Object)null)) { HubTab currentTab = owner.currentTab; for (int i = 0; i < tabs.Count; i++) { tabs[i].Refresh(currentTab); } for (int j = 0; j < languages.Count; j++) { languages[j].Refresh(owner.currentLanguage); } SetTextIfChanged(languageTitle, "Language"); SetTextIfChanged(help, "P / ESC\n닫기"); SetTextIfChanged(previousLabel, "◀ 이전"); SetTextIfChanged(nextLabel, "다음 ▶"); SetTextIfChanged(closeLabel, "닫기"); SetTextIfChanged(balance, "공유 잔액: " + owner.cachedSharedMoney + "원"); SetActiveIfChanged(((Object)(object)explanation != (Object)null) ? ((Component)explanation).gameObject : null, currentTab == HubTab.Description); SetActiveIfChanged(detailPanel, currentTab != HubTab.Description); for (int k = 0; k < craftCategoryTabs.Count; k++) { craftCategoryTabs[k].Refresh(currentTab == HubTab.Craft, owner.selectedCraftUiCategory); } switch (currentTab) { case HubTab.Description: RefreshDescription(); break; case HubTab.Upgrade: RefreshUpgrade(); break; case HubTab.Craft: RefreshCraft(); break; case HubTab.Sell: RefreshSell(); break; case HubTab.Parts: RefreshParts(); break; case HubTab.Developer: RefreshDeveloper(); break; } } } private void RefreshDescription() { SetTextIfChanged(title, "설명"); for (int i = 0; i < rows.Count; i++) { rows[i].Refresh(active: false, selected: false, string.Empty); } SetActiveIfChanged(((Component)page).gameObject, active: false); SetActiveIfChanged(((Component)previous).gameObject, active: false); SetActiveIfChanged(((Component)next).gameObject, active: false); SetActiveIfChanged(((Component)action).gameObject, active: false); SetTextIfChanged(explanation, BuildExplanationText()); } private void PrepareInteractiveTab() { SetActiveIfChanged(((Component)action).gameObject, active: true); SetActiveIfChanged(detailPanel, active: true); } private void RefreshUpgrade() { PrepareInteractiveTab(); SetTextIfChanged(title, "강화"); for (int i = 0; i < rows.Count; i++) { bool flag = i <= 4; UpgradeKind upgradeKind = (flag ? ((UpgradeKind)i) : UpgradeKind.ResourceGrade); rows[i].Refresh(flag, flag && owner.selectedUpgradeKind == upgradeKind, flag ? owner.BuildUpgradeRowText(upgradeKind) : string.Empty); } SetActiveIfChanged(((Component)page).gameObject, active: false); SetActiveIfChanged(((Component)previous).gameObject, active: false); SetActiveIfChanged(((Component)next).gameObject, active: false); SetTextIfChanged(detail, BuildUpgradeDetailText(owner)); SetTextIfChanged(status, owner.upgradeStatus); bool flag2 = owner.SelectedUpgradeCurrentLevel >= owner.SelectedUpgradeMaximumLevel; SetInteractableIfChanged(action, owner.CanAttemptUpgrade); SetTextIfChanged(actionLabel, flag2 ? "최대 단계" : ((owner.pendingRequest == PendingRequest.Upgrade) ? "처리 중..." : "강화")); } private void RefreshCraft() { PrepareInteractiveTab(); SetTextIfChanged(title, "제작 · " + GetCraftUiCategoryName(owner.selectedCraftUiCategory)); CraftRecipe selectedCraftRecipe = owner.SelectedCraftRecipe; for (int i = 0; i < rows.Count; i++) { CraftRecipe craftRecipeAtCard = owner.GetCraftRecipeAtCard(i); rows[i].Refresh(craftRecipeAtCard != null, craftRecipeAtCard != null && selectedCraftRecipe != null && craftRecipeAtCard.OutputItemId == selectedCraftRecipe.OutputItemId, BuildCraftRowText(craftRecipeAtCard), GetCraftRecipeIcon(craftRecipeAtCard), showIcon: true); } SetActiveIfChanged(((Component)page).gameObject, active: true); SetActiveIfChanged(((Component)previous).gameObject, active: true); SetActiveIfChanged(((Component)next).gameObject, active: true); SetTextIfChanged(page, GetCraftUiCategoryName(owner.selectedCraftUiCategory) + " · 페이지 " + (owner.craftPage + 1) + " / " + owner.CraftTotalPages); SetInteractableIfChanged(previous, owner.craftPage > 0); SetInteractableIfChanged(next, owner.craftPage < owner.CraftTotalPages - 1); SetTextIfChanged(detail, BuildCraftDetailText(owner, selectedCraftRecipe, out var ready)); SetTextIfChanged(status, owner.craftStatus); SetInteractableIfChanged(action, selectedCraftRecipe != null && ready && owner.pendingRequest == PendingRequest.None); SetTextIfChanged(actionLabel, (owner.pendingRequest == PendingRequest.Craft) ? "처리 중..." : "제작 시도"); } private void RefreshSell() { PrepareInteractiveTab(); SetTextIfChanged(title, "판매"); Player localPlayer = Player.localPlayer; int num = (((Object)(object)localPlayer != (Object)null && localPlayer.itemSlots != null) ? Mathf.Min(rows.Count, localPlayer.itemSlots.Length) : 0); for (int i = 0; i < rows.Count; i++) { bool flag = i < num; rows[i].Refresh(flag, flag && owner.selectedSellSlotId == i, flag ? BuildSellRowText(i) : string.Empty); } SetActiveIfChanged(((Component)page).gameObject, active: false); SetActiveIfChanged(((Component)previous).gameObject, active: false); SetActiveIfChanged(((Component)next).gameObject, active: false); bool canSell; string text = owner.BuildSelectedSellText(out canSell); SetTextIfChanged(detail, text + "\n\n가격표\nCommon 1원 · Normal 3원\nRare 7원 · Unique 15원\nLegendary 50원"); SetTextIfChanged(status, owner.sellStatus); SetInteractableIfChanged(action, canSell && owner.pendingRequest == PendingRequest.None); SetTextIfChanged(actionLabel, (owner.pendingRequest == PendingRequest.Sell) ? "처리 중..." : (owner.selectedSellQuantity + "개 판매")); } private void RefreshDeveloper() { PrepareInteractiveTab(); SetTextIfChanged(title, "개발자"); for (int i = 0; i < rows.Count; i++) { rows[i].Refresh(active: false, selected: false, string.Empty); } SetActiveIfChanged(((Component)page).gameObject, active: false); SetActiveIfChanged(((Component)previous).gameObject, active: false); SetActiveIfChanged(((Component)next).gameObject, active: false); SetTextIfChanged(detail, "개발자 테스트 치트\n\n클릭할 때마다 호스트에게 +100원 요청을 즉시 보냅니다.\n호스트는 각 요청을 독립적으로 순서대로 처리합니다.\n\n호스트 자신이 누른 경우에도 동일한 호스트 처리 경로를\n직접 실행하므로 요청이 누락되지 않습니다.\n최종 공유 돈은 Photon 방 전체에 동기화됩니다."); SetTextIfChanged(status, owner.developerStatus); SetInteractableIfChanged(action, PhotonNetwork.InRoom && PhotonNetwork.CurrentRoom != null); SetTextIfChanged(actionLabel, "공유 돈 +100"); } private void RefreshParts() { PrepareInteractiveTab(); SetTextIfChanged(title, "부품"); for (int i = 0; i < rows.Count; i++) { bool flag = i < PlanePartRecipes.Count; rows[i].Refresh(flag, flag && owner.selectedPartIndex == i, flag ? owner.BuildPartRowText(i) : string.Empty); } SetActiveIfChanged(((Component)page).gameObject, active: false); SetActiveIfChanged(((Component)previous).gameObject, active: false); SetActiveIfChanged(((Component)next).gameObject, active: false); SetTextIfChanged(detail, owner.BuildPartDetailText(owner.SelectedPartRecipe, out var ready)); SetTextIfChanged(status, owner.partsStatus); SetInteractableIfChanged(action, ready); SetTextIfChanged(actionLabel, (owner.pendingRequest == PendingRequest.Parts) ? "처리 중..." : "부품 구매"); } } public const string PluginGuid = "com.sappheiros.crafting.shop"; public const string PluginName = "Craft PEAK Unified Hub"; public const string PluginVersion = "2.13.1"; public const string DeveloperName = "Sapphire009"; private const byte SellRequestEventCode = 140; private const byte SellResultEventCode = 141; private const byte SellConsumeRequestEventCode = 153; private const byte SellConsumeAckEventCode = 154; private const byte CraftRequestEventCode = 142; private const byte CraftResultEventCode = 143; private const byte ConsumedSlotEventCode = 144; private const byte UpgradeRequestEventCode = 145; private const byte UpgradeResultEventCode = 146; private const byte PartPurchaseRequestEventCode = 147; private const byte PartPurchaseResultEventCode = 148; private const byte ProgressionNoticeEventCode = 149; private const byte FinalFlareCompletedEventCode = 150; private const byte DeveloperMoneyRequestEventCode = 151; private const byte DeveloperMoneyResultEventCode = 152; private const string SharedMoneyKey = "CraftPeak.SharedMoney"; private const string PartsProtocolKey = "CraftPeak.Parts.Protocol"; private const string PartsRevisionKey = "CraftPeak.Parts.Revision"; private const string PartsRunIdKey = "CraftPeak.Parts.RunId"; private const string PartsPurchasedMaskKey = "CraftPeak.Parts.PurchasedMask"; private const string PartsConsumedMaskKey = "CraftPeak.Parts.ConsumedMask"; private const string PeakUnlockedKey = "CraftPeak.Parts.PeakUnlocked"; private const int PartsProtocolVersion = 1; private const string RunIdKey = "CraftPeak.Run.Id"; private const string RecipeProtocolKey = "CraftPeak.Recipes.Protocol"; private const string RecipeRunIdKey = "CraftPeak.Recipes.RunId"; private const string RecipeSeedKey = "CraftPeak.Recipes.Seed"; private const int RecipeProtocolVersion = 1; private const string UpgradeProtocolKey = "CraftPeak.Upgrade.Protocol"; private const string UpgradeRevisionKey = "CraftPeak.Upgrade.Revision"; private const string UpgradeOwnerKey = "CraftPeak.Upgrade.Owner"; private const string UpgradeRunIdKey = "CraftPeak.Upgrade.RunId"; private const string UpgradeResourceKey = "CraftPeak.Upgrade.Resource"; private const string UpgradeStackKey = "CraftPeak.Upgrade.Stack"; private const string UpgradeCampfireKey = "CraftPeak.Upgrade.Campfire"; private const string UpgradeYieldKey = "CraftPeak.Upgrade.Yield"; private const string UpgradeSellMultiplierKey = "CraftPeak.Upgrade.SellMultiplier"; private const string UpgradeBaseStackKey = "CraftPeak.Upgrade.BaseStack"; private const string UpgradeBaseCampfireKey = "CraftPeak.Upgrade.BaseCampfire"; private const int UpgradeProtocolVersion = 1; private const int ResourceUpgradeMaximum = 4; private const int StackUpgradeMaximum = 4; private const int DefaultBaseStackCount = 10; private const int CampfireUpgradeMaximum = 4; private const int YieldUpgradeMaximum = 4; private const int SellValueUpgradeMaximum = 4; private const double MinimumRequestIntervalSeconds = 0.25; private const float PartyResourceCacheSeconds = 1f; private static readonly int[] StackCapacityBonuses = new int[5] { 0, 5, 10, 20, 40 }; private static readonly float[] CampfireRequirementFactors = new float[4] { 1f, 0.9f, 0.78f, 0.65f }; private static readonly ConfigDefinition InventoryMaximumDefinition = new ConfigDefinition("01. 인벤토리 적재 설정", "슬롯당 최대 적재 수량"); private static readonly ConfigDefinition CampfireWoodDefinition = new ConfigDefinition("01. 캠프파이어 재료 조건", "나뭇가지 요구 수량"); private static readonly ConfigDefinition CampfireStoneDefinition = new ConfigDefinition("01. 캠프파이어 재료 조건", "돌 요구 수량"); private static readonly ConfigDefinition CampfireTorchDefinition = new ConfigDefinition("01. 캠프파이어 재료 조건", "횃불 요구 수량"); private const float RequestTimeoutSeconds = 6f; private const int CraftRecipesPerPage = 8; private const int MaximumVisibleInventorySlots = 8; private const ushort FireWoodItemId = 28; private const ushort StoneItemId = 72; private const ushort ConchItemId = 69; private const ushort BinocularsItemId = 14; private const ushort BingBongItemId = 13; private const ushort BugleItemId = 15; private const ushort FrisbeeItemId = 99; private const ushort GuidebookItemId = 34; private const ushort ScrollItemId = 49; private const ushort WeirdShroomItemId = 51; private const ushort StrangeGemItemId = 112; private const ushort TorchItemId = 109; private const ushort FlareItemId = 32; private const ushort ScoutEffigyItemId = 67; private static readonly ushort[] SaleResourceIds = new ushort[11] { 51, 28, 14, 13, 15, 69, 99, 34, 49, 72, 112 }; private static readonly ushort[] CraftIngredientIds = new ushort[12] { 28, 72, 69, 109, 14, 13, 15, 99, 34, 49, 51, 112 }; private static readonly ushort[] CommonIds = new ushort[3] { 28, 72, 69 }; private static readonly ushort[] NormalIds = new ushort[4] { 14, 13, 15, 99 }; private static readonly ushort[] RareIds = new ushort[2] { 34, 49 }; private static readonly List PlanePartRecipes = new List(); private static readonly List NextCampfireRecipes = new List(); private static bool progressionRecipesBuilt; private static string progressionRecipeRunId = string.Empty; private int sharedRecipeSeed; private string sharedRecipeRunId = string.Empty; private bool sharedRecipeSeedLoaded; private readonly List craftRecipes = new List(); private readonly Dictionary craftRecipesByOutputId = new Dictionary(); private readonly Dictionary lastSellRequestAtByActor = new Dictionary(); private readonly Dictionary pendingSaleTransactions = new Dictionary(); private readonly HashSet reservedOrSoldItemGuids = new HashSet(StringComparer.Ordinal); private readonly HashSet pendingTempHandDrawGuids = new HashSet(StringComparer.Ordinal); private LocalPendingSale localPendingSale; private int nextSaleTransactionId; private readonly Dictionary lastCraftRequestAtByActor = new Dictionary(); private readonly Dictionary lastUpgradeRequestAtByActor = new Dictionary(); private readonly Dictionary lastPartRequestAtByActor = new Dictionary(); private CraftHubWindow activeWindow; private HubTab currentTab = HubTab.Description; private HubLanguage currentLanguage = HubLanguage.English; private UpgradeKind selectedUpgradeKind = UpgradeKind.ResourceGrade; private int selectedCraftRecipeIndex = -1; private int craftPage; private CraftUiCategory selectedCraftUiCategory = CraftUiCategory.Climbing; private int selectedSellSlotId = -1; private int selectedSellQuantity = 1; private int selectedPartIndex; private PendingRequest pendingRequest = PendingRequest.None; private float requestStartedAt; private bool waitingForNewRun = true; private int cachedSharedMoney; private UpgradeState upgradeState = UpgradeState.CreateDefault(); private bool upgradeStateLoaded; private bool gameplayScene; private bool pendingFreshUpgradeRun = true; private float partyResourceCacheUntil; private readonly Dictionary cachedPartyResourceCounts = new Dictionary(); private readonly StringBuilder sharedTextBuilder = new StringBuilder(384); private static TMP_FontAsset cachedFontAsset; private int partsRevision; private string partsRunId = string.Empty; private int purchasedPartsMask; private int consumedPartsMask; private bool peakUnlocked; private bool partsStateLoaded; private bool upgradeRoomStateDirty = true; private bool partsRoomStateDirty = true; private int lastAppliedUpgradeRevision = -1; private string lastAppliedUpgradeRunId = string.Empty; private UpgradeFormulaConfig resourceUpgradeFormula; private UpgradeFormulaConfig stackUpgradeFormula; private UpgradeFormulaConfig campfireUpgradeFormula; private ConfigEntry doubleYieldCostConfig; private UpgradeFormulaConfig sellValueUpgradeFormula; private string upgradeStatus = "강화 항목을 선택하세요."; private string craftStatus = "제작할 아이템을 선택하세요."; private string sellStatus = "판매할 인벤토리 슬롯을 선택하세요."; private string partsStatus = "현재 세그먼트에 필요한 비행기 부품을 구매하세요."; private string developerStatus = "버튼을 누르면 로컬에서 +100원이 누적된 뒤 묶어서 공유됩니다."; private int developerMoneyRequestSequence; private int developerMoneyPendingResponses; private int developerHostAuthoritativeBalance = -1; private const int DeveloperMoneyPerClick = 100; private static readonly UiTranslationEntry[] UiTranslationEntries = new UiTranslationEntry[445] { new UiTranslationEntry("모닥불 점화 조건 미충족\nP 메뉴 강화 탭의 다음 모닥불 제작에서 ", "모닥불 점화 조건 미충족\nP 메뉴 강화 탭의 다음 모닥불 제작에서 ", "Campfire requirements not met\nIn the P menu's Upgrades tab, the level ", "未满足篝火点燃条件\n请先在 P 菜单的升级标签中制作第", "焚き火の点火条件を満たしていません\nPメニューの強化タブで", "Conditions d’allumage du feu de camp non remplies\nDans l’onglet Améliorations du menu P, le niveau "), new UiTranslationEntry("모닥불 점화 조건 미충족\nP 메뉴의 부품 탭에서 현재 구간 비행기 부품을 먼저 구매하세요.", "모닥불 점화 조건 미충족\nP 메뉴의 부품 탭에서 현재 구간 비행기 부품을 먼저 구매하세요.", "Campfire requirements not met\nPurchase the aircraft parts for the current segment from the Parts tab in the P menu first.", "未满足篝火点燃条件\n请先在 P 菜单的部件标签中购买当前区域所需的飞机部件。", "焚き火の点火条件を満たしていません\nPメニューの部品タブで現在の区間に必要な飛行機部品を先に購入してください。", "Conditions d’allumage du feu de camp non remplies\nAchetez d’abord les pièces d’avion du segment actuel dans l’onglet Pièces du menu P."), new UiTranslationEntry("최종 조명탄 제작에는 Legendary 제작 등급이 필요합니다.", "최종 조명탄 제작에는 전설 제작 등급이 필요합니다.", "The Legendary crafting grade is required to craft the final flare.", "制作最终信号弹需要传说制作等级。", "最終フレアの作成にはレジェンダリークラフト等級が必要です。", "Le niveau de fabrication Légendaire est requis pour fabriquer la fusée finale."), new UiTranslationEntry("가마 구간은 비행기 부품과 모닥불 진행 대상이 아닙니다.\n정상에 도착한 뒤 P 메뉴의 제작 탭에서 최종 조명탄을 제작하세요.", "가마 구간은 비행기 부품과 모닥불 진행 대상이 아닙니다.\n정상에 도착한 뒤 P 메뉴의 제작 탭에서 최종 조명탄을 제작하세요.", "The Kiln segment does not use aircraft parts or campfire progression.\nAfter reaching the Peak, craft the final flare from the Crafting tab in the P menu.", "熔炉区域不使用飞机部件或篝火推进。\n到达山顶后,在 P 菜单的制作标签中制作最终信号弹。", "窯区間では飛行機部品と焚き火進行を使用しません。\n山頂に到達したら、Pメニューのクラフトタブで最終フレアを作成してください。", "Le segment Four n’utilise ni pièces d’avion ni progression par feu de camp.\nAprès avoir atteint le Sommet, fabriquez la fusée finale dans l’onglet Fabrication du menu P."), new UiTranslationEntry("새 등반의 비행기 부품 상태가 아직 초기화되지 않았습니다.\nP 메뉴의 부품 탭을 열어 진행 상태를 초기화하세요.", "새 등반의 비행기 부품 상태가 아직 초기화되지 않았습니다.\nP 메뉴의 부품 탭을 열어 진행 상태를 초기화하세요.", "The aircraft-part state for the new climb has not been initialized.\nOpen the Parts tab in the P menu to initialize progression.", "新攀登的飞机部件状态尚未初始化。\n打开 P 菜单中的部件标签以初始化进度。", "新しい登山の飛行機部品状態がまだ初期化されていません。\nPメニューの部品タブを開いて進行状態を初期化してください。", "L’état des pièces d’avion de la nouvelle ascension n’est pas initialisé.\nOuvrez l’onglet Pièces du menu P pour initialiser la progression."), new UiTranslationEntry("\n\n가격표\nCommon 1원 · Normal 3원\nRare 7원 · Unique 15원\nLegendary 50원", "\n\n가격표\nCommon 1원 · Normal 3원\nRare 7원 · Unique 15원\nLegendary 50원", "\n\nPrice list\nCommon 1 coin · Normal 3 coins\nRare 7 coins · Unique 15 coins\nLegendary 50 coins", "\n\n价格表\n普通 1 金币 · 标准 3 金币\n稀有 7 金币 · 独特 15 金币\n传说 50 金币", "\n\n価格表\nコモン 1コイン · ノーマル 3コイン\nレア 7コイン · ユニーク 15コイン\nレジェンダリー 50コイン", "\n\nTarifs\nCommun 1 pièce · Normal 3 pièces\nRare 7 pièces · Unique 15 pièces\nLégendaire 50 pièces"), new UiTranslationEntry("부품 상태 저장에 실패했습니다. 공유 돈은 환불되었지만 재료는 복구되지 않았습니다.", "부품 상태 저장에 실패했습니다. 공유 돈은 환불되었지만 재료는 복구되지 않았습니다.", "Failed to save the part state. Shared money was refunded, but materials were not restored.", "保存部件状态失败。共享资金已退还,但材料未恢复。", "部品状態の保存に失敗しました。共有資金は返金されましたが、素材は復元されませんでした。", "Échec de l’enregistrement de l’état de la pièce. L’argent partagé a été remboursé, mais les matériaux n’ont pas été restaurés."), new UiTranslationEntry("구매 완료.\n인벤토리에는 들어가지 않으며 모닥불 진행 조건으로 저장됩니다.", "구매 완료.\n인벤토리에는 들어가지 않으며 모닥불 진행 조건으로 저장됩니다.", "Purchase complete.\nThe part is stored as a campfire progression requirement and does not enter the inventory.", "购买完成。\n部件不会进入背包,而是保存为篝火推进条件。", "購入完了。\nインベントリには入らず、焚き火の進行条件として保存されます。", "Achat terminé.\nLa pièce est enregistrée comme condition de progression du feu de camp et n’entre pas dans l’inventaire."), new UiTranslationEntry("판매 처리 중 인벤토리가 변경되어 요청한 수량을 모두 제거하지 못했습니다.", "판매 처리 중 인벤토리가 변경되어 요청한 수량을 모두 제거하지 못했습니다.", "The inventory changed during the sale, so the full requested quantity could not be removed.", "出售过程中背包发生变化,无法移除全部请求数量。", "売却処理中にインベントリが変化し、要求数量をすべて削除できませんでした。", "L’inventaire a changé pendant la vente ; toute la quantité demandée n’a pas pu être retirée."), new UiTranslationEntry("버튼을 누르면 로컬에서 +100원이 누적된 뒤 묶어서 공유됩니다.", "버튼을 누르면 로컬에서 +100원이 누적된 뒤 묶어서 공유됩니다.", "Press the button to add +100 shared money.", "按下按钮可增加 100 共享资金。", "ボタンを押すと共有資金が100増えます。", "Appuyez sur le bouton pour ajouter 100 à l’argent partagé."), new UiTranslationEntry("현재 구간의 모닥불만 다음 세그먼트 진행에 사용할 수 있습니다.", "현재 구간의 모닥불만 다음 세그먼트 진행에 사용할 수 있습니다.", "Only the current segment’s campfire can advance to the next segment.", "只有当前区域的篝火可用于推进到下一区域。", "現在の区間の焚き火のみ次の区間への進行に使用できます。", "Seul le feu de camp du segment actuel permet de progresser vers le segment suivant."), new UiTranslationEntry("번째 다음 모닥불 제작에는 다음 비행기 모듈 구매가 필요합니다.", "번째 다음 모닥불 제작에는 다음 비행기 모듈 구매가 필요합니다.", ": crafting the next campfire requires the following aircraft modules.", ":制作后续篝火需要以下飞机模块。", "番目の次の焚き火作成には以下の飛行機モジュールが必要です。", " : la fabrication du prochain feu de camp exige les modules d’avion suivants."), new UiTranslationEntry("호스트가 이번 판 제작식과 부품 재료를 확정하는 중입니다...", "호스트가 이번 판 제작식과 부품 재료를 확정하는 중입니다...", "The host is finalizing this run’s recipes and part materials...", "主机正在确定本局的配方和部件材料……", "ホストが今回のレシピと部品素材を確定しています...", "L’hôte finalise les recettes et les matériaux des pièces de cette partie..."), new UiTranslationEntry("정상에서 최종 조명탄 제작 완료. 탈출 신호를 발사했습니다.", "정상에서 최종 조명탄 제작 완료. 탈출 신호를 발사했습니다.", "Final flare crafted at the Peak. The escape signal has been launched.", "已在山顶制作最终信号弹,并发射逃脱信号。", "山頂で最終フレアを作成し、脱出信号を発射しました。", "Fusée finale fabriquée au Sommet. Le signal d’évacuation a été lancé."), new UiTranslationEntry("가마 이후 정상에 도착하면 P → 제작 → 최종 조명탄 제작", "가마 이후 정상에 도착하면 P → 제작 → 최종 조명탄 제작", "After the Kiln, reach the Peak and choose P → Crafting → Final Flare", "熔炉之后到达山顶,选择 P → 制作 → 最终信号弹", "窯の後に山頂へ到達し、P → クラフト → 最終フレア", "Après le Four, atteignez le Sommet puis P → Fabrication → Fusée finale"), new UiTranslationEntry("클릭할 때마다 호스트에게 +100원 요청을 즉시 보냅니다.\n", "클릭할 때마다 호스트에게 +100원 요청을 즉시 보냅니다.\n", "Each click immediately sends a +100 shared-money request to the host.\n", "每次点击都会立即向主机发送增加 100 共享资金的请求。\n", "クリックするたびにホストへ共有資金+100のリクエストを即時送信します。\n", "Chaque clic envoie immédiatement à l’hôte une demande de +100 d’argent partagé.\n"), new UiTranslationEntry("재료 소비 중 인벤토리가 변경되었습니다. 다시 시도하세요.", "재료 소비 중 인벤토리가 변경되었습니다. 다시 시도하세요.", "The inventory changed while consuming materials. Try again.", "消耗材料时背包发生变化,请重试。", "素材消費中にインベントリが変化しました。もう一度お試しください。", "L’inventaire a changé pendant la consommation des matériaux. Réessayez."), new UiTranslationEntry("최종 조명탄 제작과 탈출 신호 발사가 이미 완료되었습니다.", "최종 조명탄 제작과 탈출 신호 발사가 이미 완료되었습니다.", "The final flare and escape signal have already been completed.", "最终信号弹和逃脱信号已完成。", "最終フレアの作成と脱出信号の発射は完了済みです。", "La fusée finale et le signal d’évacuation ont déjà été réalisés."), new UiTranslationEntry("판매 아이템은 제거됐지만 호스트 확인 전송에 실패했습니다.", "판매 아이템은 제거됐지만 호스트 확인 전송에 실패했습니다.", "The sold item was removed, but the host confirmation failed.", "出售物品已移除,但发送主机确认失败。", "売却アイテムは削除されましたが、ホスト確認の送信に失敗しました。", "L’objet vendu a été retiré, mais l’envoi de la confirmation à l’hôte a échoué."), new UiTranslationEntry("현재 구간에서 필요한 비행기 부품만 구매할 수 있습니다.", "현재 구간에서 필요한 비행기 부품만 구매할 수 있습니다.", "You can only purchase the aircraft part required for the current segment.", "只能购买当前区域所需的飞机部件。", "現在の区間に必要な飛行機部品のみ購入できます。", "Vous ne pouvez acheter que la pièce d’avion requise pour le segment actuel."), new UiTranslationEntry("호스트 자신이 누른 경우에도 동일한 호스트 처리 경로를\n", "호스트 자신이 누른 경우에도 동일한 호스트 처리 경로를\n", "The same host-side processing path is used even when the host clicks it,\n", "即使由主机点击,也会使用相同的主机处理流程,\n", "ホスト自身が押した場合も同じホスト処理経路を使用するため、\n", "Le même traitement côté hôte est utilisé même lorsque l’hôte clique,\n"), new UiTranslationEntry("현재 구간, 제작 등급, 재료와 공유 돈을 확인하세요.", "현재 구간, 제작 등급, 재료와 공유 돈을 확인하세요.", "Check the current segment, crafting grade, materials, and shared money.", "请检查当前区域、制作等级、材料和共享资金。", "現在の区間、クラフト等級、素材、共有資金を確認してください。", "Vérifiez le segment actuel, le niveau de fabrication, les matériaux et l’argent partagé."), new UiTranslationEntry("판매 전후 스택 수량이 정확히 1 감소하지 않았습니다.", "판매 전후 스택 수량이 정확히 1 감소하지 않았습니다.", "The stack count did not decrease by exactly one during the sale.", "出售前后堆叠数量未准确减少 1。", "売却前後でスタック数が正確に1減少しませんでした。", "La pile n’a pas diminué exactement d’une unité pendant la vente."), new UiTranslationEntry("호스트는 각 요청을 독립적으로 순서대로 처리합니다.\n\n", "호스트는 각 요청을 독립적으로 순서대로 처리합니다.\n\n", "The host processes each request independently and in order.\n\n", "主机会按顺序独立处理每个请求。\n\n", "ホストは各リクエストを独立して順番に処理します。\n\n", "L’hôte traite chaque requête séparément et dans l’ordre.\n\n"), new UiTranslationEntry("판매 슬롯 또는 아이템 GUID가 올바르지 않습니다.", "판매 슬롯 또는 아이템 GUID가 올바르지 않습니다.", "The sale slot or item GUID is invalid.", "出售栏位或物品 GUID 无效。", "売却スロットまたはアイテムGUIDが不正です。", "L’emplacement de vente ou le GUID de l’objet est invalide."), new UiTranslationEntry("이미 판매 처리 중이거나 판매가 완료된 아이템입니다.", "이미 판매 처리 중이거나 판매가 완료된 아이템입니다.", "This item is already being sold or has been sold.", "该物品正在出售或已出售。", "このアイテムは売却処理中、または売却済みです。", "Cet objet est déjà en cours de vente ou a été vendu."), new UiTranslationEntry("다음 모닥불 재료 소비 중 인벤토리가 변경되었습니다.", "다음 모닥불 재료 소비 중 인벤토리가 변경되었습니다.", "The inventory changed while consuming next-campfire materials.", "消耗后续篝火材料时背包发生变化。", "次の焚き火素材を消費中にインベントリが変化しました。", "L’inventaire a changé pendant la consommation des matériaux du prochain feu de camp."), new UiTranslationEntry("강화 상태 저장에 실패했습니다. 비용을 환불했습니다.", "강화 상태 저장에 실패했습니다. 비용을 환불했습니다.", "Failed to save the upgrade state. The cost was refunded.", "保存升级状态失败,费用已退还。", "強化状態の保存に失敗しました。費用は返金されました。", "Échec de l’enregistrement de l’amélioration. Le coût a été remboursé."), new UiTranslationEntry("최종 공유 돈은 Photon 방 전체에 동기화됩니다.", "최종 공유 돈은 Photon 방 전체에 동기화됩니다.", "The final shared-money value is synchronized across the Photon room.", "最终共享资金会同步到整个 Photon 房间。", "最終的な共有資金はPhotonルーム全体に同期されます。", "La valeur finale de l’argent partagé est synchronisée dans tout le salon Photon."), new UiTranslationEntry("현재 호스트를 찾지 못해 요청을 보내지 못했습니다.", "현재 호스트를 찾지 못해 요청을 보내지 못했습니다.", "Could not find the host, so the request was not sent.", "找不到主机,未能发送请求。", "ホストが見つからず、リクエストを送信できませんでした。", "L’hôte est introuvable ; la requête n’a pas été envoyée."), new UiTranslationEntry("최종 조명탄은 정상 구간에서만 제작할 수 있습니다.", "최종 조명탄은 정상 구간에서만 제작할 수 있습니다.", "The final flare can only be crafted at the Peak.", "最终信号弹只能在山顶制作。", "最終フレアは山頂区間でのみ作成できます。", "La fusée finale ne peut être fabriquée qu’au Sommet."), new UiTranslationEntry("완성품을 받을 빈 슬롯이 없어 제작할 수 없습니다.", "완성품을 받을 빈 슬롯이 없어 제작할 수 없습니다.", "No empty slot is available for the crafted item.", "没有空栏位可接收制作物品。", "完成品を受け取る空きスロットがありません。", "Aucun emplacement libre n’est disponible pour l’objet fabriqué."), new UiTranslationEntry("판매 아이템 제거 요청 데이터가 올바르지 않습니다.", "판매 아이템 제거 요청 데이터가 올바르지 않습니다.", "The sale-item removal request data is invalid.", "出售物品移除请求数据无效。", "売却アイテム削除リクエストデータが不正です。", "Les données de la demande de retrait de l’objet vendu sont invalides."), new UiTranslationEntry("판매 확인 시간이 초과되었습니다. 다시 시도하세요.", "판매 확인 시간이 초과되었습니다. 다시 시도하세요.", "Sale confirmation timed out. Try again.", "出售确认超时,请重试。", "売却確認がタイムアウトしました。もう一度お試しください。", "La confirmation de vente a expiré. Réessayez."), new UiTranslationEntry("현재 세그먼트에 필요한 비행기 부품을 구매하세요.", "현재 세그먼트에 필요한 비행기 부품을 구매하세요.", "Purchase the aircraft part required for the current segment.", "购买当前区域所需的飞机部件。", "現在の区間に必要な飛行機部品を購入してください。", "Achetez la pièce d’avion requise pour le segment actuel."), new UiTranslationEntry("제작 완성품을 인벤토리에 지급하지 못했습니다.\n", "제작 완성품을 인벤토리에 지급하지 못했습니다.\n", "Could not place the crafted item in the inventory.\n", "无法将制作物品放入背包。\n", "完成品をインベントリに付与できませんでした。\n", "Impossible de placer l’objet fabriqué dans l’inventaire.\n"), new UiTranslationEntry("번 슬롯의 아이템은 판매 대상 자원이 아닙니다.", "번 슬롯의 아이템은 판매 대상 자원이 아닙니다.", " slot does not contain a sellable resource.", " 号栏位中的物品不是可出售资源。", "番スロットのアイテムは売却対象資源ではありません。", " ne contient pas de ressource vendable."), new UiTranslationEntry("호스트 인벤토리 처리 상태가 올바르지 않습니다.", "호스트 인벤토리 처리 상태가 올바르지 않습니다.", "The host inventory transaction state is invalid.", "主机背包处理状态无效。", "ホストのインベントリ処理状態が不正です。", "L’état de traitement de l’inventaire de l’hôte est invalide."), new UiTranslationEntry("판매 요청 수량이 현재 보유 수량보다 많습니다.", "판매 요청 수량이 현재 보유 수량보다 많습니다.", "The requested sale quantity exceeds the amount owned.", "请求出售的数量超过当前持有量。", "要求された売却数量が現在の所持数を超えています。", "La quantité demandée dépasse la quantité possédée."), new UiTranslationEntry("판매 승인 후 아이템 GUID가 변경되었습니다.", "판매 승인 후 아이템 GUID가 변경되었습니다.", "The item GUID changed after sale approval.", "出售批准后物品 GUID 发生变化。", "売却承認後にアイテムGUIDが変更されました。", "Le GUID de l’objet a changé après l’approbation de la vente."), new UiTranslationEntry("현재 Photon 방에 입장해 있지 않습니다.", "현재 Photon 방에 입장해 있지 않습니다.", "You are not currently in a Photon room.", "当前未加入 Photon 房间。", "現在 Photon ルームに参加していません。", "Vous n’êtes actuellement dans aucun salon Photon."), new UiTranslationEntry("이 구간의 비행기 부품은 이미 사용되었습니다.", "이 구간의 비행기 부품은 이미 사용되었습니다.", "The aircraft part for this segment has already been consumed.", "本区域的飞机部件已使用。", "この区間の飛行機部品はすでに使用済みです。", "La pièce d’avion de ce segment a déjà été utilisée."), new UiTranslationEntry("선택한 슬롯에는 판매 가능한 자원이 없습니다.", "선택한 슬롯에는 판매 가능한 자원이 없습니다.", "The selected slot contains no sellable resource.", "所选栏位中没有可出售资源。", "選択したスロットに売却可能な資源がありません。", "L’emplacement sélectionné ne contient aucune ressource vendable."), new UiTranslationEntry("판매 승인 전에 아이템 정보가 변경되었습니다.", "판매 승인 전에 아이템 정보가 변경되었습니다.", "The item data changed before sale approval.", "出售批准前物品信息发生变化。", "売却承認前にアイテム情報が変更されました。", "Les données de l’objet ont changé avant l’approbation de la vente."), new UiTranslationEntry("판매 수량을 인벤토리에서 제거하지 못했습니다.", "판매 수량을 인벤토리에서 제거하지 못했습니다.", "Could not remove the sale quantity from the inventory.", "无法从背包中移除出售数量。", "売却数量をインベントリから削除できませんでした。", "Impossible de retirer la quantité vendue de l’inventaire."), new UiTranslationEntry("판매 직전에 아이템 GUID가 변경되었습니다.", "판매 직전에 아이템 GUID가 변경되었습니다.", "The item GUID changed immediately before the sale.", "出售前物品 GUID 发生变化。", "売却直前にアイテムGUIDが変更されました。", "Le GUID de l’objet a changé juste avant la vente."), new UiTranslationEntry("판매 승인 후 슬롯의 아이템이 변경되었습니다.", "판매 승인 후 슬롯의 아이템이 변경되었습니다.", "The slot item changed after sale approval.", "出售批准后栏位中的物品发生变化。", "売却承認後にスロットのアイテムが変更されました。", "L’objet de l’emplacement a changé après l’approbation de la vente."), new UiTranslationEntry("판매 확인을 전달할 호스트를 찾지 못했습니다.", "판매 확인을 전달할 호스트를 찾지 못했습니다.", "Could not find the host to send the sale confirmation.", "找不到用于发送出售确认的主机。", "売却確認を送るホストが見つかりません。", "L’hôte auquel envoyer la confirmation de vente est introuvable."), new UiTranslationEntry("공유 돈 +100 요청 전송에 실패했습니다.", "공유 돈 +100 요청 전송에 실패했습니다.", "Failed to send the +100 shared-money request.", "发送增加 100 共享资金的请求失败。", "共有資金+100のリクエスト送信に失敗しました。", "Échec de l’envoi de la demande de +100 d’argent partagé."), new UiTranslationEntry("비행기 부품 구매 요청 전송에 실패했습니다.", "비행기 부품 구매 요청 전송에 실패했습니다.", "Failed to send the aircraft-part purchase request.", "发送飞机部件购买请求失败。", "飛行機部品購入リクエストの送信に失敗しました。", "Échec de l’envoi de la demande d’achat de pièce d’avion."), new UiTranslationEntry("돈은 환불됐지만 재료는 복구되지 않았습니다.", "돈은 환불됐지만 재료는 복구되지 않았습니다.", "Money was refunded, but materials were not restored.", "资金已退还,但材料未恢复。", "お金は返金されましたが、素材は復元されませんでした。", "L’argent a été remboursé, mais les matériaux n’ont pas été restaurés."), new UiTranslationEntry("판매 직전에 슬롯의 아이템이 변경되었습니다.", "판매 직전에 슬롯의 아이템이 변경되었습니다.", "The slot item changed immediately before the sale.", "出售前栏位中的物品发生变化。", "売却直前にスロットのアイテムが変更されました。", "L’objet de l’emplacement a changé juste avant la vente."), new UiTranslationEntry("CraftHub가 아직 준비되지 않았습니다.", "CraftHub가 아직 준비되지 않았습니다.", "CraftHub is not ready yet.", "CraftHub 尚未准备好。", "CraftHubの準備がまだできていません。", "CraftHub n’est pas encore prêt."), new UiTranslationEntry("직접 실행하므로 요청이 누락되지 않습니다.\n", "직접 실행하므로 요청이 누락되지 않습니다.\n", "so requests are not lost.\n", "因此请求不会丢失。\n", "リクエストが失われることはありません。\n", "ce qui évite toute perte de requête.\n"), new UiTranslationEntry("호스트의 처리 결과를 확인하지 못했습니다.", "호스트의 처리 결과를 확인하지 못했습니다.", "Could not verify the host’s result.", "无法确认主机的处理结果。", "ホストの処理結果を確認できませんでした。", "Impossible de vérifier le résultat de l’hôte."), new UiTranslationEntry("이미 구매했거나 사용한 비행기 부품입니다.", "이미 구매했거나 사용한 비행기 부품입니다.", "This aircraft part has already been purchased or consumed.", "该飞机部件已购买或使用。", "この飛行機部品は購入済み、または使用済みです。", "Cette pièce d’avion a déjà été achetée ou utilisée."), new UiTranslationEntry("Legendary 제작 등급이 필요합니다.", "Legendary 제작 등급이 필요합니다.", "Legendary crafting grade is required.", "需要 Legendary 制作等级。", "Legendaryクラフト等級が必要です。", "Le niveau de fabrication Legendary est requis."), new UiTranslationEntry("제작 성공! 추가 손 슬롯에 장착했습니다.", "제작 성공! 추가 손 슬롯에 장착했습니다.", "crafted! Equipped in the extra hand slot.", "制作成功!已装备到额外手持栏位。", "作成成功!追加の手スロットに装備しました。", "fabriqué ! Équipé dans l’emplacement de main supplémentaire."), new UiTranslationEntry("개발자 요청 데이터가 올바르지 않습니다.", "개발자 요청 데이터가 올바르지 않습니다.", "The developer request data is invalid.", "开发者请求数据无效。", "開発者リクエストデータが不正です。", "Les données de la requête développeur sont invalides."), new UiTranslationEntry("호스트 결과 데이터가 올바르지 않습니다.", "호스트 결과 데이터가 올바르지 않습니다.", "The host result data is invalid.", "主机结果数据无效。", "ホスト結果データが不正です。", "Les données du résultat de l’hôte sont invalides."), new UiTranslationEntry("비행기 부품 구매 요청이 너무 빠릅니다.", "비행기 부품 구매 요청이 너무 빠릅니다.", "Aircraft-part purchase requests are being sent too quickly.", "飞机部件购买请求过于频繁。", "飛行機部品購入リクエストが早すぎます。", "Les demandes d’achat de pièce d’avion sont trop rapides."), new UiTranslationEntry("비행기 부품 번호를 해석하지 못했습니다.", "비행기 부품 번호를 해석하지 못했습니다.", "Could not read the aircraft-part number.", "无法解析飞机部件编号。", "飛行機部品番号を解析できませんでした。", "Impossible d’interpréter le numéro de la pièce d’avion."), new UiTranslationEntry("제작 아이템 번호를 해석하지 못했습니다.", "제작 아이템 번호를 해석하지 못했습니다.", "Could not read the crafted-item number.", "无法解析制作物品编号。", "クラフトアイテム番号を解析できませんでした。", "Impossible d’interpréter le numéro de l’objet à fabriquer."), new UiTranslationEntry("제작 데이터베이스가 준비되지 않았습니다.", "제작 데이터베이스가 준비되지 않았습니다.", "The crafting database is not ready.", "制作数据库尚未准备好。", "クラフトデータベースの準備ができていません。", "La base de données de fabrication n’est pas prête."), new UiTranslationEntry("선택한 인벤토리 슬롯을 찾지 못했습니다.", "선택한 인벤토리 슬롯을 찾지 못했습니다.", "Could not find the selected inventory slot.", "找不到所选背包栏位。", "選択したインベントリスロットが見つかりません。", "Emplacement d’inventaire sélectionné introuvable."), new UiTranslationEntry("이 아이템은 판매 대상 자원이 아닙니다.", "이 아이템은 판매 대상 자원이 아닙니다.", "This item is not a sellable resource.", "此物品不是可出售资源。", "このアイテムは売却対象資源ではありません。", "Cet objet n’est pas une ressource vendable."), new UiTranslationEntry("판매 요청 데이터를 해석하지 못했습니다.", "판매 요청 데이터를 해석하지 못했습니다.", "Could not read the sell request data.", "无法解析出售请求数据。", "売却リクエストデータを解析できませんでした。", "Impossible d’interpréter les données de la demande de vente."), new UiTranslationEntry("판매 요청 플레이어를 찾을 수 없습니다.", "판매 요청 플레이어를 찾을 수 없습니다.", "Could not find the player who requested the sale.", "找不到发起出售请求的玩家。", "売却を要求したプレイヤーが見つかりません。", "Le joueur ayant demandé la vente est introuvable."), new UiTranslationEntry("판매 가격이 설정되지 않은 아이템입니다.", "판매 가격이 설정되지 않은 아이템입니다.", "No sale price is configured for this item.", "此物品未设置售价。", "このアイテムには売却価格が設定されていません。", "Aucun prix de vente n’est défini pour cet objet."), new UiTranslationEntry("보유 수량보다 많이 판매할 수 없습니다.", "보유 수량보다 많이 판매할 수 없습니다.", "You cannot sell more than the quantity owned.", "出售数量不能超过持有数量。", "所持数を超えて売却することはできません。", "Vous ne pouvez pas vendre plus que la quantité possédée."), new UiTranslationEntry("판매 트랜잭션 정보가 올바르지 않습니다.", "판매 트랜잭션 정보가 올바르지 않습니다.", "The sale transaction data is invalid.", "出售交易信息无效。", "売却トランザクション情報が不正です。", "Les informations de la transaction de vente sont invalides."), new UiTranslationEntry("판매자 로컬 인벤토리를 찾지 못했습니다.", "판매자 로컬 인벤토리를 찾지 못했습니다.", "Could not find the seller’s local inventory.", "找不到卖家的本地背包。", "売却者のローカルインベントリが見つかりません。", "Inventaire local du vendeur introuvable."), new UiTranslationEntry("판매 결과 데이터가 올바르지 않습니다.", "판매 결과 데이터가 올바르지 않습니다.", "The sell result data is invalid.", "出售结果数据无效。", "売却結果データが不正です。", "Les données du résultat de vente sont invalides."), new UiTranslationEntry("제작 결과 데이터가 올바르지 않습니다.", "제작 결과 데이터가 올바르지 않습니다.", "The craft result data is invalid.", "制作结果数据无效。", "クラフト結果データが不正です。", "Les données du résultat de fabrication sont invalides."), new UiTranslationEntry("강화 결과 데이터가 올바르지 않습니다.", "강화 결과 데이터가 올바르지 않습니다.", "The upgrade result data is invalid.", "升级结果数据无效。", "強化結果データが不正です。", "Les données du résultat d’amélioration sont invalides."), new UiTranslationEntry("공유 돈 또는 제작 재료가 부족합니다.", "공유 돈 또는 제작 재료가 부족합니다.", "Not enough shared money or crafting materials.", "共享资金或制作材料不足。", "共有資金またはクラフト素材が不足しています。", "L’argent partagé ou les matériaux de fabrication sont insuffisants."), new UiTranslationEntry("번 슬롯을 판매 대상으로 선택했습니다.", "번 슬롯을 판매 대상으로 선택했습니다.", " slot selected for sale.", " 号栏位已选为出售目标。", "番スロットを売却対象に選択しました。", " sélectionné pour la vente."), new UiTranslationEntry("강화 상태를 아직 불러오지 못했습니다.", "강화 상태를 아직 불러오지 못했습니다.", "The upgrade state has not loaded yet.", "升级状态尚未加载。", "強化状態がまだ読み込まれていません。", "L’état des améliorations n’est pas encore chargé."), new UiTranslationEntry("모든 다음 모닥불을 이미 제작했습니다.", "모든 다음 모닥불을 이미 제작했습니다.", "All next campfires have already been crafted.", "所有后续篝火均已制作。", "すべての次の焚き火は作成済みです。", "Tous les prochains feux de camp ont déjà été fabriqués."), new UiTranslationEntry("Purple Mushroom Berry", "보라색 버섯열매", "Purple Mushroom Berry", "紫色蘑菇莓", "紫のマッシュルームベリー", "Baie-champignon violette"), new UiTranslationEntry("제작품 지급 중 오류가 발생했습니다.", "제작품 지급 중 오류가 발생했습니다.", "An error occurred while delivering the crafted item.", "发放制作物品时发生错误。", "クラフト品の付与中にエラーが発生しました。", "Une erreur est survenue lors de la remise de l’objet fabriqué."), new UiTranslationEntry("잘못된 비행기 부품 구매 요청입니다.", "잘못된 비행기 부품 구매 요청입니다.", "Invalid aircraft-part purchase request.", "飞机部件购买请求无效。", "飛行機部品購入リクエストが不正です。", "Demande d’achat de pièce d’avion invalide."), new UiTranslationEntry("비행기 부품 구매 결과를 받았습니다.", "비행기 부품 구매 결과를 받았습니다.", "Aircraft-part purchase result received.", "已收到飞机部件购买结果。", "飛行機部品購入結果を受信しました。", "Résultat de l’achat de pièce d’avion reçu."), new UiTranslationEntry("플레이어 인벤토리를 찾지 못했습니다.", "플레이어 인벤토리를 찾지 못했습니다.", "Could not find the player inventory.", "找不到玩家背包。", "プレイヤーのインベントリが見つかりません。", "Inventaire du joueur introuvable."), new UiTranslationEntry("판매할 인벤토리 슬롯을 선택하세요.", "판매할 인벤토리 슬롯을 선택하세요.", "Select an inventory slot to sell.", "请选择要出售的背包栏位。", "売却するインベントリスロットを選択してください。", "Sélectionnez un emplacement d’inventaire à vendre."), new UiTranslationEntry("허용되지 않은 공유 돈 요청입니다.", "허용되지 않은 공유 돈 요청입니다.", "This shared-money request is not allowed.", "不允许此共享资金请求。", "この共有資金リクエストは許可されていません。", "Cette demande d’argent partagé n’est pas autorisée."), new UiTranslationEntry("공유 돈 요청 처리에 실패했습니다.", "공유 돈 요청 처리에 실패했습니다.", "Failed to process the shared-money request.", "处理共享资金请求失败。", "共有資金リクエストの処理に失敗しました。", "Échec du traitement de la demande d’argent partagé."), new UiTranslationEntry("판매 아이템을 제거하지 못했습니다.", "판매 아이템을 제거하지 못했습니다.", "Could not remove the sold item.", "无法移除出售物品。", "売却アイテムを削除できませんでした。", "Impossible de retirer l’objet vendu."), new UiTranslationEntry("강화 상태를 초기화하지 못했습니다.", "강화 상태를 초기화하지 못했습니다.", "Could not initialize the upgrade state.", "无法初始化升级状态。", "強化状態を初期化できませんでした。", "Impossible d’initialiser l’état des améliorations."), new UiTranslationEntry("개 판매 요청을 처리 중입니다...", "개 판매 요청을 처리 중입니다...", " units are being processed for sale...", "个出售请求正在处理……", "個の売却リクエストを処理中です...", " unités : demande de vente en cours..."), new UiTranslationEntry("Blue Mushroom Berry", "파란색 버섯열매", "Blue Mushroom Berry", "蓝色蘑菇莓", "青いマッシュルームベリー", "Baie-champignon bleue"), new UiTranslationEntry("판매 결과를 해석하지 못했습니다.", "판매 결과를 해석하지 못했습니다.", "Could not read the sell result.", "无法解析出售结果。", "売却結果を解析できませんでした。", "Impossible d’interpréter le résultat de vente."), new UiTranslationEntry("제작 결과를 해석하지 못했습니다.", "제작 결과를 해석하지 못했습니다.", "Could not read the craft result.", "无法解析制作结果。", "クラフト結果を解析できませんでした。", "Impossible d’interpréter le résultat de fabrication."), new UiTranslationEntry("구매할 비행기 부품을 선택하세요.", "구매할 비행기 부품을 선택하세요.", "Select an aircraft part to purchase.", "请选择要购买的飞机部件。", "購入する飛行機部品を選択してください。", "Sélectionnez une pièce d’avion à acheter."), new UiTranslationEntry("존재하지 않는 비행기 부품입니다.", "존재하지 않는 비행기 부품입니다.", "That aircraft part does not exist.", "该飞机部件不存在。", "存在しない飛行機部品です。", "Cette pièce d’avion n’existe pas."), new UiTranslationEntry("판매할 아이템이 슬롯에 없습니다.", "판매할 아이템이 슬롯에 없습니다.", "The item to sell is not in the slot.", "要出售的物品不在栏位中。", "売却するアイテムがスロットにありません。", "L’objet à vendre n’est pas dans l’emplacement."), new UiTranslationEntry("판매 아이템 1개를 제거했습니다.", "판매 아이템 1개를 제거했습니다.", "Removed one sold item.", "已移除 1 个出售物品。", "売却アイテムを1個削除しました。", "Un objet vendu a été retiré."), new UiTranslationEntry("강화 종류를 해석하지 못했습니다.", "강화 종류를 해석하지 못했습니다.", "Could not read the upgrade type.", "无法解析升级类型。", "強化種類を解析できませんでした。", "Impossible d’interpréter le type d’amélioration."), new UiTranslationEntry("현재 판매 수익: 기본 판매가 x", "현재 판매 수익: 기본 판매가 x", "Current sale value: base price x", "当前出售收益:基础售价 x", "現在の売却利益: 基本価格 x", "Valeur de vente actuelle : prix de base x"), new UiTranslationEntry("공유 돈 +100 요청 전송 완료", "공유 돈 +100 요청 전송 완료", "Shared-money +100 request sent", "已发送增加 100 共享资金的请求", "共有資金+100リクエスト送信完了", "Demande de +100 d’argent partagé envoyée"), new UiTranslationEntry("개발자 치트: 공유 돈 +100원", "개발자 치트: 공유 돈 +100원", "Developer cheat: shared money +100 coins", "开发者作弊:共享资金 +100 金币", "開発者チート: 共有資金+100コイン", "Triche développeur : argent partagé +100 pièces"), new UiTranslationEntry("Orange Winterberry", "주황 겨울열매", "Orange Winterberry", "橙色冬莓", "オレンジ・ウィンターベリー", "Baie d’hiver orange"), new UiTranslationEntry("Red Mushroom Berry", "빨간색 버섯 열매", "Red Mushroom Berry", "红色蘑菇莓", "赤いマッシュルームベリー", "Baie-champignon rouge"), new UiTranslationEntry("구매 완료 · 모닥불 점화 가능", "구매 완료 · 모닥불 점화 가능", "Purchased · Campfire can be lit", "已购买 · 可点燃篝火", "購入済み・焚き火を点火可能", "Acheté · Feu de camp allumable"), new UiTranslationEntry("제작 요청 전송에 실패했습니다.", "제작 요청 전송에 실패했습니다.", "Failed to send the craft request.", "发送制作请求失败。", "クラフトリクエストの送信に失敗しました。", "Échec de l’envoi de la demande de fabrication."), new UiTranslationEntry("Shift + 휠: 5개씩 조절", "Shift + 휠: 5개씩 조절", "Shift + wheel: adjust by 5", "Shift + 滚轮:每次调整 5", "Shift + ホイール: 5個ずつ調整", "Maj + molette : ajuster de 5"), new UiTranslationEntry("판매 요청 전송에 실패했습니다.", "판매 요청 전송에 실패했습니다.", "Failed to send the sell request.", "发送出售请求失败。", "売却リクエストの送信に失敗しました。", "Échec de l’envoi de la demande de vente."), new UiTranslationEntry("강화 요청 전송에 실패했습니다.", "강화 요청 전송에 실패했습니다.", "Failed to send the upgrade request.", "发送升级请求失败。", "強化リクエストの送信に失敗しました。", "Échec de l’envoi de la demande d’amélioration."), new UiTranslationEntry("단계 모닥불을 먼저 제작하세요.", "단계 모닥불을 먼저 제작하세요.", " campfire must be crafted first.", "级篝火。", "段階の焚き火を先に作成してください。", " du feu de camp doit d’abord être fabriqué."), new UiTranslationEntry("Scoutmaster Bugle", "스카우트지도자의 나팔", "Scoutmaster Bugle", "童军领队号角", "スカウトマスターのラッパ", "Clairon du chef scout"), new UiTranslationEntry("열대/뿌리숲 → 메사/고산지대", "열대/뿌리숲 → 메사/고산지대", "Tropics/Roots → Mesa/Alpine", "热带/根系森林 → 台地/高山", "熱帯/根の森 → メサ/高山", "Tropiques/Forêt de racines → Mesa/Alpin"), new UiTranslationEntry("선택한 슬롯이 비어 있습니다.", "선택한 슬롯이 비어 있습니다.", "The selected slot is empty.", "所选栏位为空。", "選択したスロットは空です。", "L’emplacement sélectionné est vide."), new UiTranslationEntry("마우스 휠 ↑↓: 1개씩 조절", "마우스 휠 ↑↓: 1개씩 조절", "Mouse wheel ↑↓: adjust by 1", "鼠标滚轮 ↑↓:每次调整 1", "マウスホイール ↑↓: 1個ずつ調整", "Molette ↑↓ : ajuster de 1"), new UiTranslationEntry("Anti-Rope Cannon", "반전 밧줄총", "Anti-Rope Cannon", "反向绳索炮", "反転ロープキャノン", "Canon à corde inversé"), new UiTranslationEntry("Friendship Bugle", "우정 나팔", "Friendship Bugle", "友谊号角", "友情のラッパ", "Clairon de l’amitié"), new UiTranslationEntry("Golden Bing Bong", "황금 빙봉", "Golden Bing Bong", "黄金冰棒", "ゴールデン・ビン・ボン", "Bing Bong doré"), new UiTranslationEntry("Yellow Berrynana", "노란색 열매나나", "Yellow Berrynana", "黄色莓蕉", "黄色いベリーナナ", "Berrynana jaune"), new UiTranslationEntry("Red Clusterberry", "빨간 송송열매", "Red Clusterberry", "红色簇莓", "赤いクラスターベリー", "Baie en grappe rouge"), new UiTranslationEntry("Trumpet Mushroom", "나팔버섯", "Trumpet Mushroom", "喇叭蘑菇", "ラッパキノコ", "Champignon trompette"), new UiTranslationEntry("제작할 아이템을 선택하세요.", "제작할 아이템을 선택하세요.", "Select an item to craft.", "请选择要制作的物品。", "クラフトするアイテムを選択してください。", "Sélectionnez un objet à fabriquer."), new UiTranslationEntry("요청 시간이 초과되었습니다.", "요청 시간이 초과되었습니다.", "The request timed out.", "请求超时。", "リクエストがタイムアウトしました。", "La requête a expiré."), new UiTranslationEntry("다른 요청을 처리 중입니다.", "다른 요청을 처리 중입니다.", "Another request is being processed.", "正在处理其他请求。", "別のリクエストを処理中です。", "Une autre requête est en cours."), new UiTranslationEntry("제작 요청이 거부되었습니다.", "제작 요청이 거부되었습니다.", "The craft request was rejected.", "制作请求被拒绝。", "クラフト要求が拒否されました。", "La demande de fabrication a été refusée."), new UiTranslationEntry("현재는 제작할 수 없습니다.", "현재는 제작할 수 없습니다.", "Crafting is not currently available.", "当前无法制作。", "現在はクラフトできません。", "La fabrication n’est pas disponible actuellement."), new UiTranslationEntry("제작 요청이 너무 빠릅니다.", "제작 요청이 너무 빠릅니다.", "Craft requests are being sent too quickly.", "制作请求过于频繁。", "クラフトリクエストが早すぎます。", "Les demandes de fabrication sont trop rapides."), new UiTranslationEntry("등록되지 않은 제작식입니다.", "등록되지 않은 제작식입니다.", "This recipe is not registered.", "该配方未注册。", "未登録のレシピです。", "Cette recette n’est pas enregistrée."), new UiTranslationEntry("플레이어를 찾지 못했습니다.", "플레이어를 찾지 못했습니다.", "Could not find the player.", "找不到玩家。", "プレイヤーが見つかりません。", "Joueur introuvable."), new UiTranslationEntry("판매 요청이 너무 빠릅니다.", "판매 요청이 너무 빠릅니다.", "Sell requests are being sent too quickly.", "出售请求过于频繁。", "売却リクエストが早すぎます。", "Les demandes de vente sont trop rapides."), new UiTranslationEntry("현재는 강화할 수 없습니다.", "현재는 강화할 수 없습니다.", "Upgrades are not currently available.", "当前无法升级。", "現在は強化できません。", "Les améliorations ne sont pas disponibles actuellement."), new UiTranslationEntry("강화 요청이 너무 빠릅니다.", "강화 요청이 너무 빠릅니다.", "Upgrade requests are being sent too quickly.", "升级请求过于频繁。", "強化リクエストが早すぎます。", "Les demandes d’amélioration sont trop rapides."), new UiTranslationEntry("모든 다음 모닥불 제작 완료", "모든 다음 모닥불 제작 완료", "All next campfires completed", "所有后续篝火制作完成", "すべての次の焚き火を作成完了", "Tous les prochains feux de camp sont terminés"), new UiTranslationEntry("<아이템 데이터 확인 필요>", "<아이템 데이터 확인 필요>", "", "<需要物品数据>", "<アイテムデータ要確認>", ""), new UiTranslationEntry("제작 단계 | 다음 모닥불 ", "제작 단계 | 다음 모닥불 ", "crafting level | next campfire ", "制作等级 | 后续篝火 ", "クラフト段階 | 次の焚き火 ", "niveau de fabrication | prochain feu de camp "), new UiTranslationEntry("Checkpoint Flag", "체크포인트 깃발", "Checkpoint Flag", "检查点旗帜", "チェックポイント旗", "Drapeau de point de contrôle"), new UiTranslationEntry("Anti-Rope Spool", "반전 밧줄타래", "Anti-Rope Spool", "反向绳索卷", "反転ロープスプール", "Bobine de corde inversée"), new UiTranslationEntry("Green Kingberry", "녹색 대왕열매", "Green Kingberry", "绿色王莓", "緑のキングベリー", "Baie royale verte"), new UiTranslationEntry("Bundle Mushroom", "다발버섯", "Bundle Mushroom", "束状蘑菇", "束キノコ", "Champignon en bouquet"), new UiTranslationEntry("Button Mushroom", "단추버섯", "Button Mushroom", "纽扣蘑菇", "ボタンキノコ", "Champignon bouton"), new UiTranslationEntry("Honeycomb Honey", "벌집꿀", "Honeycomb Honey", "蜂巢蜜", "巣蜜", "Miel en rayon"), new UiTranslationEntry("이전 모닥불에서 사용 완료", "이전 모닥불에서 사용 완료", "Consumed at the previous campfire", "已在前一个篝火使用", "前の焚き火で使用済み", "Utilisé au feu de camp précédent"), new UiTranslationEntry("잘못된 제작 아이템입니다.", "잘못된 제작 아이템입니다.", "Invalid crafted item.", "制作物品无效。", "不正なクラフトアイテムです。", "Objet à fabriquer invalide."), new UiTranslationEntry("번 슬롯은 비어 있습니다.", "번 슬롯은 비어 있습니다.", " slot is empty.", " 号栏位为空。", "番スロットは空です。", " est vide."), new UiTranslationEntry("존재하지 않는 강화입니다.", "존재하지 않는 강화입니다.", "That upgrade does not exist.", "该升级不存在。", "存在しない強化です。", "Cette amélioration n’existe pas."), new UiTranslationEntry("최대 단계에 도달했습니다.", "최대 단계에 도달했습니다.", "Maximum level reached.", "已达到最高等级。", "最大レベルに到達しました。", "Niveau maximal atteint."), new UiTranslationEntry("Portable Stove", "휴대용 스토브", "Portable Stove", "便携炉", "携帯ストーブ", "Réchaud portable"), new UiTranslationEntry("Chain Launcher", "사슬발사기", "Chain Launcher", "链条发射器", "チェーンランチャー", "Lance-chaîne"), new UiTranslationEntry("Pirate Compass", "해적 나침반", "Pirate Compass", "海盗罗盘", "海賊のコンパス", "Boussole de pirate"), new UiTranslationEntry("Red Crispberry", "빨간색 아삭 열매", "Red Crispberry", "红色脆莓", "赤いクリスプベリー", "Baie croquante rouge"), new UiTranslationEntry("Fortified Milk", "강화우유", "Fortified Milk", "强化牛奶", "強化ミルク", "Lait fortifié"), new UiTranslationEntry("Red Thornberry", "빨간 가시열매", "Red Thornberry", "红色刺莓", "赤いソーンベリー", "Baie épineuse rouge"), new UiTranslationEntry("강화 항목을 선택하세요.", "강화 항목을 선택하세요.", "Select an upgrade.", "请选择升级项目。", "強化項目を選択してください。", "Sélectionnez une amélioration."), new UiTranslationEntry("진행 조건을 확인하세요.", "진행 조건을 확인하세요.", "Check the progression requirements.", "请检查推进条件。", "進行条件を確認してください。", "Vérifiez les conditions de progression."), new UiTranslationEntry("강화 결과를 받았습니다.", "강화 결과를 받았습니다.", "Upgrade result received.", "已收到升级结果。", "強化結果を受信しました。", "Résultat de l’amélioration reçu."), new UiTranslationEntry("메사/고산지대 → 칼데라", "메사/고산지대 → 칼데라", "Mesa/Alpine → Caldera", "台地/高山 → 火山口", "メサ/高山 → カルデラ", "Mesa/Alpin → Caldeira"), new UiTranslationEntry("아직 도달하지 않은 구간", "아직 도달하지 않은 구간", "Segment not reached yet", "尚未到达的区域", "未到達の区間", "Segment pas encore atteint"), new UiTranslationEntry("모닥불 점화 조건 미충족", "모닥불 점화 조건 미충족", "Campfire requirements not met", "未满足篝火点燃条件", "焚き火の点火条件を満たしていません", "Conditions d’allumage du feu de camp non remplies"), new UiTranslationEntry("제작 목록을 표시합니다.", "제작 목록을 표시합니다.", " crafting list displayed.", " 制作列表已显示。", " クラフト一覧を表示します。", " : liste de fabrication affichée."), new UiTranslationEntry("제작 등급이 필요합니다.", "제작 등급이 필요합니다.", "crafting grade is required.", "需要制作等级。", "クラフト等級が必要です。", "un niveau de fabrication est requis."), new UiTranslationEntry("잘못된 제작 요청입니다.", "잘못된 제작 요청입니다.", "Invalid craft request.", "制作请求无效。", "クラフトリクエストが不正です。", "Demande de fabrication invalide."), new UiTranslationEntry("잘못된 판매 요청입니다.", "잘못된 판매 요청입니다.", "Invalid sell request.", "出售请求无效。", "売却リクエストが不正です。", "Demande de vente invalide."), new UiTranslationEntry("자원 등급이 필요합니다.", "자원 등급이 필요합니다.", "resource grade is required.", "需要资源等级。", "資源等級が必要です。", "un niveau de ressource est requis."), new UiTranslationEntry("잘못된 강화 요청입니다.", "잘못된 강화 요청입니다.", "Invalid upgrade request.", "升级请求无效。", "強化リクエストが不正です。", "Demande d’amélioration invalide."), new UiTranslationEntry("구매를 요청했습니다...", "구매를 요청했습니다...", "purchase requested...", "已请求购买……", "の購入をリクエストしました...", ": achat demandé..."), new UiTranslationEntry("제작을 요청했습니다...", "제작을 요청했습니다...", "craft requested...", "已请求制作……", "のクラフトをリクエストしました...", ": fabrication demandée..."), new UiTranslationEntry("강화를 요청했습니다...", "강화를 요청했습니다...", "upgrade requested...", "已请求升级……", "の強化をリクエストしました...", ": amélioration demandée..."), new UiTranslationEntry("Climbing Gear", "등산 장비", "Climbing Gear", "攀登装备", "登山装備", "Équipement d’escalade"), new UiTranslationEntry("Utility Items", "기타 아이템", "Utility Items", "其他物品", "その他のアイテム", "Objets utilitaires"), new UiTranslationEntry("Bounce Fungus", "방방 균류", "Bounce Fungus", "弹跳菌", "バウンドキノコ", "Champignon rebondissant"), new UiTranslationEntry("Balloon Bunch", "풍선 다발", "Balloon Bunch", "气球束", "風船の束", "Bouquet de ballons"), new UiTranslationEntry("Book of Bones", "뼈의서", "Book of Bones", "骨之书", "骨の書", "Livre des os"), new UiTranslationEntry("Rainbow Candy", "무지개사탕", "Rainbow Candy", "彩虹糖", "レインボーキャンディ", "Bonbon arc-en-ciel"), new UiTranslationEntry("Fairy Lantern", "요정랜턴", "Fairy Lantern", "仙女灯笼", "妖精のランタン", "Lanterne féerique"), new UiTranslationEntry("Puff Mushroom", "통통버섯", "Puff Mushroom", "膨膨蘑菇", "パフキノコ", "Champignon gonflé"), new UiTranslationEntry("Pandora’s Box", "판도라의 상자", "Pandora’s Box", "潘多拉魔盒", "パンドラの箱", "Boîte de Pandore"), new UiTranslationEntry("First Aid Kit", "구급상자", "First Aid Kit", "急救箱", "救急箱", "Trousse de secours"), new UiTranslationEntry("공유 돈이 부족합니다.", "공유 돈이 부족합니다.", "Not enough shared money.", "共享资金不足。", "共有資金が不足しています。", "L’argent partagé est insuffisant."), new UiTranslationEntry("부활 / 스카우트 인형", "부활 / 스카우트 인형", "Revive / Scout Effigy", "复活 / 童军雕像", "蘇生 / スカウト像", "Résurrection / Effigie scout"), new UiTranslationEntry("음식 / 녹색 대왕열매", "음식 / 녹색 대왕열매", "Food / Green Kingberry", "食物 / 绿色王莓", "食料 / グリーン・キングベリー", "Nourriture / Baie royale verte"), new UiTranslationEntry("제작식을 선택했습니다.", "제작식을 선택했습니다.", " recipe selected.", " 已选择配方。", " レシピを選択しました。", " : recette sélectionnée."), new UiTranslationEntry("이(가) 부족합니다. ", "이(가) 부족합니다. ", "is insufficient. ", "不足。", "が不足しています。", "est insuffisant. "), new UiTranslationEntry("이미 최대 단계입니다.", "이미 최대 단계입니다.", "Already at maximum level.", "已达到最高等级。", "すでに最大レベルです。", "Le niveau maximal est déjà atteint."), new UiTranslationEntry("완성된 다음 모닥불: ", "완성된 다음 모닥불: ", "Completed next campfires: ", "已完成的后续篝火:", "完成した次の焚き火: ", "Prochains feux de camp terminés : "), new UiTranslationEntry("을(를) 선택했습니다.", "을(를) 선택했습니다.", " selected.", " 已选择。", "を選択しました。", " sélectionné."), new UiTranslationEntry(" 1개 판매 완료: +", " 1개 판매 완료: +", " sold 1 unit: +", " 出售 1 个:+", "を1個売却: +", " vendu, 1 unité : +"), new UiTranslationEntry("개발자 테스트 치트\n\n", "개발자 테스트 치트\n\n", "Developer test cheat\n\n", "开发者测试作弊\n\n", "開発者テストチート\n\n", "Triche de test développeur\n\n"), new UiTranslationEntry("Final Escape", "최종 탈출", "Final Escape", "最终逃脱", "最終脱出", "Évasion finale"), new UiTranslationEntry("Weird Shroom", "괴상 버섯", "Weird Shroom", "怪异蘑菇", "奇妙なキノコ", "Champignon étrange"), new UiTranslationEntry("Energy Drink", "에너지 드링크", "Energy Drink", "能量饮料", "エナジードリンク", "Boisson énergisante"), new UiTranslationEntry("Shelf Fungus", "선반 균류", "Shelf Fungus", "层孔菌", "棚キノコ", "Champignon en console"), new UiTranslationEntry("Cloud Fungus", "구름균류", "Cloud Fungus", "云菌", "雲キノコ", "Champignon nuage"), new UiTranslationEntry("Scout Cannon", "스카우트 캐논", "Scout Cannon", "童军大炮", "スカウトキャノン", "Canon scout"), new UiTranslationEntry("Cursed Skull", "저주받은 해골", "Cursed Skull", "诅咒头骨", "呪われた頭蓋骨", "Crâne maudit"), new UiTranslationEntry("Coconut Half", "코코넛 반쪽", "Coconut Half", "半个椰子", "ココナッツ半分", "Demi-noix de coco"), new UiTranslationEntry("Sports Drink", "스포츠 드링크", "Sports Drink", "运动饮料", "スポーツドリンク", "Boisson sportive"), new UiTranslationEntry("Airline Food", "기내식", "Airline Food", "飞机餐", "機内食", "Repas d’avion"), new UiTranslationEntry("Scout Cookie", "스카우트 과자", "Scout Cookie", "童军饼干", "スカウトクッキー", "Biscuit scout"), new UiTranslationEntry("Scout Effigy", "스카우트 인형", "Scout Effigy", "童军雕像", "スカウト像", "Effigie scout"), new UiTranslationEntry("판매하지 못했습니다.", "판매하지 못했습니다.", "Sale failed.", "出售失败。", "売却に失敗しました。", "Échec de la vente."), new UiTranslationEntry("제작에 성공했습니다.", "제작에 성공했습니다.", "Crafting succeeded.", "制作成功。", "クラフトに成功しました。", "Fabrication réussie."), new UiTranslationEntry("첫 번째 다음 모닥불", "첫 번째 다음 모닥불", "First next campfire", "第一个后续篝火", "1つ目の次の焚き火", "Premier prochain feu de camp"), new UiTranslationEntry("두 번째 다음 모닥불", "두 번째 다음 모닥불", "Second next campfire", "第二个后续篝火", "2つ目の次の焚き火", "Deuxième prochain feu de camp"), new UiTranslationEntry("세 번째 다음 모닥불", "세 번째 다음 모닥불", "Third next campfire", "第三个后续篝火", "3つ目の次の焚き火", "Troisième prochain feu de camp"), new UiTranslationEntry("네 번째 다음 모닥불", "네 번째 다음 모닥불", "Fourth next campfire", "第四个后续篝火", "4つ目の次の焚き火", "Quatrième prochain feu de camp"), new UiTranslationEntry("해안 → 열대/뿌리숲", "해안 → 열대/뿌리숲", "Beach → Tropics/Roots", "海滩 → 热带/根系森林", "海岸 → 熱帯/根の森", "Plage → Tropiques/Forêt de racines"), new UiTranslationEntry("제작식을 선택하세요.", "제작식을 선택하세요.", "Select a recipe.", "请选择配方。", "レシピを選択してください。", "Sélectionnez une recette."), new UiTranslationEntry("제작에 성공했습니다!", "제작에 성공했습니다!", "crafted successfully!", "制作成功!", "クラフトに成功しました!", "fabriqué avec succès !"), new UiTranslationEntry("항목을 선택했습니다.", "항목을 선택했습니다.", " selected.", " 已选择。", "を選択しました。", " sélectionné."), new UiTranslationEntry("현재 최대 적재량: ", "현재 최대 적재량: ", "Current maximum stack: ", "当前最大堆叠:", "現在の最大スタック: ", "Pile maximale actuelle : "), new UiTranslationEntry("이번 판 무작위 조합", "이번 판 무작위 조합", "This run’s randomized combination", "本局随机组合", "今回のランダム組み合わせ", "Combinaison aléatoire de cette partie"), new UiTranslationEntry("스카우트지도자의 나팔", "스카우트지도자의 나팔", "Scoutmaster Bugle", "童军领队号角", "スカウトマスターのラッパ", "Clairon du chef scout"), new UiTranslationEntry("\n처리 대기 요청: ", "\n처리 대기 요청: ", "\nPending requests: ", "\n待处理请求:", "\n処理待ちリクエスト: ", "\nRequêtes en attente : "), new UiTranslationEntry("원이 반영되었습니다.", "원이 반영되었습니다.", " coins applied.", " 金币已应用。", "コインが反映されました。", " pièces ajoutées."), new UiTranslationEntry("Description", "설명", "Description", "说明", "説明", "Description"), new UiTranslationEntry("Strange Gem", "이상한 보석", "Strange Gem", "奇异宝石", "不思議な宝石", "Gemme étrange"), new UiTranslationEntry("Rescue Hook", "구조갈고리", "Rescue Hook", "救援钩", "レスキューフック", "Crochet de secours"), new UiTranslationEntry("Rope Cannon", "밧줄총", "Rope Cannon", "绳索炮", "ロープキャノン", "Canon à corde"), new UiTranslationEntry("Marshmallow", "마시멜로우", "Marshmallow", "棉花糖", "マシュマロ", "Guimauve"), new UiTranslationEntry("Granola Bar", "그래놀라바", "Granola Bar", "燕麦棒", "グラノーラバー", "Barre de granola"), new UiTranslationEntry("Cooked Bird", "요리된 새", "Cooked Bird", "烤鸟", "調理済みの鳥", "Oiseau cuit"), new UiTranslationEntry("Sleep Berry", "수면 열매", "Sleep Berry", "睡眠莓", "睡眠ベリー", "Baie du sommeil"), new UiTranslationEntry("P / ESC\n닫기", "P / ESC\n닫기", "P / ESC\nClose", "P / ESC\n关闭", "P / ESC\n閉じる", "P / ESC\nFermer"), new UiTranslationEntry("필요 제작 등급: ", "필요 제작 등급: ", "Required crafting grade: ", "所需制作等级:", "必要クラフト等級: ", "Niveau de fabrication requis : "), new UiTranslationEntry("음식 / 강화 우유", "음식 / 강화 우유", "Food / Fortified Milk", "食物 / 强化牛奶", "食料 / 強化ミルク", "Nourriture / Lait fortifié"), new UiTranslationEntry("현재 해금 등급: ", "현재 해금 등급: ", "Current unlocked grade: ", "当前解锁等级:", "現在の解放等級: ", "Niveau débloqué actuel : "), new UiTranslationEntry("맵 자원 수집량 x", "맵 자원 수집량 x", "map resource yield x", "地图资源采集量 x", "マップ資源収集量 x", "rendement des ressources de la carte x"), new UiTranslationEntry("개로 설정했습니다.", "개로 설정했습니다.", " units.", " 个。", "個に設定しました。", " unités."), new UiTranslationEntry("\n현재 공유 돈: ", "\n현재 공유 돈: ", "\nCurrent shared money: ", "\n当前共享资金:", "\n現在の共有資金: ", "\nArgent partagé actuel : "), new UiTranslationEntry(" | 비행기 부품 ", " | 비행기 부품 ", " | aircraft part ", " | 飞机部件 ", " | 飛行機部品 ", " | pièce d’avion "), new UiTranslationEntry("개 판매 완료: +", "개 판매 완료: +", " units sold: +", " 个出售完成:+", "個売却完了: +", " unités vendues : +"), new UiTranslationEntry("Essentials", "필수", "Essentials", "必需品", "必需品", "Essentiels"), new UiTranslationEntry("Binoculars", "망원경", "Binoculars", "双筒望远镜", "双眼鏡", "Jumelles"), new UiTranslationEntry("Rope Spool", "밧줄타래", "Rope Spool", "绳索卷", "ロープスプール", "Bobine de corde"), new UiTranslationEntry("Magic Bean", "마법의 콩", "Magic Bean", "魔法豆", "魔法の豆", "Haricot magique"), new UiTranslationEntry("공유 돈 +100", "공유 돈 +100", "Shared money +100", "共享资金 +100", "共有資金 +100", "Argent partagé +100"), new UiTranslationEntry("알 수 없는 모듈", "알 수 없는 모듈", "Unknown module", "未知模块", "不明なモジュール", "Module inconnu"), new UiTranslationEntry("필요 비행기 모듈", "필요 비행기 모듈", "Required aircraft modules", "所需飞机模块", "必要な飛行機モジュール", "Modules d’avion requis"), new UiTranslationEntry("다음 모닥불 제작", "다음 모닥불 제작", "Next Campfire", "后续篝火", "次の焚き火", "Prochain feu de camp"), new UiTranslationEntry("아이템 판매 수익", "아이템 판매 수익", "Sale Value", "出售收益", "売却利益", "Valeur de vente"), new UiTranslationEntry("알 수 없는 강화", "알 수 없는 강화", "Unknown Upgrade", "未知升级", "不明な強化", "Amélioration inconnue"), new UiTranslationEntry("현재 수집량: x", "현재 수집량: x", "Current gather yield: x", "当前采集倍率:x", "現在の収集量: x", "Rendement actuel : x"), new UiTranslationEntry("빨간색 아삭 열매", "빨간색 아삭 열매", "Red Crispberry", "红色脆莓", "赤いクリスプベリー", "Baie croquante rouge"), new UiTranslationEntry("빨간색 버섯 열매", "빨간색 버섯 열매", "Red Mushroom Berry", "红色蘑菇莓", "赤いマッシュルームベリー", "Baie-champignon rouge"), new UiTranslationEntry("Developer", "개발자", "Developer", "开发者", "開発者", "Développeur"), new UiTranslationEntry("Bing Bong", "빙봉", "Bing Bong", "冰棒", "ビン・ボン", "Bing Bong"), new UiTranslationEntry("Guidebook", "가이드북", "Guidebook", "指南书", "ガイドブック", "Guide"), new UiTranslationEntry("Aloe Vera", "알로에 베라", "Aloe Vera", "芦荟", "アロエベラ", "Aloe vera"), new UiTranslationEntry("Heat Pack", "핫팩", "Heat Pack", "暖宝宝", "カイロ", "Chaufferette"), new UiTranslationEntry("Sunscreen", "선크림", "Sunscreen", "防晒霜", "日焼け止め", "Crème solaire"), new UiTranslationEntry("Trail Mix", "트레일 믹스", "Trail Mix", "什锦坚果", "トレイルミックス", "Mélange montagnard"), new UiTranslationEntry("Legendary", "전설", "Legendary", "传说", "レジェンダリー", "Légendaire"), new UiTranslationEntry("Language", "언어", "Language", "语言", "言語", "Langue"), new UiTranslationEntry("연료 제어 모듈", "연료 제어 모듈", "Fuel Control Module", "燃料控制模块", "燃料制御モジュール", "Module de contrôle du carburant"), new UiTranslationEntry("날개 연결 모듈", "날개 연결 모듈", "Wing Coupling Module", "机翼连接模块", "翼接続モジュール", "Module de raccordement des ailes"), new UiTranslationEntry("고도 조절 모듈", "고도 조절 모듈", "Altitude Control Module", "高度控制模块", "高度調整モジュール", "Module de contrôle d’altitude"), new UiTranslationEntry("내열 추진 모듈", "내열 추진 모듈", "Heat-Resistant Propulsion Module", "耐热推进模块", "耐熱推進モジュール", "Module de propulsion résistant à la chaleur"), new UiTranslationEntry("칼데라 → 가마", "칼데라 → 가마", "Caldera → Kiln", "火山口 → 熔炉", "カルデラ → 窯", "Caldeira → Four"), new UiTranslationEntry("이미 지난 구간", "이미 지난 구간", "Segment already passed", "已经通过的区域", "通過済みの区間", "Segment déjà franchi"), new UiTranslationEntry("선택 아이템: ", "선택 아이템: ", "Selected item: ", "所选物品:", "選択アイテム: ", "Objet sélectionné : "), new UiTranslationEntry("개당 판매가: ", "개당 판매가: ", "Price per unit: ", "单价:", "1個あたりの売却価格: ", "Prix unitaire : "), new UiTranslationEntry("예상 판매액: ", "예상 판매액: ", "Expected proceeds: ", "预计收入:", "予想売却額: ", "Produit estimé : "), new UiTranslationEntry("인벤토리 적재량", "인벤토리 적재량", "Inventory Capacity", "背包容量", "インベントリ容量", "Capacité d’inventaire"), new UiTranslationEntry("기본 판매가 x", "기본 판매가 x", "base sale price x", "基础售价 x", "基本売却価格 x", "prix de vente de base x"), new UiTranslationEntry("체크포인트 깃발", "체크포인트 깃발", "Checkpoint Flag", "检查点旗帜", "チェックポイント旗", "Drapeau de point de contrôle"), new UiTranslationEntry("노란색 열매나나", "노란색 열매나나", "Yellow Berrynana", "黄色莓蕉", "黄色いベリーナナ", "Berrynana jaune"), new UiTranslationEntry("파란색 버섯열매", "파란색 버섯열매", "Blue Mushroom Berry", "蓝色蘑菇莓", "青いマッシュルームベリー", "Baie-champignon bleue"), new UiTranslationEntry("보라색 버섯열매", "보라색 버섯열매", "Purple Mushroom Berry", "紫色蘑菇莓", "紫のマッシュルームベリー", "Baie-champignon violette"), new UiTranslationEntry("\n남은 수량: ", "\n남은 수량: ", "\nRemaining quantity: ", "\n剩余数量:", "\n残り数量: ", "\nQuantité restante : "), new UiTranslationEntry("필요 제작 등급", "필요 제작 등급", "Required crafting grade", "所需制作等级", "必要クラフト等級", "Niveau de fabrication requis"), new UiTranslationEntry("Upgrades", "강화", "Upgrades", "升级", "強化", "Améliorations"), new UiTranslationEntry("Crafting", "제작", "Crafting", "制作", "クラフト", "Fabrication"), new UiTranslationEntry("Climbing", "등산", "Climbing", "攀登", "登山", "Escalade"), new UiTranslationEntry("Backpack", "배낭", "Backpack", "背包", "バックパック", "Sac à dos"), new UiTranslationEntry("Antidote", "해독제", "Antidote", "解毒剂", "解毒剤", "Antidote"), new UiTranslationEntry("Dynamite", "다이너마이트", "Dynamite", "炸药", "ダイナマイト", "Dynamite"), new UiTranslationEntry("Cure-All", "만병통치약", "Cure-All", "万能药", "万能薬", "Remède universel"), new UiTranslationEntry("공유 잔액: ", "공유 잔액: ", "Shared balance: ", "共享余额:", "共有残高: ", "Solde partagé : "), new UiTranslationEntry("처리 중...", "처리 중...", "Processing...", "处理中……", "処理中...", "Traitement..."), new UiTranslationEntry("판매했습니다.", "판매했습니다.", "Sold successfully.", "出售成功。", "売却しました。", "Vente réussie."), new UiTranslationEntry("진행 조건: ", "진행 조건: ", "Progression requirements: ", "推进条件:", "進行条件: ", "Conditions de progression : "), new UiTranslationEntry("판매 수량: ", "판매 수량: ", "Quantity to sell: ", "出售数量:", "売却数量: ", "Quantité à vendre : "), new UiTranslationEntry("다음 효과: ", "다음 효과: ", "Next effect: ", "下一效果:", "次の効果: ", "Effet suivant : "), new UiTranslationEntry("다음 제작: ", "다음 제작: ", "Next craft: ", "下一制作:", "次の作成: ", "Prochaine fabrication : "), new UiTranslationEntry("필요 등급: ", "필요 등급: ", "Required grade: ", "所需等级:", "必要等級: ", "Niveau requis : "), new UiTranslationEntry("<이름 없음>", "<이름 없음>", "", "<无名称>", "<名前なし>", ""), new UiTranslationEntry("플라잉 디스크", "플라잉 디스크", "Frisbee", "飞盘", "フリスビー", "Frisbee"), new UiTranslationEntry("에너지 드링크", "에너지 드링크", "Energy Drink", "能量饮料", "エナジードリンク", "Boisson énergisante"), new UiTranslationEntry("휴대용 스토브", "휴대용 스토브", "Portable Stove", "便携炉", "携帯ストーブ", "Réchaud portable"), new UiTranslationEntry("반전 밧줄타래", "반전 밧줄타래", "Anti-Rope Spool", "反向绳索卷", "反転ロープスプール", "Bobine de corde inversée"), new UiTranslationEntry("스카우트 캐논", "스카우트 캐논", "Scout Cannon", "童军大炮", "スカウトキャノン", "Canon scout"), new UiTranslationEntry("저주받은 해골", "저주받은 해골", "Cursed Skull", "诅咒头骨", "呪われた頭蓋骨", "Crâne maudit"), new UiTranslationEntry("스포츠 드링크", "스포츠 드링크", "Sports Drink", "运动饮料", "スポーツドリンク", "Boisson sportive"), new UiTranslationEntry("빨간 송송열매", "빨간 송송열매", "Red Clusterberry", "红色簇莓", "赤いクラスターベリー", "Baie en grappe rouge"), new UiTranslationEntry("녹색 대왕열매", "녹색 대왕열매", "Green Kingberry", "绿色王莓", "緑のキングベリー", "Baie royale verte"), new UiTranslationEntry("주황 겨울열매", "주황 겨울열매", "Orange Winterberry", "橙色冬莓", "オレンジ・ウィンターベリー", "Baie d’hiver orange"), new UiTranslationEntry("빨간 가시열매", "빨간 가시열매", "Red Thornberry", "红色刺莓", "赤いソーンベリー", "Baie épineuse rouge"), new UiTranslationEntry("스카우트 과자", "스카우트 과자", "Scout Cookie", "童军饼干", "スカウトクッキー", "Biscuit scout"), new UiTranslationEntry("판도라의 상자", "판도라의 상자", "Pandora’s Box", "潘多拉魔盒", "パンドラの箱", "Boîte de Pandore"), new UiTranslationEntry("스카우트 인형", "스카우트 인형", "Scout Effigy", "童军雕像", "スカウト像", "Effigie scout"), new UiTranslationEntry("판매 수량을 ", "판매 수량을 ", "Sale quantity set to ", "出售数量设为 ", "売却数量を ", "Quantité de vente réglée sur "), new UiTranslationEntry(" · 페이지 ", " · 페이지 ", " · Page ", " · 第 ", " · ページ ", " · Page "), new UiTranslationEntry("Healing", "회복", "Healing", "治疗", "回復", "Soins"), new UiTranslationEntry("Frisbee", "플라잉디스크", "Frisbee", "飞盘", "フリスビー", "Frisbee"), new UiTranslationEntry("Balloon", "풍선", "Balloon", "气球", "風船", "Ballon"), new UiTranslationEntry("Lantern", "랜턴", "Lantern", "灯笼", "ランタン", "Lanterne"), new UiTranslationEntry("Parasol", "파라솔", "Parasol", "阳伞", "パラソル", "Parasol"), new UiTranslationEntry("Hot Dog", "핫도그", "Hot Dog", "热狗", "ホットドッグ", "Hot-dog"), new UiTranslationEntry("Pop Pop", "뾱뾱이", "Pop Pop", "泡泡纸", "プチプチ", "Papier bulle"), new UiTranslationEntry("Bandage", "붕대", "Bandage", "绷带", "包帯", "Bandage"), new UiTranslationEntry("기타 아이템", "기타 아이템", "Utility Items", "其他物品", "その他のアイテム", "Objets utilitaires"), new UiTranslationEntry("수집량 배율", "수집량 배율", "Gather Yield", "采集倍率", "収集倍率", "Rendement de collecte"), new UiTranslationEntry("알 수 없음", "알 수 없음", "Unknown", "未知", "不明", "Inconnu"), new UiTranslationEntry("플라잉디스크", "플라잉디스크", "Frisbee", "飞盘", "フリスビー", "Frisbee"), new UiTranslationEntry("이상한 보석", "이상한 보석", "Strange Gem", "奇异宝石", "不思議な宝石", "Gemme étrange"), new UiTranslationEntry("반전 밧줄총", "반전 밧줄총", "Anti-Rope Cannon", "反向绳索炮", "反転ロープキャノン", "Canon à corde inversé"), new UiTranslationEntry("해적 나침반", "해적 나침반", "Pirate Compass", "海盗罗盘", "海賊のコンパス", "Boussole de pirate"), new UiTranslationEntry("알로에 베라", "알로에 베라", "Aloe Vera", "芦荟", "アロエベラ", "Aloe vera"), new UiTranslationEntry("다이너마이트", "다이너마이트", "Dynamite", "炸药", "ダイナマイト", "Dynamite"), new UiTranslationEntry("코코넛 반쪽", "코코넛 반쪽", "Coconut Half", "半个椰子", "ココナッツ半分", "Demi-noix de coco"), new UiTranslationEntry("트레일 믹스", "트레일 믹스", "Trail Mix", "什锦坚果", "トレイルミックス", "Mélange montagnard"), new UiTranslationEntry("공유 돈 +", "공유 돈 +", "Shared money +", "共享资金 +", "共有資金 +", "Argent partagé +"), new UiTranslationEntry("강화 완료\n", "강화 완료\n", "Upgrade complete\n", "升级完成\n", "強化完了\n", "Amélioration terminée\n"), new UiTranslationEntry("Revive", "부활", "Revive", "复活", "蘇生", "Résurrection"), new UiTranslationEntry("Scroll", "스크롤", "Scroll", "卷轴", "巻物", "Parchemin"), new UiTranslationEntry("Cactus", "선인장", "Cactus", "仙人掌", "サボテン", "Cactus"), new UiTranslationEntry("Unique", "고유", "Unique", "独特", "ユニーク", "Unique"), new UiTranslationEntry("Normal", "보통", "Normal", "标准", "ノーマル", "Normal"), new UiTranslationEntry("Common", "일반", "Common", "普通", "コモン", "Commun"), new UiTranslationEntry("제작 시도", "제작 시도", "Craft", "制作", "クラフト", "Fabriquer"), new UiTranslationEntry("부품 구매", "부품 구매", "Purchase part", "购买部件", "部品を購入", "Acheter la pièce"), new UiTranslationEntry("최대 단계", "최대 단계", "Maximum level", "最高等级", "最大レベル", "Niveau maximal"), new UiTranslationEntry("판매 불가", "판매 불가", "Cannot sell", "不可出售", "売却不可", "Invendable"), new UiTranslationEntry("비어 있음", "비어 있음", "Empty", "空", "空", "Vide"), new UiTranslationEntry("구매 완료", "구매 완료", "Purchased", "已购买", "購入済み", "Acheté"), new UiTranslationEntry("사용 완료", "사용 완료", "Consumed", "已使用", "使用済み", "Utilisé"), new UiTranslationEntry("공유 돈 ", "공유 돈 ", "Shared money ", "共享资金 ", "共有資金 ", "Argent partagé "), new UiTranslationEntry("구매 가능", "구매 가능", "Available to purchase", "可购买", "購入可能", "Disponible à l’achat"), new UiTranslationEntry("제작 완료", "제작 완료", "Crafted", "已制作", "作成済み", "Fabriqué"), new UiTranslationEntry("등산 장비", "등산 장비", "Climbing Gear", "攀登装备", "登山装備", "Équipement d’escalade"), new UiTranslationEntry("강화 우유", "강화 우유", "Fortified Milk", "强化牛奶", "強化ミルク", "Lait fortifié"), new UiTranslationEntry("최종 탈출", "최종 탈출", "Final Escape", "最终逃脱", "最終脱出", "Évasion finale"), new UiTranslationEntry("강화 완료", "강화 완료", "Upgrade complete", "升级完成", "強化完了", "Amélioration terminée"), new UiTranslationEntry("자원 등급", "자원 등급", "Resource Grade", "资源等级", "資源等級", "Niveau des ressources"), new UiTranslationEntry("등급 해금", "등급 해금", "grade unlocked", "等级解锁", "等級解放", "niveau débloqué"), new UiTranslationEntry("괴상 버섯", "괴상 버섯", "Weird Shroom", "怪异蘑菇", "奇妙なキノコ", "Champignon étrange"), new UiTranslationEntry("선반 균류", "선반 균류", "Shelf Fungus", "层孔菌", "棚キノコ", "Champignon en console"), new UiTranslationEntry("방방 균류", "방방 균류", "Bounce Fungus", "弹跳菌", "バウンドキノコ", "Champignon rebondissant"), new UiTranslationEntry("풍선 다발", "풍선 다발", "Balloon Bunch", "气球束", "風船の束", "Bouquet de ballons"), new UiTranslationEntry("구조갈고리", "구조갈고리", "Rescue Hook", "救援钩", "レスキューフック", "Crochet de secours"), new UiTranslationEntry("사슬발사기", "사슬발사기", "Chain Launcher", "链条发射器", "チェーンランチャー", "Lance-chaîne"), new UiTranslationEntry("마법의 콩", "마법의 콩", "Magic Bean", "魔法豆", "魔法の豆", "Haricot magique"), new UiTranslationEntry("우정 나팔", "우정 나팔", "Friendship Bugle", "友谊号角", "友情のラッパ", "Clairon de l’amitié"), new UiTranslationEntry("무지개사탕", "무지개사탕", "Rainbow Candy", "彩虹糖", "レインボーキャンディ", "Bonbon arc-en-ciel"), new UiTranslationEntry("황금 빙봉", "황금 빙봉", "Golden Bing Bong", "黄金冰棒", "ゴールデン・ビン・ボン", "Bing Bong doré"), new UiTranslationEntry("마시멜로우", "마시멜로우", "Marshmallow", "棉花糖", "マシュマロ", "Guimauve"), new UiTranslationEntry("그래놀라바", "그래놀라바", "Granola Bar", "燕麦棒", "グラノーラバー", "Barre de granola"), new UiTranslationEntry("요리된 새", "요리된 새", "Cooked Bird", "烤鸟", "調理済みの鳥", "Oiseau cuit"), new UiTranslationEntry("수면 열매", "수면 열매", "Sleep Berry", "睡眠莓", "睡眠ベリー", "Baie du sommeil"), new UiTranslationEntry("만병통치약", "만병통치약", "Cure-All", "万能药", "万能薬", "Remède universel"), new UiTranslationEntry("제작 · ", "제작 · ", "Crafting · ", "制作 · ", "クラフト · ", "Fabrication · "), new UiTranslationEntry("Parts", "부품", "Parts", "部件", "部品", "Pièces"), new UiTranslationEntry("Stick", "나뭇가지", "Stick", "树枝", "枝", "Branche"), new UiTranslationEntry("Stone", "돌", "Stone", "石头", "石", "Pierre"), new UiTranslationEntry("Conch", "소라고둥", "Conch", "海螺", "巻き貝", "Conque"), new UiTranslationEntry("Bugle", "나팔", "Bugle", "号角", "ラッパ", "Clairon"), new UiTranslationEntry("Piton", "피톤", "Piton", "岩钉", "ハーケン", "Piton"), new UiTranslationEntry("Torch", "횃불", "Torch", "火把", "松明", "Torche"), new UiTranslationEntry("Flare", "조명탄", "Flare", "信号弹", "フレア", "Fusée éclairante"), new UiTranslationEntry("◀ 이전", "◀ 이전", "◀ Previous", "◀ 上一页", "◀ 前へ", "◀ Précédent"), new UiTranslationEntry("다음 ▶", "다음 ▶", "Next ▶", "下一页 ▶", "次へ ▶", "Suivant ▶"), new UiTranslationEntry("보유 중", "보유 중", "Owned", "已拥有", "所持中", "Possédé"), new UiTranslationEntry("상태: ", "상태: ", "Status: ", "状态:", "状態: ", "État : "), new UiTranslationEntry("등급: ", "등급: ", "Grade: ", "等级:", "等級: ", "Niveau : "), new UiTranslationEntry("보유: ", "보유: ", "Owned: ", "持有:", "所持数: ", "Possédé : "), new UiTranslationEntry("슬롯당 ", "슬롯당 ", "per slot ", "每栏位 ", "スロットあたり ", "par emplacement "), new UiTranslationEntry("나뭇가지", "나뭇가지", "Stick", "树枝", "枝", "Branche"), new UiTranslationEntry("소라고둥", "소라고둥", "Conch", "海螺", "巻き貝", "Conque"), new UiTranslationEntry("소라고동", "소라고둥", "Conch", "海螺", "巻き貝", "Conque"), new UiTranslationEntry("가이드북", "가이드북", "Guidebook", "指南书", "ガイドブック", "Guide"), new UiTranslationEntry("구름균류", "구름균류", "Cloud Fungus", "云菌", "雲キノコ", "Champignon nuage"), new UiTranslationEntry("밧줄타래", "밧줄타래", "Rope Spool", "绳索卷", "ロープスプール", "Bobine de corde"), new UiTranslationEntry("뼈의 서", "뼈의 서", "Book of Bones", "骨之书", "骨の書", "Livre des os"), new UiTranslationEntry("요정랜턴", "요정랜턴", "Fairy Lantern", "仙女灯笼", "妖精のランタン", "Lanterne féerique"), new UiTranslationEntry("강화우유", "강화우유", "Fortified Milk", "强化牛奶", "強化ミルク", "Lait fortifié"), new UiTranslationEntry("통통버섯", "통통버섯", "Puff Mushroom", "膨膨蘑菇", "パフキノコ", "Champignon gonflé"), new UiTranslationEntry("나팔버섯", "나팔버섯", "Trumpet Mushroom", "喇叭蘑菇", "ラッパキノコ", "Champignon trompette"), new UiTranslationEntry("다발버섯", "다발버섯", "Bundle Mushroom", "束状蘑菇", "束キノコ", "Champignon en bouquet"), new UiTranslationEntry("단추버섯", "단추버섯", "Button Mushroom", "纽扣蘑菇", "ボタンキノコ", "Champignon bouton"), new UiTranslationEntry("구급상자", "구급상자", "First Aid Kit", "急救箱", "救急箱", "Trousse de secours"), new UiTranslationEntry("개 판매", "개 판매", " units to sell", "个待出售", "個を売却", " unités à vendre"), new UiTranslationEntry("Sell", "판매", "Sell", "出售", "売却", "Vendre"), new UiTranslationEntry("Food", "음식", "Food", "食物", "食料", "Nourriture"), new UiTranslationEntry("Snow", "눈", "Snow", "雪", "雪", "Neige"), new UiTranslationEntry("Tick", "진드기", "Tick", "蜱虫", "ダニ", "Tique"), new UiTranslationEntry("Rare", "희귀", "Rare", "稀有", "レア", "Rare"), new UiTranslationEntry("개발자", "개발자", "Developer", "开发者", "開発者", "Développeur"), new UiTranslationEntry("가격표", "가격표", "Price list", "价格表", "価格表", "Tarifs"), new UiTranslationEntry("페이지", "페이지", "Page", "页", "ページ", "Page"), new UiTranslationEntry("미구매", "미구매", "Not purchased", "未购买", "未購入", "Non acheté"), new UiTranslationEntry("미충족", "미충족", "Not met", "未满足", "未達成", "Non rempli"), new UiTranslationEntry("미제작", "미제작", "Not crafted", "未制作", "未作成", "Non fabriqué"), new UiTranslationEntry("최고급", "최고급", "Masterwork", "大师级", "最高級", "Chef-d’œuvre"), new UiTranslationEntry("망원경", "망원경", "Binoculars", "双筒望远镜", "双眼鏡", "Jumelles"), new UiTranslationEntry("스크롤", "스크롤", "Scroll", "卷轴", "巻物", "Parchemin"), new UiTranslationEntry("밧줄총", "밧줄총", "Rope Cannon", "绳索炮", "ロープキャノン", "Canon à corde"), new UiTranslationEntry("뼈의서", "뼈의서", "Book of Bones", "骨之书", "骨の書", "Livre des os"), new UiTranslationEntry("해독제", "해독제", "Antidote", "解毒剂", "解毒剤", "Antidote"), new UiTranslationEntry("파라솔", "파라솔", "Parasol", "阳伞", "パラソル", "Parasol"), new UiTranslationEntry("선크림", "선크림", "Sunscreen", "防晒霜", "日焼け止め", "Crème solaire"), new UiTranslationEntry("선인장", "선인장", "Cactus", "仙人掌", "サボテン", "Cactus"), new UiTranslationEntry("조명탄", "조명탄", "Flare", "信号弹", "フレア", "Fusée éclairante"), new UiTranslationEntry("진드기", "진드기", "Tick", "蜱虫", "ダニ", "Tique"), new UiTranslationEntry("핫도그", "핫도그", "Hot Dog", "热狗", "ホットドッグ", "Hot-dog"), new UiTranslationEntry("기내식", "기내식", "Airline Food", "飞机餐", "機内食", "Repas d’avion"), new UiTranslationEntry("벌집꿀", "벌집꿀", "Honeycomb Honey", "蜂巢蜜", "巣蜜", "Miel en rayon"), new UiTranslationEntry("뾱뾱이", "뾱뾱이", "Pop Pop", "泡泡纸", "プチプチ", "Papier bulle"), new UiTranslationEntry("비용 ", "비용 ", "Cost ", "费用 ", "費用 ", "Coût "), new UiTranslationEntry("설명", "설명", "Description", "说明", "説明", "Description"), new UiTranslationEntry("강화", "강화", "Upgrades", "升级", "強化", "Améliorations"), new UiTranslationEntry("제작", "제작", "Crafting", "制作", "クラフト", "Fabrication"), new UiTranslationEntry("판매", "판매", "Sell", "出售", "売却", "Vendre"), new UiTranslationEntry("부품", "부품", "Parts", "部件", "部品", "Pièces"), new UiTranslationEntry("등산", "등산", "Climbing", "攀登", "登山", "Escalade"), new UiTranslationEntry("음식", "음식", "Food", "食物", "食料", "Nourriture"), new UiTranslationEntry("부활", "부활", "Revive", "复活", "蘇生", "Résurrection"), new UiTranslationEntry("필수", "필수", "Essentials", "必需品", "必需品", "Essentiels"), new UiTranslationEntry("닫기", "닫기", "Close", "关闭", "閉じる", "Fermer"), new UiTranslationEntry("충족", "충족", "Met", "满足", "達成", "Rempli"), new UiTranslationEntry("재료", "재료", "Materials", "材料", "素材", "Matériaux"), new UiTranslationEntry("회복", "회복", "Healing", "恢复", "回復", "Soins"), new UiTranslationEntry("기초", "기초", "Basic", "基础", "基礎", "Basique"), new UiTranslationEntry("일반", "일반", "Standard", "普通", "標準", "Standard"), new UiTranslationEntry("고급", "고급", "Advanced", "高级", "上級", "Avancé"), new UiTranslationEntry("특수", "특수", "Special", "特殊", "特殊", "Spécial"), new UiTranslationEntry("빙봉", "빙봉", "Bing Bong", "冰棒", "ビン・ボン", "Bing Bong"), new UiTranslationEntry("나팔", "나팔", "Bugle", "号角", "ラッパ", "Clairon"), new UiTranslationEntry("배낭", "배낭", "Backpack", "背包", "バックパック", "Sac à dos"), new UiTranslationEntry("피톤", "피톤", "Piton", "岩钉", "ハーケン", "Piton"), new UiTranslationEntry("풍선", "풍선", "Balloon", "气球", "風船", "Ballon"), new UiTranslationEntry("핫팩", "핫팩", "Heat Pack", "暖宝宝", "カイロ", "Chaufferette"), new UiTranslationEntry("횃불", "횃불", "Torch", "火把", "松明", "Torche"), new UiTranslationEntry("랜턴", "랜턴", "Lantern", "灯笼", "ランタン", "Lanterne"), new UiTranslationEntry("붕대", "붕대", "Bandage", "绷带", "包帯", "Bandage"), new UiTranslationEntry("힐", "힐", "Healing", "治疗", "回復", "Soins"), new UiTranslationEntry("돌", "돌", "Stone", "石头", "石", "Pierre"), new UiTranslationEntry("눈", "눈", "Snow", "雪", "雪", "Neige") }; private static readonly string[] FoodCraftUiNames = new string[48] { "빨간색아삭열매", "빨간아삭열매", "코코넛반쪽", "트레일믹스", "노란색열매나나", "노란열매나나", "파란색버섯열매", "파란버섯열매", "스포츠드링크", "진드기", "빨간송송열매", "녹색대왕열매", "강화우유", "마시멜로우", "그래놀라바", "통통버섯", "나팔버섯", "다발버섯", "단추버섯", "주황겨울열매", "빨간가시열매", "보라색버섯열매", "보라버섯열매", "핫도그", "요리된새", "기내식", "벌집꿀", "스카우트과자", "빨간색버섯열매", "빨간버섯열매", "판도라의상자", "수면열매", "뾱뾱이", "crispberry", "coconuthalf", "trailmix", "berrynana", "sportdrink", "tick", "marshmallow", "granolabar", "hotdog", "cookedbird", "airlinemeal", "honeycomb", "scoutcookies", "pandorasbox", "sleepberry" }; private static readonly string[] ClimbingCraftUiNames = new string[38] { "배낭", "피톤", "에너지드링크", "풍선", "휴대용스토브", "선반균류", "구름균류", "밧줄타래", "방방균류", "체크포인트깃발", "풍선다발", "구조갈고리", "사슬발사기", "마법의콩", "밧줄총", "뼈의서", "반전밧줄총", "반전밧줄타래", "우정나팔", "backpack", "piton", "balloon", "portablestove", "shelffungus", "cloudfungus", "rope", "coil", "bouncyfungus", "checkpointflag", "balloonbundle", "rescuehook", "chainlauncher", "magicbean", "ropecannon", "ropegun", "bookofbones", "invertedrope", "friendshipbugle" }; private static readonly string[] HealCraftUiNames = new string[8] { "붕대", "구급상자", "만병통치약", "bandage", "firstaidkit", "medkit", "cureall", "panacea" }; private static readonly string[] ReviveCraftUiNames = new string[11] { "스카우트인형", "스카우트석상", "스카우트조각상", "부활인형", "부활석상", "생명의석상", "scouteffigy", "scoutstatue", "effigy", "revive", "resurrection" }; private static readonly string[] EssentialCraftUiNames = new string[36] { "해적나침반", "눈", "알로에베라", "핫팩", "횃불", "랜턴", "해독제", "무지개사탕", "파라솔", "선크림", "선인장", "다이너마이트", "스카우트캐논", "스카우트지도자의나팔", "저주받은해골", "요정랜턴", "황금빙봉", "조명탄", "piratecompass", "eye", "aloevera", "heatpack", "torch", "lantern", "antidote", "rainbowcandy", "parasol", "sunscreen", "cactus", "dynamite", "scoutcannon", "scoutmasterbugle", "cursedskull", "fairylantern", "goldenbingbong", "flare" }; internal static CraftHub Instance { get; private set; } internal static ManualLogSource ModLogger { get; private set; } public static int ResourceYieldMultiplier { get; private set; } = 1; public static int ResourceUpgradeLevel => ((Object)(object)Instance != (Object)null) ? Instance.upgradeState.ResourceLevel : 0; public static int StackUpgradeLevel => ((Object)(object)Instance != (Object)null) ? Instance.upgradeState.StackLevel : 0; public static int CampfireUpgradeLevel => ((Object)(object)Instance != (Object)null) ? Instance.upgradeState.CampfireLevel : 0; public static bool DoubleYieldUnlocked => ResourceYieldMultiplier >= 2; internal HubTab CurrentTab => currentTab; internal HubLanguage CurrentLanguage => currentLanguage; internal int SharedMoney => cachedSharedMoney; private PlanePartRecipe SelectedPartRecipe { get { EnsureProgressionRecipesBuilt(); return (selectedPartIndex >= 0 && selectedPartIndex < PlanePartRecipes.Count) ? PlanePartRecipes[selectedPartIndex] : null; } } internal int CraftPage => craftPage; internal CraftUiCategory SelectedCraftUiCategory => selectedCraftUiCategory; internal int CraftTotalPages { get { int filteredCraftRecipeCount = GetFilteredCraftRecipeCount(); return (filteredCraftRecipeCount == 0) ? 1 : Mathf.CeilToInt((float)filteredCraftRecipeCount / 8f); } } internal CraftRecipe SelectedCraftRecipe => (selectedCraftRecipeIndex >= 0 && selectedCraftRecipeIndex < craftRecipes.Count) ? craftRecipes[selectedCraftRecipeIndex] : null; internal UpgradeKind SelectedUpgradeKind => selectedUpgradeKind; internal int SelectedUpgradeCurrentLevel => GetUpgradeCurrentLevel(selectedUpgradeKind); internal int SelectedUpgradeMaximumLevel => GetUpgradeMaximumLevel(selectedUpgradeKind); internal int SelectedUpgradeCost => GetNextUpgradeCost(selectedUpgradeKind); internal string SelectedUpgradeCurrentEffect => GetUpgradeCurrentEffect(selectedUpgradeKind); internal string SelectedUpgradeNextEffect => GetUpgradeNextEffect(selectedUpgradeKind); internal bool CanAttemptUpgrade { get { bool flag = upgradeStateLoaded && pendingRequest == PendingRequest.None && GetUpgradeCurrentLevel(selectedUpgradeKind) < GetUpgradeMaximumLevel(selectedUpgradeKind) && ReadSharedMoney() >= GetNextUpgradeCost(selectedUpgradeKind); if (!flag || selectedUpgradeKind != UpgradeKind.CampfireEfficiency) { return flag; } CampfireBuildRecipe nextCampfireRecipe = GetNextCampfireRecipe(); if (nextCampfireRecipe == null || GetCurrentResourceLevel() < nextCampfireRecipe.RequiredResourceLevel) { return false; } if (!HasRequiredModulesForCampfireStage(nextCampfireRecipe.Stage, out var _)) { return false; } CraftConsumptionPlan plan; string missingMessage; return TryBuildCraftConsumptionPlan(MakeTemporaryRecipe(nextCampfireRecipe.Ingredients), out plan, out missingMessage); } } internal int SelectedSellSlotId => selectedSellSlotId; internal int VisibleInventorySlotCount { get { Player localPlayer = Player.localPlayer; return ((Object)(object)localPlayer != (Object)null && localPlayer.itemSlots != null) ? Mathf.Min(8, localPlayer.itemSlots.Length) : 0; } } private void Awake() { //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) Instance = this; ModLogger = ((BaseUnityPlugin)this).Logger; BindUpgradeConfig(); SceneManager.sceneLoaded += HandleSceneLoaded; SceneManager.sceneUnloaded += HandleSceneUnloaded; Scene activeScene = SceneManager.GetActiveScene(); if (((Scene)(ref activeScene)).IsValid() && ((Scene)(ref activeScene)).isLoaded) { HandleSceneLoaded(activeScene, (LoadSceneMode)0); } ((BaseUnityPlugin)this).Logger.LogInfo((object)("Craft PEAK Unified Hub 2.13.1 loaded. Existing Shop/Store/Upgrade files are not required. Press P. CustomEventCodes=" + (byte)140 + "-" + (byte)152 + " (Photon-safe range) | Room-fixed host recipe synchronization enabled")); } private void OnEnable() { PhotonNetwork.AddCallbackTarget((object)this); } private void OnDisable() { PhotonNetwork.RemoveCallbackTarget((object)this); CloseHub(); } private void OnDestroy() { SceneManager.sceneLoaded -= HandleSceneLoaded; SceneManager.sceneUnloaded -= HandleSceneUnloaded; CloseHub(); lastSellRequestAtByActor.Clear(); pendingSaleTransactions.Clear(); reservedOrSoldItemGuids.Clear(); localPendingSale = null; lastCraftRequestAtByActor.Clear(); lastUpgradeRequestAtByActor.Clear(); lastPartRequestAtByActor.Clear(); if ((Object)(object)Instance == (Object)(object)this) { Instance = null; } ModLogger = null; } private void Update() { UpdatePendingRequest(); UpdateSellQuantityInput(); if ((Object)(object)activeWindow != (Object)null && !((MenuWindow)activeWindow).isOpen) { DestroyHubObject(); } Keyboard current = Keyboard.current; if (current != null && ((ButtonControl)current.pKey).wasPressedThisFrame) { if ((Object)(object)activeWindow != (Object)null) { CloseHub(); } else if (CanOpenHub()) { OpenHub(); } } } private void UpdateSellQuantityInput() { //IL_0049: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)activeWindow == (Object)null || currentTab != HubTab.Sell || pendingRequest != PendingRequest.None) { return; } Mouse current = Mouse.current; if (current == null) { return; } float y = ((InputControl)(object)current.scroll).ReadValue().y; if (!(Mathf.Abs(y) < 0.01f)) { int num = 1; Keyboard current2 = Keyboard.current; if (current2 != null && (((ButtonControl)current2.leftShiftKey).isPressed || ((ButtonControl)current2.rightShiftKey).isPressed)) { num = 5; } AdjustSelectedSellQuantity((y > 0f) ? num : (-num)); } } private void AdjustSelectedSellQuantity(int delta) { Player localPlayer = Player.localPlayer; if ((Object)(object)localPlayer == (Object)null || localPlayer.itemSlots == null || selectedSellSlotId < 0 || selectedSellSlotId >= localPlayer.itemSlots.Length) { selectedSellQuantity = 1; return; } ItemSlot itemSlot = localPlayer.GetItemSlot((byte)selectedSellSlotId); if (itemSlot == null || itemSlot.IsEmpty() || (Object)(object)itemSlot.prefab == (Object)null || !Spawn.IsSaleResourceId(itemSlot.prefab.itemID)) { selectedSellQuantity = 1; return; } int num = Mathf.Max(1, InventoryStack.GetStackCount(localPlayer, (byte)selectedSellSlotId)); int num2 = Mathf.Clamp(selectedSellQuantity + delta, 1, num); if (num2 != selectedSellQuantity) { selectedSellQuantity = num2; SetTabStatus(HubTab.Sell, "판매 수량을 " + selectedSellQuantity + "개로 설정했습니다."); } } private void HandleSceneLoaded(Scene scene, LoadSceneMode mode) { //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Unknown result type (might be due to invalid IL or missing references) CloseHub(); pendingRequest = PendingRequest.None; pendingSaleTransactions.Clear(); reservedOrSoldItemGuids.Clear(); localPendingSale = null; nextSaleTransactionId = 0; developerMoneyRequestSequence = 0; developerMoneyPendingResponses = 0; developerHostAuthoritativeBalance = -1; currentTab = HubTab.Description; selectedUpgradeKind = UpgradeKind.ResourceGrade; selectedCraftRecipeIndex = -1; selectedSellSlotId = -1; selectedSellQuantity = 1; craftPage = 0; upgradeStatus = "강화 항목을 선택하세요."; craftStatus = "제작할 아이템을 선택하세요."; sellStatus = "판매할 인벤토리 슬롯을 선택하세요."; partsStatus = "현재 세그먼트에 필요한 비행기 부품을 구매하세요."; selectedPartIndex = 0; partsRevision = 0; partsRunId = string.Empty; purchasedPartsMask = 0; consumedPartsMask = 0; peakUnlocked = false; bool flag = IsExcludedScene(scene); gameplayScene = !flag; if (IsAirportScene(scene)) { waitingForNewRun = true; cachedSharedMoney = 0; pendingFreshUpgradeRun = true; partsStateLoaded = false; upgradeRoomStateDirty = true; partsRoomStateDirty = true; ResetLocalRecipeCaches("Airport / waiting for new run"); RestoreBaseUpgradeEffects(); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Unified hub disabled in Airport. Next gameplay run starts with fresh money and upgrades."); } else if (flag) { RestoreBaseUpgradeEffects(); } else { upgradeStateLoaded = false; partsStateLoaded = false; upgradeRoomStateDirty = true; partsRoomStateDirty = true; partyResourceCacheUntil = 0f; EnsureSharedRecipeSeed(); ((BaseUnityPlugin)this).Logger.LogInfo((object)("Unified hub enabled in gameplay scene: " + ((Scene)(ref scene)).name)); } } private void HandleSceneUnloaded(Scene scene) { CloseHub(); } private static bool CanOpenHub() { if (!IsGameplayScene() || LoadingScreenHandler.loading || (Object)(object)Character.localCharacter == (Object)null || (Object)(object)Player.localPlayer == (Object)null) { return false; } GUIManager instance = GUIManager.instance; return (Object)(object)instance != (Object)null && !GUIManager.InPauseMenu && !instance.wheelActive; } private static bool IsGameplayScene() { //IL_0001: Unknown result type (might be due to invalid IL or missing references) return !IsExcludedScene(SceneManager.GetActiveScene()); } private static bool IsAirportScene(Scene scene) { return ((Scene)(ref scene)).IsValid() && ((Scene)(ref scene)).isLoaded && string.Equals(((Scene)(ref scene)).name, "Airport", StringComparison.OrdinalIgnoreCase); } private static bool IsExcludedScene(Scene scene) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) if (!((Scene)(ref scene)).IsValid() || !((Scene)(ref scene)).isLoaded) { return true; } return IsAirportScene(scene) || string.Equals(((Scene)(ref scene)).name, "Title", StringComparison.OrdinalIgnoreCase) || string.Equals(((Scene)(ref scene)).name, "Pretitle", StringComparison.OrdinalIgnoreCase); } private void LoadHubDataOnDemand() { RefreshSharedMoneyFromRoom(); InitializeRunMoneyIfNeeded(); EnsureSharedRecipeSeed(); EnsureProgressionRecipesBuilt(); LoadUpgradeStateOnDemand(); LoadPartsStateOnDemand(); int currentSegmentIndex = GetCurrentSegmentIndex(); if (currentSegmentIndex >= 0 && currentSegmentIndex <= 3) { selectedPartIndex = currentSegmentIndex; } partyResourceCacheUntil = 0f; } private void OpenHub() { //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Expected O, but got Unknown //IL_00ac: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)activeWindow != (Object)null)) { LoadHubDataOnDemand(); GameObject val = new GameObject("CraftPeak_UnifiedHub", new Type[5] { typeof(RectTransform), typeof(Canvas), typeof(CanvasScaler), typeof(GraphicRaycaster), typeof(CraftHubWindow) }); Object.DontDestroyOnLoad((Object)(object)val); Canvas component = val.GetComponent(); component.renderMode = (RenderMode)0; component.sortingOrder = 530; CanvasScaler component2 = val.GetComponent(); component2.uiScaleMode = (ScaleMode)1; component2.referenceResolution = new Vector2(1920f, 1080f); component2.screenMatchMode = (ScreenMatchMode)0; component2.matchWidthOrHeight = 0.5f; activeWindow = val.GetComponent(); BuildHubVisuals(activeWindow); activeWindow.Initialize(this); if (selectedCraftRecipeIndex < 0 && craftRecipes.Count > 0) { selectedCraftRecipeIndex = 0; } RefreshWindow(); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Unified hub opened."); } } public void CloseHub() { if (!((Object)(object)activeWindow == (Object)null)) { DestroyHubObject(); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Unified hub closed."); } } internal void OpenCompatibilityTab(HubTab tab) { if (tab == HubTab.Developer) { tab = HubTab.Description; } currentTab = tab; if ((Object)(object)activeWindow == (Object)null && CanOpenHub()) { OpenHub(); } SelectTab(tab); } internal void RefreshCompatibilityWindow() { RefreshWindow(); } private void DestroyHubObject() { if (!((Object)(object)activeWindow == (Object)null)) { CraftHubWindow craftHubWindow = activeWindow; activeWindow = null; MenuWindow.AllActiveWindows.Remove((MenuWindow)(object)craftHubWindow); if ((Object)(object)craftHubWindow != (Object)null && (Object)(object)((Component)craftHubWindow).gameObject != (Object)null) { Object.Destroy((Object)(object)((Component)craftHubWindow).gameObject); } } } internal void SelectTab(HubTab tab) { if (tab == HubTab.Developer) { tab = HubTab.Description; } currentTab = tab; if (tab == HubTab.Craft && craftRecipes.Count == 0) { if (!EnsureCraftRecipesBuilt()) { SetTabStatus(HubTab.Craft, "호스트가 이번 판 제작식과 부품 재료를 확정하는 중입니다..."); } CraftRecipe selectedCraftRecipe = SelectedCraftRecipe; if (selectedCraftRecipe == null || GetCraftUiCategory(selectedCraftRecipe) != selectedCraftUiCategory) { selectedCraftRecipeIndex = GetRecipeIndexAtFilteredPosition(0); } } if (tab == HubTab.Craft) { LogScoutEffigyFinalState("Craft tab opened"); } if (tab == HubTab.Parts) { int currentSegmentIndex = GetCurrentSegmentIndex(); if (currentSegmentIndex >= 0 && currentSegmentIndex <= 3) { selectedPartIndex = currentSegmentIndex; } } partyResourceCacheUntil = 0f; RefreshWindow(); } internal void SelectLanguage(HubLanguage language) { if (currentLanguage != language) { currentLanguage = language; RefreshWindow(); } } private static string GetHubTabDisplayName(HubTab tab) { return tab switch { HubTab.Upgrade => "강화", HubTab.Craft => "제작", HubTab.Sell => "판매", HubTab.Parts => "부품", HubTab.Developer => "개발자", _ => "설명", }; } private void RefreshWindow() { if ((Object)(object)activeWindow != (Object)null) { activeWindow.RefreshContents(); } } internal bool IsPending(PendingRequest request) { return pendingRequest == request; } internal string GetTabStatus(HubTab tab) { return tab switch { HubTab.Upgrade => upgradeStatus, HubTab.Craft => craftStatus, HubTab.Sell => sellStatus, HubTab.Parts => partsStatus, HubTab.Developer => developerStatus, HubTab.Description => string.Empty, _ => string.Empty, }; } private void SetTabStatus(HubTab tab, string message) { string text = message ?? string.Empty; switch (tab) { case HubTab.Upgrade: upgradeStatus = text; break; case HubTab.Craft: craftStatus = text; break; case HubTab.Sell: sellStatus = text; break; case HubTab.Parts: partsStatus = text; break; case HubTab.Developer: developerStatus = text; break; } RefreshWindow(); } public void OnEvent(EventData photonEvent) { if (photonEvent == null) { return; } if (photonEvent.Code == 140) { if (PhotonNetwork.IsMasterClient) { ProcessSellRequestOnHost(photonEvent.Sender, photonEvent.CustomData as object[]); } } else if (photonEvent.Code == 141) { HandleSellResult(photonEvent.CustomData as object[]); } else if (photonEvent.Code == 153) { HandleSellConsumeRequest(photonEvent.CustomData as object[]); } else if (photonEvent.Code == 154) { if (PhotonNetwork.IsMasterClient) { ProcessSellConsumeAckOnHost(photonEvent.Sender, photonEvent.CustomData as object[]); } } else if (photonEvent.Code == 142) { if (PhotonNetwork.IsMasterClient) { ProcessCraftRequestOnHost(photonEvent.Sender, photonEvent.CustomData as object[]); } } else if (photonEvent.Code == 143) { HandleCraftResult(photonEvent.CustomData as object[]); } else if (photonEvent.Code == 144) { HandleConsumedSelectedSlots(photonEvent.CustomData as object[]); } else if (photonEvent.Code == 145) { if (PhotonNetwork.IsMasterClient) { ProcessUpgradeRequestOnHost(photonEvent.Sender, photonEvent.CustomData as object[]); } } else if (photonEvent.Code == 146) { HandleUpgradeResult(photonEvent.CustomData as object[]); } else if (photonEvent.Code == 147) { if (PhotonNetwork.IsMasterClient) { ProcessPartPurchaseRequestOnHost(photonEvent.Sender, photonEvent.CustomData as object[]); } } else if (photonEvent.Code == 148) { HandlePartPurchaseResult(photonEvent.CustomData as object[]); } else if (photonEvent.Code == 149) { string text = ((photonEvent.CustomData is object[] array && array.Length != 0) ? (array[0] as string) : null); CampfireGate.NotifyLocalPlayer(string.IsNullOrEmpty(text) ? "진행 조건을 확인하세요." : text); } else if (photonEvent.Code == 151) { if (PhotonNetwork.IsMasterClient) { ProcessDeveloperMoneyRequestOnHost(photonEvent.Sender, photonEvent.CustomData as object[]); } } else if (photonEvent.Code == 152) { HandleDeveloperMoneyResult(photonEvent.CustomData as object[]); } else if (photonEvent.Code == 150) { CloseHub(); CampfireGate.NotifyLocalPlayer("정상에서 최종 조명탄 제작 완료. 탈출 신호를 발사했습니다."); } } private void UpdatePendingRequest() { if (pendingRequest != PendingRequest.None && !(Time.unscaledTime - requestStartedAt <= 6f)) { HubTab tab = RequestToTab(pendingRequest); pendingRequest = PendingRequest.None; SetTabStatus(tab, "요청 시간이 초과되었습니다."); } } private static HubTab RequestToTab(PendingRequest request) { return request switch { PendingRequest.Sell => HubTab.Sell, PendingRequest.Craft => HubTab.Craft, PendingRequest.Upgrade => HubTab.Upgrade, PendingRequest.Parts => HubTab.Parts, _ => HubTab.Upgrade, }; } private void HandleSellResult(object[] resultData) { pendingRequest = PendingRequest.None; partyResourceCacheUntil = 0f; if (resultData == null || resultData.Length < 6) { SetTabStatus(HubTab.Sell, "판매 결과 데이터가 올바르지 않습니다."); return; } bool flag; string text; int num; try { flag = Convert.ToBoolean(resultData[0]); text = resultData[1] as string; num = Convert.ToInt32(resultData[3]); int num2 = Convert.ToInt32(resultData[4]); } catch (Exception) { SetTabStatus(HubTab.Sell, "판매 결과를 해석하지 못했습니다."); return; } cachedSharedMoney = Mathf.Max(0, num); if (flag) { selectedSellQuantity = 1; } SetTabStatus(HubTab.Sell, (!string.IsNullOrEmpty(text)) ? text : (flag ? "판매했습니다." : "판매하지 못했습니다.")); } private void HandleCraftResult(object[] resultData) { pendingRequest = PendingRequest.None; partyResourceCacheUntil = 0f; if (resultData == null || resultData.Length < 5) { SetTabStatus(HubTab.Craft, "제작 결과 데이터가 올바르지 않습니다."); return; } try { bool flag = Convert.ToBoolean(resultData[0]); bool flag2 = Convert.ToBoolean(resultData[1]); string text = (resultData[3] as string) ?? string.Empty; if (string.IsNullOrEmpty(text)) { text = (flag2 ? "제작에 성공했습니다." : (flag ? "제작품 지급 중 오류가 발생했습니다." : "제작 요청이 거부되었습니다.")); } SetTabStatus(HubTab.Craft, text); } catch (Exception) { SetTabStatus(HubTab.Craft, "제작 결과를 해석하지 못했습니다."); } } private void HandleUpgradeResult(object[] resultData) { pendingRequest = PendingRequest.None; if (resultData == null || resultData.Length < 3) { SetTabStatus(HubTab.Upgrade, "강화 결과 데이터가 올바르지 않습니다."); return; } string message = (resultData[1] as string) ?? "강화 결과를 받았습니다."; ReadUpgradeStateFromRoom(force: false); SetTabStatus(HubTab.Upgrade, message); } private void InitializeRunMoneyIfNeeded() { if (waitingForNewRun && PhotonNetwork.InRoom && PhotonNetwork.CurrentRoom != null && gameplayScene) { if (((Dictionary)(object)((RoomInfo)PhotonNetwork.CurrentRoom).CustomProperties).TryGetValue((object)"CraftPeak.SharedMoney", out object _)) { waitingForNewRun = false; cachedSharedMoney = ReadSharedMoney(); } else if (PhotonNetwork.IsMasterClient) { SetSharedMoneyOnHost(0); developerHostAuthoritativeBalance = 0; waitingForNewRun = false; ((BaseUnityPlugin)this).Logger.LogInfo((object)"New run shared money initialized to 0."); } } } private static int ReadSharedMoney() { if (!PhotonNetwork.InRoom || PhotonNetwork.CurrentRoom == null) { return ((Object)(object)Instance != (Object)null) ? Instance.cachedSharedMoney : 0; } if (!((Dictionary)(object)((RoomInfo)PhotonNetwork.CurrentRoom).CustomProperties).TryGetValue((object)"CraftPeak.SharedMoney", out object value) || value == null) { return 0; } try { return Mathf.Max(0, Convert.ToInt32(value)); } catch (Exception) { return 0; } } private void RefreshSharedMoneyFromRoom() { int num = ReadSharedMoney(); if (num != cachedSharedMoney) { cachedSharedMoney = num; RefreshWindow(); } } internal void RequestDeveloperMoney() { //IL_0107: Unknown result type (might be due to invalid IL or missing references) //IL_010e: Expected O, but got Unknown //IL_0129: Unknown result type (might be due to invalid IL or missing references) //IL_0138: Unknown result type (might be due to invalid IL or missing references) //IL_013a: Unknown result type (might be due to invalid IL or missing references) //IL_015a: Unknown result type (might be due to invalid IL or missing references) if (!PhotonNetwork.InRoom || PhotonNetwork.CurrentRoom == null) { SetTabStatus(HubTab.Developer, "현재 Photon 방에 입장해 있지 않습니다."); return; } int num = ++developerMoneyRequestSequence; developerMoneyPendingResponses++; SetTabStatus(HubTab.Developer, "공유 돈 +100 요청 전송 완료" + ((developerMoneyPendingResponses > 1) ? ("\n처리 대기 요청: " + developerMoneyPendingResponses + "개") : string.Empty)); if (PhotonNetwork.IsMasterClient) { ProcessDeveloperMoneyRequestOnHost((PhotonNetwork.LocalPlayer != null) ? PhotonNetwork.LocalPlayer.ActorNumber : 0, new object[2] { num, 100 }); return; } Player masterClient = PhotonNetwork.MasterClient; if (masterClient == null) { developerMoneyPendingResponses = Mathf.Max(0, developerMoneyPendingResponses - 1); SetTabStatus(HubTab.Developer, "현재 호스트를 찾지 못해 요청을 보내지 못했습니다."); return; } RaiseEventOptions val = new RaiseEventOptions(); val.TargetActors = new int[1] { masterClient.ActorNumber }; RaiseEventOptions val2 = val; SendOptions val3 = default(SendOptions); ((SendOptions)(ref val3)).Reliability = true; SendOptions val4 = val3; if (!PhotonNetwork.RaiseEvent((byte)151, (object)new object[2] { num, 100 }, val2, val4)) { developerMoneyPendingResponses = Mathf.Max(0, developerMoneyPendingResponses - 1); SetTabStatus(HubTab.Developer, "공유 돈 +100 요청 전송에 실패했습니다."); } } private void ProcessDeveloperMoneyRequestOnHost(int actorNumber, object[] payload) { if (!PhotonNetwork.IsMasterClient || PhotonNetwork.CurrentRoom == null) { return; } int requestId = 0; int num = 100; try { if (payload != null && payload.Length != 0) { requestId = Convert.ToInt32(payload[0]); } if (payload != null && payload.Length > 1) { num = Convert.ToInt32(payload[1]); } } catch (Exception) { SendDeveloperMoneyResult(actorNumber, success: false, requestId, ReadSharedMoney(), 0, "개발자 요청 데이터가 올바르지 않습니다."); return; } if (num != 100) { SendDeveloperMoneyResult(actorNumber, success: false, requestId, ReadSharedMoney(), 0, "허용되지 않은 공유 돈 요청입니다."); return; } int num2 = ReadSharedMoney(); if (developerHostAuthoritativeBalance < 0) { developerHostAuthoritativeBalance = num2; } else if (num2 > developerHostAuthoritativeBalance) { developerHostAuthoritativeBalance = num2; } developerHostAuthoritativeBalance = Mathf.Max(0, developerHostAuthoritativeBalance + 100); int num3 = developerHostAuthoritativeBalance; SetSharedMoneyOnHost(num3); cachedSharedMoney = num3; SendDeveloperMoneyResult(actorNumber, success: true, requestId, num3, 100, "개발자 치트: 공유 돈 +100원"); } private static void SendDeveloperMoneyResult(int actorNumber, bool success, int requestId, int balance, int appliedAmount, string message) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Expected O, but got Unknown //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) if (actorNumber > 0) { RaiseEventOptions val = new RaiseEventOptions(); val.TargetActors = new int[1] { actorNumber }; RaiseEventOptions val2 = val; SendOptions val3 = default(SendOptions); ((SendOptions)(ref val3)).Reliability = true; SendOptions val4 = val3; PhotonNetwork.RaiseEvent((byte)152, (object)new object[5] { success, requestId, balance, appliedAmount, message ?? string.Empty }, val2, val4); } } private void HandleDeveloperMoneyResult(object[] payload) { developerMoneyPendingResponses = Mathf.Max(0, developerMoneyPendingResponses - 1); if (payload == null || payload.Length < 5) { SetTabStatus(HubTab.Developer, "호스트의 처리 결과를 확인하지 못했습니다."); return; } try { bool flag = Convert.ToBoolean(payload[0]); int num = Convert.ToInt32(payload[1]); int num2 = Convert.ToInt32(payload[2]); int num3 = Convert.ToInt32(payload[3]); string text = (payload[4] as string) ?? string.Empty; cachedSharedMoney = Mathf.Max(0, num2); partyResourceCacheUntil = 0f; SetTabStatus(HubTab.Developer, ((!string.IsNullOrEmpty(text)) ? text : (flag ? ("공유 돈 +" + num3 + "원이 반영되었습니다.") : "공유 돈 요청 처리에 실패했습니다.")) + "\n현재 공유 돈: " + cachedSharedMoney + "원" + ((developerMoneyPendingResponses > 0) ? ("\n처리 대기 요청: " + developerMoneyPendingResponses + "개") : string.Empty)); RefreshWindow(); } catch (Exception) { SetTabStatus(HubTab.Developer, "호스트 결과 데이터가 올바르지 않습니다."); } } private static void SetSharedMoneyOnHost(int money) { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Expected O, but got Unknown //IL_0041: Expected O, but got Unknown if (PhotonNetwork.InRoom && PhotonNetwork.IsMasterClient && PhotonNetwork.CurrentRoom != null) { int num = Mathf.Max(0, money); Hashtable val = new Hashtable(); ((Dictionary)val).Add((object)"CraftPeak.SharedMoney", (object)num); Hashtable val2 = val; PhotonNetwork.CurrentRoom.SetCustomProperties(val2, (Hashtable)null, (WebFlags)null); if ((Object)(object)Instance != (Object)null) { Instance.cachedSharedMoney = num; Instance.RefreshWindow(); } } } private bool EnsureSharedRecipeSeed() { //IL_0124: Unknown result type (might be due to invalid IL or missing references) //IL_0129: Unknown result type (might be due to invalid IL or missing references) //IL_013a: Expected O, but got Unknown //IL_013b: Unknown result type (might be due to invalid IL or missing references) //IL_0148: Expected O, but got Unknown //IL_0149: Unknown result type (might be due to invalid IL or missing references) //IL_015b: Expected O, but got Unknown //IL_015e: Expected O, but got Unknown if (!PhotonNetwork.InRoom || PhotonNetwork.CurrentRoom == null || !gameplayScene) { return false; } Hashtable customProperties = ((RoomInfo)PhotonNetwork.CurrentRoom).CustomProperties; object value = null; object value2 = null; object value3 = null; if (((Dictionary)(object)customProperties).TryGetValue((object)"CraftPeak.Recipes.Protocol", out value) && ((Dictionary)(object)customProperties).TryGetValue((object)"CraftPeak.Recipes.RunId", out value2) && ((Dictionary)(object)customProperties).TryGetValue((object)"CraftPeak.Recipes.Seed", out value3)) { try { int num = Convert.ToInt32(value); string text = (value2 as string) ?? Convert.ToString(value2); int seed = Convert.ToInt32(value3); if (num == 1 && !string.IsNullOrEmpty(text)) { ApplySharedRecipeSeed(text, seed, "Room properties"); return true; } } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Shared recipe seed read failed: " + ex.Message)); } } if (!PhotonNetwork.IsMasterClient) { return false; } string text2 = Guid.NewGuid().ToString("N"); int num2 = CreateHostRecipeSeed(text2); Hashtable val = new Hashtable(); ((Dictionary)val).Add((object)"CraftPeak.Recipes.Protocol", (object)1); ((Dictionary)val).Add((object)"CraftPeak.Recipes.RunId", (object)text2); ((Dictionary)val).Add((object)"CraftPeak.Recipes.Seed", (object)num2); Hashtable val2 = val; if (!PhotonNetwork.CurrentRoom.SetCustomProperties(val2, (Hashtable)null, (WebFlags)null)) { ((BaseUnityPlugin)this).Logger.LogError((object)"Host failed to publish shared recipe seed."); return false; } ApplySharedRecipeSeed(text2, num2, "Host generated for room"); ((BaseUnityPlugin)this).Logger.LogInfo((object)("Shared recipe seed published. RecipeRoundId=" + text2 + " | Seed=" + num2)); return true; } private static int CreateHostRecipeSeed(string runId) { string text = ((PhotonNetwork.CurrentRoom != null) ? PhotonNetwork.CurrentRoom.Name : string.Empty); string value = (runId ?? string.Empty) + "|" + text + "|" + PhotonNetwork.ServerTimestamp + "|" + Guid.NewGuid().ToString("N"); int num = StableProgressionSeed(value); return (num == 0) ? 1 : num; } private void ApplySharedRecipeSeed(string runId, int seed, string reason) { string text = runId ?? string.Empty; bool flag = !sharedRecipeSeedLoaded || sharedRecipeSeed != seed || !string.Equals(sharedRecipeRunId, text, StringComparison.Ordinal); sharedRecipeSeed = seed; sharedRecipeRunId = text; sharedRecipeSeedLoaded = true; if (flag) { ResetGeneratedRecipeLists(); ((BaseUnityPlugin)this).Logger.LogInfo((object)("Shared recipe seed applied. Reason=" + reason + " | RunId=" + text + " | Seed=" + seed)); } } private void ResetLocalRecipeCaches(string reason) { sharedRecipeSeed = 0; sharedRecipeRunId = string.Empty; sharedRecipeSeedLoaded = false; ResetGeneratedRecipeLists(); ((BaseUnityPlugin)this).Logger.LogDebug((object)("Local recipe caches reset. Reason=" + reason)); } private void ResetGeneratedRecipeLists() { craftRecipes.Clear(); craftRecipesByOutputId.Clear(); NextCampfireRecipes.Clear(); PlanePartRecipes.Clear(); progressionRecipesBuilt = false; progressionRecipeRunId = string.Empty; selectedCraftRecipeIndex = -1; craftPage = 0; } private void LoadPartsStateOnDemand() { partsRoomStateDirty = false; if (!PhotonNetwork.InRoom || PhotonNetwork.CurrentRoom == null) { return; } Hashtable customProperties = ((RoomInfo)PhotonNetwork.CurrentRoom).CustomProperties; string text = ReadRunId(); object value = null; object value2 = null; if (!((Dictionary)(object)customProperties).TryGetValue((object)"CraftPeak.Parts.Protocol", out value) || !((Dictionary)(object)customProperties).TryGetValue((object)"CraftPeak.Parts.Revision", out value2)) { if (PhotonNetwork.IsMasterClient) { PublishPartsState(0, 0, unlockedPeak: false, text, "Fresh parts state"); return; } partsRevision = 0; partsRunId = text; purchasedPartsMask = 0; consumedPartsMask = 0; peakUnlocked = false; partsStateLoaded = true; return; } try { int num = Convert.ToInt32(value); if (num != 1) { ((BaseUnityPlugin)this).Logger.LogError((object)("Parts protocol mismatch. Room=" + num + " | Local=" + 1)); return; } string text2 = ReadString(customProperties, "CraftPeak.Parts.RunId"); if (!string.IsNullOrEmpty(text) && !string.Equals(text2, text, StringComparison.Ordinal)) { if (PhotonNetwork.IsMasterClient) { PublishPartsState(0, 0, unlockedPeak: false, text, "New run parts reset"); return; } partsRevision = 0; partsRunId = text; purchasedPartsMask = 0; consumedPartsMask = 0; peakUnlocked = false; partsStateLoaded = true; } else { partsRevision = Mathf.Max(0, Convert.ToInt32(value2)); partsRunId = (string.IsNullOrEmpty(text) ? text2 : text); purchasedPartsMask = ReadInt(customProperties, "CraftPeak.Parts.PurchasedMask", 0) & 0xF; consumedPartsMask = ReadInt(customProperties, "CraftPeak.Parts.ConsumedMask", 0) & 0xF; peakUnlocked = ReadBool(customProperties, "CraftPeak.Parts.PeakUnlocked", fallback: false); partsStateLoaded = true; } } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogError((object)("Parts state read failed: " + ex)); } } private bool PublishPartsState(int purchasedMask, int consumedMask, bool unlockedPeak, string runId, string reason) { //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Expected O, but got Unknown //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Expected O, but got Unknown //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Expected O, but got Unknown //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Expected O, but got Unknown //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Expected O, but got Unknown //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Expected O, but got Unknown //IL_00b5: Expected O, but got Unknown if (!PhotonNetwork.InRoom || !PhotonNetwork.IsMasterClient || PhotonNetwork.CurrentRoom == null) { return false; } int num = Mathf.Max(0, partsRevision) + 1; string value = runId ?? string.Empty; Hashtable val = new Hashtable(); ((Dictionary)val).Add((object)"CraftPeak.Parts.Protocol", (object)1); ((Dictionary)val).Add((object)"CraftPeak.Parts.Revision", (object)num); ((Dictionary)val).Add((object)"CraftPeak.Parts.RunId", (object)value); ((Dictionary)val).Add((object)"CraftPeak.Parts.PurchasedMask", (object)(purchasedMask & 0xF)); ((Dictionary)val).Add((object)"CraftPeak.Parts.ConsumedMask", (object)(consumedMask & 0xF)); ((Dictionary)val).Add((object)"CraftPeak.Parts.PeakUnlocked", (object)unlockedPeak); Hashtable val2 = val; if (!PhotonNetwork.CurrentRoom.SetCustomProperties(val2, (Hashtable)null, (WebFlags)null)) { ((BaseUnityPlugin)this).Logger.LogError((object)("Parts state publish failed. Reason=" + reason)); return false; } partsRevision = num; partsRunId = value; purchasedPartsMask = purchasedMask & 0xF; consumedPartsMask = consumedMask & 0xF; peakUnlocked = unlockedPeak; partsStateLoaded = true; partsRoomStateDirty = false; RefreshWindow(); ((BaseUnityPlugin)this).Logger.LogInfo((object)("Parts state published. Reason=" + reason + " | PurchasedMask=" + purchasedPartsMask + " | ConsumedMask=" + consumedPartsMask + " | PeakUnlocked=" + peakUnlocked + ".")); return true; } private bool EnsureProgressionRecipesBuilt() { if (progressionRecipesBuilt && NextCampfireRecipes.Count == 4 && PlanePartRecipes.Count == 4) { return true; } ItemDatabase instance = SingletonAsset.Instance; if ((Object)(object)instance == (Object)null || instance.itemLookup == null || instance.itemLookup.Count == 0) { return false; } if (!EnsureSharedRecipeSeed()) { return false; } string text = sharedRecipeRunId; if (progressionRecipesBuilt && NextCampfireRecipes.Count == 4 && PlanePartRecipes.Count == 4 && string.Equals(progressionRecipeRunId, text, StringComparison.Ordinal)) { return true; } List pool = BuildResolvedPool(instance, new string[3] { "FireWood", "Fire Wood", "나뭇가지" }, new string[3] { "Stone", "Rock", "돌" }, new string[4] { "Conch", "Shell", "소라고둥", "소라고동" }); List pool2 = BuildResolvedPool(instance, new string[2] { "Binoculars", "망원경" }, new string[3] { "Bing Bong", "BingBong", "빙봉" }, new string[2] { "Bugle", "나팔" }, new string[3] { "Frisbee", "Flying Disc", "플라잉디스크" }); List pool3 = BuildResolvedPool(instance, new string[3] { "Guidebook", "Guide Book", "가이드북" }, new string[2] { "Scroll", "스크롤" }); List pool4 = BuildResolvedPool(instance, new string[3] { "Weird Shroom", "WeirdShroom", "괴상 버섯" }); List pool5 = BuildResolvedPool(instance, new string[3] { "Strange Gem", "StrangeGem", "이상한 보석" }); List pool6 = BuildResolvedPool(instance, new string[2] { "Backpack", "배낭" }, new string[2] { "Piton", "피톤" }, new string[3] { "Energy Drink", "EnergyDrink", "에너지 드링크" }, new string[2] { "Balloon", "풍선" }, new string[3] { "Portable Stove", "PortableStove", "휴대용 스토브" }); List pool7 = BuildResolvedPool(instance, new string[3] { "Shelf Fungus", "ShelfFungus", "선반 균류" }, new string[3] { "Cloud Fungus", "CloudFungus", "구름균류" }, new string[3] { "Rope Spool", "RopeSpool", "밧줄타래" }, new string[3] { "Bounce Fungus", "BounceFungus", "방방 균류" }, new string[3] { "Checkpoint Flag", "CheckpointFlag", "체크포인트 깃발" }); List pool8 = BuildResolvedPool(instance, new string[3] { "Balloon Bunch", "Bunch of Balloons", "풍선 다발" }, new string[3] { "Rescue Hook", "RescueHook", "구조갈고리" }, new string[3] { "Chain Launcher", "ChainLauncher", "사슬발사기" }, new string[3] { "Magic Bean", "MagicBean", "마법의 콩" }, new string[3] { "Rope Cannon", "RopeCannon", "밧줄총" }); List list = BuildResolvedPool(instance, new string[4] { "Book of Bones", "Bone Book", "뼈의서", "뼈의 서" }, new string[3] { "Anti-Rope Cannon", "Reverse Rope Cannon", "반전 밧줄총" }, new string[3] { "Anti-Rope Spool", "Reverse Rope Spool", "반전 밧줄타래" }, new string[3] { "Friendship Bugle", "Friendship Horn", "우정 나팔" }); List pool9 = BuildResolvedPool(instance, new string[3] { "Pirate Compass", "PirateCompass", "해적 나침반" }, new string[2] { "Snow", "눈" }, new string[3] { "Aloe Vera", "AloeVera", "알로에 베라" }, new string[3] { "Heat Pack", "HeatPack", "핫팩" }, new string[2] { "Torch", "횃불" }); List pool10 = BuildResolvedPool(instance, new string[2] { "Lantern", "랜턴" }, new string[2] { "Antidote", "해독제" }, new string[3] { "Rainbow Candy", "RainbowCandy", "무지개사탕" }, new string[2] { "Parasol", "파라솔" }, new string[2] { "Sunscreen", "선크림" }); List pool11 = BuildResolvedPool(instance, new string[2] { "Cactus", "선인장" }, new string[2] { "Dynamite", "다이너마이트" }, new string[3] { "Scout Cannon", "ScoutCannon", "스카우트 캐논" }); List pool12 = BuildResolvedPool(instance, new string[3] { "Scoutmaster Bugle", "Scoutmaster Horn", "스카우트지도자의 나팔" }, new string[3] { "Cursed Skull", "CursedSkull", "저주받은 해골" }, new string[3] { "Fairy Lantern", "FairyLantern", "요정랜턴" }); List pool13 = BuildResolvedPool(instance, new string[3] { "Golden Bing Bong", "GoldenBingBong", "황금 빙봉" }, new string[2] { "Flare", "조명탄" }); List list2 = BuildResolvedPool(instance, new string[3] { "Red Crispberry", "Red Crisp Berry", "빨간색 아삭 열매" }, new string[3] { "Coconut Half", "Half Coconut", "코코넛 반쪽" }, new string[3] { "Trail Mix", "TrailMix", "트레일 믹스" }, new string[3] { "Yellow Berrynana", "Yellow Banana", "노란색 열매나나" }, new string[3] { "Blue Mushroom Berry", "Blue MushroomBerry", "파란색 버섯열매" }, new string[3] { "Sports Drink", "SportsDrink", "스포츠 드링크" }); List list3 = BuildResolvedPool(instance, new string[2] { "Tick", "진드기" }, new string[3] { "Red Clusterberry", "Red Cluster Berry", "빨간 송송열매" }, new string[3] { "Green Bigberry", "Green Big Berry", "녹색 대왕열매" }, new string[3] { "Fortified Milk", "Reinforced Milk", "강화우유" }, new string[2] { "Marshmallow", "마시멜로우" }, new string[3] { "Granola Bar", "GranolaBar", "그래놀라바" }, new string[3] { "Puff Mushroom", "PuffMushroom", "통통버섯" }, new string[3] { "Trumpet Mushroom", "TrumpetMushroom", "나팔버섯" }, new string[3] { "Bundle Mushroom", "BundleMushroom", "다발버섯" }, new string[3] { "Button Mushroom", "ButtonMushroom", "단추버섯" }, new string[3] { "Orange Winterberry", "Orange Winter Berry", "주황 겨울열매" }, new string[3] { "Red Thornberry", "Red Thorn Berry", "빨간 가시열매" }, new string[3] { "Purple Mushroom Berry", "Purple MushroomBerry", "보라색 버섯열매" }); List pool14 = BuildResolvedPool(instance, new string[3] { "Hot Dog", "Hotdog", "핫도그" }, new string[3] { "Cooked Bird", "CookedBird", "요리된 새" }, new string[3] { "Airline Food", "Airline Meal", "기내식" }, new string[3] { "Honeycomb Honey", "Honey", "벌집꿀" }, new string[3] { "Scout Cookie", "Scout Snack", "스카우트 과자" }, new string[3] { "Red Mushroom Berry", "Red MushroomBerry", "빨간색 버섯 열매" }); List pool15 = BuildResolvedPool(instance, new string[3] { "Pandora's Box", "Pandora Box", "판도라의 상자" }, new string[3] { "Sleep Berry", "SleepBerry", "수면 열매" }, new string[3] { "Pop Pop", "Bubble Wrap", "뾱뾱이" }); List list4 = BuildResolvedPool(instance, new string[2] { "Bandage", "붕대" }); List list5 = BuildResolvedPool(instance, new string[3] { "First Aid Kit", "Medkit", "구급상자" }); List list6 = BuildResolvedPool(instance, new string[3] { "Cure-All", "Panacea", "만병통치약" }); NextCampfireRecipes.Clear(); PlanePartRecipes.Clear(); int num = sharedRecipeSeed; AddRandomCampfireRecipe(1, "첫 번째 다음 모닥불", 0, 30, num + 101, new PoolRequest(pool, 2), new PoolRequest(pool9, 1), new PoolRequest(pool6, 1)); AddRandomCampfireRecipe(2, "두 번째 다음 모닥불", 1, 110, num + 202, new PoolRequest(pool2, 2), new PoolRequest(pool10, 1), new PoolRequest(pool7, 1), new PoolRequest(MergePools(list3, list5), 1)); AddRandomCampfireRecipe(3, "세 번째 다음 모닥불", 2, 275, num + 303, new PoolRequest(pool3, 1), new PoolRequest(pool11, 1), new PoolRequest(pool7, 2), new PoolRequest(pool14, 1)); AddRandomCampfireRecipe(4, "네 번째 다음 모닥불", 3, 600, num + 404, new PoolRequest(pool4, 1), new PoolRequest(pool12, 1), new PoolRequest(pool8, 1), new PoolRequest(pool15, 1), new PoolRequest(pool3, 1)); AddRandomPlanePartRecipe(0, "연료 제어 모듈", "해안 → 열대/뿌리숲", 0, 65, num + 501, new PoolRequest(pool, 2), new PoolRequest(pool9, 1)); AddRandomPlanePartRecipe(1, "날개 연결 모듈", "열대/뿌리숲 → 메사/고산지대", 1, 165, num + 502, new PoolRequest(pool2, 2), new PoolRequest(pool10, 1)); AddRandomPlanePartRecipe(2, "고도 조절 모듈", "메사/고산지대 → 칼데라", 2, 360, num + 503, new PoolRequest(pool3, 1), new PoolRequest(pool11, 1), new PoolRequest(pool2, 1)); AddRandomPlanePartRecipe(3, "내열 추진 모듈", "칼데라 → 가마", 4, 800, num + 504, new PoolRequest(pool4, 1), new PoolRequest(pool13, 1), new PoolRequest(pool5, 1)); progressionRecipeRunId = text; progressionRecipesBuilt = NextCampfireRecipes.Count == 4 && PlanePartRecipes.Count == 4; if (progressionRecipesBuilt) { ((BaseUnityPlugin)this).Logger.LogInfo((object)("Run-random progression recipes built. RunId=" + text)); } return progressionRecipesBuilt; } private void AddRandomCampfireRecipe(int stage, string name, int requiredResourceLevel, int moneyCost, int seed, params PoolRequest[] requests) { List list = BuildRandomIngredients(seed, requests); NextCampfireRecipes.Add(new CampfireBuildRecipe(stage, name, requiredResourceLevel, moneyCost, list.ToArray())); } private void AddRandomPlanePartRecipe(int index, string name, string route, int requiredResourceLevel, int moneyCost, int seed, params PoolRequest[] requests) { List list = BuildRandomIngredients(seed, requests); PlanePartRecipes.Add(new PlanePartRecipe(index, name, route, requiredResourceLevel, moneyCost, list.ToArray())); } private static List BuildRandomIngredients(int seed, params PoolRequest[] requests) { List list = new List(); HashSet hashSet = new HashSet(); Random random = new Random(seed); if (requests == null) { return list; } foreach (PoolRequest poolRequest in requests) { if (poolRequest == null || poolRequest.Pool == null || poolRequest.Pool.Count == 0) { continue; } List list2 = new List(poolRequest.Pool); for (int j = 0; j < poolRequest.Count; j++) { if (list2.Count <= 0) { break; } int index = random.Next(list2.Count); ushort num = list2[index]; list2.RemoveAt(index); if (num == 0 || !hashSet.Add(num)) { j--; } else { list.Add(new IngredientCost(num, 1)); } } } return list; } private static List BuildResolvedPool(ItemDatabase database, params string[][] aliasGroups) { List list = new List(); if (aliasGroups == null) { return list; } for (int i = 0; i < aliasGroups.Length; i++) { ushort num = ResolveProgressionItemId(database, aliasGroups[i]); if (num != 0 && !list.Contains(num)) { list.Add(num); } } list.Sort(); return list; } private static List MergePools(params List[] pools) { List list = new List(); if (pools == null) { return list; } foreach (List list2 in pools) { if (list2 == null) { continue; } for (int j = 0; j < list2.Count; j++) { ushort num = list2[j]; if (num != 0 && !list.Contains(num)) { list.Add(num); } } } list.Sort(); return list; } private static int StableProgressionSeed(string value) { int num = 17; string text = value ?? string.Empty; for (int i = 0; i < text.Length; i++) { num = num * 31 + text[i]; } return num; } private static ushort ResolveProgressionItemId(ItemDatabase database, params string[] aliases) { if ((Object)(object)database == (Object)null || database.itemLookup == null || aliases == null) { return 0; } string[] array = new string[aliases.Length]; for (int i = 0; i < aliases.Length; i++) { array[i] = NormalizeProgressionName(aliases[i]); } foreach (KeyValuePair item in database.itemLookup) { Item value = item.Value; if ((Object)(object)value == (Object)null) { continue; } string value2 = (((Object)(object)((Component)value).gameObject != (Object)null) ? ((Object)((Component)value).gameObject).name : string.Empty); string itemDisplayName = GetItemDisplayName(value); string text = NormalizeProgressionName(value2); string text2 = NormalizeProgressionName(itemDisplayName); foreach (string text3 in array) { if (!string.IsNullOrEmpty(text3) && (text == text3 || text2 == text3)) { return item.Key; } } } foreach (KeyValuePair item2 in database.itemLookup) { Item value3 = item2.Value; if ((Object)(object)value3 == (Object)null) { continue; } string value4 = (((Object)(object)((Component)value3).gameObject != (Object)null) ? ((Object)((Component)value3).gameObject).name : string.Empty); string itemDisplayName2 = GetItemDisplayName(value3); string text4 = NormalizeProgressionName(value4); string text5 = NormalizeProgressionName(itemDisplayName2); foreach (string value5 in array) { if (!string.IsNullOrEmpty(value5) && (text4.Contains(value5) || text5.Contains(value5))) { return item2.Key; } } } return 0; } private static string NormalizeProgressionName(string value) { if (string.IsNullOrEmpty(value)) { return string.Empty; } StringBuilder stringBuilder = new StringBuilder(value.Length); foreach (char c in value) { if (char.IsLetterOrDigit(c)) { stringBuilder.Append(char.ToLowerInvariant(c)); } } return stringBuilder.ToString(); } private static int GetRequiredModuleMaskForCampfireStage(int stage) { int num = Mathf.Clamp(stage, 1, 4); return (1 << num) - 1; } private bool HasRequiredModulesForCampfireStage(int stage, out string message) { message = string.Empty; if (!partsStateLoaded || partsRoomStateDirty) { LoadPartsStateOnDemand(); } int requiredModuleMaskForCampfireStage = GetRequiredModuleMaskForCampfireStage(stage); int num = requiredModuleMaskForCampfireStage & ~purchasedPartsMask; if (num == 0) { return true; } StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append(stage); stringBuilder.Append("번째 다음 모닥불 제작에는 다음 비행기 모듈 구매가 필요합니다."); for (int i = 0; i < 4; i++) { int num2 = 1 << i; if ((num & num2) != 0) { stringBuilder.Append("\n- "); if (i >= 0 && i < PlanePartRecipes.Count) { stringBuilder.Append(PlanePartRecipes[i].Name); } else { stringBuilder.Append(GetPlaneModuleFallbackName(i)); } } } message = stringBuilder.ToString(); return false; } private static string GetPlaneModuleFallbackName(int index) { return index switch { 0 => "연료 제어 모듈", 1 => "날개 연결 모듈", 2 => "고도 조절 모듈", 3 => "내열 추진 모듈", _ => "알 수 없는 모듈", }; } private string BuildCampfireModuleRequirementText(int stage) { if (!partsStateLoaded || partsRoomStateDirty) { LoadPartsStateOnDemand(); } int requiredModuleMaskForCampfireStage = GetRequiredModuleMaskForCampfireStage(stage); StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append("필요 비행기 모듈"); for (int i = 0; i < stage && i < 4; i++) { int num = 1 << i; bool flag = (purchasedPartsMask & num) != 0; stringBuilder.Append("\n"); stringBuilder.Append(flag ? "" : ""); if (i >= 0 && i < PlanePartRecipes.Count) { stringBuilder.Append(PlanePartRecipes[i].Name); } else { stringBuilder.Append(GetPlaneModuleFallbackName(i)); } stringBuilder.Append(flag ? " 구매 완료" : " 미구매"); stringBuilder.Append(""); } return stringBuilder.ToString(); } private CampfireBuildRecipe GetNextCampfireRecipe() { EnsureProgressionRecipesBuilt(); int num = upgradeState.CampfireLevel + 1; return (num >= 1 && num <= NextCampfireRecipes.Count) ? NextCampfireRecipes[num - 1] : null; } private static CraftRecipe MakeTemporaryRecipe(IList ingredients) { CraftRecipe craftRecipe = new CraftRecipe(); int num = 0; while (ingredients != null && num < ingredients.Count) { IngredientCost ingredientCost = ingredients[num]; craftRecipe.Ingredients.Add(new IngredientCost(ingredientCost.ItemId, ingredientCost.Count)); num++; } return craftRecipe; } internal void SelectPart(int partIndex) { if (partIndex >= 0 && partIndex < PlanePartRecipes.Count) { selectedPartIndex = partIndex; SetTabStatus(HubTab.Parts, PlanePartRecipes[partIndex].Name + "을(를) 선택했습니다."); } } private string BuildPartRowText(int partIndex) { EnsureProgressionRecipesBuilt(); if (partIndex < 0 || partIndex >= PlanePartRecipes.Count) { return string.Empty; } PlanePartRecipe planePartRecipe = PlanePartRecipes[partIndex]; int num = 1 << partIndex; string text = (((consumedPartsMask & num) != 0) ? "사용 완료" : (((purchasedPartsMask & num) != 0) ? "보유 중" : "미구매")); return partIndex + 1 + ". " + planePartRecipe.Name + " | " + planePartRecipe.Route + " | " + text; } private string BuildPartDetailText(PlanePartRecipe recipe, out bool ready) { EnsureProgressionRecipesBuilt(); ready = false; if (recipe == null) { return "구매할 비행기 부품을 선택하세요."; } Dictionary dictionary = GetCachedPartyResourceCounts(); int currentSegmentIndex = GetCurrentSegmentIndex(); int currentResourceLevel = GetCurrentResourceLevel(); int num = 1 << recipe.Index; bool flag = (purchasedPartsMask & num) != 0; bool flag2 = (consumedPartsMask & num) != 0; bool flag3 = currentSegmentIndex == recipe.Index; bool flag4 = currentResourceLevel >= recipe.RequiredResourceLevel; bool flag5 = cachedSharedMoney >= recipe.MoneyCost; bool flag6 = true; sharedTextBuilder.Length = 0; sharedTextBuilder.Append(recipe.Name); sharedTextBuilder.Append("\n"); sharedTextBuilder.Append(recipe.Route); sharedTextBuilder.Append("\n\n필요 제작 등급: "); sharedTextBuilder.Append(GetResourceGradeName(recipe.RequiredResourceLevel)); sharedTextBuilder.Append(flag4 ? " 충족" : " 미충족"); sharedTextBuilder.Append("\n\n재료"); for (int i = 0; i < recipe.Ingredients.Count; i++) { IngredientCost ingredientCost = recipe.Ingredients[i]; dictionary.TryGetValue(ingredientCost.ItemId, out var value); bool flag7 = value >= ingredientCost.Count; flag6 = flag6 && flag7; sharedTextBuilder.Append("\n"); sharedTextBuilder.Append(flag7 ? "" : ""); sharedTextBuilder.Append(GetIngredientDisplayName(ingredientCost.ItemId)); sharedTextBuilder.Append(" "); sharedTextBuilder.Append(value); sharedTextBuilder.Append("/"); sharedTextBuilder.Append(ingredientCost.Count); sharedTextBuilder.Append(""); } sharedTextBuilder.Append("\n"); sharedTextBuilder.Append(flag5 ? "" : ""); sharedTextBuilder.Append("공유 돈 "); sharedTextBuilder.Append(cachedSharedMoney); sharedTextBuilder.Append("/"); sharedTextBuilder.Append(recipe.MoneyCost); sharedTextBuilder.Append("원"); sharedTextBuilder.Append("\n\n상태: "); if (flag2) { sharedTextBuilder.Append("이전 모닥불에서 사용 완료"); } else if (flag) { sharedTextBuilder.Append("구매 완료 · 모닥불 점화 가능"); } else if (!flag3) { sharedTextBuilder.Append((currentSegmentIndex < recipe.Index) ? "아직 도달하지 않은 구간" : "이미 지난 구간"); } else { sharedTextBuilder.Append("구매 가능"); } ready = !flag && !flag2 && flag3 && flag4 && flag5 && flag6 && pendingRequest == PendingRequest.None; return sharedTextBuilder.ToString(); } private void RequestPartPurchase() { //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Expected O, but got Unknown //IL_00c5: Unknown result type (might be due to invalid IL or missing references) if (pendingRequest != PendingRequest.None) { SetTabStatus(HubTab.Parts, "다른 요청을 처리 중입니다."); return; } PlanePartRecipe selectedPartRecipe = SelectedPartRecipe; BuildPartDetailText(selectedPartRecipe, out var ready); if (selectedPartRecipe == null || !ready) { SetTabStatus(HubTab.Parts, "현재 구간, 제작 등급, 재료와 공유 돈을 확인하세요."); return; } pendingRequest = PendingRequest.Parts; requestStartedAt = Time.unscaledTime; SetTabStatus(HubTab.Parts, selectedPartRecipe.Name + " 구매를 요청했습니다..."); object[] array = new object[1] { selectedPartRecipe.Index }; RaiseEventOptions val = new RaiseEventOptions { Receivers = (ReceiverGroup)2 }; if (PhotonNetwork.IsMasterClient) { ProcessPartPurchaseRequestOnHost(LocalActorNumber(), array); } else if (!PhotonNetwork.RaiseEvent((byte)147, (object)array, val, SendOptions.SendReliable)) { pendingRequest = PendingRequest.None; SetTabStatus(HubTab.Parts, "비행기 부품 구매 요청 전송에 실패했습니다."); } } private void ProcessPartPurchaseRequestOnHost(int actorNumber, object[] payload) { EnsureProgressionRecipesBuilt(); if (!PhotonNetwork.IsMasterClient) { return; } if (payload == null || payload.Length < 1) { SendPartPurchaseResult(actorNumber, success: false, "잘못된 비행기 부품 구매 요청입니다."); return; } double time = PhotonNetwork.Time; if (lastPartRequestAtByActor.TryGetValue(actorNumber, out var value) && time - value < 0.25) { SendPartPurchaseResult(actorNumber, success: false, "비행기 부품 구매 요청이 너무 빠릅니다."); return; } lastPartRequestAtByActor[actorNumber] = time; int num; try { num = Convert.ToInt32(payload[0]); } catch (Exception) { SendPartPurchaseResult(actorNumber, success: false, "비행기 부품 번호를 해석하지 못했습니다."); return; } if (num < 0 || num >= PlanePartRecipes.Count) { SendPartPurchaseResult(actorNumber, success: false, "존재하지 않는 비행기 부품입니다."); return; } LoadPartsStateOnDemand(); PlanePartRecipe planePartRecipe = PlanePartRecipes[num]; int currentSegmentIndex = GetCurrentSegmentIndex(); if (currentSegmentIndex != planePartRecipe.Index) { SendPartPurchaseResult(actorNumber, success: false, "현재 구간에서 필요한 비행기 부품만 구매할 수 있습니다."); return; } int currentResourceLevel = GetCurrentResourceLevel(); if (currentResourceLevel < planePartRecipe.RequiredResourceLevel) { SendPartPurchaseResult(actorNumber, success: false, GetResourceGradeName(planePartRecipe.RequiredResourceLevel) + " 제작 등급이 필요합니다."); return; } int num2 = 1 << planePartRecipe.Index; if ((purchasedPartsMask & num2) != 0 || (consumedPartsMask & num2) != 0) { SendPartPurchaseResult(actorNumber, success: false, "이미 구매했거나 사용한 비행기 부품입니다."); return; } int num3 = ReadSharedMoney(); if (num3 < planePartRecipe.MoneyCost) { SendPartPurchaseResult(actorNumber, success: false, "공유 돈이 부족합니다."); return; } if (!TryBuildPartConsumptionPlan(planePartRecipe, out var plan, out var missingMessage)) { SendPartPurchaseResult(actorNumber, success: false, missingMessage); return; } if (!TryConsumePlan(plan, out var consumedSelectedSlots)) { SendPartPurchaseResult(actorNumber, success: false, "재료 소비 중 인벤토리가 변경되었습니다. 다시 시도하세요."); return; } SetSharedMoneyOnHost(num3 - planePartRecipe.MoneyCost); BroadcastConsumedSelectedSlots(consumedSelectedSlots); int purchasedMask = purchasedPartsMask | num2; if (!PublishPartsState(purchasedMask, consumedPartsMask, peakUnlocked, ReadRunId(), "Plane part purchased: " + planePartRecipe.Name)) { SetSharedMoneyOnHost(ReadSharedMoney() + planePartRecipe.MoneyCost); SendPartPurchaseResult(actorNumber, success: false, "부품 상태 저장에 실패했습니다. 공유 돈은 환불되었지만 재료는 복구되지 않았습니다."); } else { partyResourceCacheUntil = 0f; SendPartPurchaseResult(actorNumber, success: true, planePartRecipe.Name + " 구매 완료.\n인벤토리에는 들어가지 않으며 모닥불 진행 조건으로 저장됩니다."); } } private static bool TryBuildPartConsumptionPlan(PlanePartRecipe recipe, out CraftConsumptionPlan plan, out string missingMessage) { CraftRecipe craftRecipe = new CraftRecipe(); for (int i = 0; i < recipe.Ingredients.Count; i++) { IngredientCost ingredientCost = recipe.Ingredients[i]; craftRecipe.Ingredients.Add(new IngredientCost(ingredientCost.ItemId, ingredientCost.Count)); } return TryBuildCraftConsumptionPlan(craftRecipe, out plan, out missingMessage); } private void SendPartPurchaseResult(int targetActor, bool success, string message) { //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Expected O, but got Unknown //IL_0070: Unknown result type (might be due to invalid IL or missing references) object[] array = new object[3] { success, message ?? string.Empty, ReadSharedMoney() }; if (PhotonNetwork.LocalPlayer != null && PhotonNetwork.LocalPlayer.ActorNumber == targetActor) { HandlePartPurchaseResult(array); return; } RaiseEventOptions val = new RaiseEventOptions(); val.TargetActors = new int[1] { targetActor }; RaiseEventOptions val2 = val; PhotonNetwork.RaiseEvent((byte)148, (object)array, val2, SendOptions.SendReliable); } private void HandlePartPurchaseResult(object[] payload) { pendingRequest = PendingRequest.None; string text = ((payload != null && payload.Length > 1) ? (payload[1] as string) : null); if (payload != null && payload.Length > 2) { try { cachedSharedMoney = Mathf.Max(0, Convert.ToInt32(payload[2])); } catch (Exception) { } } partyResourceCacheUntil = 0f; partsRoomStateDirty = true; LoadPartsStateOnDemand(); SetTabStatus(HubTab.Parts, string.IsNullOrEmpty(text) ? "비행기 부품 구매 결과를 받았습니다." : text); } internal static bool IsCurrentCampfireRequest(object[] requestData) { if (requestData == null || requestData.Length < 1) { return false; } try { int num = Convert.ToInt32(requestData[0]); PhotonView val = PhotonView.Find(num); Campfire val2 = (((Object)(object)val != (Object)null) ? ((Component)val).GetComponent() : null); return (Object)(object)val2 != (Object)null && IsCurrentSegmentCampfire(val2); } catch (Exception) { return false; } } internal static bool IsCurrentSegmentCampfire(Campfire campfire) { if ((Object)(object)campfire == (Object)null) { return false; } try { return (Object)(object)MapHandler.CurrentCampfire == (Object)(object)campfire; } catch (Exception) { return false; } } internal static bool ValidateCampfireProgression(out string message) { message = string.Empty; if ((Object)(object)Instance == (Object)null || !PhotonNetwork.InRoom || PhotonNetwork.CurrentRoom == null) { return true; } int currentSegmentIndex = GetCurrentSegmentIndex(); if (currentSegmentIndex == 4) { message = "가마 구간은 비행기 부품과 모닥불 진행 대상이 아닙니다.\n정상에 도착한 뒤 P 메뉴의 제작 탭에서 최종 조명탄을 제작하세요."; return false; } if (currentSegmentIndex < 0 || currentSegmentIndex > 3) { return true; } string text = ReadRoomString("CraftPeak.Run.Id"); string b = ReadRoomString("CraftPeak.Parts.RunId"); if (!string.IsNullOrEmpty(text) && !string.Equals(text, b, StringComparison.Ordinal)) { message = "새 등반의 비행기 부품 상태가 아직 초기화되지 않았습니다.\nP 메뉴의 부품 탭을 열어 진행 상태를 초기화하세요."; return false; } int num = currentSegmentIndex + 1; int num2 = Mathf.Clamp(ReadRoomInt("CraftPeak.Upgrade.Campfire", CampfireUpgradeLevel), 0, 4); if (num2 < num) { message = "모닥불 점화 조건 미충족\nP 메뉴 강화 탭의 다음 모닥불 제작에서 " + num + "단계 모닥불을 먼저 제작하세요."; return false; } int num3 = Mathf.Clamp(currentSegmentIndex, 0, 4); int currentResourceLevel = GetCurrentResourceLevel(); if (currentResourceLevel < num3) { message = "모닥불 점화 조건 미충족\n" + GetResourceGradeName(num3) + " 제작 등급이 필요합니다."; return false; } int num4 = 1 << currentSegmentIndex; int num5 = ReadRoomInt("CraftPeak.Parts.PurchasedMask", 0); int num6 = ReadRoomInt("CraftPeak.Parts.ConsumedMask", 0); if ((num6 & num4) != 0) { message = "이 구간의 비행기 부품은 이미 사용되었습니다."; return false; } if ((num5 & num4) == 0) { message = "모닥불 점화 조건 미충족\nP 메뉴의 부품 탭에서 현재 구간 비행기 부품을 먼저 구매하세요."; return false; } return true; } internal static string BuildCampfireProgressionPrompt() { if ((Object)(object)Instance == (Object)null) { return string.Empty; } int currentSegmentIndex = GetCurrentSegmentIndex(); if (currentSegmentIndex == 4) { return "\n가마 이후 정상에 도착하면 P → 제작 → 최종 조명탄 제작"; } if (currentSegmentIndex < 0 || currentSegmentIndex > 3) { return string.Empty; } int num = Mathf.Clamp(currentSegmentIndex, 0, 4); int resourceLevel = Instance.upgradeState.ResourceLevel; int num2 = 1 << currentSegmentIndex; int requiredModuleMaskForCampfireStage = GetRequiredModuleMaskForCampfireStage(currentSegmentIndex + 1); bool flag = (Instance.purchasedPartsMask & requiredModuleMaskForCampfireStage) == requiredModuleMaskForCampfireStage; bool flag2 = resourceLevel >= num; bool flag3 = Instance.upgradeState.CampfireLevel >= currentSegmentIndex + 1; string text = ((flag && flag2 && flag3) ? "#79E081" : "#FF8A80"); return "\n진행 조건: " + GetResourceGradeName(num) + " 제작 단계 | 다음 모닥불 " + (flag3 ? "제작 완료" : "미제작") + " | 비행기 부품 " + (flag ? "구매 완료" : "미구매") + ""; } internal void MarkPartConsumedAfterCampfire(int sourceSegment) { if (PhotonNetwork.IsMasterClient && sourceSegment >= 0 && sourceSegment <= 3) { LoadPartsStateOnDemand(); int num = 1 << sourceSegment; if ((purchasedPartsMask & num) != 0 && (consumedPartsMask & num) == 0) { PublishPartsState(purchasedPartsMask, consumedPartsMask | num, peakUnlocked, ReadRunId(), "Campfire consumed plane part for segment " + sourceSegment); } } } internal void SendProgressionNotice(int targetActor, string message) { //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Expected O, but got Unknown //IL_0059: Unknown result type (might be due to invalid IL or missing references) object[] array = new object[1] { message ?? string.Empty }; if (PhotonNetwork.LocalPlayer != null && PhotonNetwork.LocalPlayer.ActorNumber == targetActor) { CampfireGate.NotifyLocalPlayer(message); return; } RaiseEventOptions val = new RaiseEventOptions(); val.TargetActors = new int[1] { targetActor }; RaiseEventOptions val2 = val; PhotonNetwork.RaiseEvent((byte)149, (object)array, val2, SendOptions.SendReliable); } private void MarkFinalFlareCompletedAndNotify() { //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Expected O, but got Unknown //IL_0064: Unknown result type (might be due to invalid IL or missing references) if (PhotonNetwork.IsMasterClient) { LoadPartsStateOnDemand(); if (!peakUnlocked) { PublishPartsState(purchasedPartsMask, consumedPartsMask, unlockedPeak: true, ReadRunId(), "Final flare crafted at Peak"); } CloseHub(); RaiseEventOptions val = new RaiseEventOptions { Receivers = (ReceiverGroup)1 }; PhotonNetwork.RaiseEvent((byte)150, (object)Array.Empty(), val, SendOptions.SendReliable); } } private static int GetCurrentSegmentIndex() { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Expected I4, but got Unknown try { return (int)MapHandler.CurrentSegmentNumber; } catch (Exception) { return -1; } } private static int GetCurrentResourceLevel() { if (PhotonNetwork.InRoom && PhotonNetwork.CurrentRoom != null) { return Mathf.Clamp(ReadRoomInt("CraftPeak.Upgrade.Resource", ResourceUpgradeLevel), 0, 4); } return ResourceUpgradeLevel; } private static string ReadRoomString(string key) { if (!PhotonNetwork.InRoom || PhotonNetwork.CurrentRoom == null) { return string.Empty; } if (!((Dictionary)(object)((RoomInfo)PhotonNetwork.CurrentRoom).CustomProperties).TryGetValue((object)key, out object value) || value == null) { return string.Empty; } return (value as string) ?? Convert.ToString(value); } private static int ReadRoomInt(string key, int fallback) { if (!PhotonNetwork.InRoom || PhotonNetwork.CurrentRoom == null) { return fallback; } if (!((Dictionary)(object)((RoomInfo)PhotonNetwork.CurrentRoom).CustomProperties).TryGetValue((object)key, out object value) || value == null) { return fallback; } try { return Convert.ToInt32(value); } catch (Exception) { return fallback; } } private static bool ReadBool(Hashtable properties, string key, bool fallback) { if (!((Dictionary)(object)properties).TryGetValue((object)key, out object value) || value == null) { return fallback; } try { return Convert.ToBoolean(value); } catch (Exception) { return fallback; } } private bool EnsureCraftRecipesBuilt() { if (!EnsureSharedRecipeSeed()) { ((BaseUnityPlugin)this).Logger.LogWarning((object)"CraftHub is waiting for the host shared recipe seed."); return false; } if (craftRecipes.Count > 0) { return true; } ItemDatabase instance = SingletonAsset.Instance; if ((Object)(object)instance == (Object)null || instance.itemLookup == null || instance.itemLookup.Count == 0) { ((BaseUnityPlugin)this).Logger.LogWarning((object)"CraftHub could not build recipes because ItemDatabase is not ready."); return false; } craftRecipes.Clear(); craftRecipesByOutputId.Clear(); List list = BuildResolvedPool(instance, new string[3] { "FireWood", "Fire Wood", "나뭇가지" }, new string[3] { "Stone", "Rock", "돌" }, new string[4] { "Conch", "Shell", "소라고둥", "소라고동" }); List list2 = BuildResolvedPool(instance, new string[2] { "Binoculars", "망원경" }, new string[3] { "Bing Bong", "BingBong", "빙봉" }, new string[2] { "Bugle", "나팔" }, new string[3] { "Frisbee", "Flying Disc", "플라잉디스크" }); List list3 = BuildResolvedPool(instance, new string[3] { "Guidebook", "Guide Book", "가이드북" }, new string[2] { "Scroll", "스크롤" }); List list4 = BuildResolvedPool(instance, new string[3] { "Weird Shroom", "WeirdShroom", "괴상 버섯" }); List salePool = BuildResolvedPool(instance, new string[3] { "Strange Gem", "StrangeGem", "이상한 보석" }); List list5 = new List(); List list6 = new List(); List list7 = new List(); List list8 = new List(); AddNamedCraftGroup(instance, "음식", RecipeTier.Basic, 0, 4, 9, list, null, list5, new string[3] { "Red Crispberry", "Red Crisp Berry", "빨간색 아삭 열매" }, new string[3] { "Coconut Half", "Half Coconut", "코코넛 반쪽" }, new string[3] { "Trail Mix", "TrailMix", "트레일 믹스" }, new string[3] { "Yellow Berrynana", "Yellow Banana", "노란색 열매나나" }, new string[3] { "Blue Mushroom Berry", "Blue MushroomBerry", "파란색 버섯열매" }, new string[3] { "Sports Drink", "SportsDrink", "스포츠 드링크" }); AddNamedCraftGroup(instance, "등산 장비", RecipeTier.Basic, 0, 7, 13, list, list5, list5, new string[2] { "Backpack", "배낭" }, new string[2] { "Piton", "피톤" }, new string[3] { "Energy Drink", "EnergyDrink", "에너지 드링크" }, new string[2] { "Balloon", "풍선" }, new string[3] { "Portable Stove", "PortableStove", "휴대용 스토브" }); AddNamedCraftGroup(instance, "기타 아이템", RecipeTier.Basic, 0, 5, 11, list, list5, list5, new string[3] { "Pirate Compass", "PirateCompass", "해적 나침반" }, new string[2] { "Snow", "눈" }, new string[3] { "Aloe Vera", "AloeVera", "알로에 베라" }, new string[3] { "Heat Pack", "HeatPack", "핫팩" }, new string[2] { "Torch", "횃불" }); AddNamedCraftGroup(instance, "회복", RecipeTier.Basic, 0, 8, 8, list, null, list5, new string[2] { "Bandage", "붕대" }); AddNamedCraftGroup(instance, "음식", RecipeTier.Standard, 1, 14, 25, MergePools(list, list2), list5, list6, new string[2] { "Tick", "진드기" }, new string[3] { "Red Clusterberry", "Red Cluster Berry", "빨간 송송열매" }, new string[3] { "Kingberry Green", "KingberryGreen", "녹색 대왕열매" }, new string[4] { "FortifiedMilk", "Fortified Milk", "강화 우유", "강화우유" }, new string[2] { "Marshmallow", "마시멜로우" }, new string[3] { "Granola Bar", "GranolaBar", "그래놀라바" }, new string[3] { "Puff Mushroom", "PuffMushroom", "통통버섯" }, new string[3] { "Trumpet Mushroom", "TrumpetMushroom", "나팔버섯" }, new string[3] { "Bundle Mushroom", "BundleMushroom", "다발버섯" }, new string[3] { "Button Mushroom", "ButtonMushroom", "단추버섯" }, new string[3] { "Orange Winterberry", "Orange Winter Berry", "주황 겨울열매" }, new string[3] { "Red Thornberry", "Red Thorn Berry", "빨간 가시열매" }, new string[3] { "Purple Mushroom Berry", "Purple MushroomBerry", "보라색 버섯열매" }); AddNamedCraftGroup(instance, "등산 장비", RecipeTier.Standard, 1, 22, 34, list2, list5, list6, new string[3] { "Shelf Fungus", "ShelfFungus", "선반 균류" }, new string[3] { "Cloud Fungus", "CloudFungus", "구름균류" }, new string[3] { "Rope Spool", "RopeSpool", "밧줄타래" }, new string[3] { "Bounce Fungus", "BounceFungus", "방방 균류" }, new string[3] { "Checkpoint Flag", "CheckpointFlag", "체크포인트 깃발" }); AddNamedCraftGroup(instance, "기타 아이템", RecipeTier.Standard, 1, 18, 29, list2, list5, list6, new string[2] { "Lantern", "랜턴" }, new string[2] { "Antidote", "해독제" }, new string[3] { "Rainbow Candy", "RainbowCandy", "무지개사탕" }, new string[2] { "Parasol", "파라솔" }, new string[2] { "Sunscreen", "선크림" }); AddNamedCraftGroup(instance, "회복", RecipeTier.Standard, 1, 28, 28, list2, list5, list6, new string[4] { "First Aid Kit", "First-Aid Kit", "Medkit", "구급상자" }); AddNamedCraftGroup(instance, "음식", RecipeTier.Advanced, 2, 42, 64, list3, list6, list7, new string[3] { "Hot Dog", "Hotdog", "핫도그" }, new string[3] { "Cooked Bird", "CookedBird", "요리된 새" }, new string[3] { "Airline Food", "Airline Meal", "기내식" }, new string[3] { "Honeycomb Honey", "Honey", "벌집꿀" }, new string[3] { "Scout Cookie", "Scout Snack", "스카우트 과자" }, new string[3] { "Red Mushroom Berry", "Red MushroomBerry", "빨간색 버섯 열매" }); AddNamedCraftGroup(instance, "기타 아이템", RecipeTier.Advanced, 2, 58, 78, list3, list6, list7, new string[2] { "Cactus", "선인장" }, new string[2] { "Dynamite", "다이너마이트" }, new string[3] { "Scout Cannon", "ScoutCannon", "스카우트 캐논" }); AddScoutStatueRecipe(instance, list7); AddNamedCraftGroup(instance, "음식", RecipeTier.Special, 3, 90, 130, MergePools(list3, list4), list7, list8, new string[3] { "Pandora's Box", "Pandora Box", "판도라의 상자" }, new string[3] { "Sleep Berry", "SleepBerry", "수면 열매" }, new string[3] { "Pop Pop", "Bubble Wrap", "뾱뾱이" }); AddNamedCraftGroup(instance, "등산 장비", RecipeTier.Special, 3, 120, 170, MergePools(list3, list4), list6, list8, new string[3] { "Balloon Bunch", "Bunch of Balloons", "풍선 다발" }, new string[3] { "Rescue Hook", "RescueHook", "구조갈고리" }, new string[3] { "Chain Launcher", "ChainLauncher", "사슬발사기" }, new string[3] { "Magic Bean", "MagicBean", "마법의 콩" }, new string[3] { "Rope Cannon", "RopeCannon", "밧줄총" }); AddNamedCraftGroup(instance, "기타 아이템", RecipeTier.Special, 3, 115, 160, MergePools(list3, list4), list7, list8, new string[3] { "Scoutmaster Bugle", "Scoutmaster Horn", "스카우트지도자의 나팔" }, new string[3] { "Cursed Skull", "CursedSkull", "저주받은 해골" }, new string[3] { "Fairy Lantern", "FairyLantern", "요정랜턴" }); AddNamedCraftGroup(instance, "등산 장비", RecipeTier.Masterwork, 4, 230, 330, salePool, list8, null, new string[4] { "Book of Bones", "Bone Book", "뼈의서", "뼈의 서" }, new string[3] { "Anti-Rope Cannon", "Reverse Rope Cannon", "반전 밧줄총" }, new string[3] { "Anti-Rope Spool", "Reverse Rope Spool", "반전 밧줄타래" }, new string[3] { "Friendship Bugle", "Friendship Horn", "우정 나팔" }); AddNamedCraftGroup(instance, "기타 아이템", RecipeTier.Masterwork, 4, 240, 340, salePool, list8, null, new string[3] { "Golden Bing Bong", "GoldenBingBong", "황금 빙봉" }); AddNamedCraftGroup(instance, "회복", RecipeTier.Masterwork, 4, 280, 280, salePool, list8, null, new string[4] { "Cure-All", "Cure All", "Panacea", "만병통치약" }); AddExplicitRecipe(instance, 32, "최종 탈출", RecipeTier.Masterwork, 4, 500, new IngredientCost(112, 1), new IngredientCost(51, 1), PickIngredient(list8, 32, 1), PickIngredient(list7, 32, 2)); craftRecipes.Sort(CompareRecipes); ((BaseUnityPlugin)this).Logger.LogInfo((object)("CraftHub full categorized crafting catalog built. Count=" + craftRecipes.Count + ".")); LogVerifiedCraftItemMapping(67, "부활 / 스카우트 인형"); LogVerifiedCraftItemMapping(56, "음식 / 녹색 대왕열매"); LogVerifiedCraftItemMapping(152, "음식 / 강화 우유"); LogScoutEffigyFinalState("Craft catalog built"); return craftRecipes.Count > 0; } private void LogVerifiedCraftItemMapping(ushort itemId, string expectedCategory) { if (craftRecipesByOutputId.TryGetValue(itemId, out var value) && value != null) { ((BaseUnityPlugin)this).Logger.LogInfo((object)("Verified craft item mapping. ItemID=" + itemId + " | Name=" + value.DisplayName + " | ExpectedUI=" + expectedCategory)); } else { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Expected craft item was not generated. ItemID=" + itemId + " | ExpectedUI=" + expectedCategory)); } } private void AddNamedCraftGroup(ItemDatabase database, string category, RecipeTier tier, int requiredResourceLevel, int minimumMoney, int maximumMoney, List salePool, List previousCraftPool, List outputCollector, params string[][] aliasGroups) { if (aliasGroups == null) { return; } for (int i = 0; i < aliasGroups.Length; i++) { ushort num = ResolveProgressionItemId(database, aliasGroups[i]); if (num != 0 && !IsSaleResourceId(num) && !craftRecipesByOutputId.ContainsKey(num)) { int deterministicSeed = GetDeterministicSeed(num, category); int num2 = Mathf.Max(0, maximumMoney - minimumMoney); int moneyCost = minimumMoney + ((num2 > 0) ? PositiveModulo(deterministicSeed, num2 + 1) : 0); List list = new List(); int wantedCount = ((requiredResourceLevel <= 0) ? 2 : ((requiredResourceLevel != 1) ? 1 : 2)); AddPickedIngredients(list, salePool, num, deterministicSeed, wantedCount); if (requiredResourceLevel > 0) { AddPickedIngredients(list, previousCraftPool, num, deterministicSeed / 3 + 17, (requiredResourceLevel < 3) ? 1 : 2); } if (requiredResourceLevel >= 2) { AddPickedIngredients(list, (CommonIds != null) ? new List(CommonIds) : null, num, deterministicSeed / 7 + 31, 1); } AddExplicitRecipe(database, num, category, tier, requiredResourceLevel, moneyCost, list.ToArray()); if (outputCollector != null && !outputCollector.Contains(num)) { outputCollector.Add(num); } } } } private void AddScoutStatueRecipe(ItemDatabase database, List outputCollector) { ushort num = ResolveProgressionItemId(database, "ScoutEffigy", "Scout Effigy", "스카우트 인형", "Scout Statue", "Scout Statue Item", "Scoutmaster Statue", "Scout Effigy Item", "Scoutmaster Effigy", "Effigy", "Revive Statue", "Resurrection Statue", "스카우트 석상", "스카우트석상", "스카우트 조각상", "부활 석상", "부활석상"); if (num == 0 && (Object)(object)database != (Object)null && database.itemLookup != null && database.itemLookup.TryGetValue(67, out var value) && (Object)(object)value != (Object)null) { num = 67; ((BaseUnityPlugin)this).Logger.LogInfo((object)("[CraftCategoryDiag] ScoutEffigy resolved by fixed ItemID fallback. ItemID=" + num + " | DisplayName=" + GetItemDisplayName(value) + " | ObjectName=" + (((Object)(object)((Component)value).gameObject != (Object)null) ? ((Object)((Component)value).gameObject).name : ""))); } if (num == 0) { ((BaseUnityPlugin)this).Logger.LogError((object)"[CraftCategoryDiag] ScoutEffigy recipe was not created because no matching item was found."); return; } if (craftRecipesByOutputId.ContainsKey(num)) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("[CraftCategoryDiag] ScoutEffigy recipe already exists before AddScoutStatueRecipe. ItemID=" + num)); return; } AddExplicitRecipe(database, num, "특수", RecipeTier.Advanced, 2, 100); if (craftRecipesByOutputId.TryGetValue(num, out var value2) && value2 != null) { ((BaseUnityPlugin)this).Logger.LogInfo((object)("[CraftCategoryDiag] ScoutEffigy recipe created. ItemID=" + num + " | DisplayName=" + value2.DisplayName + " | SourceCategory=" + value2.Category + " | UiCategory=" + GetCraftUiCategoryName(GetCraftUiCategory(value2)) + " | Price=" + value2.MoneyCost)); } else { ((BaseUnityPlugin)this).Logger.LogError((object)("[CraftCategoryDiag] AddExplicitRecipe returned without registering ScoutEffigy. ItemID=" + num)); } outputCollector?.Add(num); } private static void AddPickedIngredients(List destination, List pool, ushort excludedItemId, int seed, int wantedCount) { if (destination == null || pool == null || pool.Count == 0 || wantedCount <= 0) { return; } HashSet hashSet = new HashSet(); for (int i = 0; i < destination.Count; i++) { hashSet.Add(destination[i].ItemId); } for (int j = 0; j < pool.Count; j++) { if (wantedCount <= 0) { break; } ushort num = pool[PositiveModulo(seed + j * 13, pool.Count)]; if (num != 0 && num != excludedItemId && hashSet.Add(num)) { destination.Add(new IngredientCost(num, 1)); wantedCount--; } } } private static IngredientCost PickIngredient(List pool, ushort excludedItemId, int seed) { if (pool == null || pool.Count == 0) { return new IngredientCost(28, 1); } for (int i = 0; i < pool.Count; i++) { ushort num = pool[PositiveModulo(seed + i, pool.Count)]; if (num != 0 && num != excludedItemId) { return new IngredientCost(num, 1); } } return new IngredientCost(28, 1); } private static bool IsSaleResourceId(ushort itemId) { switch (itemId) { case 13: case 14: case 15: case 28: case 34: case 49: case 51: case 69: case 72: case 99: case 112: return true; default: return false; } } private int GetDeterministicSeed(ushort itemId, string category) { int num = (sharedRecipeSeedLoaded ? sharedRecipeSeed : 17); string text = category ?? string.Empty; for (int i = 0; i < text.Length; i++) { num = num * 31 + text[i]; } return num * 31 + itemId; } private static int PositiveModulo(int value, int modulus) { if (modulus <= 0) { return 0; } int num = value % modulus; return (num < 0) ? (num + modulus) : num; } private void AddExplicitRecipe(ItemDatabase database, ushort outputItemId, string category, RecipeTier tier, int requiredResourceLevel, int moneyCost, params IngredientCost[] ingredients) { if ((Object)(object)database == (Object)null || database.itemLookup == null || !database.itemLookup.TryGetValue(outputItemId, out var value) || (Object)(object)value == (Object)null || (Object)(object)((Component)value).gameObject == (Object)null || value.UIData == null) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("CraftHub skipped explicit recipe because item was not found. ItemID=" + outputItemId)); return; } CraftRecipe craftRecipe = new CraftRecipe { OutputItemId = outputItemId, OutputPrefab = value, DisplayName = GetItemDisplayName(value), Category = (category ?? string.Empty), Tier = tier, RequiredResourceLevel = Mathf.Clamp(requiredResourceLevel, 0, 4), MoneyCost = Mathf.Max(0, moneyCost) }; if (ingredients != null) { foreach (IngredientCost ingredientCost in ingredients) { if (ingredientCost != null) { AddIngredient(craftRecipe, ingredientCost.ItemId, ingredientCost.Count); } } } craftRecipes.Add(craftRecipe); craftRecipesByOutputId[outputItemId] = craftRecipe; } private static int CompareRecipes(CraftRecipe left, CraftRecipe right) { int num = left.RequiredResourceLevel.CompareTo(right.RequiredResourceLevel); if (num != 0) { return num; } num = left.Tier.CompareTo(right.Tier); if (num != 0) { return num; } return string.Compare(left.DisplayName, right.DisplayName, StringComparison.Ordinal); } private static void AddIngredient(CraftRecipe recipe, ushort itemId, int count) { if (recipe == null || count <= 0) { return; } for (int i = 0; i < recipe.Ingredients.Count; i++) { if (recipe.Ingredients[i].ItemId == itemId) { recipe.Ingredients[i].Count += count; return; } } recipe.Ingredients.Add(new IngredientCost(itemId, count)); } private static bool IsCraftIngredientId(ushort itemId) { return itemId != 0; } internal CraftRecipe GetCraftRecipeAtCard(int cardIndex) { int filteredPosition = craftPage * 8 + cardIndex; int recipeIndexAtFilteredPosition = GetRecipeIndexAtFilteredPosition(filteredPosition); return (recipeIndexAtFilteredPosition >= 0 && recipeIndexAtFilteredPosition < craftRecipes.Count) ? craftRecipes[recipeIndexAtFilteredPosition] : null; } internal void SelectCraftUiCategory(CraftUiCategory category) { if (selectedCraftUiCategory != category) { selectedCraftUiCategory = category; craftPage = 0; selectedCraftRecipeIndex = GetRecipeIndexAtFilteredPosition(0); LogCraftCategoryContents(category, "Category selected"); SetTabStatus(HubTab.Craft, GetCraftUiCategoryName(category) + " 제작 목록을 표시합니다."); RefreshWindow(); } } internal void SelectCraftCard(int cardIndex) { int filteredPosition = craftPage * 8 + cardIndex; int recipeIndexAtFilteredPosition = GetRecipeIndexAtFilteredPosition(filteredPosition); if (recipeIndexAtFilteredPosition >= 0 && recipeIndexAtFilteredPosition < craftRecipes.Count) { selectedCraftRecipeIndex = recipeIndexAtFilteredPosition; SetTabStatus(HubTab.Craft, craftRecipes[recipeIndexAtFilteredPosition].DisplayName + " 제작식을 선택했습니다."); } } internal void PreviousCraftPage() { if (craftPage > 0) { craftPage--; selectedCraftRecipeIndex = GetRecipeIndexAtFilteredPosition(craftPage * 8); RefreshWindow(); } } internal void NextCraftPage() { if (craftPage < CraftTotalPages - 1) { craftPage++; selectedCraftRecipeIndex = GetRecipeIndexAtFilteredPosition(craftPage * 8); RefreshWindow(); } } private int GetFilteredCraftRecipeCount() { int num = 0; for (int i = 0; i < craftRecipes.Count; i++) { if (GetCraftUiCategory(craftRecipes[i]) == selectedCraftUiCategory) { num++; } } return num; } private int GetRecipeIndexAtFilteredPosition(int filteredPosition) { if (filteredPosition < 0) { return -1; } int num = 0; for (int i = 0; i < craftRecipes.Count; i++) { if (GetCraftUiCategory(craftRecipes[i]) == selectedCraftUiCategory) { if (num == filteredPosition) { return i; } num++; } } return -1; } private static CraftUiCategory GetCraftUiCategory(CraftRecipe recipe) { if (recipe == null) { return CraftUiCategory.Essential; } if (recipe.OutputItemId == 67) { return CraftUiCategory.Revive; } string normalizedName = NormalizeCraftUiName(recipe.DisplayName); if (MatchesCraftUiName(normalizedName, FoodCraftUiNames)) { return CraftUiCategory.Food; } if (MatchesCraftUiName(normalizedName, HealCraftUiNames)) { return CraftUiCategory.Heal; } if (MatchesCraftUiName(normalizedName, ReviveCraftUiNames)) { return CraftUiCategory.Revive; } if (MatchesCraftUiName(normalizedName, ClimbingCraftUiNames)) { return CraftUiCategory.Climbing; } if (MatchesCraftUiName(normalizedName, EssentialCraftUiNames)) { return CraftUiCategory.Essential; } return CraftUiCategory.Essential; } private static bool MatchesCraftUiName(string normalizedName, string[] candidates) { if (string.IsNullOrEmpty(normalizedName) || candidates == null) { return false; } for (int i = 0; i < candidates.Length; i++) { if (normalizedName.IndexOf(candidates[i], StringComparison.OrdinalIgnoreCase) >= 0) { return true; } } return false; } private static string NormalizeCraftUiName(string value) { if (string.IsNullOrEmpty(value)) { return string.Empty; } StringBuilder stringBuilder = new StringBuilder(value.Length); foreach (char c in value) { if (char.IsLetterOrDigit(c)) { stringBuilder.Append(char.ToLowerInvariant(c)); } } return stringBuilder.ToString(); } internal static string GetCraftUiCategoryName(CraftUiCategory category) { return category switch { CraftUiCategory.Climbing => "등산", CraftUiCategory.Food => "음식", CraftUiCategory.Heal => "힐", CraftUiCategory.Revive => "부활", _ => "필수", }; } internal static Texture GetCraftRecipeIcon(CraftRecipe recipe) { if (recipe == null || (Object)(object)recipe.OutputPrefab == (Object)null || recipe.OutputPrefab.UIData == null) { return null; } return (Texture)(object)recipe.OutputPrefab.UIData.GetIcon(); } private void LogCraftCategoryContents(CraftUiCategory category, string reason) { if (ModLogger == null) { return; } int num = 0; bool flag = false; StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < craftRecipes.Count; i++) { CraftRecipe craftRecipe = craftRecipes[i]; if (craftRecipe != null && GetCraftUiCategory(craftRecipe) == category) { num++; if (craftRecipe.OutputItemId == 67) { flag = true; } if (stringBuilder.Length > 0) { stringBuilder.Append(", "); } stringBuilder.Append(craftRecipe.OutputItemId); stringBuilder.Append(":"); stringBuilder.Append(craftRecipe.DisplayName); } } ModLogger.LogInfo((object)("[CraftCategoryDiag] Reason=" + reason + " | Category=" + GetCraftUiCategoryName(category) + " | Count=" + num + " | ScoutEffigyFound=" + flag + " | Items=[" + stringBuilder?.ToString() + "]")); } private void LogScoutEffigyFinalState(string reason) { if (ModLogger != null) { CraftRecipe value; bool flag = craftRecipesByOutputId.TryGetValue(67, out value) && value != null; ModLogger.LogInfo((object)("[CraftCategoryDiag] Final scout state. Reason=" + reason + " | Exists=" + flag + " | CraftRecipeCount=" + craftRecipes.Count + (flag ? (" | DisplayName=" + value.DisplayName + " | SourceCategory=" + value.Category + " | UiCategory=" + GetCraftUiCategoryName(GetCraftUiCategory(value)) + " | Price=" + value.MoneyCost) : string.Empty))); LogCraftCategoryContents(CraftUiCategory.Revive, reason); } } internal string BuildCraftRequirementText(CraftRecipe recipe, out bool ready) { ready = false; if (recipe == null) { return "제작식을 선택하세요."; } int currentResourceLevel = GetCurrentResourceLevel(); if (currentResourceLevel < recipe.RequiredResourceLevel) { return "" + GetResourceGradeName(recipe.RequiredResourceLevel) + " 제작 등급이 필요합니다."; } if (recipe.OutputItemId == 32) { int currentSegmentIndex = GetCurrentSegmentIndex(); int currentResourceLevel2 = GetCurrentResourceLevel(); if (currentSegmentIndex != 5) { return "최종 조명탄은 정상 구간에서만 제작할 수 있습니다."; } if (currentResourceLevel2 < 4) { return "Legendary 제작 등급이 필요합니다."; } if (peakUnlocked) { return "최종 조명탄 제작과 탈출 신호 발사가 이미 완료되었습니다."; } } Dictionary dictionary = GetCachedPartyResourceCounts(); StringBuilder stringBuilder = sharedTextBuilder; stringBuilder.Length = 0; bool flag = true; for (int i = 0; i < recipe.Ingredients.Count; i++) { IngredientCost ingredientCost = recipe.Ingredients[i]; dictionary.TryGetValue(ingredientCost.ItemId, out var value); bool flag2 = value >= ingredientCost.Count; flag = flag && flag2; if (i > 0) { stringBuilder.Append('\n'); } stringBuilder.Append(flag2 ? "" : ""); stringBuilder.Append(GetIngredientDisplayName(ingredientCost.ItemId)); stringBuilder.Append(' '); stringBuilder.Append(value); stringBuilder.Append('/'); stringBuilder.Append(ingredientCost.Count); stringBuilder.Append(""); } int num = cachedSharedMoney; bool flag3 = num >= recipe.MoneyCost; flag = flag && flag3; if (stringBuilder.Length > 0) { stringBuilder.Append('\n'); } stringBuilder.Append(flag3 ? "" : ""); stringBuilder.Append("공유 돈 "); stringBuilder.Append(num); stringBuilder.Append('/'); stringBuilder.Append(recipe.MoneyCost); stringBuilder.Append("원"); ready = flag; return stringBuilder.ToString(); } private Dictionary GetCachedPartyResourceCounts() { float unscaledTime = Time.unscaledTime; if (unscaledTime < partyResourceCacheUntil) { return cachedPartyResourceCounts; } cachedPartyResourceCounts.Clear(); FillPartyResourceCounts(cachedPartyResourceCounts); partyResourceCacheUntil = unscaledTime + 1f; return cachedPartyResourceCounts; } internal void RequestCraft() { //IL_011a: Unknown result type (might be due to invalid IL or missing references) //IL_011f: Unknown result type (might be due to invalid IL or missing references) //IL_0121: Unknown result type (might be due to invalid IL or missing references) //IL_0128: Expected O, but got Unknown //IL_0130: Unknown result type (might be due to invalid IL or missing references) if (pendingRequest != PendingRequest.None) { SetTabStatus(HubTab.Craft, "다른 요청을 처리 중입니다."); return; } CraftRecipe selectedCraftRecipe = SelectedCraftRecipe; if (selectedCraftRecipe == null) { SetTabStatus(HubTab.Craft, "제작할 아이템을 선택하세요."); return; } BuildCraftRequirementText(selectedCraftRecipe, out var ready); if (!ready) { SetTabStatus(HubTab.Craft, "공유 돈 또는 제작 재료가 부족합니다."); return; } Player localPlayer = Player.localPlayer; if ((Object)(object)localPlayer == (Object)null) { SetTabStatus(HubTab.Craft, "플레이어 인벤토리를 찾지 못했습니다."); return; } if (!localPlayer.HasEmptySlot(selectedCraftRecipe.OutputItemId)) { SetTabStatus(HubTab.Craft, "완성품을 받을 빈 슬롯이 없어 제작할 수 없습니다."); return; } pendingRequest = PendingRequest.Craft; requestStartedAt = Time.unscaledTime; SetTabStatus(HubTab.Craft, selectedCraftRecipe.DisplayName + " 제작을 요청했습니다..."); object[] array = new object[1] { (int)selectedCraftRecipe.OutputItemId }; int actorNumber = LocalActorNumber(); if (PhotonNetwork.IsMasterClient) { ProcessCraftRequestOnHost(actorNumber, array); return; } RaiseEventOptions val = new RaiseEventOptions { Receivers = (ReceiverGroup)2 }; if (!PhotonNetwork.RaiseEvent((byte)142, (object)array, val, SendOptions.SendReliable)) { pendingRequest = PendingRequest.None; SetTabStatus(HubTab.Craft, "제작 요청 전송에 실패했습니다."); } } internal void SelectUpgradeKind(UpgradeKind kind) { selectedUpgradeKind = kind; SetTabStatus(HubTab.Upgrade, GetUpgradeDisplayName(kind) + " 항목을 선택했습니다."); } internal void RequestUpgrade() { //IL_01d4: Unknown result type (might be due to invalid IL or missing references) //IL_01d9: Unknown result type (might be due to invalid IL or missing references) //IL_01db: Unknown result type (might be due to invalid IL or missing references) //IL_01e2: Expected O, but got Unknown //IL_01ea: Unknown result type (might be due to invalid IL or missing references) if (!upgradeStateLoaded) { SetTabStatus(HubTab.Upgrade, "강화 상태를 아직 불러오지 못했습니다."); return; } if (pendingRequest != PendingRequest.None) { SetTabStatus(HubTab.Upgrade, "다른 요청을 처리 중입니다."); return; } int upgradeCurrentLevel = GetUpgradeCurrentLevel(selectedUpgradeKind); if (upgradeCurrentLevel >= GetUpgradeMaximumLevel(selectedUpgradeKind)) { SetTabStatus(HubTab.Upgrade, "이미 최대 단계입니다."); return; } int nextUpgradeCost = GetNextUpgradeCost(selectedUpgradeKind); if (selectedUpgradeKind == UpgradeKind.CampfireEfficiency) { CampfireBuildRecipe nextCampfireRecipe = GetNextCampfireRecipe(); if (nextCampfireRecipe == null) { SetTabStatus(HubTab.Upgrade, "모든 다음 모닥불을 이미 제작했습니다."); return; } if (GetCurrentResourceLevel() < nextCampfireRecipe.RequiredResourceLevel) { SetTabStatus(HubTab.Upgrade, GetResourceGradeName(nextCampfireRecipe.RequiredResourceLevel) + " 자원 등급이 필요합니다."); return; } if (!HasRequiredModulesForCampfireStage(nextCampfireRecipe.Stage, out var message)) { SetTabStatus(HubTab.Upgrade, message); return; } if (!TryBuildCraftConsumptionPlan(MakeTemporaryRecipe(nextCampfireRecipe.Ingredients), out var _, out var missingMessage)) { SetTabStatus(HubTab.Upgrade, missingMessage); return; } } if (ReadSharedMoney() < nextUpgradeCost) { SetTabStatus(HubTab.Upgrade, "공유 돈이 부족합니다."); return; } pendingRequest = PendingRequest.Upgrade; requestStartedAt = Time.unscaledTime; SetTabStatus(HubTab.Upgrade, GetUpgradeDisplayName(selectedUpgradeKind) + " 강화를 요청했습니다..."); object[] array = new object[1] { (int)selectedUpgradeKind }; int actor = LocalActorNumber(); if (PhotonNetwork.IsMasterClient) { ProcessUpgradeRequestOnHost(actor, array); return; } RaiseEventOptions val = new RaiseEventOptions { Receivers = (ReceiverGroup)2 }; if (!PhotonNetwork.RaiseEvent((byte)145, (object)array, val, SendOptions.SendReliable)) { pendingRequest = PendingRequest.None; SetTabStatus(HubTab.Upgrade, "강화 요청 전송에 실패했습니다."); } } internal void SelectSellSlot(int slotId) { Player localPlayer = Player.localPlayer; if ((Object)(object)localPlayer == (Object)null || localPlayer.itemSlots == null || slotId < 0 || slotId >= localPlayer.itemSlots.Length) { selectedSellSlotId = -1; SetTabStatus(HubTab.Sell, "선택한 인벤토리 슬롯을 찾지 못했습니다."); return; } selectedSellSlotId = slotId; selectedSellQuantity = 1; ItemSlot itemSlot = localPlayer.GetItemSlot((byte)slotId); if (itemSlot == null || itemSlot.IsEmpty() || (Object)(object)itemSlot.prefab == (Object)null) { SetTabStatus(HubTab.Sell, slotId + 1 + "번 슬롯은 비어 있습니다."); } else if (!Spawn.IsSaleResourceId(itemSlot.prefab.itemID)) { SetTabStatus(HubTab.Sell, slotId + 1 + "번 슬롯의 아이템은 판매 대상 자원이 아닙니다."); } else { SetTabStatus(HubTab.Sell, slotId + 1 + "번 슬롯을 판매 대상으로 선택했습니다."); } } internal string BuildSelectedSellText(out bool canSell) { canSell = false; Player localPlayer = Player.localPlayer; if ((Object)(object)localPlayer == (Object)null || localPlayer.itemSlots == null || selectedSellSlotId < 0 || selectedSellSlotId >= localPlayer.itemSlots.Length) { selectedSellQuantity = 1; return "판매할 인벤토리 슬롯을 선택하세요."; } ItemSlot itemSlot = localPlayer.GetItemSlot((byte)selectedSellSlotId); if (itemSlot == null || itemSlot.IsEmpty() || (Object)(object)itemSlot.prefab == (Object)null) { selectedSellQuantity = 1; return "선택한 슬롯이 비어 있습니다."; } ushort itemID = itemSlot.prefab.itemID; if (!Spawn.IsSaleResourceId(itemID)) { selectedSellQuantity = 1; return "선택 아이템: " + GetItemDisplayName(itemSlot.prefab) + "\n이 아이템은 판매 대상 자원이 아닙니다."; } int num = Mathf.Max(0, GetSellPrice(itemID) * NormalizeSellMultiplier(upgradeState.SellMultiplier)); int num2 = Mathf.Max(1, InventoryStack.GetStackCount(localPlayer, (byte)selectedSellSlotId)); selectedSellQuantity = Mathf.Clamp(selectedSellQuantity, 1, num2); int num3 = num * selectedSellQuantity; canSell = num > 0 && pendingRequest == PendingRequest.None; return "선택 아이템: " + GetItemDisplayName(itemSlot.prefab) + "\n등급: " + GetRarityName(itemID) + " | 보유: " + num2 + "개\n판매 수량: " + selectedSellQuantity + "/" + num2 + "개\n개당 판매가: " + num + "원\n예상 판매액: " + num3 + "원\n\n마우스 휠 ↑↓: 1개씩 조절\nShift + 휠: 5개씩 조절"; } internal void RequestSell() { //IL_019d: Unknown result type (might be due to invalid IL or missing references) //IL_01a2: Unknown result type (might be due to invalid IL or missing references) //IL_01a4: Unknown result type (might be due to invalid IL or missing references) //IL_01ab: Expected O, but got Unknown //IL_01b4: Unknown result type (might be due to invalid IL or missing references) if (pendingRequest != PendingRequest.None) { SetTabStatus(HubTab.Sell, "다른 요청을 처리 중입니다."); return; } Player localPlayer = Player.localPlayer; if ((Object)(object)localPlayer == (Object)null || localPlayer.itemSlots == null || selectedSellSlotId < 0 || selectedSellSlotId >= localPlayer.itemSlots.Length) { SetTabStatus(HubTab.Sell, "판매할 인벤토리 슬롯을 선택하세요."); return; } byte b = (byte)selectedSellSlotId; ItemSlot itemSlot = localPlayer.GetItemSlot(b); if (itemSlot == null || itemSlot.IsEmpty() || (Object)(object)itemSlot.prefab == (Object)null || !Spawn.IsSaleResourceId(itemSlot.prefab.itemID)) { SetTabStatus(HubTab.Sell, "선택한 슬롯에는 판매 가능한 자원이 없습니다."); return; } ushort itemID = itemSlot.prefab.itemID; int num = Mathf.Max(1, InventoryStack.GetStackCount(localPlayer, b)); int num2 = (selectedSellQuantity = Mathf.Clamp(selectedSellQuantity, 1, num)); string text = ((itemSlot.data != null) ? itemSlot.data.guid.ToString() : string.Empty); pendingRequest = PendingRequest.Sell; requestStartedAt = Time.unscaledTime; SetTabStatus(HubTab.Sell, num2 + "개 판매 요청을 처리 중입니다..."); object[] array = new object[4] { selectedSellSlotId, (int)itemID, text, num2 }; int actorNumber = LocalActorNumber(); if (PhotonNetwork.IsMasterClient) { ProcessSellRequestOnHost(actorNumber, array); return; } RaiseEventOptions val = new RaiseEventOptions { Receivers = (ReceiverGroup)2 }; if (!PhotonNetwork.RaiseEvent((byte)140, (object)array, val, SendOptions.SendReliable)) { pendingRequest = PendingRequest.None; SetTabStatus(HubTab.Sell, "판매 요청 전송에 실패했습니다."); } } private static void FillPartyResourceCounts(Dictionary counts) { if (counts == null) { return; } List allPlayerCharacters = PlayerHandler.GetAllPlayerCharacters(); for (int i = 0; i < allPlayerCharacters.Count; i++) { Character val = allPlayerCharacters[i]; if ((Object)(object)val == (Object)null || (Object)(object)val.player == (Object)null || (Object)(object)((MonoBehaviourPun)val).photonView == (Object)null || ((MonoBehaviourPun)val).photonView.Owner == null || ((MonoBehaviourPun)val).photonView.Owner.IsInactive) { continue; } Player player = val.player; if (player.itemSlots != null) { for (int j = 0; j < player.itemSlots.Length; j++) { AddSlotCount(counts, player, player.itemSlots[j], (byte)j, stackAware: true); } } AddSlotCount(counts, player, player.tempFullSlot, (byte)((player.tempFullSlot != null) ? player.tempFullSlot.itemSlotID : 250), stackAware: true); BackpackData val2 = null; if (player.backpackSlot != null && !((ItemSlot)player.backpackSlot).IsEmpty() && ((ItemSlot)player.backpackSlot).data != null && ((ItemSlot)player.backpackSlot).data.TryGetDataEntry((DataEntryKey)7, ref val2) && val2 != null && val2.itemSlots != null) { for (int k = 0; k < val2.itemSlots.Length; k++) { AddSlotCount(counts, player, val2.itemSlots[k], 0, stackAware: false); } } } } private static void AddSlotCount(Dictionary counts, Player player, ItemSlot slot, byte slotId, bool stackAware) { if (slot != null && !slot.IsEmpty() && !((Object)(object)slot.prefab == (Object)null) && IsCraftIngredientId(slot.prefab.itemID)) { int num = Mathf.Max(1, InventoryStack.GetStackCount(slot)); counts.TryGetValue(slot.prefab.itemID, out var value); counts[slot.prefab.itemID] = value + num; } } private static int CountLocalNormalSlotUnits(int slotId, ushort itemId) { Player localPlayer = Player.localPlayer; if ((Object)(object)localPlayer == (Object)null || localPlayer.itemSlots == null || slotId < 0 || slotId >= localPlayer.itemSlots.Length) { return 0; } ItemSlot itemSlot = localPlayer.GetItemSlot((byte)slotId); if (itemSlot == null || itemSlot.IsEmpty() || (Object)(object)itemSlot.prefab == (Object)null || itemSlot.prefab.itemID != itemId) { return 0; } return Mathf.Max(1, InventoryStack.GetStackCount(localPlayer, (byte)slotId)); } private static int CountLocalItemUnits(ushort itemId) { Player localPlayer = Player.localPlayer; if ((Object)(object)localPlayer == (Object)null) { return 0; } int num = 0; if (localPlayer.itemSlots != null) { for (int i = 0; i < localPlayer.itemSlots.Length; i++) { ItemSlot val = localPlayer.itemSlots[i]; if (val != null && !val.IsEmpty() && !((Object)(object)val.prefab == (Object)null) && val.prefab.itemID == itemId) { num += Mathf.Max(1, InventoryStack.GetStackCount(localPlayer, (byte)i)); } } } ItemSlot tempFullSlot = localPlayer.tempFullSlot; if (tempFullSlot != null && !tempFullSlot.IsEmpty() && (Object)(object)tempFullSlot.prefab != (Object)null && tempFullSlot.prefab.itemID == itemId) { num += Mathf.Max(1, InventoryStack.GetStackCount(localPlayer, tempFullSlot.itemSlotID)); } BackpackData val2 = null; if (localPlayer.backpackSlot != null && !((ItemSlot)localPlayer.backpackSlot).IsEmpty() && ((ItemSlot)localPlayer.backpackSlot).data != null && ((ItemSlot)localPlayer.backpackSlot).data.TryGetDataEntry((DataEntryKey)7, ref val2) && val2 != null && val2.itemSlots != null) { for (int j = 0; j < val2.itemSlots.Length; j++) { ItemSlot val3 = val2.itemSlots[j]; if (val3 != null && !val3.IsEmpty() && (Object)(object)val3.prefab != (Object)null && val3.prefab.itemID == itemId) { num += Mathf.Max(1, InventoryStack.GetStackCount(val3)); } } } return num; } private static string GetItemDisplayName(Item item) { if ((Object)(object)item == (Object)null) { return "<이름 없음>"; } string name = item.GetName(); if (!string.IsNullOrEmpty(name)) { return name; } if (item.UIData != null && !string.IsNullOrEmpty(item.UIData.itemName)) { return item.UIData.itemName; } return ((Object)(object)((Component)item).gameObject != (Object)null) ? ((Object)((Component)item).gameObject).name : "<이름 없음>"; } private static string GetIngredientDisplayName(ushort itemId) { Item val = default(Item); if (ItemDatabase.TryGetItem(itemId, ref val) && (Object)(object)val != (Object)null) { return GetItemDisplayName(val); } return itemId switch { 0 => "<아이템 데이터 확인 필요>", 28 => "나뭇가지", 72 => "돌", 69 => "소라고둥", 109 => "횃불", 14 => "망원경", 13 => "빙봉", 15 => "나팔", 99 => "플라잉 디스크", 34 => "가이드북", 49 => "스크롤", 51 => "괴상 버섯", 112 => "이상한 보석", _ => "Item " + itemId, }; } private static string GetTierName(RecipeTier tier) { return tier switch { RecipeTier.Basic => "기초", RecipeTier.Standard => "일반", RecipeTier.Advanced => "고급", RecipeTier.Special => "특수", RecipeTier.Masterwork => "최고급", _ => "알 수 없음", }; } private void BindUpgradeConfig() { //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Expected O, but got Unknown resourceUpgradeFormula = BindFormula("02. 자원 등급 강화", 20, 40); stackUpgradeFormula = BindFormula("04. 인벤토리 적재 강화", 12, 18); campfireUpgradeFormula = BindFormula("05. 모닥불 효율 강화", 20, 50); doubleYieldCostConfig = ((BaseUnityPlugin)this).Config.Bind("06. 수집량 배율 강화", "강화 비용", 60, new ConfigDescription("수집량 배율 강화의 1단계 기본 비용입니다. x3, x4, x5 단계는 각각 기본 비용의 2배, 3배, 4배입니다.", (AcceptableValueBase)(object)new AcceptableValueRange(0, 100000), Array.Empty())); sellValueUpgradeFormula = BindFormula("07. 아이템 판매 수익 강화", 40, 60); } private UpgradeFormulaConfig BindFormula(string section, int baseCost, int costGrowth) { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Expected O, but got Unknown //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Expected O, but got Unknown return new UpgradeFormulaConfig { BaseCost = ((BaseUnityPlugin)this).Config.Bind(section, "1단계 기본 비용", baseCost, new ConfigDescription("첫 단계 강화 비용입니다.", (AcceptableValueBase)(object)new AcceptableValueRange(0, 100000), Array.Empty())), CostGrowth = ((BaseUnityPlugin)this).Config.Bind(section, "단계별 추가 비용", costGrowth, new ConfigDescription("다음 단계마다 추가되는 비용입니다.", (AcceptableValueBase)(object)new AcceptableValueRange(0, 100000), Array.Empty())) }; } private void LoadUpgradeStateOnDemand() { upgradeRoomStateDirty = false; if (!PhotonNetwork.InRoom || PhotonNetwork.CurrentRoom == null || !gameplayScene) { return; } bool flag = ReadUpgradeStateFromRoom(force: false); string text = ReadRunId(); if (!PhotonNetwork.IsMasterClient) { if (!flag) { upgradeState = UpgradeState.CreateDefault(); upgradeState.RunId = text; upgradeState.BaseStackCount = 10; upgradeState.BaseCampfireMaterials = new int[3] { Mathf.Max(0, CampfireGate.RequiredFireWoodCount), Mathf.Max(0, CampfireGate.RequiredStoneCount), Mathf.Max(0, CampfireGate.RequiredTorchCount) }; upgradeStateLoaded = true; ApplyUpgradeEffects("Client default state until host initializes room"); } return; } bool flag2 = upgradeStateLoaded && !string.IsNullOrEmpty(text) && !string.Equals(upgradeState.RunId, text, StringComparison.Ordinal); if (!flag || !upgradeStateLoaded || flag2) { InitializeFreshState(string.IsNullOrEmpty(text) ? Guid.NewGuid().ToString("N") : text); pendingFreshUpgradeRun = false; return; } if (pendingFreshUpgradeRun && !string.IsNullOrEmpty(text) && !string.Equals(upgradeState.RunId, text, StringComparison.Ordinal)) { InitializeFreshState(text); } pendingFreshUpgradeRun = false; } private void InitializeFreshState(string runId) { if (PhotonNetwork.IsMasterClient) { UpgradeState upgradeState = UpgradeState.CreateDefault(); upgradeState.RunId = runId ?? string.Empty; upgradeState.OwnerActor = LocalActorNumber(); upgradeState.BaseStackCount = 10; upgradeState.BaseCampfireMaterials = new int[3] { Mathf.Max(0, CampfireGate.RequiredFireWoodCount), Mathf.Max(0, CampfireGate.RequiredStoneCount), Mathf.Max(0, CampfireGate.RequiredTorchCount) }; PublishUpgradeState(upgradeState, "Fresh run"); } } private string ReadRunId() { if (!((Dictionary)(object)((RoomInfo)PhotonNetwork.CurrentRoom).CustomProperties).TryGetValue((object)"CraftPeak.Run.Id", out object value) || value == null) { return string.Empty; } return (value as string) ?? Convert.ToString(value); } private bool ReadUpgradeStateFromRoom(bool force) { if (!PhotonNetwork.InRoom || PhotonNetwork.CurrentRoom == null) { return false; } Hashtable customProperties = ((RoomInfo)PhotonNetwork.CurrentRoom).CustomProperties; object value = null; object value2 = null; if (!((Dictionary)(object)customProperties).TryGetValue((object)"CraftPeak.Upgrade.Protocol", out value) || !((Dictionary)(object)customProperties).TryGetValue((object)"CraftPeak.Upgrade.Revision", out value2)) { return false; } try { if (Convert.ToInt32(value) != 1) { return false; } UpgradeState upgradeState = UpgradeState.CreateDefault(); upgradeState.Protocol = 1; upgradeState.Revision = Convert.ToInt32(value2); upgradeState.OwnerActor = ReadInt(customProperties, "CraftPeak.Upgrade.Owner", 0); upgradeState.RunId = ReadString(customProperties, "CraftPeak.Upgrade.RunId"); upgradeState.ResourceLevel = Mathf.Clamp(ReadInt(customProperties, "CraftPeak.Upgrade.Resource", 0), 0, 4); upgradeState.StackLevel = Mathf.Clamp(ReadInt(customProperties, "CraftPeak.Upgrade.Stack", 0), 0, 4); upgradeState.CampfireLevel = Mathf.Clamp(ReadInt(customProperties, "CraftPeak.Upgrade.Campfire", 0), 0, 4); upgradeState.YieldMultiplier = Mathf.Clamp(ReadInt(customProperties, "CraftPeak.Upgrade.Yield", 1), 1, 5); upgradeState.SellMultiplier = NormalizeSellMultiplier(ReadInt(customProperties, "CraftPeak.Upgrade.SellMultiplier", 1)); upgradeState.BaseStackCount = 10; upgradeState.BaseCampfireMaterials = ReadIntArray(customProperties, "CraftPeak.Upgrade.BaseCampfire", new int[3] { 1, 1, 1 }); upgradeState = NormalizeUpgradeState(upgradeState); if (!force && upgradeStateLoaded && upgradeState.Revision < this.upgradeState.Revision) { return true; } bool flag = !upgradeStateLoaded || force || upgradeState.Revision != this.upgradeState.Revision || !string.Equals(upgradeState.RunId, this.upgradeState.RunId, StringComparison.Ordinal); this.upgradeState = upgradeState; upgradeStateLoaded = true; if (flag) { ApplyUpgradeEffects("Room upgradeState"); RefreshWindow(); } return true; } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogError((object)("Upgrade upgradeState read failed: " + ex)); return false; } } private bool PublishUpgradeState(UpgradeState value, string reason) { //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Expected O, but got Unknown //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Expected O, but got Unknown //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Expected O, but got Unknown //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Expected O, but got Unknown //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Expected O, but got Unknown //IL_00e0: Unknown result type (might be due to invalid IL or missing references) //IL_00f6: Expected O, but got Unknown //IL_00f7: Unknown result type (might be due to invalid IL or missing references) //IL_010d: Expected O, but got Unknown //IL_010e: Unknown result type (might be due to invalid IL or missing references) //IL_0124: Expected O, but got Unknown //IL_0125: Unknown result type (might be due to invalid IL or missing references) //IL_013b: Expected O, but got Unknown //IL_013c: Unknown result type (might be due to invalid IL or missing references) //IL_0152: Expected O, but got Unknown //IL_0153: Unknown result type (might be due to invalid IL or missing references) //IL_0169: Expected O, but got Unknown //IL_016b: Expected O, but got Unknown if (!PhotonNetwork.InRoom || !PhotonNetwork.IsMasterClient || PhotonNetwork.CurrentRoom == null || value == null) { return false; } UpgradeState upgradeState = NormalizeUpgradeState(value.Clone()); upgradeState.Protocol = 1; upgradeState.Revision = Mathf.Max(this.upgradeState.Revision, upgradeState.Revision) + 1; upgradeState.OwnerActor = LocalActorNumber(); Hashtable val = new Hashtable(); ((Dictionary)val).Add((object)"CraftPeak.Upgrade.Protocol", (object)upgradeState.Protocol); ((Dictionary)val).Add((object)"CraftPeak.Upgrade.Revision", (object)upgradeState.Revision); ((Dictionary)val).Add((object)"CraftPeak.Upgrade.Owner", (object)upgradeState.OwnerActor); ((Dictionary)val).Add((object)"CraftPeak.Upgrade.RunId", (object)(upgradeState.RunId ?? string.Empty)); ((Dictionary)val).Add((object)"CraftPeak.Upgrade.Resource", (object)upgradeState.ResourceLevel); ((Dictionary)val).Add((object)"CraftPeak.Upgrade.Stack", (object)upgradeState.StackLevel); ((Dictionary)val).Add((object)"CraftPeak.Upgrade.Campfire", (object)upgradeState.CampfireLevel); ((Dictionary)val).Add((object)"CraftPeak.Upgrade.Yield", (object)upgradeState.YieldMultiplier); ((Dictionary)val).Add((object)"CraftPeak.Upgrade.SellMultiplier", (object)upgradeState.SellMultiplier); ((Dictionary)val).Add((object)"CraftPeak.Upgrade.BaseStack", (object)upgradeState.BaseStackCount); ((Dictionary)val).Add((object)"CraftPeak.Upgrade.BaseCampfire", (object)CloneIntArray(upgradeState.BaseCampfireMaterials)); Hashtable val2 = val; if (!PhotonNetwork.CurrentRoom.SetCustomProperties(val2, (Hashtable)null, (WebFlags)null)) { ((BaseUnityPlugin)this).Logger.LogError((object)("Upgrade upgradeState publish failed: " + reason)); return false; } this.upgradeState = upgradeState; upgradeStateLoaded = true; ApplyUpgradeEffects(reason); RefreshWindow(); ((BaseUnityPlugin)this).Logger.LogInfo((object)("Upgrade upgradeState published. Reason=" + reason + " | Resource=" + upgradeState.ResourceLevel + " | Stack=" + upgradeState.StackLevel + " | Campfire=" + upgradeState.CampfireLevel + " | Yield=x" + upgradeState.YieldMultiplier + " | Sell=x" + upgradeState.SellMultiplier)); return true; } private static UpgradeState NormalizeUpgradeState(UpgradeState value) { UpgradeState upgradeState = value ?? UpgradeState.CreateDefault(); upgradeState.Protocol = 1; upgradeState.Revision = Mathf.Max(0, upgradeState.Revision); upgradeState.OwnerActor = Mathf.Max(0, upgradeState.OwnerActor); upgradeState.RunId = upgradeState.RunId ?? string.Empty; upgradeState.ResourceLevel = Mathf.Clamp(upgradeState.ResourceLevel, 0, 4); upgradeState.StackLevel = Mathf.Clamp(upgradeState.StackLevel, 0, 4); upgradeState.CampfireLevel = Mathf.Clamp(upgradeState.CampfireLevel, 0, 4); upgradeState.YieldMultiplier = Mathf.Clamp(upgradeState.YieldMultiplier, 1, 5); upgradeState.SellMultiplier = NormalizeSellMultiplier(upgradeState.SellMultiplier); upgradeState.BaseStackCount = 10; upgradeState.BaseCampfireMaterials = EnsureThree(upgradeState.BaseCampfireMaterials, new int[3] { 1, 1, 1 }); for (int i = 0; i < upgradeState.BaseCampfireMaterials.Length; i++) { upgradeState.BaseCampfireMaterials[i] = Mathf.Max(0, upgradeState.BaseCampfireMaterials[i]); } return upgradeState; } private static int NormalizeSellMultiplier(int value) { if (value >= 16) { return 16; } if (value >= 8) { return 8; } if (value >= 4) { return 4; } if (value >= 2) { return 2; } return 1; } private void EnsureUpgradeEffectsApplied() { bool flag = lastAppliedUpgradeRevision != upgradeState.Revision || !string.Equals(lastAppliedUpgradeRunId, upgradeState.RunId, StringComparison.Ordinal); if (PhotonNetwork.IsMasterClient && Spawn.CurrentUpgradeGrade != (Spawn.ResourceUpgradeGrade)upgradeState.ResourceLevel) { flag = true; } if (PhotonNetwork.IsMasterClient && InventoryStack.MaximumStackCount != CalculateEffectiveStackMaximum(upgradeState)) { flag = true; } if (flag) { ApplyUpgradeEffects("Verification"); } } private void ApplyUpgradeEffects(string reason) { ResourceYieldMultiplier = Mathf.Clamp(upgradeState.YieldMultiplier, 1, 5); ApplyCampfireRequirements(CalculateEffectiveCampfireMaterials(upgradeState)); if (PhotonNetwork.IsMasterClient) { Spawn.SetUpgradeGrade(upgradeState.ResourceLevel); SetConfigValue(((Object)(object)InventoryStack.Instance != (Object)null) ? ((BaseUnityPlugin)InventoryStack.Instance).Config : null, InventoryMaximumDefinition, CalculateEffectiveStackMaximum(upgradeState)); } lastAppliedUpgradeRevision = upgradeState.Revision; lastAppliedUpgradeRunId = upgradeState.RunId ?? string.Empty; ((BaseUnityPlugin)this).Logger.LogDebug((object)("Upgrade effects applied. Reason=" + reason + " | Stack=" + CalculateEffectiveStackMaximum(upgradeState) + " | Yield=x" + ResourceYieldMultiplier)); } private void RestoreBaseUpgradeEffects() { ResourceYieldMultiplier = 1; if (upgradeStateLoaded) { ApplyCampfireRequirements(CloneIntArray(upgradeState.BaseCampfireMaterials)); if (PhotonNetwork.IsMasterClient) { Spawn.SetUpgradeGrade(0); SetConfigValue(((Object)(object)InventoryStack.Instance != (Object)null) ? ((BaseUnityPlugin)InventoryStack.Instance).Config : null, InventoryMaximumDefinition, upgradeState.BaseStackCount); } lastAppliedUpgradeRevision = -1; lastAppliedUpgradeRunId = string.Empty; } } private static int CalculateEffectiveStackMaximum(UpgradeState value) { int num = Mathf.Clamp(value.StackLevel, 0, 4); return Mathf.Clamp(value.BaseStackCount + StackCapacityBonuses[num], 1, 100); } private static int[] CalculateEffectiveCampfireMaterials(UpgradeState value) { return new int[3]; } private static void ApplyCampfireRequirements(int[] values) { int[] array = EnsureThree(values, new int[3] { 1, 1, 1 }); ConfigFile config = (((Object)(object)CampfireGate.Instance != (Object)null) ? ((BaseUnityPlugin)CampfireGate.Instance).Config : null); SetConfigValue(config, CampfireWoodDefinition, array[0]); SetConfigValue(config, CampfireStoneDefinition, array[1]); SetConfigValue(config, CampfireTorchDefinition, array[2]); } private void ProcessUpgradeRequestOnHost(int actor, object[] payload) { if (!PhotonNetwork.IsMasterClient) { return; } if (!upgradeStateLoaded) { LoadUpgradeStateOnDemand(); } if (!upgradeStateLoaded) { SendUpgradeResult(actor, success: false, "강화 상태를 초기화하지 못했습니다."); return; } if (!IsGameplayScene()) { SendUpgradeResult(actor, success: false, "현재는 강화할 수 없습니다."); return; } if (payload == null || payload.Length < 1) { SendUpgradeResult(actor, success: false, "잘못된 강화 요청입니다."); return; } double time = PhotonNetwork.Time; if (lastUpgradeRequestAtByActor.TryGetValue(actor, out var value) && time - value < 0.25) { SendUpgradeResult(actor, success: false, "강화 요청이 너무 빠릅니다."); return; } lastUpgradeRequestAtByActor[actor] = time; int num; try { num = Convert.ToInt32(payload[0]); } catch (Exception) { SendUpgradeResult(actor, success: false, "강화 종류를 해석하지 못했습니다."); return; } if (num < 0 || num > 4) { SendUpgradeResult(actor, success: false, "존재하지 않는 강화입니다."); return; } UpgradeKind upgradeKind = (UpgradeKind)num; int upgradeCurrentLevel = GetUpgradeCurrentLevel(upgradeKind); if (upgradeCurrentLevel >= GetUpgradeMaximumLevel(upgradeKind)) { SendUpgradeResult(actor, success: false, "이미 최대 단계입니다."); return; } int nextUpgradeCost = GetNextUpgradeCost(upgradeKind); int num2 = ReadSharedMoney(); CraftConsumptionPlan plan = null; if (upgradeKind == UpgradeKind.CampfireEfficiency) { CampfireBuildRecipe nextCampfireRecipe = GetNextCampfireRecipe(); if (nextCampfireRecipe == null) { SendUpgradeResult(actor, success: false, "모든 다음 모닥불을 이미 제작했습니다."); return; } if (GetCurrentResourceLevel() < nextCampfireRecipe.RequiredResourceLevel) { SendUpgradeResult(actor, success: false, GetResourceGradeName(nextCampfireRecipe.RequiredResourceLevel) + " 자원 등급이 필요합니다."); return; } if (!HasRequiredModulesForCampfireStage(nextCampfireRecipe.Stage, out var message)) { SendUpgradeResult(actor, success: false, message); return; } if (!TryBuildCraftConsumptionPlan(MakeTemporaryRecipe(nextCampfireRecipe.Ingredients), out plan, out var missingMessage)) { SendUpgradeResult(actor, success: false, missingMessage); return; } } if (num2 < nextUpgradeCost) { SendUpgradeResult(actor, success: false, "공유 돈이 부족합니다."); return; } if (plan != null) { if (!TryConsumePlan(plan, out var consumedSelectedSlots)) { SendUpgradeResult(actor, success: false, "다음 모닥불 재료 소비 중 인벤토리가 변경되었습니다."); return; } BroadcastConsumedSelectedSlots(consumedSelectedSlots); partyResourceCacheUntil = 0f; } SetSharedMoneyOnHost(num2 - nextUpgradeCost); UpgradeState upgradeState = this.upgradeState.Clone(); IncreaseUpgradeLevel(upgradeState, upgradeKind); if (!PublishUpgradeState(upgradeState, "Upgrade completed: " + upgradeKind)) { SetSharedMoneyOnHost(ReadSharedMoney() + nextUpgradeCost); SendUpgradeResult(actor, success: false, "강화 상태 저장에 실패했습니다. 비용을 환불했습니다."); return; } SendUpgradeResult(actor, success: true, GetUpgradeDisplayName(upgradeKind) + " 강화 완료\n" + GetUpgradeCurrentEffect(upgradeKind)); if (upgradeKind == UpgradeKind.CampfireEfficiency) { ((BaseUnityPlugin)this).Logger.LogInfo((object)("Campfire stage unlocked with cumulative modules. Stage=" + upgradeState.CampfireLevel + " | RequiredMask=" + GetRequiredModuleMaskForCampfireStage(upgradeState.CampfireLevel) + " | PurchasedMask=" + purchasedPartsMask)); } } private static void IncreaseUpgradeLevel(UpgradeState value, UpgradeKind kind) { switch (kind) { case UpgradeKind.ResourceGrade: value.ResourceLevel = Mathf.Min(4, value.ResourceLevel + 1); break; case UpgradeKind.StackCapacity: value.StackLevel = Mathf.Min(4, value.StackLevel + 1); break; case UpgradeKind.CampfireEfficiency: value.CampfireLevel = Mathf.Min(4, value.CampfireLevel + 1); break; case UpgradeKind.DoubleYield: value.YieldMultiplier = Mathf.Min(5, Mathf.Max(1, value.YieldMultiplier) + 1); break; case UpgradeKind.SellValue: value.SellMultiplier = Mathf.Min(16, Mathf.Max(1, value.SellMultiplier) * 2); break; } } private void SendUpgradeResult(int actor, bool success, string message) { //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Expected O, but got Unknown //IL_0070: Unknown result type (might be due to invalid IL or missing references) object[] array = new object[3] { success, message ?? string.Empty, ReadSharedMoney() }; if (PhotonNetwork.LocalPlayer != null && PhotonNetwork.LocalPlayer.ActorNumber == actor) { HandleUpgradeResult(array); return; } RaiseEventOptions val = new RaiseEventOptions(); val.TargetActors = new int[1] { actor }; RaiseEventOptions val2 = val; PhotonNetwork.RaiseEvent((byte)146, (object)array, val2, SendOptions.SendReliable); } private int GetUpgradeCurrentLevel(UpgradeKind kind) { return kind switch { UpgradeKind.ResourceGrade => upgradeState.ResourceLevel, UpgradeKind.StackCapacity => upgradeState.StackLevel, UpgradeKind.CampfireEfficiency => upgradeState.CampfireLevel, UpgradeKind.DoubleYield => Mathf.Clamp(upgradeState.YieldMultiplier - 1, 0, 4), UpgradeKind.SellValue => NormalizeSellMultiplier(upgradeState.SellMultiplier) switch { 2 => 1, 4 => 2, 8 => 3, 16 => 4, _ => 0, }, _ => 0, }; } private static int GetUpgradeMaximumLevel(UpgradeKind kind) { return kind switch { UpgradeKind.ResourceGrade => 4, UpgradeKind.StackCapacity => 4, UpgradeKind.CampfireEfficiency => 4, UpgradeKind.DoubleYield => 4, UpgradeKind.SellValue => 4, _ => 0, }; } private UpgradeFormulaConfig GetUpgradeFormula(UpgradeKind kind) { return kind switch { UpgradeKind.ResourceGrade => resourceUpgradeFormula, UpgradeKind.StackCapacity => stackUpgradeFormula, UpgradeKind.CampfireEfficiency => campfireUpgradeFormula, UpgradeKind.SellValue => sellValueUpgradeFormula, _ => null, }; } private int GetNextUpgradeCost(UpgradeKind kind) { int num = GetUpgradeCurrentLevel(kind) + 1; switch (kind) { case UpgradeKind.CampfireEfficiency: return GetNextCampfireRecipe()?.MoneyCost ?? 0; case UpgradeKind.DoubleYield: { int num4 = ((doubleYieldCostConfig != null) ? Mathf.Max(0, doubleYieldCostConfig.Value) : 60); return num4 * Mathf.Clamp(num, 1, 4); } default: { UpgradeFormulaConfig upgradeFormula = GetUpgradeFormula(kind); int num2 = ((upgradeFormula != null && upgradeFormula.BaseCost != null) ? Mathf.Max(0, upgradeFormula.BaseCost.Value) : 0); int num3 = ((upgradeFormula != null && upgradeFormula.CostGrowth != null) ? Mathf.Max(0, upgradeFormula.CostGrowth.Value) : 0); return num2 + num3 * Mathf.Max(0, num - 1); } } } internal string GetUpgradeDisplayName(UpgradeKind kind) { return kind switch { UpgradeKind.ResourceGrade => "자원 등급", UpgradeKind.StackCapacity => "인벤토리 적재량", UpgradeKind.CampfireEfficiency => "다음 모닥불 제작", UpgradeKind.DoubleYield => "수집량 배율", UpgradeKind.SellValue => "아이템 판매 수익", _ => "알 수 없는 강화", }; } internal string GetUpgradeCurrentEffect(UpgradeKind kind) { return kind switch { UpgradeKind.ResourceGrade => "현재 해금 등급: " + GetResourceGradeName(upgradeState.ResourceLevel), UpgradeKind.StackCapacity => "현재 최대 적재량: " + CalculateEffectiveStackMaximum(upgradeState) + "개", UpgradeKind.CampfireEfficiency => "완성된 다음 모닥불: " + upgradeState.CampfireLevel + "/4", UpgradeKind.DoubleYield => "현재 수집량: x" + upgradeState.YieldMultiplier, UpgradeKind.SellValue => "현재 판매 수익: 기본 판매가 x" + NormalizeSellMultiplier(upgradeState.SellMultiplier), _ => string.Empty, }; } internal string GetUpgradeNextEffect(UpgradeKind kind) { if (GetUpgradeCurrentLevel(kind) >= GetUpgradeMaximumLevel(kind)) { return "최대 단계에 도달했습니다."; } UpgradeState upgradeState = this.upgradeState.Clone(); IncreaseUpgradeLevel(upgradeState, kind); switch (kind) { case UpgradeKind.ResourceGrade: return "다음 효과: " + GetResourceGradeName(upgradeState.ResourceLevel) + " 등급 해금"; case UpgradeKind.StackCapacity: return "다음 효과: 슬롯당 " + CalculateEffectiveStackMaximum(upgradeState) + "개"; case UpgradeKind.CampfireEfficiency: { CampfireBuildRecipe nextCampfireRecipe = GetNextCampfireRecipe(); if (nextCampfireRecipe == null) { return "모든 다음 모닥불 제작 완료"; } StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append("이번 판 무작위 조합\n"); stringBuilder.Append("다음 제작: "); stringBuilder.Append(nextCampfireRecipe.Name); stringBuilder.Append("\n필요 등급: "); stringBuilder.Append(GetResourceGradeName(nextCampfireRecipe.RequiredResourceLevel)); for (int i = 0; i < nextCampfireRecipe.Ingredients.Count; i++) { IngredientCost ingredientCost = nextCampfireRecipe.Ingredients[i]; stringBuilder.Append("\n"); stringBuilder.Append(GetIngredientDisplayName(ingredientCost.ItemId)); stringBuilder.Append(" x"); stringBuilder.Append(ingredientCost.Count); } stringBuilder.Append("\n\n"); stringBuilder.Append(BuildCampfireModuleRequirementText(nextCampfireRecipe.Stage)); return stringBuilder.ToString(); } case UpgradeKind.DoubleYield: return "다음 효과: 맵 자원 수집량 x" + upgradeState.YieldMultiplier; case UpgradeKind.SellValue: return "다음 효과: 기본 판매가 x" + NormalizeSellMultiplier(upgradeState.SellMultiplier); default: return string.Empty; } } private static string GetResourceGradeName(int level) { int num = Mathf.Clamp(level, 0, 4); switch (GetCurrentLanguage()) { case HubLanguage.Korean: switch (num) { case 0: return "일반"; case 1: return "보통"; case 2: return "희귀"; case 3: return "고유"; case 4: return "전설"; } break; case HubLanguage.Chinese: switch (num) { case 0: return "普通"; case 1: return "标准"; case 2: return "稀有"; case 3: return "独特"; case 4: return "传说"; } break; case HubLanguage.Japanese: switch (num) { case 0: return "コモン"; case 1: return "ノーマル"; case 2: return "レア"; case 3: return "ユニーク"; case 4: return "レジェンダリー"; } break; case HubLanguage.French: switch (num) { case 0: return "Commun"; case 1: return "Normal"; case 2: return "Rare"; case 3: return "Unique"; case 4: return "Légendaire"; } break; default: switch (num) { case 0: return "Common"; case 1: return "Normal"; case 2: return "Rare"; case 3: return "Unique"; case 4: return "Legendary"; } break; } return "Common"; } public void OnPlayerEnteredRoom(Player newPlayer) { upgradeRoomStateDirty = true; partsRoomStateDirty = true; } public void OnPlayerLeftRoom(Player otherPlayer) { } public void OnRoomPropertiesUpdate(Hashtable changed) { if (changed == null) { return; } bool flag = ContainsRecipeProperty(changed); bool flag2 = ContainsUpgradeProperty(changed); bool flag3 = ContainsPartsProperty(changed); if (flag && EnsureSharedRecipeSeed() && (Object)(object)activeWindow != (Object)null) { EnsureProgressionRecipesBuilt(); if (currentTab == HubTab.Craft) { EnsureCraftRecipesBuilt(); } RefreshWindow(); } if (flag2) { upgradeRoomStateDirty = true; if ((Object)(object)activeWindow != (Object)null) { LoadUpgradeStateOnDemand(); } } if (flag3) { partsRoomStateDirty = true; if ((Object)(object)activeWindow != (Object)null) { LoadPartsStateOnDemand(); RefreshWindow(); } } if (((Dictionary)(object)changed).ContainsKey((object)"CraftPeak.Run.Id")) { upgradeRoomStateDirty = true; partsRoomStateDirty = true; EnsureSharedRecipeSeed(); if ((Object)(object)activeWindow != (Object)null) { EnsureProgressionRecipesBuilt(); LoadUpgradeStateOnDemand(); LoadPartsStateOnDemand(); if (currentTab == HubTab.Craft) { EnsureCraftRecipesBuilt(); } RefreshWindow(); } } if (((Dictionary)(object)changed).ContainsKey((object)"CraftPeak.SharedMoney") && (Object)(object)activeWindow != (Object)null) { cachedSharedMoney = ReadSharedMoney(); RefreshWindow(); } } public void OnPlayerPropertiesUpdate(Player targetPlayer, Hashtable changedProps) { } public void OnMasterClientSwitched(Player newMasterClient) { if (newMasterClient == null) { return; } upgradeRoomStateDirty = true; partsRoomStateDirty = true; if (PhotonNetwork.LocalPlayer == null || newMasterClient.ActorNumber != PhotonNetwork.LocalPlayer.ActorNumber) { return; } EnsureSharedRecipeSeed(); if ((Object)(object)activeWindow != (Object)null) { LoadHubDataOnDemand(); if (upgradeStateLoaded) { PublishUpgradeState(upgradeState, "Host migration"); } if (partsStateLoaded) { PublishPartsState(purchasedPartsMask, consumedPartsMask, peakUnlocked, partsRunId, "Host migration"); } } } private static bool ContainsRecipeProperty(Hashtable values) { return ((Dictionary)(object)values).ContainsKey((object)"CraftPeak.Recipes.Protocol") || ((Dictionary)(object)values).ContainsKey((object)"CraftPeak.Recipes.RunId") || ((Dictionary)(object)values).ContainsKey((object)"CraftPeak.Recipes.Seed"); } private static bool ContainsUpgradeProperty(Hashtable values) { return ((Dictionary)(object)values).ContainsKey((object)"CraftPeak.Upgrade.Protocol") || ((Dictionary)(object)values).ContainsKey((object)"CraftPeak.Upgrade.Revision") || ((Dictionary)(object)values).ContainsKey((object)"CraftPeak.Upgrade.Owner") || ((Dictionary)(object)values).ContainsKey((object)"CraftPeak.Upgrade.RunId") || ((Dictionary)(object)values).ContainsKey((object)"CraftPeak.Upgrade.Resource") || ((Dictionary)(object)values).ContainsKey((object)"CraftPeak.Upgrade.Stack") || ((Dictionary)(object)values).ContainsKey((object)"CraftPeak.Upgrade.Campfire") || ((Dictionary)(object)values).ContainsKey((object)"CraftPeak.Upgrade.Yield") || ((Dictionary)(object)values).ContainsKey((object)"CraftPeak.Upgrade.SellMultiplier") || ((Dictionary)(object)values).ContainsKey((object)"CraftPeak.Upgrade.BaseStack") || ((Dictionary)(object)values).ContainsKey((object)"CraftPeak.Upgrade.BaseCampfire"); } private static bool ContainsPartsProperty(Hashtable values) { return ((Dictionary)(object)values).ContainsKey((object)"CraftPeak.Parts.Protocol") || ((Dictionary)(object)values).ContainsKey((object)"CraftPeak.Parts.Revision") || ((Dictionary)(object)values).ContainsKey((object)"CraftPeak.Parts.RunId") || ((Dictionary)(object)values).ContainsKey((object)"CraftPeak.Parts.PurchasedMask") || ((Dictionary)(object)values).ContainsKey((object)"CraftPeak.Parts.ConsumedMask") || ((Dictionary)(object)values).ContainsKey((object)"CraftPeak.Parts.PeakUnlocked"); } private static void SetConfigValue(ConfigFile config, ConfigDefinition definition, object value) { if (config == null || definition == (ConfigDefinition)null || !config.ContainsKey(definition)) { return; } ConfigEntryBase val = config[definition]; if (val == null) { return; } try { object obj = null; if (val.SettingType == typeof(int)) { obj = Convert.ToInt32(value); } else if (val.SettingType == typeof(float)) { obj = Convert.ToSingle(value); } else if (val.SettingType == typeof(bool)) { obj = Convert.ToBoolean(value); } if (obj != null && !object.Equals(val.BoxedValue, obj)) { val.BoxedValue = obj; } } catch (Exception ex) { if (ModLogger != null) { ModLogger.LogWarning((object)("Upgrade config effect failed. Definition=" + ((object)definition)?.ToString() + " | Error=" + ex.Message)); } } } private static int ReadInt(Hashtable props, string key, int fallback) { if (!((Dictionary)(object)props).TryGetValue((object)key, out object value) || value == null) { return fallback; } try { return Convert.ToInt32(value); } catch (Exception) { return fallback; } } private static float ReadFloat(Hashtable props, string key, float fallback) { if (!((Dictionary)(object)props).TryGetValue((object)key, out object value) || value == null) { return fallback; } try { return Convert.ToSingle(value); } catch (Exception) { return fallback; } } private static string ReadString(Hashtable props, string key) { if (!((Dictionary)(object)props).TryGetValue((object)key, out object value) || value == null) { return string.Empty; } return (value as string) ?? Convert.ToString(value); } private static int[] ReadIntArray(Hashtable props, string key, int[] fallback) { if (!((Dictionary)(object)props).TryGetValue((object)key, out object value) || value == null) { return CloneIntArray(fallback); } if (value is int[] source) { return EnsureThree(source, fallback); } if (!(value is object[] array)) { return CloneIntArray(fallback); } int[] array2 = new int[array.Length]; try { for (int i = 0; i < array.Length; i++) { array2[i] = Convert.ToInt32(array[i]); } return EnsureThree(array2, fallback); } catch (Exception) { return CloneIntArray(fallback); } } private static int[] EnsureThree(int[] source, int[] fallback) { int[] array = new int[3]; for (int i = 0; i < array.Length; i++) { array[i] = ((source != null && i < source.Length) ? source[i] : fallback[i]); } return array; } private static int[] CloneIntArray(int[] source) { if (source == null) { return Array.Empty(); } int[] array = new int[source.Length]; Array.Copy(source, array, source.Length); return array; } private static int LocalActorNumber() { return (PhotonNetwork.LocalPlayer != null) ? PhotonNetwork.LocalPlayer.ActorNumber : 0; } internal static int CountPlayerResourceUnits(Player player, ushort itemId) { if ((Object)(object)player == (Object)null) { return 0; } int num = 0; if (player.itemSlots != null) { for (int i = 0; i < player.itemSlots.Length; i++) { ItemSlot val = player.itemSlots[i]; if (val != null && !val.IsEmpty() && !((Object)(object)val.prefab == (Object)null) && val.prefab.itemID == itemId) { num += Mathf.Max(1, InventoryStack.GetStackCount(player, val.itemSlotID)); } } } ItemSlot tempFullSlot = player.tempFullSlot; if (tempFullSlot != null && !tempFullSlot.IsEmpty() && (Object)(object)tempFullSlot.prefab != (Object)null && tempFullSlot.prefab.itemID == itemId) { num += Mathf.Max(1, InventoryStack.GetStackCount(player, tempFullSlot.itemSlotID)); } return num; } internal static void GrantPickupBonus(Player player, ushort itemId, int countBefore) { if ((Object)(object)Instance == (Object)null || !PhotonNetwork.IsMasterClient || ResourceYieldMultiplier <= 1 || (Object)(object)player == (Object)null || !Spawn.IsSaleResourceId(itemId)) { return; } int num = Mathf.Max(0, countBefore); int num2 = CountPlayerResourceUnits(player, itemId); if (num2 <= num) { return; } int num3 = num + Mathf.Max(1, ResourceYieldMultiplier); int num4 = Mathf.Max(0, num3 - num2); int num5 = 0; ItemSlot val = default(ItemSlot); for (int i = 0; i < num4; i++) { if (!player.AddItem(itemId, (ItemInstanceData)null, ref val)) { break; } num5++; } if (ModLogger != null) { ModLogger.LogInfo((object)("Resource yield reconciliation. ItemID=" + itemId + " | Multiplier=x" + ResourceYieldMultiplier + " | Before=" + num + " | BeforeReconcile=" + num2 + " | Expected=" + num3 + " | Granted=" + num5 + "/" + num4 + " | Final=" + CountPlayerResourceUnits(player, itemId))); } } private void ProcessSellRequestOnHost(int actorNumber, object[] requestData) { if (!PhotonNetwork.IsMasterClient) { return; } CleanupExpiredSaleTransactions(); double time = PhotonNetwork.Time; if (lastSellRequestAtByActor.TryGetValue(actorNumber, out var value) && time - value < 0.25) { SendSellResult(actorNumber, success: false, "판매 요청이 너무 빠릅니다.", 0, ReadSharedMoney(), -1, -1); return; } lastSellRequestAtByActor[actorNumber] = time; if (requestData == null || requestData.Length < 3) { SendSellResult(actorNumber, success: false, "잘못된 판매 요청입니다.", 0, ReadSharedMoney(), -1, -1); return; } int num; int num2; string text; int num3; try { num = Convert.ToInt32(requestData[0]); num2 = Convert.ToInt32(requestData[1]); text = requestData[2] as string; num3 = ((requestData.Length <= 3) ? 1 : Convert.ToInt32(requestData[3])); } catch (Exception) { SendSellResult(actorNumber, success: false, "판매 요청 데이터를 해석하지 못했습니다.", 0, ReadSharedMoney(), -1, -1); return; } if (num < 0 || num > 255 || string.IsNullOrEmpty(text) || num3 <= 0) { SendSellResult(actorNumber, success: false, "판매 슬롯 또는 아이템 GUID가 올바르지 않습니다.", 0, ReadSharedMoney(), num, num2); return; } if (reservedOrSoldItemGuids.Contains(text)) { SendSellResult(actorNumber, success: false, "이미 판매 처리 중이거나 판매가 완료된 아이템입니다.", 0, ReadSharedMoney(), num, num2); return; } Player player = PlayerHandler.GetPlayer(actorNumber); if ((Object)(object)player == (Object)null) { SendSellResult(actorNumber, success: false, "판매 요청 플레이어를 찾을 수 없습니다.", 0, ReadSharedMoney(), num, num2); return; } byte b = (byte)num; ItemSlot itemSlot = player.GetItemSlot(b); if (itemSlot == null || itemSlot.IsEmpty() || (Object)(object)itemSlot.prefab == (Object)null) { SendSellResult(actorNumber, success: false, "판매할 아이템이 슬롯에 없습니다.", 0, ReadSharedMoney(), num, num2); return; } ushort itemID = itemSlot.prefab.itemID; string text2 = ((itemSlot.data != null) ? itemSlot.data.guid.ToString() : string.Empty); if (itemID != (ushort)num2 || !Spawn.IsSaleResourceId(itemID) || string.IsNullOrEmpty(text2) || !string.Equals(text2, text, StringComparison.Ordinal)) { SendSellResult(actorNumber, success: false, "판매 승인 전에 아이템 정보가 변경되었습니다.", 0, ReadSharedMoney(), num, itemID); return; } int num4 = Mathf.Max(0, GetSellPrice(itemID) * NormalizeSellMultiplier(upgradeState.SellMultiplier)); if (num4 <= 0) { SendSellResult(actorNumber, success: false, "판매 가격이 설정되지 않은 아이템입니다.", 0, ReadSharedMoney(), num, itemID); return; } int num5 = Mathf.Max(1, InventoryStack.GetStackCount(player, b)); if (num3 > num5) { SendSellResult(actorNumber, success: false, "보유 수량보다 많이 판매할 수 없습니다.", 0, ReadSharedMoney(), num, itemID); return; } reservedOrSoldItemGuids.Add(text2); int consumedQuantity; int remainingCount; string remainingGuid; string failureMessage; bool flag = TryConsumeSaleQuantityOnHost(player, b, itemID, text2, num3, out consumedQuantity, out remainingCount, out remainingGuid, out failureMessage); reservedOrSoldItemGuids.Remove(text2); if (!flag || consumedQuantity <= 0) { SendSellResult(actorNumber, success: false, string.IsNullOrEmpty(failureMessage) ? "판매 수량을 인벤토리에서 제거하지 못했습니다." : failureMessage, 0, ReadSharedMoney(), num, itemID); return; } int num6 = num4 * consumedQuantity; int num7 = Mathf.Max(0, ReadSharedMoney() + num6); SetSharedMoneyOnHost(num7); partyResourceCacheUntil = 0f; SendSellResult(actorNumber, success: true, GetItemDisplayName(itemSlot.prefab) + " " + consumedQuantity + "개 판매 완료: +" + num6 + "원" + ((remainingCount > 0) ? ("\n남은 수량: " + remainingCount + "개") : string.Empty), num6, num7, num, itemID); } private static bool TryConsumeSaleQuantityOnHost(Player player, byte slotId, ushort itemId, string expectedGuid, int requestedQuantity, out int consumedQuantity, out int remainingCount, out string remainingGuid, out string failureMessage) { consumedQuantity = 0; remainingCount = 0; remainingGuid = string.Empty; failureMessage = string.Empty; if (!PhotonNetwork.IsMasterClient || (Object)(object)InventoryStack.Instance == (Object)null || (Object)(object)player == (Object)null || requestedQuantity <= 0) { failureMessage = "호스트 인벤토리 처리 상태가 올바르지 않습니다."; return false; } ItemSlot itemSlot = player.GetItemSlot(slotId); if (itemSlot == null || itemSlot.IsEmpty() || (Object)(object)itemSlot.prefab == (Object)null || itemSlot.prefab.itemID != itemId) { failureMessage = "판매 직전에 슬롯의 아이템이 변경되었습니다."; return false; } string text = ((itemSlot.data != null) ? itemSlot.data.guid.ToString() : string.Empty); if (string.IsNullOrEmpty(text) || !string.Equals(text, expectedGuid, StringComparison.Ordinal)) { failureMessage = "판매 직전에 아이템 GUID가 변경되었습니다."; return false; } int num = Mathf.Max(1, InventoryStack.GetStackCount(player, slotId)); if (requestedQuantity > num) { failureMessage = "판매 요청 수량이 현재 보유 수량보다 많습니다."; return false; } bool flag = (Object)(object)player.character != (Object)null && player.character.refs != null && (Object)(object)player.character.refs.items != (Object)null && player.character.refs.items.currentSelectedSlot.IsSome && player.character.refs.items.currentSelectedSlot.Value == slotId; for (int i = 0; i < requestedQuantity; i++) { ItemSlot itemSlot2 = player.GetItemSlot(slotId); if (itemSlot2 == null || itemSlot2.IsEmpty() || (Object)(object)itemSlot2.prefab == (Object)null || itemSlot2.prefab.itemID != itemId) { break; } string a = ((itemSlot2.data != null) ? itemSlot2.data.guid.ToString() : string.Empty); if (!string.Equals(a, expectedGuid, StringComparison.Ordinal)) { break; } int num2 = Mathf.Max(1, InventoryStack.GetStackCount(player, slotId)); if (num2 > 1) { if (!InventoryStack.Instance.HostConsumeOneFromSlot(player, slotId, "CraftHub.BatchSale", synchronizeInventory: false)) { break; } } else { itemSlot2.EmptyOut(); } consumedQuantity++; } SyncPlayerInventoryFromHost(player); HashSet players = new HashSet { player }; RefreshCarryWeights(players); ItemSlot itemSlot3 = player.GetItemSlot(slotId); if (itemSlot3 != null && !itemSlot3.IsEmpty() && (Object)(object)itemSlot3.prefab != (Object)null && itemSlot3.prefab.itemID == itemId) { remainingCount = Mathf.Max(1, InventoryStack.GetStackCount(player, slotId)); remainingGuid = ((itemSlot3.data != null) ? itemSlot3.data.guid.ToString() : string.Empty); } if (flag && remainingCount <= 0 && (Object)(object)player.character != (Object)null && (Object)(object)((MonoBehaviourPun)player.character).photonView != (Object)null && ((MonoBehaviourPun)player.character).photonView.Owner != null && (Object)(object)Instance != (Object)null) { Instance.BroadcastConsumedSelectedSlots(new List { new ConsumedSelectedSlot { ActorNumber = ((MonoBehaviourPun)player.character).photonView.Owner.ActorNumber, SlotId = slotId } }); } if (consumedQuantity != requestedQuantity) { failureMessage = "판매 처리 중 인벤토리가 변경되어 요청한 수량을 모두 제거하지 못했습니다."; return false; } return true; } private bool SendSellConsumeRequest(PendingSaleTransaction transaction) { //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Expected O, but got Unknown //IL_0098: Unknown result type (might be due to invalid IL or missing references) object[] array = new object[4] { transaction.TransactionId, (int)transaction.SlotId, (int)transaction.ItemId, transaction.ItemGuid ?? string.Empty }; if (PhotonNetwork.LocalPlayer != null && PhotonNetwork.LocalPlayer.ActorNumber == transaction.ActorNumber) { HandleSellConsumeRequest(array); return true; } RaiseEventOptions val = new RaiseEventOptions(); val.TargetActors = new int[1] { transaction.ActorNumber }; RaiseEventOptions val2 = val; return PhotonNetwork.RaiseEvent((byte)153, (object)array, val2, SendOptions.SendReliable); } private void HandleSellConsumeRequest(object[] payload) { int num = 0; int num2; int num3; string text; try { if (payload == null || payload.Length < 4) { throw new InvalidOperationException(); } num = Convert.ToInt32(payload[0]); num2 = Convert.ToInt32(payload[1]); num3 = Convert.ToInt32(payload[2]); text = payload[3] as string; } catch (Exception) { SendSellConsumeAck(num, removed: false, "판매 아이템 제거 요청 데이터가 올바르지 않습니다.", 0, string.Empty); return; } if (num <= 0 || num2 < 0 || num2 > 255 || num3 < 0 || num3 > 65535 || string.IsNullOrEmpty(text)) { SendSellConsumeAck(num, removed: false, "판매 트랜잭션 정보가 올바르지 않습니다.", 0, string.Empty); return; } localPendingSale = new LocalPendingSale { TransactionId = num, SlotId = (byte)num2, ItemId = (ushort)num3, ItemGuid = text }; int remainingCount; string remainingGuid; string failureMessage; bool flag = TryRemoveSoldLocalItem(localPendingSale, out remainingCount, out remainingGuid, out failureMessage); SendSellConsumeAck(num, flag, flag ? "판매 아이템 1개를 제거했습니다." : failureMessage, remainingCount, remainingGuid); if (!flag) { localPendingSale = null; } } private static bool TryRemoveSoldLocalItem(LocalPendingSale sale, out int remainingCount, out string remainingGuid, out string failureMessage) { //IL_0199: Unknown result type (might be due to invalid IL or missing references) //IL_0186: Unknown result type (might be due to invalid IL or missing references) remainingCount = 0; remainingGuid = string.Empty; failureMessage = string.Empty; Player localPlayer = Player.localPlayer; if (sale == null || (Object)(object)localPlayer == (Object)null || localPlayer.itemSlots == null || sale.SlotId >= localPlayer.itemSlots.Length) { failureMessage = "판매자 로컬 인벤토리를 찾지 못했습니다."; return false; } ItemSlot itemSlot = localPlayer.GetItemSlot(sale.SlotId); if (itemSlot == null || itemSlot.IsEmpty() || (Object)(object)itemSlot.prefab == (Object)null || itemSlot.prefab.itemID != sale.ItemId) { failureMessage = "판매 승인 후 슬롯의 아이템이 변경되었습니다."; return false; } string a = ((itemSlot.data != null) ? itemSlot.data.guid.ToString() : string.Empty); if (!string.Equals(a, sale.ItemGuid, StringComparison.Ordinal)) { failureMessage = "판매 승인 후 아이템 GUID가 변경되었습니다."; return false; } int num = Mathf.Max(1, InventoryStack.GetStackCount(localPlayer, sale.SlotId)); Character localCharacter = Character.localCharacter; if ((Object)(object)localCharacter != (Object)null && localCharacter.refs != null && (Object)(object)localCharacter.refs.items != (Object)null && !localCharacter.refs.items.currentSelectedSlot.IsNone && localCharacter.refs.items.currentSelectedSlot.Value == sale.SlotId) { localCharacter.refs.items.EquipSlot(Optionable.None); } localPlayer.EmptySlot(Optionable.Some(sale.SlotId)); ItemSlot itemSlot2 = localPlayer.GetItemSlot(sale.SlotId); int num2 = 0; if (itemSlot2 != null && !itemSlot2.IsEmpty() && (Object)(object)itemSlot2.prefab != (Object)null && itemSlot2.prefab.itemID == sale.ItemId) { num2 = Mathf.Max(1, InventoryStack.GetStackCount(localPlayer, sale.SlotId)); remainingGuid = ((itemSlot2.data != null) ? itemSlot2.data.guid.ToString() : string.Empty); } if (num2 != Mathf.Max(0, num - 1)) { failureMessage = "판매 전후 스택 수량이 정확히 1 감소하지 않았습니다."; if (ModLogger != null) { ModLogger.LogError((object)("[SaleDiag] Client stack decrement verification failed. Transaction=" + sale.TransactionId + " | Slot=" + sale.SlotId + " | ItemID=" + sale.ItemId + " | Guid=" + sale.ItemGuid + " | Before=" + num + " | After=" + num2)); } return false; } remainingCount = num2; if (ModLogger != null) { ModLogger.LogInfo((object)("[SaleDiag] Client sold exactly one stack unit. Transaction=" + sale.TransactionId + " | Slot=" + sale.SlotId + " | ItemID=" + sale.ItemId + " | Guid=" + sale.ItemGuid + " | Before=" + num + " | Remaining=" + remainingCount + " | RemainingGuid=" + remainingGuid)); } return true; } private void SendSellConsumeAck(int transactionId, bool removed, string message, int remainingCount, string remainingGuid) { //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Expected O, but got Unknown //IL_00ac: Unknown result type (might be due to invalid IL or missing references) object[] array = new object[5] { transactionId, removed, message ?? string.Empty, Mathf.Max(0, remainingCount), remainingGuid ?? string.Empty }; if (PhotonNetwork.IsMasterClient) { ProcessSellConsumeAckOnHost(LocalActorNumber(), array); return; } Player masterClient = PhotonNetwork.MasterClient; if (masterClient == null) { pendingRequest = PendingRequest.None; SetTabStatus(HubTab.Sell, "판매 확인을 전달할 호스트를 찾지 못했습니다."); return; } RaiseEventOptions val = new RaiseEventOptions(); val.TargetActors = new int[1] { masterClient.ActorNumber }; RaiseEventOptions val2 = val; if (!PhotonNetwork.RaiseEvent((byte)154, (object)array, val2, SendOptions.SendReliable)) { pendingRequest = PendingRequest.None; SetTabStatus(HubTab.Sell, "판매 아이템은 제거됐지만 호스트 확인 전송에 실패했습니다."); } } private void ProcessSellConsumeAckOnHost(int senderActorNumber, object[] payload) { if (!PhotonNetwork.IsMasterClient || payload == null || payload.Length < 5) { return; } int key; bool flag; string text; int num; string a; try { key = Convert.ToInt32(payload[0]); flag = Convert.ToBoolean(payload[1]); text = (payload[2] as string) ?? string.Empty; num = Mathf.Max(0, Convert.ToInt32(payload[3])); a = (payload[4] as string) ?? string.Empty; } catch (Exception) { return; } if (!pendingSaleTransactions.TryGetValue(key, out var value) || value == null || value.ActorNumber != senderActorNumber) { return; } pendingSaleTransactions.Remove(key); if (!flag) { reservedOrSoldItemGuids.Remove(value.ItemGuid); SendSellResult(value.ActorNumber, success: false, string.IsNullOrEmpty(text) ? "판매 아이템을 제거하지 못했습니다." : text, 0, ReadSharedMoney(), value.SlotId, value.ItemId); return; } if (num > 0 && string.Equals(a, value.ItemGuid, StringComparison.Ordinal)) { reservedOrSoldItemGuids.Remove(value.ItemGuid); } int num2 = Mathf.Max(0, ReadSharedMoney() + value.SalePrice); SetSharedMoneyOnHost(num2); SendSellResult(value.ActorNumber, success: true, value.ItemName + " 1개 판매 완료: +" + value.SalePrice + "원" + ((num > 0) ? ("\n남은 수량: " + num + "개") : string.Empty), value.SalePrice, num2, value.SlotId, value.ItemId); } private void CleanupExpiredSaleTransactions() { if (pendingSaleTransactions.Count == 0) { return; } double time = PhotonNetwork.Time; List list = new List(); foreach (KeyValuePair pendingSaleTransaction in pendingSaleTransactions) { if (pendingSaleTransaction.Value == null || time - pendingSaleTransaction.Value.CreatedAt > 6.0) { list.Add(pendingSaleTransaction.Key); } } for (int i = 0; i < list.Count; i++) { if (pendingSaleTransactions.TryGetValue(list[i], out var value)) { pendingSaleTransactions.Remove(list[i]); if (value != null) { reservedOrSoldItemGuids.Remove(value.ItemGuid); SendSellResult(value.ActorNumber, success: false, "판매 확인 시간이 초과되었습니다. 다시 시도하세요.", 0, ReadSharedMoney(), value.SlotId, value.ItemId); } } } } private void SendSellResult(int targetActorNumber, bool success, string message, int price, int balance, int slotId, int itemId) { //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Expected O, but got Unknown //IL_008b: Unknown result type (might be due to invalid IL or missing references) object[] array = new object[6] { success, message ?? string.Empty, price, balance, slotId, itemId }; if (PhotonNetwork.LocalPlayer != null && targetActorNumber == PhotonNetwork.LocalPlayer.ActorNumber) { HandleSellResult(array); return; } RaiseEventOptions val = new RaiseEventOptions(); val.TargetActors = new int[1] { targetActorNumber }; RaiseEventOptions val2 = val; PhotonNetwork.RaiseEvent((byte)141, (object)array, val2, SendOptions.SendReliable); } private static int GetSellPrice(ushort itemId) { switch (itemId) { case 28: case 69: case 72: return 1; case 13: case 14: case 15: case 99: return 3; case 34: case 49: return 7; case 51: return 15; case 112: return 50; default: return 0; } } private static string GetRarityName(ushort itemId) { switch (itemId) { case 28: case 69: case 72: return "Common"; case 13: case 14: case 15: case 99: return "Normal"; case 34: case 49: return "Rare"; case 51: return "Unique"; case 112: return "Legendary"; default: return "Unknown"; } } private static bool IsRealCraftGrantSlot(Player player, ItemSlot grantedSlot, ushort expectedItemId) { if ((Object)(object)player == (Object)null || player.itemSlots == null || grantedSlot == null || grantedSlot.IsEmpty() || (Object)(object)grantedSlot.prefab == (Object)null || grantedSlot.prefab.itemID != expectedItemId) { return false; } if (IsTemporaryCraftGrantSlot(player, grantedSlot)) { return true; } for (int i = 0; i < player.itemSlots.Length; i++) { ItemSlot val = player.itemSlots[i]; if (val == grantedSlot) { return true; } if (val != null && val.itemSlotID == grantedSlot.itemSlotID && !val.IsEmpty() && (Object)(object)val.prefab != (Object)null && val.prefab.itemID == expectedItemId) { return true; } } return false; } private static bool IsTemporaryCraftGrantSlot(Player player, ItemSlot slot) { if ((Object)(object)player == (Object)null || slot == null) { return false; } ItemSlot tempFullSlot = player.tempFullSlot; return tempFullSlot == slot || (tempFullSlot != null && tempFullSlot.itemSlotID == slot.itemSlotID); } private void ProcessCraftRequestOnHost(int actorNumber, object[] payload) { if (!PhotonNetwork.IsMasterClient) { return; } if (!PhotonNetwork.InRoom || !IsGameplayScene()) { SendCraftResult(actorNumber, materialsConsumed: false, success: false, 0, "현재는 제작할 수 없습니다."); return; } if (payload == null || payload.Length < 1) { SendCraftResult(actorNumber, materialsConsumed: false, success: false, 0, "잘못된 제작 요청입니다."); return; } double time = PhotonNetwork.Time; if (lastCraftRequestAtByActor.TryGetValue(actorNumber, out var value) && time - value < 0.25) { SendCraftResult(actorNumber, materialsConsumed: false, success: false, 0, "제작 요청이 너무 빠릅니다."); return; } lastCraftRequestAtByActor[actorNumber] = time; int num; try { num = Convert.ToInt32(payload[0]); } catch (Exception) { SendCraftResult(actorNumber, materialsConsumed: false, success: false, 0, "제작 아이템 번호를 해석하지 못했습니다."); return; } if (num < 0 || num > 65535) { SendCraftResult(actorNumber, materialsConsumed: false, success: false, 0, "잘못된 제작 아이템입니다."); return; } if (!EnsureCraftRecipesBuilt()) { SendCraftResult(actorNumber, materialsConsumed: false, success: false, 0, "제작 데이터베이스가 준비되지 않았습니다."); return; } ushort num2 = (ushort)num; if (!craftRecipesByOutputId.TryGetValue(num2, out var value2) || value2 == null) { SendCraftResult(actorNumber, materialsConsumed: false, success: false, num2, "등록되지 않은 제작식입니다."); return; } int currentResourceLevel = GetCurrentResourceLevel(); if (currentResourceLevel < value2.RequiredResourceLevel) { SendCraftResult(actorNumber, materialsConsumed: false, success: false, num2, GetResourceGradeName(value2.RequiredResourceLevel) + " 제작 등급이 필요합니다."); return; } if (num2 == 32) { LoadPartsStateOnDemand(); if (GetCurrentSegmentIndex() != 5) { SendCraftResult(actorNumber, materialsConsumed: false, success: false, num2, "최종 조명탄은 정상 구간에서만 제작할 수 있습니다."); return; } if (GetCurrentResourceLevel() < 4) { SendCraftResult(actorNumber, materialsConsumed: false, success: false, num2, "최종 조명탄 제작에는 Legendary 제작 등급이 필요합니다."); return; } if (peakUnlocked) { SendCraftResult(actorNumber, materialsConsumed: false, success: false, num2, "최종 조명탄 제작과 탈출 신호 발사가 이미 완료되었습니다."); return; } } Player player = PlayerHandler.GetPlayer(actorNumber); if ((Object)(object)player == (Object)null) { SendCraftResult(actorNumber, materialsConsumed: false, success: false, num2, "플레이어를 찾지 못했습니다."); return; } if (!player.HasEmptySlot(num2)) { SendCraftResult(actorNumber, materialsConsumed: false, success: false, num2, "완성품을 받을 빈 슬롯이 없어 제작할 수 없습니다."); return; } int num3 = ReadSharedMoney(); if (num3 < value2.MoneyCost) { SendCraftResult(actorNumber, materialsConsumed: false, success: false, num2, "공유 돈이 부족합니다."); return; } if (!TryBuildCraftConsumptionPlan(value2, out var plan, out var missingMessage)) { SendCraftResult(actorNumber, materialsConsumed: false, success: false, num2, missingMessage); return; } if (!TryConsumePlan(plan, out var consumedSelectedSlots)) { SendCraftResult(actorNumber, materialsConsumed: false, success: false, num2, "재료 소비 중 인벤토리가 변경되었습니다. 다시 시도하세요."); return; } SetSharedMoneyOnHost(num3 - value2.MoneyCost); BroadcastConsumedSelectedSlots(consumedSelectedSlots); ItemSlot val = default(ItemSlot); bool flag = player.AddItem(num2, (ItemInstanceData)null, ref val); bool flag2 = flag && IsRealCraftGrantSlot(player, val, num2); bool flag3 = flag2 && IsTemporaryCraftGrantSlot(player, val); if (!flag2) { SetSharedMoneyOnHost(ReadSharedMoney() + value2.MoneyCost); SendCraftResult(actorNumber, materialsConsumed: true, success: false, num2, "제작 완성품을 인벤토리에 지급하지 못했습니다.\n돈은 환불됐지만 재료는 복구되지 않았습니다."); ((BaseUnityPlugin)this).Logger.LogError((object)("Craft output inventory delivery failed. Actor=" + actorNumber + " | OutputID=" + num2 + " | Granted=" + flag + " | GrantedSlot=" + ((val != null) ? val.itemSlotID.ToString() : ""))); return; } if (flag3 && (Object)(object)((MonoBehaviourPun)player).photonView != (Object)null && ((MonoBehaviourPun)player).photonView.IsMine) { ((MonoBehaviour)this).StartCoroutine(EquipCraftedTempHandLocally(player, num2)); } SendCraftResult(actorNumber, materialsConsumed: true, success: true, num2, value2.DisplayName + (flag3 ? " 제작 성공! 추가 손 슬롯에 장착했습니다." : " 제작에 성공했습니다!")); if (num2 == 32) { MarkFinalFlareCompletedAndNotify(); } ((BaseUnityPlugin)this).Logger.LogInfo((object)("Craft succeeded. Actor=" + actorNumber + " | OutputID=" + num2 + " | Destination=" + (flag3 ? "SelectedHand250" : "Inventory") + " | Slot=" + ((val != null) ? val.itemSlotID.ToString() : "") + " | Money=" + value2.MoneyCost)); } private IEnumerator EquipCraftedTempHandLocally(Player requester, ushort expectedItemId) { yield return null; if ((Object)(object)requester == (Object)null || requester.tempFullSlot == null || requester.tempFullSlot.IsEmpty() || (Object)(object)requester.tempFullSlot.prefab == (Object)null || requester.tempFullSlot.prefab.itemID != expectedItemId || (Object)(object)requester.character == (Object)null || requester.character.refs == null || (Object)(object)requester.character.refs.items == (Object)null) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Local crafted temp-hand equip skipped. ExpectedItemID=" + expectedItemId)); yield break; } string tempGuid = ((requester.tempFullSlot.data != null) ? requester.tempFullSlot.data.guid.ToString() : string.Empty); if (!string.IsNullOrEmpty(tempGuid) && !pendingTempHandDrawGuids.Add(tempGuid)) { ((BaseUnityPlugin)this).Logger.LogInfo((object)("Duplicate crafted temp-hand draw request ignored. ItemID=" + expectedItemId + " | Guid=" + tempGuid)); yield break; } CharacterItems items = requester.character.refs.items; items.EquipSlot(Optionable.Some((byte)250)); yield return (object)new WaitForSecondsRealtime(0.2f); bool selectedTemp = items.currentSelectedSlot.IsSome && items.currentSelectedSlot.Value == 250; Item currentItem = (((Object)(object)requester.character.data != (Object)null) ? requester.character.data.currentItem : null); bool heldCorrectly = (Object)(object)currentItem != (Object)null && currentItem.itemID == expectedItemId && (int)currentItem.itemState == 1; if (!selectedTemp || !heldCorrectly) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("First local crafted temp-hand equip did not complete. Retrying once. ExpectedItemID=" + expectedItemId + " | Selected250=" + selectedTemp + " | CurrentItem=" + (((Object)(object)currentItem != (Object)null) ? currentItem.itemID.ToString() : ""))); items.EquipSlot(Optionable.Some((byte)250)); yield return (object)new WaitForSecondsRealtime(0.2f); selectedTemp = items.currentSelectedSlot.IsSome && items.currentSelectedSlot.Value == 250; currentItem = (((Object)(object)requester.character.data != (Object)null) ? requester.character.data.currentItem : null); heldCorrectly = (Object)(object)currentItem != (Object)null && currentItem.itemID == expectedItemId && (int)currentItem.itemState == 1; } if (selectedTemp && heldCorrectly) { ((BaseUnityPlugin)this).Logger.LogInfo((object)("Crafted temp-hand item drawn successfully. ItemID=" + expectedItemId + " | Slot=250 | ViewID=" + (((Object)(object)((MonoBehaviourPun)currentItem).photonView != (Object)null) ? ((MonoBehaviourPun)currentItem).photonView.ViewID.ToString() : ""))); } else { ((BaseUnityPlugin)this).Logger.LogError((object)("Crafted temp-hand item draw failed. ItemID=" + expectedItemId + " | Selected250=" + selectedTemp + " | TempSlot=" + ((requester.tempFullSlot != null && !requester.tempFullSlot.IsEmpty() && (Object)(object)requester.tempFullSlot.prefab != (Object)null) ? requester.tempFullSlot.prefab.itemID.ToString() : "") + " | CurrentItem=" + (((Object)(object)currentItem != (Object)null) ? currentItem.itemID.ToString() : ""))); } if (!string.IsNullOrEmpty(tempGuid)) { pendingTempHandDrawGuids.Remove(tempGuid); } } private void SendCraftResult(int targetActor, bool materialsConsumed, bool success, ushort outputId, string message) { //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Expected O, but got Unknown //IL_0084: Unknown result type (might be due to invalid IL or missing references) object[] array = new object[5] { materialsConsumed, success, (int)outputId, message ?? string.Empty, ReadSharedMoney() }; if (PhotonNetwork.LocalPlayer != null && PhotonNetwork.LocalPlayer.ActorNumber == targetActor) { HandleCraftResult(array); return; } RaiseEventOptions val = new RaiseEventOptions(); val.TargetActors = new int[1] { targetActor }; RaiseEventOptions val2 = val; PhotonNetwork.RaiseEvent((byte)143, (object)array, val2, SendOptions.SendReliable); } private static bool TryBuildCraftConsumptionPlan(CraftRecipe recipe, out CraftConsumptionPlan plan, out string missingMessage) { plan = new CraftConsumptionPlan(); missingMessage = string.Empty; List list = CollectPartyIngredientLocations(); for (int i = 0; i < recipe.Ingredients.Count; i++) { IngredientCost ingredientCost = recipe.Ingredients[i]; List list2 = new List(); int num = 0; for (int j = 0; j < list.Count; j++) { IngredientLocation ingredientLocation = list[j]; if (ingredientLocation.ItemId == ingredientCost.ItemId && ingredientLocation.AvailableCount > 0) { list2.Add(ingredientLocation); num += ingredientLocation.AvailableCount; } } if (num < ingredientCost.Count) { missingMessage = GetIngredientDisplayName(ingredientCost.ItemId) + "이(가) 부족합니다. " + num + "/" + ingredientCost.Count; return false; } list2.Sort(CompareIngredientLocations); int num2 = ingredientCost.Count; for (int k = 0; k < list2.Count; k++) { if (num2 <= 0) { break; } IngredientLocation ingredientLocation2 = list2[k]; int num3 = Mathf.Min(ingredientLocation2.AvailableCount, num2); for (int l = 0; l < num3; l++) { plan.Units.Add(new PlannedIngredientUnit { Location = ingredientLocation2, ItemId = ingredientCost.ItemId }); } num2 -= num3; } } return true; } private static bool TryConsumePlan(CraftConsumptionPlan plan, out List consumedSelectedSlots) { consumedSelectedSlots = new List(); if (!ValidateCraftConsumptionPlan(plan)) { return false; } HashSet hashSet = new HashSet(); HashSet hashSet2 = new HashSet(); HashSet hashSet3 = new HashSet(); for (int i = 0; i < plan.Units.Count; i++) { PlannedIngredientUnit plannedIngredientUnit = plan.Units[i]; IngredientLocation location = plannedIngredientUnit.Location; if (IsCurrentlySelected(location) && (Object)(object)location.Character != (Object)null && (Object)(object)((MonoBehaviourPun)location.Character).photonView != (Object)null && ((MonoBehaviourPun)location.Character).photonView.Owner != null) { int actorNumber = ((MonoBehaviourPun)location.Character).photonView.Owner.ActorNumber; string item = actorNumber + ":" + location.ExternalSlotId; if (hashSet3.Add(item)) { consumedSelectedSlots.Add(new ConsumedSelectedSlot { ActorNumber = actorNumber, SlotId = location.ExternalSlotId }); } } location.Slot.EmptyOut(); hashSet.Add(location.Player); if (location.IsBackpackInternal) { hashSet2.Add(location.Character); } } foreach (Player item2 in hashSet) { SyncPlayerInventoryFromHost(item2); } foreach (Character item3 in hashSet2) { RefreshBackpackVisuals(item3); } RefreshCarryWeights(hashSet); return true; } private static bool ValidateCraftConsumptionPlan(CraftConsumptionPlan plan) { if (plan == null) { return false; } Dictionary dictionary = new Dictionary(); for (int i = 0; i < plan.Units.Count; i++) { PlannedIngredientUnit plannedIngredientUnit = plan.Units[i]; if (plannedIngredientUnit == null || plannedIngredientUnit.Location == null || !IsLocationValid(plannedIngredientUnit.Location, plannedIngredientUnit.ItemId)) { return false; } dictionary.TryGetValue(plannedIngredientUnit.Location, out var value); dictionary[plannedIngredientUnit.Location] = value + 1; } foreach (KeyValuePair item in dictionary) { IngredientLocation key = item.Key; int locationAvailableCount = GetLocationAvailableCount(key.Player, key.Slot, key.IsBackpackInternal, key.ExternalSlotId); if (locationAvailableCount < item.Value) { return false; } } return true; } private static List CollectPartyIngredientLocations() { List result = new List(); List allPlayerCharacters = PlayerHandler.GetAllPlayerCharacters(); for (int i = 0; i < allPlayerCharacters.Count; i++) { Character val = allPlayerCharacters[i]; if ((Object)(object)val == (Object)null || (Object)(object)val.player == (Object)null || (Object)(object)((MonoBehaviourPun)val).photonView == (Object)null || ((MonoBehaviourPun)val).photonView.Owner == null || ((MonoBehaviourPun)val).photonView.Owner.IsInactive) { continue; } Player player = val.player; if (player.itemSlots != null) { for (int j = 0; j < player.itemSlots.Length; j++) { AddIngredientLocation(result, player, val, player.itemSlots[j], backpackInternal: false, (byte)j, -1); } } AddIngredientLocation(result, player, val, player.tempFullSlot, backpackInternal: false, 250, -1); BackpackData val2 = null; if (player.backpackSlot != null && !((ItemSlot)player.backpackSlot).IsEmpty() && ((ItemSlot)player.backpackSlot).data != null && ((ItemSlot)player.backpackSlot).data.TryGetDataEntry((DataEntryKey)7, ref val2) && val2 != null && val2.itemSlots != null) { for (int k = 0; k < val2.itemSlots.Length; k++) { AddIngredientLocation(result, player, val, val2.itemSlots[k], backpackInternal: true, byte.MaxValue, k); } } } return result; } private static void AddIngredientLocation(List result, Player player, Character character, ItemSlot slot, bool backpackInternal, byte externalSlotId, int backpackSlotIndex) { if (slot != null && !slot.IsEmpty() && !((Object)(object)slot.prefab == (Object)null) && IsCraftIngredientId(slot.prefab.itemID)) { result.Add(new IngredientLocation { Player = player, Character = character, Slot = slot, IsBackpackInternal = backpackInternal, ExternalSlotId = externalSlotId, BackpackSlotIndex = backpackSlotIndex, ItemId = slot.prefab.itemID, AvailableCount = GetLocationAvailableCount(player, slot, backpackInternal, externalSlotId) }); } } private static int GetLocationAvailableCount(Player player, ItemSlot slot, bool backpackInternal, byte externalSlotId) { if (slot == null || slot.IsEmpty()) { return 0; } return backpackInternal ? 1 : Mathf.Max(1, InventoryStack.GetStackCount(player, externalSlotId)); } private static int CompareIngredientLocations(IngredientLocation left, IngredientLocation right) { int num = GetConsumptionPriority(left).CompareTo(GetConsumptionPriority(right)); if (num != 0) { return num; } num = GetActorNumber(left).CompareTo(GetActorNumber(right)); if (num != 0) { return num; } if (left.IsBackpackInternal != right.IsBackpackInternal) { return (!left.IsBackpackInternal) ? 1 : (-1); } return left.IsBackpackInternal ? left.BackpackSlotIndex.CompareTo(right.BackpackSlotIndex) : left.ExternalSlotId.CompareTo(right.ExternalSlotId); } private static int GetConsumptionPriority(IngredientLocation location) { if (location == null) { return int.MaxValue; } if (location.IsBackpackInternal) { return 0; } return (!IsCurrentlySelected(location)) ? 1 : 2; } private static bool IsCurrentlySelected(IngredientLocation location) { //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) if (location == null || location.IsBackpackInternal || (Object)(object)location.Character == (Object)null || location.Character.refs == null || (Object)(object)location.Character.refs.items == (Object)null) { return false; } Optionable currentSelectedSlot = location.Character.refs.items.currentSelectedSlot; return currentSelectedSlot.IsSome && currentSelectedSlot.Value == location.ExternalSlotId; } private static int GetActorNumber(IngredientLocation location) { if (location == null || (Object)(object)location.Character == (Object)null || (Object)(object)((MonoBehaviourPun)location.Character).photonView == (Object)null || ((MonoBehaviourPun)location.Character).photonView.Owner == null) { return int.MaxValue; } return ((MonoBehaviourPun)location.Character).photonView.Owner.ActorNumber; } private static bool IsLocationValid(IngredientLocation location, ushort expectedItemId) { return location != null && (Object)(object)location.Player != (Object)null && (Object)(object)location.Character != (Object)null && location.Slot != null && !location.Slot.IsEmpty() && (Object)(object)location.Slot.prefab != (Object)null && location.Slot.prefab.itemID == expectedItemId; } private static void SyncPlayerInventoryFromHost(Player player) { //IL_0057: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player == (Object)null || !PhotonNetwork.IsMasterClient) { return; } PhotonView component = ((Component)player).GetComponent(); if (!((Object)(object)component == (Object)null)) { InventorySyncData val = default(InventorySyncData); ((InventorySyncData)(ref val))..ctor(player.itemSlots, player.backpackSlot, player.tempFullSlot); component.RPC("SyncInventoryRPC", (RpcTarget)1, new object[2] { IBinarySerializable.ToManagedArray(val), false }); if (player.itemsChangedAction != null) { player.itemsChangedAction(player.itemSlots); } } } private static void RefreshBackpackVisuals(Character character) { if (!((Object)(object)character == (Object)null)) { CharacterBackpackHandler component = ((Component)character).GetComponent(); if ((Object)(object)component != (Object)null && (Object)(object)component.backpackVisuals != (Object)null) { ((BackpackVisuals)component.backpackVisuals).RefreshVisuals(); } } } private static void RefreshCarryWeights(HashSet players) { foreach (Player player in players) { if (!((Object)(object)player == (Object)null) && !((Object)(object)player.character == (Object)null) && player.character.refs != null && !((Object)(object)player.character.refs.items == (Object)null)) { player.character.refs.items.RefreshAllCharacterCarryWeight(); } } } private void BroadcastConsumedSelectedSlots(List slots) { //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Expected O, but got Unknown //IL_0094: Unknown result type (might be due to invalid IL or missing references) if (slots != null && slots.Count != 0) { object[] array = new object[1 + slots.Count * 2]; array[0] = slots.Count; for (int i = 0; i < slots.Count; i++) { array[1 + i * 2] = slots[i].ActorNumber; array[2 + i * 2] = (int)slots[i].SlotId; } RaiseEventOptions val = new RaiseEventOptions { Receivers = (ReceiverGroup)1 }; PhotonNetwork.RaiseEvent((byte)144, (object)array, val, SendOptions.SendReliable); } } private static void HandleConsumedSelectedSlots(object[] payload) { if ((Object)(object)Instance != (Object)null) { Instance.partyResourceCacheUntil = 0f; } if (payload == null || payload.Length < 1 || PhotonNetwork.LocalPlayer == null) { return; } int num; try { num = Convert.ToInt32(payload[0]); } catch (Exception) { return; } int actorNumber = PhotonNetwork.LocalPlayer.ActorNumber; for (int i = 0; i < num; i++) { int num2 = 1 + i * 2; int num3 = num2 + 1; if (num3 >= payload.Length) { break; } try { int num4 = Convert.ToInt32(payload[num2]); int slotId = Convert.ToInt32(payload[num3]); if (num4 == actorNumber) { UnequipConsumedSlotIfEmpty(slotId); } } catch (Exception) { } } } private static void UnequipConsumedSlotIfEmpty(int slotId) { //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Unknown result type (might be due to invalid IL or missing references) Character localCharacter = Character.localCharacter; Player localPlayer = Player.localPlayer; if ((Object)(object)localCharacter == (Object)null || (Object)(object)localPlayer == (Object)null || localCharacter.refs == null || (Object)(object)localCharacter.refs.items == (Object)null || slotId < 0 || slotId > 255) { return; } Optionable currentSelectedSlot = localCharacter.refs.items.currentSelectedSlot; if (!currentSelectedSlot.IsNone && currentSelectedSlot.Value == (byte)slotId) { ItemSlot itemSlot = localPlayer.GetItemSlot((byte)slotId); if (itemSlot == null || itemSlot.IsEmpty()) { localCharacter.refs.items.EquipSlot(Optionable.None); } } } private void BuildHubVisuals(CraftHubWindow window) { //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_0118: Unknown result type (might be due to invalid IL or missing references) //IL_0135: Unknown result type (might be due to invalid IL or missing references) //IL_0144: Unknown result type (might be due to invalid IL or missing references) //IL_0153: Unknown result type (might be due to invalid IL or missing references) //IL_0162: Unknown result type (might be due to invalid IL or missing references) //IL_01aa: Unknown result type (might be due to invalid IL or missing references) //IL_01b9: Unknown result type (might be due to invalid IL or missing references) //IL_01c8: Unknown result type (might be due to invalid IL or missing references) //IL_01d7: Unknown result type (might be due to invalid IL or missing references) //IL_0215: Unknown result type (might be due to invalid IL or missing references) //IL_0224: Unknown result type (might be due to invalid IL or missing references) //IL_0233: Unknown result type (might be due to invalid IL or missing references) //IL_0242: Unknown result type (might be due to invalid IL or missing references) //IL_0280: Unknown result type (might be due to invalid IL or missing references) //IL_028f: Unknown result type (might be due to invalid IL or missing references) //IL_029e: Unknown result type (might be due to invalid IL or missing references) //IL_02ad: Unknown result type (might be due to invalid IL or missing references) //IL_034c: Unknown result type (might be due to invalid IL or missing references) //IL_0351: Unknown result type (might be due to invalid IL or missing references) //IL_0370: Unknown result type (might be due to invalid IL or missing references) //IL_037f: Unknown result type (might be due to invalid IL or missing references) //IL_0398: Unknown result type (might be due to invalid IL or missing references) //IL_03a7: Unknown result type (might be due to invalid IL or missing references) //IL_03c1: Unknown result type (might be due to invalid IL or missing references) //IL_03cb: Expected O, but got Unknown //IL_043b: Unknown result type (might be due to invalid IL or missing references) //IL_044a: Unknown result type (might be due to invalid IL or missing references) //IL_0459: Unknown result type (might be due to invalid IL or missing references) //IL_0468: Unknown result type (might be due to invalid IL or missing references) //IL_051e: Unknown result type (might be due to invalid IL or missing references) //IL_0523: Unknown result type (might be due to invalid IL or missing references) //IL_0662: Unknown result type (might be due to invalid IL or missing references) //IL_067e: Unknown result type (might be due to invalid IL or missing references) //IL_068d: Unknown result type (might be due to invalid IL or missing references) //IL_069c: Unknown result type (might be due to invalid IL or missing references) //IL_06ab: Unknown result type (might be due to invalid IL or missing references) //IL_06e9: Unknown result type (might be due to invalid IL or missing references) //IL_06f8: Unknown result type (might be due to invalid IL or missing references) //IL_0707: Unknown result type (might be due to invalid IL or missing references) //IL_0716: Unknown result type (might be due to invalid IL or missing references) //IL_07bb: Unknown result type (might be due to invalid IL or missing references) //IL_07c0: Unknown result type (might be due to invalid IL or missing references) //IL_07df: Unknown result type (might be due to invalid IL or missing references) //IL_07ee: Unknown result type (might be due to invalid IL or missing references) //IL_0807: Unknown result type (might be due to invalid IL or missing references) //IL_0816: Unknown result type (might be due to invalid IL or missing references) //IL_083d: Unknown result type (might be due to invalid IL or missing references) //IL_0847: Expected O, but got Unknown //IL_08cc: Unknown result type (might be due to invalid IL or missing references) //IL_08d1: Unknown result type (might be due to invalid IL or missing references) //IL_08f0: Unknown result type (might be due to invalid IL or missing references) //IL_08ff: Unknown result type (might be due to invalid IL or missing references) //IL_0918: Unknown result type (might be due to invalid IL or missing references) //IL_0927: Unknown result type (might be due to invalid IL or missing references) //IL_095b: Unknown result type (might be due to invalid IL or missing references) //IL_0965: Expected O, but got Unknown //IL_0998: Unknown result type (might be due to invalid IL or missing references) //IL_099f: Expected O, but got Unknown //IL_09c9: Unknown result type (might be due to invalid IL or missing references) //IL_09d8: Unknown result type (might be due to invalid IL or missing references) //IL_09e7: Unknown result type (might be due to invalid IL or missing references) //IL_09f6: Unknown result type (might be due to invalid IL or missing references) //IL_0a2e: Unknown result type (might be due to invalid IL or missing references) //IL_0a38: Unknown result type (might be due to invalid IL or missing references) //IL_0a91: Unknown result type (might be due to invalid IL or missing references) //IL_0aae: Unknown result type (might be due to invalid IL or missing references) //IL_0abd: Unknown result type (might be due to invalid IL or missing references) //IL_0acc: Unknown result type (might be due to invalid IL or missing references) //IL_0adb: Unknown result type (might be due to invalid IL or missing references) //IL_0b1a: Unknown result type (might be due to invalid IL or missing references) //IL_0b29: Unknown result type (might be due to invalid IL or missing references) //IL_0b38: Unknown result type (might be due to invalid IL or missing references) //IL_0b47: Unknown result type (might be due to invalid IL or missing references) //IL_0b8b: Unknown result type (might be due to invalid IL or missing references) //IL_0ba7: Unknown result type (might be due to invalid IL or missing references) //IL_0bb6: Unknown result type (might be due to invalid IL or missing references) //IL_0bc5: Unknown result type (might be due to invalid IL or missing references) //IL_0bd4: Unknown result type (might be due to invalid IL or missing references) //IL_0c05: Unknown result type (might be due to invalid IL or missing references) //IL_0c1e: Unknown result type (might be due to invalid IL or missing references) //IL_0c3d: Unknown result type (might be due to invalid IL or missing references) //IL_0c4c: Unknown result type (might be due to invalid IL or missing references) //IL_0c5b: Unknown result type (might be due to invalid IL or missing references) //IL_0c6a: Unknown result type (might be due to invalid IL or missing references) //IL_0c83: Unknown result type (might be due to invalid IL or missing references) //IL_0c8d: Expected O, but got Unknown //IL_0cc1: Unknown result type (might be due to invalid IL or missing references) //IL_0cd0: Unknown result type (might be due to invalid IL or missing references) //IL_0cdf: Unknown result type (might be due to invalid IL or missing references) //IL_0cee: Unknown result type (might be due to invalid IL or missing references) //IL_0d1e: Unknown result type (might be due to invalid IL or missing references) //IL_0d23: Unknown result type (might be due to invalid IL or missing references) //IL_0d42: Unknown result type (might be due to invalid IL or missing references) //IL_0d51: Unknown result type (might be due to invalid IL or missing references) //IL_0d60: Unknown result type (might be due to invalid IL or missing references) //IL_0d6f: Unknown result type (might be due to invalid IL or missing references) //IL_0d88: Unknown result type (might be due to invalid IL or missing references) //IL_0d92: Expected O, but got Unknown //IL_0db8: Unknown result type (might be due to invalid IL or missing references) //IL_0dbd: Unknown result type (might be due to invalid IL or missing references) //IL_0ddc: Unknown result type (might be due to invalid IL or missing references) //IL_0deb: Unknown result type (might be due to invalid IL or missing references) //IL_0dfa: Unknown result type (might be due to invalid IL or missing references) //IL_0e09: Unknown result type (might be due to invalid IL or missing references) //IL_0e22: Unknown result type (might be due to invalid IL or missing references) //IL_0e2c: Expected O, but got Unknown //IL_0e52: Unknown result type (might be due to invalid IL or missing references) //IL_0e57: Unknown result type (might be due to invalid IL or missing references) //IL_0e76: Unknown result type (might be due to invalid IL or missing references) //IL_0e85: Unknown result type (might be due to invalid IL or missing references) //IL_0e94: Unknown result type (might be due to invalid IL or missing references) //IL_0ea3: Unknown result type (might be due to invalid IL or missing references) //IL_0ebc: Unknown result type (might be due to invalid IL or missing references) //IL_0ec6: Expected O, but got Unknown //IL_0595: Unknown result type (might be due to invalid IL or missing references) //IL_05a4: Unknown result type (might be due to invalid IL or missing references) //IL_05ad: Unknown result type (might be due to invalid IL or missing references) //IL_05b9: Unknown result type (might be due to invalid IL or missing references) //IL_05e9: Unknown result type (might be due to invalid IL or missing references) //IL_05f3: Expected O, but got Unknown TMP_FontAsset font = ResolveFont(); Image val = CreateImage("Backdrop", ((Component)window).transform, new Color(0f, 0f, 0f, 0.72f)); Stretch(((Graphic)val).rectTransform); Image val2 = CreateImage("Panel", ((Component)window).transform, new Color(0.085f, 0.095f, 0.115f, 0.99f)); Center(((Graphic)val2).rectTransform, new Vector2(1280f, 760f), Vector2.zero); Image val3 = CreateImage("TopLine", ((Component)val2).transform, new Color(0.82f, 0.65f, 0.26f, 1f)); Anchor(((Graphic)val3).rectTransform, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0f, -5f), new Vector2(1280f, 10f)); Image val4 = CreateImage("Sidebar", ((Component)val2).transform, new Color(0.105f, 0.115f, 0.135f, 1f)); Anchor(((Graphic)val4).rectTransform, new Vector2(0f, 0.5f), new Vector2(0f, 0.5f), new Vector2(100f, 0f), new Vector2(200f, 750f)); TextMeshProUGUI val5 = CreateText("Logo", ((Component)val4).transform, font, "CRAFT\nPEAK", 29f, (TextAlignmentOptions)514); ((TMP_Text)val5).fontStyle = (FontStyles)1; Anchor(((TMP_Text)val5).rectTransform, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0f, -72f), new Vector2(170f, 92f)); TextMeshProUGUI val6 = CreateText("Title", ((Component)val2).transform, font, "설명", 38f, (TextAlignmentOptions)514); Anchor(((TMP_Text)val6).rectTransform, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(700f, -52f), new Vector2(720f, 58f)); TextMeshProUGUI val7 = CreateText("Balance", ((Component)val2).transform, font, string.Empty, 24f, (TextAlignmentOptions)260); Anchor(((TMP_Text)val7).rectTransform, new Vector2(1f, 1f), new Vector2(1f, 1f), new Vector2(-155f, -52f), new Vector2(260f, 42f)); List list = new List(); string[] array = new string[5] { "설명", "강화", "제작", "판매", "부품" }; for (int i = 0; i < array.Length; i++) { HubTab captured; HubTab value = (captured = (HubTab)i); TextMeshProUGUI label; Button val8 = CreateButton("Tab_" + value, ((Component)val4).transform, font, array[i], new Color(0.18f, 0.19f, 0.22f, 1f), Color.white, out label); Anchor(((Component)val8).GetComponent(), new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0f, -190f - (float)i * 78f), new Vector2(168f, 66f)); ((UnityEvent)val8.onClick).AddListener((UnityAction)delegate { SelectTab(captured); }); list.Add(new LiteTabView(value, val8, ((Component)val8).GetComponent(), label)); } TextMeshProUGUI val9 = CreateText("LanguageTitle", ((Component)val4).transform, font, "Language", 16f, (TextAlignmentOptions)514); ((TMP_Text)val9).fontStyle = (FontStyles)1; Anchor(((TMP_Text)val9).rectTransform, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0f, -552f), new Vector2(165f, 24f)); List list2 = new List(); HubLanguage[] array2 = new HubLanguage[5] { HubLanguage.English, HubLanguage.Korean, HubLanguage.Chinese, HubLanguage.Japanese, HubLanguage.French }; string[] array3 = new string[5] { "English", "한국어", "中文", "日本語", "Français" }; for (int num = 0; num < array2.Length; num++) { HubLanguage capturedLanguage = array2[num]; TextMeshProUGUI label2; Button val10 = CreateButton("Language_" + capturedLanguage, ((Component)val4).transform, font, array3[num], new Color(0.18f, 0.19f, 0.22f, 1f), Color.white, out label2); bool flag = num < 3; float num2 = (flag ? (-56f + (float)num * 56f) : (-40f + (float)(num - 3) * 80f)); float num3 = (flag ? (-584f) : (-618f)); float num4 = (flag ? 52f : 76f); Anchor(((Component)val10).GetComponent(), new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(num2, num3), new Vector2(num4, 28f)); ((TMP_Text)label2).fontSize = 11.5f; ((TMP_Text)label2).textWrappingMode = (TextWrappingModes)0; ((UnityEvent)val10.onClick).AddListener((UnityAction)delegate { SelectLanguage(capturedLanguage); }); list2.Add(new LiteLanguageView(capturedLanguage, val10, ((Component)val10).GetComponent())); } TextMeshProUGUI val11 = CreateText("Help", ((Component)val4).transform, font, "P / ESC\n닫기", 17f, (TextAlignmentOptions)514); ((Graphic)val11).color = new Color(0.64f, 0.67f, 0.72f, 1f); Anchor(((TMP_Text)val11).rectTransform, new Vector2(0.5f, 0f), new Vector2(0.5f, 0f), new Vector2(0f, 60f), new Vector2(165f, 65f)); TextMeshProUGUI val12 = CreateText("Explanation", ((Component)val2).transform, font, BuildExplanationText(), 21f, (TextAlignmentOptions)257); Anchor(((TMP_Text)val12).rectTransform, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(730f, -390f), new Vector2(980f, 565f)); List list3 = new List(); List list4 = new List(); string[] array4 = new string[5] { "등산", "음식", "힐", "부활", "필수" }; for (int num5 = 0; num5 < array4.Length; num5++) { CraftUiCategory capturedCategory; CraftUiCategory value2 = (capturedCategory = (CraftUiCategory)num5); TextMeshProUGUI label3; Button val13 = CreateButton("CraftCategory_" + value2, ((Component)val2).transform, font, array4[num5], new Color(0.18f, 0.19f, 0.22f, 1f), Color.white, out label3); Anchor(((Component)val13).GetComponent(), new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(295f + (float)num5 * 118f, -105f), new Vector2(108f, 42f)); ((TMP_Text)label3).fontSize = 17f; ((UnityEvent)val13.onClick).AddListener((UnityAction)delegate { SelectCraftUiCategory(capturedCategory); }); list4.Add(new LiteCraftCategoryView(value2, val13, ((Component)val13).GetComponent(), label3)); } for (int num6 = 0; num6 < 8; num6++) { int capturedRow = num6; TextMeshProUGUI label4; Button val14 = CreateButton("Row_" + num6, ((Component)val2).transform, font, string.Empty, new Color(0.14f, 0.15f, 0.18f, 1f), Color.white, out label4); Anchor(((Component)val14).GetComponent(), new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(515f, -165f - (float)num6 * 61f), new Vector2(590f, 50f)); ((TMP_Text)label4).alignment = (TextAlignmentOptions)513; ((TMP_Text)label4).fontSize = 19f; ((UnityEvent)val14.onClick).AddListener((UnityAction)delegate { SelectVisibleRow(capturedRow); }); GameObject val15 = new GameObject("CraftIcon", new Type[3] { typeof(RectTransform), typeof(CanvasRenderer), typeof(RawImage) }); val15.transform.SetParent(((Component)val14).transform, false); RectTransform component = val15.GetComponent(); Anchor(component, new Vector2(0f, 0.5f), new Vector2(0f, 0.5f), new Vector2(27f, 0f), new Vector2(40f, 40f)); RawImage component2 = val15.GetComponent(); ((Graphic)component2).raycastTarget = false; val15.SetActive(false); RectTransform rectTransform = ((TMP_Text)label4).rectTransform; rectTransform.offsetMin = new Vector2(56f, rectTransform.offsetMin.y); list3.Add(new LiteRowView(val14, ((Component)val14).GetComponent(), label4, component2)); } Image val16 = CreateImage("DetailPanel", ((Component)val2).transform, new Color(0.125f, 0.135f, 0.16f, 1f)); Anchor(((Graphic)val16).rectTransform, new Vector2(1f, 0.5f), new Vector2(1f, 0.5f), new Vector2(-235f, -5f), new Vector2(430f, 590f)); TextMeshProUGUI val17 = CreateText("Detail", ((Component)val16).transform, font, string.Empty, 21f, (TextAlignmentOptions)257); Anchor(((TMP_Text)val17).rectTransform, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0f, -185f), new Vector2(380f, 315f)); TextMeshProUGUI val18 = CreateText("Status", ((Component)val16).transform, font, string.Empty, 18f, (TextAlignmentOptions)514); ((Graphic)val18).color = new Color(0.76f, 0.78f, 0.83f, 1f); Anchor(((TMP_Text)val18).rectTransform, new Vector2(0.5f, 0f), new Vector2(0.5f, 0f), new Vector2(0f, 126f), new Vector2(380f, 86f)); TextMeshProUGUI label5; Button val19 = CreateButton("Action", ((Component)val16).transform, font, "강화", new Color(0.82f, 0.65f, 0.26f, 1f), new Color(0.07f, 0.07f, 0.08f, 1f), out label5); Anchor(((Component)val19).GetComponent(), new Vector2(0.5f, 0f), new Vector2(0.5f, 0f), new Vector2(0f, 48f), new Vector2(350f, 66f)); ((UnityEvent)val19.onClick).AddListener(new UnityAction(ExecuteCurrentTabAction)); TextMeshProUGUI val20 = CreateText("Page", ((Component)val2).transform, font, string.Empty, 18f, (TextAlignmentOptions)514); Anchor(((TMP_Text)val20).rectTransform, new Vector2(0f, 0f), new Vector2(0f, 0f), new Vector2(515f, 40f), new Vector2(250f, 35f)); TextMeshProUGUI label6; Button val21 = CreateButton("Previous", ((Component)val2).transform, font, "◀ 이전", new Color(0.22f, 0.24f, 0.28f, 1f), Color.white, out label6); Anchor(((Component)val21).GetComponent(), new Vector2(0f, 0f), new Vector2(0f, 0f), new Vector2(315f, 40f), new Vector2(130f, 50f)); ((UnityEvent)val21.onClick).AddListener(new UnityAction(PreviousCraftPage)); TextMeshProUGUI label7; Button val22 = CreateButton("Next", ((Component)val2).transform, font, "다음 ▶", new Color(0.22f, 0.24f, 0.28f, 1f), Color.white, out label7); Anchor(((Component)val22).GetComponent(), new Vector2(0f, 0f), new Vector2(0f, 0f), new Vector2(715f, 40f), new Vector2(130f, 50f)); ((UnityEvent)val22.onClick).AddListener(new UnityAction(NextCraftPage)); TextMeshProUGUI label8; Button val23 = CreateButton("Close", ((Component)val2).transform, font, "닫기", new Color(0.26f, 0.28f, 0.32f, 1f), Color.white, out label8); Anchor(((Component)val23).GetComponent(), new Vector2(1f, 0f), new Vector2(1f, 0f), new Vector2(-90f, 30f), new Vector2(130f, 48f)); ((UnityEvent)val23.onClick).AddListener(new UnityAction(CloseHub)); window.SetReferences(list, list4, list2, list3, val6, val7, val9, val11, val12, ((Component)val16).gameObject, val17, val18, val19, label5, val20, val21, label6, val22, label7, val23, label8); } private void SelectVisibleRow(int rowIndex) { switch (currentTab) { case HubTab.Description: break; case HubTab.Upgrade: if (rowIndex >= 0 && rowIndex <= 4) { SelectUpgradeKind((UpgradeKind)rowIndex); } break; case HubTab.Craft: SelectCraftCard(rowIndex); break; case HubTab.Sell: SelectSellSlot(rowIndex); break; case HubTab.Parts: SelectPart(rowIndex); break; case HubTab.Developer: break; } } private void ExecuteCurrentTabAction() { switch (currentTab) { case HubTab.Description: break; case HubTab.Upgrade: RequestUpgrade(); break; case HubTab.Craft: RequestCraft(); break; case HubTab.Sell: RequestSell(); break; case HubTab.Parts: RequestPartPurchase(); break; case HubTab.Developer: RequestDeveloperMoney(); break; } } private static string BuildExplanationText() { return GetCurrentLanguage() switch { HubLanguage.Korean => "Craft PEAK는 PEAK의 등산을 자원 수집과 제작 중심의 크래프팅 게임으로 바꾸는 모드입니다. 게임 중 P키를 누르면 통합 상점이 열리며 설명, 강화, 제작, 판매, 부품 탭을 사용할 수 있습니다.\n\n맵에 흩어진 자원을 모아 판매하면 파티 공유 돈을 얻습니다. 공유 돈과 재료는 강화, 장비 제작, 비행기 부품 구매에 함께 사용됩니다. 강화 탭에서 제작 등급을 Common에서 Normal, Rare, Unique, Legendary 순서로 올려야 다음 단계의 자원과 제작식이 열립니다. 상위 제작품은 반드시 이전 단계 제작품을 재료로 요구합니다.\n\n모닥불은 단순한 휴식 장소가 아니라 다음 세그먼트로 이동하기 위한 진행 트리거입니다. 해안, 열대/뿌리숲, 메사/고산지대, 칼데라에서는 현재 구간에 맞는 제작 등급과 비행기 부품이 필요합니다. 부품은 인벤토리에 들어가지 않고 Photon 방의 공동 진행 상태로 저장되며, 모닥불을 성공적으로 켤 때 사용 완료됩니다.\n\n진행 순서는 해안 → 열대/뿌리숲 → 메사/고산지대 → 칼데라 → 가마 → 정상입니다. 가마에서 정상으로 갈 때는 모닥불과 비행기 부품을 사용하지 않습니다. 정상에 도착한 뒤 제작 탭에서 Legendary 재료가 들어가는 가장 비싼 최종 조명탄을 제작하면 최종 탈출 신호가 완성됩니다.\n\n핵심 흐름은 자원 수집 → 판매 → 강화와 제작 → 비행기 부품 구매 → 모닥불 점화 → 다음 구간 이동입니다.", HubLanguage.Chinese => "Craft PEAK 将 PEAK 的攀登玩法改造成以资源收集和制作为核心的生存制作模式。游戏中按 P 可打开综合商店,使用说明、升级、制作、出售和部件标签。\n\n收集地图中的资源并出售,可获得队伍共享资金。共享资金与材料用于升级、制作装备和购买飞机部件。必须在升级标签中按 Common、Normal、Rare、Unique、Legendary 的顺序提升制作等级,才能解锁后续资源与配方。高级物品会要求前一阶段的制作品作为材料。\n\n篝火不仅是休息地点,也是前往下一区域的推进触发器。在海滩、热带/根系森林、台地/高山和火山口区域,需要满足当前区域对应的制作等级并拥有指定飞机部件。部件不会进入背包,而是保存为 Photon 房间的共享进度,并在成功点燃篝火时消耗。\n\n推进顺序为海滩 → 热带/根系森林 → 台地/高山 → 火山口 → 熔炉 → 山顶。从熔炉前往山顶时不使用篝火或飞机部件。抵达山顶后,在制作标签中制作需要 Legendary 材料、价格最高的最终信号弹,即可完成最终逃脱信号。\n\n核心流程:收集资源 → 出售 → 升级与制作 → 购买飞机部件 → 点燃篝火 → 前往下一区域。", HubLanguage.Japanese => "Craft PEAKは、PEAKの登山を資源収集とクラフト中心のゲームへ変えるMODです。ゲーム中にPキーを押すと統合ショップが開き、説明・強化・クラフト・売却・部品タブを使用できます。\n\nマップ上の資源を集めて売却すると、パーティー共有資金を獲得できます。共有資金と素材は、強化、装備のクラフト、飛行機部品の購入に使用します。強化タブでクラフト等級をCommon、Normal、Rare、Unique、Legendaryの順に上げると、次の資源とレシピが解放されます。上位の完成品には前段階の完成品が素材として必要です。\n\n焚き火は休憩場所だけでなく、次の区間へ進むための進行トリガーです。海岸、熱帯/根の森、メサ/高山、カルデラでは、現在の区間に対応するクラフト等級と飛行機部品が必要です。部品はインベントリには入らず、Photonルームの共有進行状態として保存され、焚き火の点火成功時に使用済みになります。\n\n進行順は海岸 → 熱帯/根の森 → メサ/高山 → カルデラ → 窯 → 山頂です。窯から山頂へ進む際は焚き火と飛行機部品を使用しません。山頂到達後、クラフトタブでLegendary素材を使う最も高価な最終フレアを作成すると、最終脱出信号が完成します。\n\n基本の流れは、資源収集 → 売却 → 強化とクラフト → 飛行機部品購入 → 焚き火点火 → 次の区間へ移動、です。", HubLanguage.French => "Craft PEAK transforme l’ascension de PEAK en un jeu de fabrication centré sur la collecte de ressources. Pendant une partie, appuyez sur P pour ouvrir la boutique unifiée et accéder aux onglets Description, Améliorations, Fabrication, Vente et Pièces.\n\nRamassez les ressources dispersées sur la carte puis vendez-les pour gagner de l’argent partagé par le groupe. Cet argent et les matériaux servent aux améliorations, à la fabrication d’équipement et à l’achat de pièces d’avion. Dans l’onglet Améliorations, augmentez le niveau de fabrication dans l’ordre Common, Normal, Rare, Unique puis Legendary afin de débloquer les ressources et recettes suivantes. Les objets avancés exigent des objets fabriqués au niveau précédent.\n\nLes feux de camp ne sont pas seulement des lieux de repos : ils déclenchent la progression vers le segment suivant. Sur la Plage, dans les Tropiques/Forêt de racines, sur le Mesa/Alpin et dans la Caldeira, vous devez posséder le niveau de fabrication et la pièce d’avion correspondant au segment actuel. Les pièces ne vont pas dans l’inventaire ; elles sont enregistrées dans l’état partagé du salon Photon et sont consommées lorsqu’un feu de camp est allumé avec succès.\n\nL’ordre de progression est Plage → Tropiques/Forêt de racines → Mesa/Alpin → Caldeira → Four → Sommet. Le passage du Four au Sommet n’utilise ni feu de camp ni pièce d’avion. Une fois au Sommet, fabriquez dans l’onglet Fabrication la fusée finale la plus coûteuse, qui exige des matériaux Legendary, pour terminer le signal d’évacuation.\n\nBoucle principale : collecter → vendre → améliorer et fabriquer → acheter les pièces d’avion → allumer le feu de camp → rejoindre le segment suivant.", _ => "Craft PEAK turns PEAK’s climb into a crafting game focused on gathering resources. Press P during a run to open the unified shop and use the Description, Upgrades, Crafting, Sell, and Parts tabs.\n\nGather resources across the map and sell them to earn party-shared money. Shared money and materials are used for upgrades, equipment crafting, and aircraft parts. In the Upgrades tab, raise the crafting grade in order from Common to Normal, Rare, Unique, and Legendary to unlock later resources and recipes. Higher-tier items require crafted items from the previous tier as ingredients.\n\nCampfires are not only rest points; they trigger progression to the next segment. At the Beach, Tropics/Roots, Mesa/Alpine, and Caldera, you need the crafting grade and aircraft part assigned to the current segment. Parts do not enter the inventory. They are stored as shared Photon-room progression and are consumed when the campfire is lit successfully.\n\nThe route is Beach → Tropics/Roots → Mesa/Alpine → Caldera → Kiln → Peak. Moving from the Kiln to the Peak does not use a campfire or aircraft part. After reaching the Peak, craft the most expensive final flare with Legendary materials in the Crafting tab to complete the escape signal.\n\nCore loop: gather resources → sell → upgrade and craft → purchase aircraft parts → light the campfire → move to the next segment.", }; } private static string BuildCraftRowText(CraftRecipe recipe) { if (recipe == null) { return string.Empty; } return recipe.DisplayName + " | " + GetResourceGradeName(recipe.RequiredResourceLevel) + " / " + recipe.Category + " | " + recipe.MoneyCost + "원"; } private string BuildUpgradeRowText(UpgradeKind kind) { return GetUpgradeDisplayName(kind) + " | " + GetUpgradeCurrentLevel(kind) + "/" + GetUpgradeMaximumLevel(kind); } private static string BuildSellRowText(int slotId) { Player localPlayer = Player.localPlayer; if ((Object)(object)localPlayer == (Object)null || localPlayer.itemSlots == null || slotId < 0 || slotId >= localPlayer.itemSlots.Length) { return string.Empty; } ItemSlot itemSlot = localPlayer.GetItemSlot((byte)slotId); if (itemSlot == null || itemSlot.IsEmpty() || (Object)(object)itemSlot.prefab == (Object)null) { return slotId + 1 + "번 슬롯 | 비어 있음"; } int num = Mathf.Max(1, InventoryStack.GetStackCount(localPlayer, (byte)slotId)); bool flag = Spawn.IsSaleResourceId(itemSlot.prefab.itemID); return slotId + 1 + "번 슬롯 | " + GetItemDisplayName(itemSlot.prefab) + ((num > 1) ? (" x" + num) : string.Empty) + (flag ? (" | " + GetSellPrice(itemSlot.prefab.itemID) + "원") : " | 판매 불가"); } private static string BuildUpgradeDetailText(CraftHub owner) { UpgradeKind kind = owner.SelectedUpgradeKind; int selectedUpgradeCurrentLevel = owner.SelectedUpgradeCurrentLevel; int selectedUpgradeMaximumLevel = owner.SelectedUpgradeMaximumLevel; StringBuilder stringBuilder = owner.sharedTextBuilder; stringBuilder.Length = 0; stringBuilder.Append(owner.GetUpgradeDisplayName(kind)); stringBuilder.Append("\n\n"); stringBuilder.Append(owner.SelectedUpgradeCurrentEffect); stringBuilder.Append("\n\n"); stringBuilder.Append(owner.SelectedUpgradeNextEffect); stringBuilder.Append("\n\n"); if (selectedUpgradeCurrentLevel >= selectedUpgradeMaximumLevel) { stringBuilder.Append("최대 단계"); } else { stringBuilder.Append("비용 "); stringBuilder.Append(owner.SelectedUpgradeCost); stringBuilder.Append("원"); } return stringBuilder.ToString(); } private static string BuildCraftDetailText(CraftHub owner, CraftRecipe recipe, out bool ready) { ready = false; if (recipe == null) { return "제작할 아이템을 선택하세요."; } string value = owner.BuildCraftRequirementText(recipe, out ready); StringBuilder stringBuilder = owner.sharedTextBuilder; stringBuilder.Length = 0; stringBuilder.Append(recipe.DisplayName); stringBuilder.Append("\n"); stringBuilder.Append(GetResourceGradeName(recipe.RequiredResourceLevel)); stringBuilder.Append(" / "); stringBuilder.Append(recipe.Category); stringBuilder.Append("\n\n"); stringBuilder.Append(value); return stringBuilder.ToString(); } private static void SetActiveIfChanged(GameObject gameObject, bool active) { if ((Object)(object)gameObject != (Object)null && gameObject.activeSelf != active) { gameObject.SetActive(active); } } private static HubLanguage GetCurrentLanguage() { return ((Object)(object)Instance != (Object)null) ? Instance.currentLanguage : HubLanguage.English; } private static string LocalizeUiText(string text) { string text2 = text ?? string.Empty; if (text2.Length == 0) { return text2; } HubLanguage hubLanguage = GetCurrentLanguage(); for (int i = 0; i < UiTranslationEntries.Length; i++) { UiTranslationEntry uiTranslationEntry = UiTranslationEntries[i]; if (uiTranslationEntry != null && !string.IsNullOrEmpty(uiTranslationEntry.Source) && text2.IndexOf(uiTranslationEntry.Source, StringComparison.Ordinal) >= 0) { text2 = text2.Replace(uiTranslationEntry.Source, uiTranslationEntry.Get(hubLanguage)); } } if (hubLanguage == HubLanguage.Korean) { return text2; } return LocalizeNumericUnits(text2, hubLanguage); } private static string LocalizeNumericUnits(string text, HubLanguage language) { if (string.IsNullOrEmpty(text)) { return text ?? string.Empty; } string input = Regex.Replace(text, "(\\d+)번 슬롯", delegate(Match match) { string value = match.Groups[1].Value; return language switch { HubLanguage.Chinese => "栏位 " + value, HubLanguage.Japanese => "スロット" + value, HubLanguage.French => "Emplacement " + value, _ => "Slot " + value, }; }); input = Regex.Replace(input, "(\\d+)번째", delegate(Match match) { string value = match.Groups[1].Value; return language switch { HubLanguage.Chinese => "第" + value, HubLanguage.Japanese => value + "番目", HubLanguage.French => "Étape " + value, _ => "Stage " + value, }; }); input = Regex.Replace(input, "(\\d+)단계", delegate(Match match) { string value = match.Groups[1].Value; return language switch { HubLanguage.Chinese => value + "级", HubLanguage.Japanese => "レベル" + value, HubLanguage.French => "Niveau " + value, _ => "Level " + value, }; }); input = Regex.Replace(input, "(\\d+)개", delegate(Match match) { string value = match.Groups[1].Value; return language switch { HubLanguage.Chinese => value + "个", HubLanguage.Japanese => value + "個", HubLanguage.French => value + ((value == "1") ? " unité" : " unités"), _ => value + ((value == "1") ? " unit" : " units"), }; }); return Regex.Replace(input, "(\\d+)원", delegate(Match match) { string value = match.Groups[1].Value; return language switch { HubLanguage.Chinese => value + "金币", HubLanguage.Japanese => value + "コイン", HubLanguage.French => value + ((value == "1") ? " pièce" : " pièces"), _ => value + ((value == "1") ? " coin" : " coins"), }; }); } private static void SetTextIfChanged(TextMeshProUGUI label, string text) { if (!((Object)(object)label == (Object)null)) { string text2 = LocalizeUiText(text); if (!string.Equals(((TMP_Text)label).text, text2, StringComparison.Ordinal)) { ((TMP_Text)label).text = text2; } } } private static void SetInteractableIfChanged(Button button, bool interactable) { if ((Object)(object)button != (Object)null && ((Selectable)button).interactable != interactable) { ((Selectable)button).interactable = interactable; } } private static TMP_FontAsset ResolveFont() { if ((Object)(object)cachedFontAsset != (Object)null) { return cachedFontAsset; } GUIManager instance = GUIManager.instance; if ((Object)(object)instance != (Object)null) { TextMeshProUGUI componentInChildren = ((Component)instance).GetComponentInChildren(true); if ((Object)(object)componentInChildren != (Object)null && (Object)(object)((TMP_Text)componentInChildren).font != (Object)null) { cachedFontAsset = ((TMP_Text)componentInChildren).font; return cachedFontAsset; } } cachedFontAsset = TMP_Settings.defaultFontAsset; return cachedFontAsset; } private static Image CreateImage(string objectName, Transform parent, Color color) { //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Expected O, but got Unknown //IL_004b: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject(objectName, new Type[3] { typeof(RectTransform), typeof(CanvasRenderer), typeof(Image) }); val.transform.SetParent(parent, false); Image component = val.GetComponent(); ((Graphic)component).color = color; return component; } private static TextMeshProUGUI CreateText(string objectName, Transform parent, TMP_FontAsset font, string text, float fontSize, TextAlignmentOptions alignment) { //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Expected O, but got Unknown //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject(objectName, new Type[3] { typeof(RectTransform), typeof(CanvasRenderer), typeof(TextMeshProUGUI) }); val.transform.SetParent(parent, false); TextMeshProUGUI component = val.GetComponent(); ((TMP_Text)component).font = font; ((TMP_Text)component).text = LocalizeUiText(text); ((TMP_Text)component).fontSize = fontSize; ((TMP_Text)component).alignment = alignment; ((Graphic)component).color = Color.white; ((TMP_Text)component).textWrappingMode = (TextWrappingModes)1; ((Graphic)component).raycastTarget = false; return component; } private static Button CreateButton(string objectName, Transform parent, TMP_FontAsset font, string labelText, Color backgroundColor, Color textColor, out TextMeshProUGUI label) { //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0049: 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_008b: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Unknown result type (might be due to invalid IL or missing references) Image val = CreateImage(objectName, parent, backgroundColor); Button val2 = ((Component)val).gameObject.AddComponent