Decompiled source of Craft PEAK v0.0.2

Craft PEAK.dll

Decompiled a day ago
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<IngredientLocation> FireWood = new List<IngredientLocation>();

		public readonly List<IngredientLocation> Stone = new List<IngredientLocation>();

		public readonly List<IngredientLocation> Torch = new List<IngredientLocation>();
	}

	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<int> requiredFireWoodCountConfig;

	private static ConfigEntry<int> requiredStoneCountConfig;

	private static ConfigEntry<int> requiredTorchCountConfig;

	private static ConfigEntry<bool> requireEveryoneInRangeConfig;

	private static ConfigEntry<float> everyoneInRangeDistanceConfig;

	private static ConfigEntry<float> maximumRequesterDistanceConfig;

	private const byte IgniteRequestEventCode = 170;

	private const byte IgniteResultEventCode = 171;

	private const byte ConsumedSelectedSlotEventCode = 172;

	private readonly HashSet<int> committedCampfireViewIds = new HashSet<int>();

	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<int>("01. 캠프파이어 재료 조건", "나뭇가지 요구 수량", 1, new ConfigDescription("모닥불 하나를 점화할 때 소비할 나뭇가지 수량입니다. 0으로 설정하면 나뭇가지를 요구하거나 소비하지 않습니다.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 20), Array.Empty<object>()));
		requiredStoneCountConfig = ((BaseUnityPlugin)this).Config.Bind<int>("01. 캠프파이어 재료 조건", "돌 요구 수량", 1, new ConfigDescription("모닥불 하나를 점화할 때 소비할 돌 수량입니다. 0으로 설정하면 돌을 요구하거나 소비하지 않습니다.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 20), Array.Empty<object>()));
		requiredTorchCountConfig = ((BaseUnityPlugin)this).Config.Bind<int>("01. 캠프파이어 재료 조건", "횃불 요구 수량", 1, new ConfigDescription("모닥불 하나를 점화할 때 소비할 횃불 수량입니다. 0으로 설정하면 횃불을 요구하거나 소비하지 않습니다.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 20), Array.Empty<object>()));
		requireEveryoneInRangeConfig = ((BaseUnityPlugin)this).Config.Bind<bool>("02. 캠프파이어 집결 조건", "모든 생존 플레이어 집결 필요", true, "활성화하면 기존 PEAK처럼 모든 생존 플레이어가 모닥불 근처에 모여야 점화할 수 있습니다.");
		everyoneInRangeDistanceConfig = ((BaseUnityPlugin)this).Config.Bind<float>("02. 캠프파이어 집결 조건", "집결 판정 거리", 15f, new ConfigDescription("모든 생존 플레이어 집결 조건에 사용하는 모닥불 중심 거리입니다.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 50f), Array.Empty<object>()));
		maximumRequesterDistanceConfig = ((BaseUnityPlugin)this).Config.Bind<float>("02. 캠프파이어 집결 조건", "점화 요청 허용 거리", 4f, new ConfigDescription("상호작용한 플레이어가 호스트 검증 시 모닥불에서 떨어질 수 있는 최대 거리입니다.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 15f), Array.Empty<object>()));
		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<MapHandler>() != (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<PhotonView>();
		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<Campfire>();
		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<FireState, FireState>(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<FireState, FireState>(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<FireState, FireState>(ref campfire.state)/*cast due to .constrained prefix*/).ToString() : "<destroyed>") + " | 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<IngredientLocation> 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<IngredientLocation> locations, ushort itemId, int requiredCount, List<IngredientLocation> destination)
	{
		if (destination == null)
		{
			return false;
		}
		destination.Clear();
		if (requiredCount <= 0)
		{
			return true;
		}
		List<IngredientLocation> list = new List<IngredientLocation>();
		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<IngredientLocation> CollectAllIngredientLocations()
	{
		List<IngredientLocation> list = new List<IngredientLocation>();
		List<Character> 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<BackpackData>((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<IngredientLocation> 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<byte> currentSelectedSlot = location.Character.refs.items.currentSelectedSlot;
		return currentSelectedSlot.IsSome && currentSelectedSlot.Value == location.ExternalSlotId;
	}

	private static int CountAvailableUnits(List<IngredientLocation> 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<ConsumedSelectedSlot> consumedSelectedSlots)
	{
		consumedSelectedSlots = new List<ConsumedSelectedSlot>();
		if (plan == null || !ValidatePlannedUnits(plan.FireWood, 28, RequiredFireWoodCount) || !ValidatePlannedUnits(plan.Stone, 72, RequiredStoneCount) || !ValidatePlannedUnits(plan.Torch, 109, RequiredTorchCount))
		{
			return false;
		}
		List<IngredientLocation> list = new List<IngredientLocation>();
		list.AddRange(plan.FireWood);
		list.AddRange(plan.Stone);
		list.AddRange(plan.Torch);
		HashSet<Player> hashSet = new HashSet<Player>();
		HashSet<Character> hashSet2 = new HashSet<Character>();
		HashSet<string> hashSet3 = new HashSet<string>();
		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<IngredientLocation> plannedUnits, ushort expectedItemId, int expectedCount)
	{
		if (expectedCount <= 0)
		{
			return plannedUnits != null && plannedUnits.Count == 0;
		}
		if (plannedUnits == null || plannedUnits.Count != expectedCount)
		{
			return false;
		}
		Dictionary<IngredientLocation, int> dictionary = new Dictionary<IngredientLocation, int>();
		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<IngredientLocation, int> 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<PhotonView>();
		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<InventorySyncData>(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<CharacterBackpackHandler>();
			if (!((Object)(object)component == (Object)null) && !((Object)(object)component.backpackVisuals == (Object)null))
			{
				((BackpackVisuals)component.backpackVisuals).RefreshVisuals();
			}
		}
	}

	private static void RefreshAllCarryWeights(HashSet<Player> 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<ConsumedSelectedSlot> 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<byte> currentSelectedSlot = localCharacter.refs.items.currentSelectedSlot;
			if (!currentSelectedSlot.IsNone && currentSelectedSlot.Value == (byte)consumedSlotId)
			{
				localCharacter.refs.items.EquipSlot(Optionable<byte>.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<UI_Notifications>();
			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<IngredientLocation> 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<color=" + text + ">필요: " + BuildMaterialProgressText(num, num2, num3) + "</color>";
	}

	private static string BuildMaterialProgressText(int fireWoodCount, int stoneCount, int torchCount)
	{
		List<string> list = new List<string>();
		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<string> list = new List<string>();
		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<IngredientLocation> locations = CollectAllIngredientLocations();
		return CountAvailableUnits(locations, itemId);
	}

	private static string BuildMaterialCountLog()
	{
		List<IngredientLocation> 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 "<unknown>";
		}
		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<IngredientCost> Ingredients = new List<IngredientCost>();
	}

	private sealed class CampfireBuildRecipe
	{
		public int Stage;

		public string Name;

		public int RequiredResourceLevel;

		public int MoneyCost;

		public readonly List<IngredientCost> Ingredients = new List<IngredientCost>();

		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<IngredientCost> Ingredients = new List<IngredientCost>();

		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<int> BaseCost;

		public ConfigEntry<int> 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<PlannedIngredientUnit> Units = new List<PlannedIngredientUnit>();
	}

	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<ushort> Pool;

		public readonly int Count;

		public PoolRequest(List<ushort> pool, int count)
		{
			Pool = pool ?? new List<ushort>();
			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<LiteTabView> tabs = new List<LiteTabView>();

		private List<LiteCraftCategoryView> craftCategoryTabs = new List<LiteCraftCategoryView>();

		private List<LiteLanguageView> languages = new List<LiteLanguageView>();

		private List<LiteRowView> rows = new List<LiteRowView>();

		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<LiteTabView> tabViews, List<LiteCraftCategoryView> craftCategoryViews, List<LiteLanguageView> languageViews, List<LiteRowView> 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<LiteTabView>();
			craftCategoryTabs = craftCategoryViews ?? new List<LiteCraftCategoryView>();
			languages = languageViews ?? new List<LiteLanguageView>();
			rows = rowViews ?? new List<LiteRowView>();
			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<PlanePartRecipe> PlanePartRecipes = new List<PlanePartRecipe>();

	private static readonly List<CampfireBuildRecipe> NextCampfireRecipes = new List<CampfireBuildRecipe>();

	private static bool progressionRecipesBuilt;

	private static string progressionRecipeRunId = string.Empty;

	private int sharedRecipeSeed;

	private string sharedRecipeRunId = string.Empty;

	private bool sharedRecipeSeedLoaded;

	private readonly List<CraftRecipe> craftRecipes = new List<CraftRecipe>();

	private readonly Dictionary<ushort, CraftRecipe> craftRecipesByOutputId = new Dictionary<ushort, CraftRecipe>();

	private readonly Dictionary<int, double> lastSellRequestAtByActor = new Dictionary<int, double>();

	private readonly Dictionary<int, PendingSaleTransaction> pendingSaleTransactions = new Dictionary<int, PendingSaleTransaction>();

	private readonly HashSet<string> reservedOrSoldItemGuids = new HashSet<string>(StringComparer.Ordinal);

	private readonly HashSet<string> pendingTempHandDrawGuids = new HashSet<string>(StringComparer.Ordinal);

	private LocalPendingSale localPendingSale;

	private int nextSaleTransactionId;

	private readonly Dictionary<int, double> lastCraftRequestAtByActor = new Dictionary<int, double>();

	private readonly Dictionary<int, double> lastUpgradeRequestAtByActor = new Dictionary<int, double>();

	private readonly Dictionary<int, double> lastPartRequestAtByActor = new Dictionary<int, double>();

	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<ushort, int> cachedPartyResourceCounts = new Dictionary<ushort, int>();

	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<int> 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.",