Decompiled source of PaintYourBuild v1.10.0

BepInEx/plugins/PaintYourBuild/PaintYourBuild.dll

Decompiled 15 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Text;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using BrushCameraCompatibility;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using NaturalPaintLogic;
using PaintCameraLogic;
using PaintFixtureSupport;
using TMPro;
using UnityEngine;
using UnityEngine.Audio;
using UnityEngine.EventSystems;
using UnityEngine.Events;
using UnityEngine.InputSystem;
using UnityEngine.UI;
using ValheimBuildCamera;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyFileVersion("1.6.15")]
[assembly: AssemblyVersion("1.6.15.0")]
[BepInPlugin("valheim.paintyourbuild", "Paint Your Build", "1.6.15")]
public sealed class NaturalPaintPlugin : BaseUnityPlugin
{
	private enum BuildingStage
	{
		None,
		Selecting,
		Detecting,
		Review,
		Running
	}

	private sealed class BuildingRecord
	{
		internal Piece Piece;

		internal ZDOID Id;

		internal ZNetView View;

		internal int Finish;

		internal float Opacity;

		internal float OwnershipDeadline;

		internal int AppliedFinish;

		internal float AppliedOpacity = -1f;
	}

	internal struct BrushTarget
	{
		internal Piece Piece;

		internal WearNTear Wear;

		internal NaturalPaintLogic.PaintMaterial Kind;

		internal Vector3 Point;
	}

	private sealed class NetworkPaintStore : IPaintStore
	{
		private readonly ZNetView _view;

		public bool CanWrite
		{
			get
			{
				if (Object.op_Implicit((Object)(object)_view) && _view.IsValid())
				{
					return _view.IsOwner();
				}
				return false;
			}
		}

		public int FinishId
		{
			get
			{
				return _view.GetZDO().GetInt(PaintHash, 0);
			}
			set
			{
				if (!CanWrite)
				{
					throw new InvalidOperationException("Piece ownership changed");
				}
				_view.GetZDO().Set(PaintHash, value, false);
			}
		}

		internal NetworkPaintStore(ZNetView view)
		{
			_view = view;
		}
	}

	private sealed class PaintedPiece
	{
		internal Piece Piece;

		internal WearNTear Wear;

		internal NaturalPaintLogic.PaintMaterial Kind;

		internal ZNetView View;

		internal int Saved;

		internal float Opacity = -1f;

		internal bool Highlighted;

		internal bool Built;

		internal readonly List<Surface> Surfaces = new List<Surface>();
	}

	private sealed class Slot
	{
		internal int Index;

		internal int Property;

		internal Color Base;
	}

	private sealed class Surface
	{
		internal Renderer Renderer;

		internal PaintedPiece Piece;

		internal readonly List<Slot> Slots = new List<Slot>();

		internal readonly MaterialPropertyBlock Block = new MaterialPropertyBlock();

		internal bool Applied;
	}

	private sealed class GameInventory : IBrushInventory
	{
		private sealed class Snapshot
		{
			internal List<ItemData> Items;

			internal int[] Stacks;
		}

		private readonly Player _player;

		private readonly Inventory _inventory;

		internal bool LastCreatedCheated;

		public int FreeSlots => _inventory.GetEmptySlots();

		internal GameInventory(Player player)
		{
			_player = player;
			_inventory = ((Humanoid)player).GetInventory();
		}

		private static string ResourceName(string prefab)
		{
			GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(prefab);
			if (!Object.op_Implicit((Object)(object)itemPrefab))
			{
				throw new InvalidOperationException("Missing resource prefab " + prefab);
			}
			return itemPrefab.GetComponent<ItemDrop>().m_itemData.m_shared.m_name;
		}

		public int Count(string resource)
		{
			return _inventory.CountItems(ResourceName(resource), -1, true);
		}

		public object Capture()
		{
			List<ItemData> list = new List<ItemData>(_inventory.GetAllItems());
			return new Snapshot
			{
				Items = list,
				Stacks = list.Select((ItemData item) => item.m_stack).ToArray()
			};
		}

		public void Restore(object state)
		{
			Snapshot snapshot = (Snapshot)state;
			List<ItemData> allItems = _inventory.GetAllItems();
			allItems.Clear();
			allItems.AddRange(snapshot.Items);
			for (int i = 0; i < allItems.Count; i++)
			{
				allItems[i].m_stack = snapshot.Stacks[i];
			}
			try
			{
				NotifyInventory.Invoke(_inventory, new object[2] { false, false });
			}
			catch (Exception ex)
			{
				Instance.ReportOnce("rollback.notify", ex);
			}
		}

		public bool AddBrush()
		{
			ItemData val = CreateBrushItem();
			LastCreatedCheated |= Instance._craftCompatibility.IsItemCheated(val);
			if (_inventory.AddItem(val))
			{
				return _inventory.ContainsItem(val);
			}
			return false;
		}

		internal ItemData CreateBrushItem()
		{
			//IL_006c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0071: Unknown result type (might be due to invalid IL or missing references)
			ItemData val = BrushPrefab.GetComponent<ItemDrop>().m_itemData.Clone();
			val.m_dropPrefab = BrushPrefab;
			val.m_stack = 1;
			val.m_quality = 1;
			val.m_variant = 0;
			val.m_worldLevel = (byte)Game.m_worldLevel;
			val.m_crafterID = _player.GetPlayerID();
			val.m_crafterName = _player.GetPlayerName();
			val.m_gridPos = new Vector2i(-1, -1);
			val.m_equipped = false;
			val.m_durability = 100f;
			CraftCompatibility compatibility = Instance._craftCompatibility;
			if (compatibility.HasItemFlag)
			{
				string woodName = ResourceName("Wood");
				bool resourceCheated = _inventory.GetAllItems().Any((ItemData resource) => resource.m_shared.m_name == woodName && resource.m_worldLevel >= Game.m_worldLevel && compatibility.IsItemCheated(resource));
				int? stationCheatedKey = compatibility.StationCheatedKey;
				CraftingStation currentCraftingStation = _player.GetCurrentCraftingStation();
				object obj;
				if (currentCraftingStation == null)
				{
					obj = null;
				}
				else
				{
					ZNetView component = ((Component)currentCraftingStation).GetComponent<ZNetView>();
					obj = ((component != null) ? component.GetZDO() : null);
				}
				ZDO val2 = (ZDO)obj;
				bool stationCheated = stationCheatedKey.HasValue && val2 != null && val2.GetBool(stationCheatedKey.Value, false);
				compatibility.SetItemMetadata(val, _player.NoCostCheat(), resourceCheated, stationCheated);
			}
			return val;
		}

		public void Spend(string resource, int amount)
		{
			_inventory.RemoveItem(ResourceName(resource), amount, -1, true);
		}
	}

	private GameObject _prefabRoot;

	private string _brushAnimation;

	private ObjectDB _registeredDb;

	private ZNetScene _registeredScene;

	private bool _registering;

	private Sprite _icon;

	private readonly List<Material> _materials = new List<Material>();

	private ConfigEntry<KeyboardShortcut> _cameraKey;

	private ConfigEntry<KeyboardShortcut> _cameraResetKey;

	private ConfigEntry<bool> _cameraController;

	private ConfigEntry<bool> _cameraHideBody;

	private ConfigEntry<bool> _cameraExitDamage;

	private ConfigEntry<float> _cameraSpeed;

	private ConfigEntry<float> _cameraFast;

	private ConfigEntry<float> _cameraRadius;

	private ConfigEntry<float> _cameraReach;

	private ConfigEntry<float> _cameraMultiplier;

	private ConfigEntry<float> _cameraPitch;

	private ConfigEntry<float> _cameraFov;

	private readonly CameraToggleInput _cameraToggle = new CameraToggleInput();

	private readonly Dictionary<Renderer, bool> _hiddenBody = new Dictionary<Renderer, bool>();

	private readonly List<Renderer> _bodyRenderers = new List<Renderer>(32);

	private readonly List<Renderer> _retiredBody = new List<Renderer>(8);

	private bool _paintCamera;

	private bool _cameraChord;

	private int _cameraFrame = -1;

	private float _cameraSuppressUntil;

	private float _nextBodyScan;

	private float _flightYaw;

	private float _flightPitch;

	private float _flightFov;

	private Player _cameraPlayer;

	private GameCamera _paintGameCamera;

	private Camera _paintLens;

	private Camera _paintSky;

	private Vector3 _bodyAnchor;

	private Vector3 _flightPosition;

	private Vector3 _previousCameraPosition;

	private Quaternion _flightRotation;

	private Quaternion _previousCameraRotation;

	private float _previousFov;

	private float _previousSkyFov;

	private float _previousNearClip;

	private BaseUnityPlugin _externalBuildCamera;

	private FieldInfo _externalCameraActive;

	private MethodInfo _externalCameraStop;

	private ConfigEntry<float> _soundVolume;

	private Hud _brushHud;

	private readonly AudioSource[] _brushSources = (AudioSource[])(object)new AudioSource[4];

	private AudioClip _brushSound;

	private AudioMixerGroup _brushMixer;

	private int _nextBrushSource;

	private static FieldInfo HotbarElements;

	private static FieldInfo HotbarElementEquipped;

	private static MethodInfo UpdateHotbarIcons;

	private BuildingStage _buildingStage;

	private BuildingConnectivity _buildingGraph;

	private readonly List<PaintedPiece> _buildingCandidates = new List<PaintedPiece>();

	private readonly List<PaintedPiece> _buildingNodes = new List<PaintedPiece>();

	private int _buildingScanIndex;

	private int _buildingSeedIndex;

	private int _buildingCollectIndex;

	private int _buildingProtected;

	private bool _buildingGraphStarted;

	private bool _buildingHasBounds;

	private readonly List<Bounds> _buildingNodeBounds = new List<Bounds>();

	private Piece _buildingSeed;

	private NaturalPaintLogic.PaintMaterial _buildingClearMaterial;

	private Vector3 _buildingSeedPosition;

	private int _buildingOutsideScope;

	private int _buildingInvalidBounds;

	private Bounds _buildingBounds;

	private Player _buildingPlayer;

	private Player _undoPlayer;

	private readonly List<BuildingRecord> _buildingSelection = new List<BuildingRecord>();

	private readonly List<BuildingRecord> _buildingUndo = new List<BuildingRecord>();

	private readonly Queue<BuildingRecord> _buildingQueue = new Queue<BuildingRecord>();

	private bool _buildingIsUndo;

	private bool _buildingStartedUndoRecord;

	private int _buildingChanged;

	private int _buildingSkipped;

	private int _buildingReviewFrame;

	private GameObject _buildingCanvas;

	private TMP_Text _buildingMessage;

	private TMP_Text _buildingTitle;

	private Button _buildingConfirm;

	private Button _buildingCancel;

	private Button _buildingEdit;

	private readonly HashSet<Piece> _buildingPreviewPieces = new HashSet<Piece>();

	private static readonly Color BuildingPreviewColour = new Color(0.12f, 0.65f, 0.28f, 1f);

	private bool _buildingMultiPaint;

	private int _multiFinish;

	private float _multiOpacity;

	private BrushTarget _hoverTarget;

	private readonly Dictionary<Piece, PaintedPiece> _painted = new Dictionary<Piece, PaintedPiece>();

	private readonly Dictionary<Renderer, Surface> _surfaces = new Dictionary<Renderer, Surface>();

	private readonly List<Piece> _deadPieces = new List<Piece>();

	private Piece _previewPiece;

	private int _previewFinish;

	private float _previewOpacity = -1f;

	private float _nextPaintSync;

	private ConfigEntry<KeyboardShortcut> _paletteKey;

	private ConfigEntry<int> _selectedId;

	private ConfigEntry<float> _selectedOpacity;

	private GameObject _paletteCanvas;

	private readonly Button[] _paletteButtons = (Button[])(object)new Button[21];

	private readonly Image[] _swatchImages = (Image[])(object)new Image[15];

	private readonly TMP_Text[] _swatchLabels = (TMP_Text[])(object)new TMP_Text[15];

	private TMP_Text _pageTitle;

	private TMP_Text _pageHint;

	private TMP_Text _opacityLabel;

	private TMP_Text _colourSetLabel;

	private GameObject _colourSetControls;

	private int _colourSet;

	private GameObject _opacityControls;

	private Slider _opacitySlider;

	private float _paletteOpacity = -1f;

	private readonly PaletteReleaseGuard _releaseGuard = new PaletteReleaseGuard();

	private static readonly string[] PaletteButtonsHeld = new string[13]
	{
		"JoyButtonA", "JoyButtonB", "JoyButtonX", "JoyButtonY", "JoyBuildMenu", "JoyDPadLeft", "JoyDPadRight", "JoyDPadUp", "JoyDPadDown", "JoyLBumper",
		"JoyRBumper", "JoyLTrigger", "JoyRTrigger"
	};

	private TMP_Text _paletteCaption;

	private Piece _paletteTarget;

	private int _focusSlot;

	private int _pageIndex;

	private int _lastPageIndex;

	private int _openedFrame;

	private bool _explicitPaletteToolSwitch;

	public const string PluginGuid = "valheim.paintyourbuild";

	public const string PluginName = "Paint Your Build";

	public const string PluginVersion = "1.6.15";

	internal const string BrushPrefabName = "PaintYourBuild_Paintbrush";

	internal const int LegacyBrushHash = -1067267138;

	internal static readonly int PaintHash = -945813667;

	internal static readonly int CrystalOpacityHash = StringExtensionMethods.GetStableHashCode("PaintYourBuild.CrystalOpacity");

	internal static NaturalPaintPlugin Instance;

	internal static GameObject BrushPrefab;

	internal static Recipe BrushRecipe;

	internal static bool Ready;

	private Harmony _harmony;

	private float _nextMaintenance;

	private float _nextUse;

	private int _lastCraftFrame = -1;

	private Recipe _lastSelected;

	private ObjectDB _selfTestDb;

	private bool _selfTestPassed;

	private ConfigEntry<KeyboardShortcut> _diagnosticKey;

	private CraftCompatibility _craftCompatibility;

	private readonly HashSet<string> _reportedErrors = new HashSet<string>();

	internal static FieldInfo SelectedRecipeField;

	internal static PropertyInfo SelectedRecipeProperty;

	internal static PropertyInfo SelectedItemProperty;

	internal static MethodInfo NativeCraftComplete;

	internal static MethodInfo RefreshPanel;

	internal static MethodInfo NotifyInventory;

	private static MethodInfo AddKnownRecipe;

	private static FieldInfo DbByHash;

	private static FieldInfo DbByData;

	private static FieldInfo SceneByHash;

	private static FieldInfo RemoveRayMask;

	private static FieldInfo SnappingIcon;

	internal const int ExpectedPatchCount = 42;

	private Queue<PaintedPiece> _syncQueue;

	private int _syncFrame;

	private int _syncChecked;

	private int _syncChanged;

	private double _syncMs;

	private double _syncMax;

	private float _syncNextReport;

	internal bool PaintCameraActive
	{
		get
		{
			if (Ready)
			{
				return _paintCamera;
			}
			return false;
		}
	}

	internal bool CameraInputSuppressed
	{
		get
		{
			if (!_cameraChord)
			{
				return Time.unscaledTime < _cameraSuppressUntil;
			}
			return true;
		}
	}

	internal bool CameraOwnsControls
	{
		get
		{
			if (!PaintCameraActive)
			{
				return CameraInputSuppressed;
			}
			return true;
		}
	}

	internal float CameraPaintReach => CameraMath.FreeCameraRange(_cameraReach.Value, 5f, _cameraMultiplier.Value);

	private float CameraRadius => CameraMath.FreeCameraRange(_cameraRadius.Value, 8f, _cameraMultiplier.Value);

	private bool BuildingClearActive => _buildingStage != BuildingStage.None;

	private bool BuildingModalOpen
	{
		get
		{
			if (_buildingStage != BuildingStage.Detecting && _buildingStage != BuildingStage.Review)
			{
				return _buildingStage == BuildingStage.Running;
			}
			return true;
		}
	}

	private string BuildingSelectionHint
	{
		get
		{
			if (_buildingStage != BuildingStage.Selecting)
			{
				return "Detecting house…";
			}
			return "Multi-paint · " + Palette.Get(_multiFinish).Name + " · " + _buildingSelection.Count + " selected\nClick / attack: select or deselect\nRight-click / Enter / Y: review · P / Esc / B: cancel";
		}
	}

	internal static bool PaletteOpen
	{
		get
		{
			if (Object.op_Implicit((Object)(object)Instance) && Object.op_Implicit((Object)(object)Instance._paletteCanvas))
			{
				return Instance._paletteCanvas.activeSelf;
			}
			return false;
		}
	}

	internal static bool OverlayBlocksInput
	{
		get
		{
			if (Ready && Object.op_Implicit((Object)(object)Instance))
			{
				if (!PaletteOpen && !Instance.BuildingModalOpen)
				{
					return Instance._releaseGuard.Blocked;
				}
				return true;
			}
			return false;
		}
	}

	internal static bool PaintModalOpen
	{
		get
		{
			if (!PaletteOpen)
			{
				if (Object.op_Implicit((Object)(object)Instance))
				{
					return Instance.BuildingModalOpen;
				}
				return false;
			}
			return true;
		}
	}

	internal static bool ExplicitPaletteToolSwitch
	{
		get
		{
			if (Object.op_Implicit((Object)(object)Instance))
			{
				return Instance._explicitPaletteToolSwitch;
			}
			return false;
		}
	}

	private Finish SelectedFinish => Palette.Get(_selectedId.Value);

	internal static void SafeRegister(ObjectDB db)
	{
		if (!Ready || !Object.op_Implicit((Object)(object)Instance) || !Object.op_Implicit((Object)(object)db))
		{
			return;
		}
		try
		{
			Instance.Register(db);
		}
		catch (Exception ex)
		{
			Instance.ReportOnce("registration", ex);
		}
	}

	private void Register(ObjectDB db)
	{
		//IL_02a8: 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_02b9: Unknown result type (might be due to invalid IL or missing references)
		//IL_02c0: Unknown result type (might be due to invalid IL or missing references)
		//IL_02c7: Unknown result type (might be due to invalid IL or missing references)
		//IL_02cf: Expected O, but got Unknown
		if (_registering || ((Object)(object)_registeredDb == (Object)(object)db && (Object)(object)_registeredScene == (Object)(object)ZNetScene.instance && Object.op_Implicit((Object)(object)BrushPrefab) && Object.op_Implicit((Object)(object)BrushRecipe) && (Object)(object)db.GetItemPrefab("PaintYourBuild_Paintbrush") == (Object)(object)BrushPrefab && db.m_recipes.Contains(BrushRecipe)))
		{
			return;
		}
		_registering = true;
		try
		{
			GameObject itemPrefab = db.GetItemPrefab("Hammer");
			GameObject itemPrefab2 = db.GetItemPrefab("Wood");
			if (!Object.op_Implicit((Object)(object)itemPrefab) || !Object.op_Implicit((Object)(object)itemPrefab2))
			{
				return;
			}
			if (!Object.op_Implicit((Object)(object)BrushPrefab))
			{
				CreatePrefab(itemPrefab);
			}
			int stableHashCode = StringExtensionMethods.GetStableHashCode("PaintYourBuild_Paintbrush");
			Dictionary<int, GameObject> obj = (Dictionary<int, GameObject>)DbByHash.GetValue(db);
			if (obj.TryGetValue(stableHashCode, out var value) && Object.op_Implicit((Object)(object)value) && (Object)(object)value != (Object)(object)BrushPrefab)
			{
				throw new InvalidOperationException("Brush hash already belongs to " + ((Object)value).name + ". Remove duplicate mod copies.");
			}
			if (!db.m_items.Contains(BrushPrefab))
			{
				db.m_items.Add(BrushPrefab);
			}
			obj[stableHashCode] = BrushPrefab;
			obj[-1067267138] = BrushPrefab;
			ItemDrop component = BrushPrefab.GetComponent<ItemDrop>();
			((Dictionary<SharedData, GameObject>)DbByData.GetValue(db))[component.m_itemData.m_shared] = BrushPrefab;
			ZNetScene instance = ZNetScene.instance;
			if (Object.op_Implicit((Object)(object)instance))
			{
				Dictionary<int, GameObject> obj2 = (Dictionary<int, GameObject>)SceneByHash.GetValue(instance);
				if (obj2.TryGetValue(stableHashCode, out value) && Object.op_Implicit((Object)(object)value) && (Object)(object)value != (Object)(object)BrushPrefab)
				{
					throw new InvalidOperationException("Duplicate brush network prefab");
				}
				if (!instance.m_prefabs.Contains(BrushPrefab))
				{
					instance.m_prefabs.Add(BrushPrefab);
				}
				obj2[stableHashCode] = BrushPrefab;
				obj2[-1067267138] = BrushPrefab;
			}
			CraftingStation val = FindWorkbench(db, itemPrefab, instance);
			if (Object.op_Implicit((Object)(object)val))
			{
				if (!Object.op_Implicit((Object)(object)BrushRecipe))
				{
					BrushRecipe = ScriptableObject.CreateInstance<Recipe>();
					((Object)BrushRecipe).name = "Recipe_Paintbrush";
				}
				BrushRecipe.m_item = component;
				BrushRecipe.m_amount = 1;
				BrushRecipe.m_enabled = true;
				BrushRecipe.m_noCraftOnlyUpgrade = false;
				BrushRecipe.m_minStationLevel = 1;
				BrushRecipe.m_craftingStation = val;
				BrushRecipe.m_repairStation = val;
				BrushRecipe.m_requireOnlyOneIngredient = false;
				BrushRecipe.m_resources = (Requirement[])(object)new Requirement[1]
				{
					new Requirement
					{
						m_resItem = itemPrefab2.GetComponent<ItemDrop>(),
						m_amount = 2,
						m_amountPerLevel = 0,
						m_recover = true
					}
				};
				db.m_recipes.RemoveAll((Recipe r) => Object.op_Implicit((Object)(object)r) && (Object)(object)r != (Object)(object)BrushRecipe && IsBrushRecipe(r));
				if (!db.m_recipes.Contains(BrushRecipe))
				{
					db.m_recipes.Add(BrushRecipe);
				}
				_registeredDb = db;
				_registeredScene = instance;
				((BaseUnityPlugin)this).Logger.LogInfo((object)("[register.ok] prefab=PaintYourBuild_Paintbrush; hash=" + stableHashCode + "; activeSelf=" + BrushPrefab.activeSelf + "; activeInHierarchy=" + BrushPrefab.activeInHierarchy + "; station=" + val.m_name + "; cost=2 Wood; network=" + ((Object)(object)instance != (Object)null)));
			}
		}
		finally
		{
			_registering = false;
		}
	}

	private static CraftingStation FindWorkbench(ObjectDB db, GameObject hammer, ZNetScene scene)
	{
		GameObject val = (Object.op_Implicit((Object)(object)scene) ? scene.GetPrefab("piece_workbench") : null);
		if (Object.op_Implicit((Object)(object)val))
		{
			return val.GetComponent<CraftingStation>();
		}
		PieceTable buildPieces = hammer.GetComponent<ItemDrop>().m_itemData.m_shared.m_buildPieces;
		if (Object.op_Implicit((Object)(object)buildPieces))
		{
			foreach (GameObject piece in buildPieces.m_pieces)
			{
				if (Object.op_Implicit((Object)(object)piece) && ((Object)piece).name == "piece_workbench")
				{
					return piece.GetComponent<CraftingStation>();
				}
			}
		}
		foreach (Recipe recipe in db.m_recipes)
		{
			if (Object.op_Implicit((Object)(object)recipe) && Object.op_Implicit((Object)(object)recipe.m_craftingStation) && recipe.m_craftingStation.m_name == "$piece_workbench")
			{
				return recipe.m_craftingStation;
			}
		}
		return null;
	}

	private void CreatePrefab(GameObject hammer)
	{
		//IL_0013: Unknown result type (might be due to invalid IL or missing references)
		//IL_001d: Expected O, but got Unknown
		//IL_0094: Unknown result type (might be due to invalid IL or missing references)
		//IL_009e: Expected O, but got Unknown
		//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ab: Expected O, but got Unknown
		//IL_00c7: Unknown result type (might be due to invalid IL or missing references)
		//IL_00cf: 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_0144: Expected O, but got Unknown
		//IL_0146: Unknown result type (might be due to invalid IL or missing references)
		//IL_0150: Expected O, but got Unknown
		//IL_0290: Unknown result type (might be due to invalid IL or missing references)
		//IL_02b2: Unknown result type (might be due to invalid IL or missing references)
		//IL_02d4: Unknown result type (might be due to invalid IL or missing references)
		//IL_02fa: Unknown result type (might be due to invalid IL or missing references)
		//IL_030e: Unknown result type (might be due to invalid IL or missing references)
		//IL_032f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0343: Unknown result type (might be due to invalid IL or missing references)
		//IL_039c: Unknown result type (might be due to invalid IL or missing references)
		//IL_03bc: Unknown result type (might be due to invalid IL or missing references)
		if (!Object.op_Implicit((Object)(object)_prefabRoot))
		{
			_prefabRoot = new GameObject("NaturalPaint_PrefabRoot");
			_prefabRoot.SetActive(false);
			Object.DontDestroyOnLoad((Object)(object)_prefabRoot);
		}
		GameObject val = Object.Instantiate<GameObject>(hammer, _prefabRoot.transform, false);
		try
		{
			((Object)val).name = "PaintYourBuild_Paintbrush";
			val.SetActive(true);
			ItemDrop component = val.GetComponent<ItemDrop>();
			if (!Object.op_Implicit((Object)(object)component))
			{
				throw new InvalidOperationException("Hammer has no ItemDrop");
			}
			_brushAnimation = hammer.GetComponent<ItemDrop>().m_itemData.m_shared.m_attack.m_attackAnimation;
			component.m_itemData = new ItemData();
			ItemData itemData = component.m_itemData;
			SharedData val2 = new SharedData();
			val2.m_name = "Paintbrush";
			val2.m_description = "Paint wooden and stone building pieces in natural shades. Right-click or P opens the palette; left-click paints. Includes a live preview and Restore original.";
			val2.m_itemType = (ItemType)19;
			val2.m_animationState = (AnimationState)1;
			val2.m_maxStackSize = 1;
			val2.m_maxQuality = 1;
			val2.m_weight = 0.5f;
			val2.m_useDurability = false;
			val2.m_maxDurability = 100f;
			val2.m_equipDuration = 0.25f;
			val2.m_buildPieces = null;
			val2.m_dlc = "";
			val2.m_attackForce = 0f;
			val2.m_variants = 1;
			val2.m_attack = new Attack();
			val2.m_secondaryAttack = new Attack();
			val2.m_icons = (Sprite[])(Application.isBatchMode ? ((Array)hammer.GetComponent<ItemDrop>().m_itemData.m_shared.m_icons) : ((Array)new Sprite[1] { LoadIcon() }));
			itemData.m_shared = val2;
			component.m_itemData.m_dropPrefab = val;
			component.m_itemData.m_quality = 1;
			component.m_itemData.m_stack = 1;
			component.m_itemData.m_durability = 100f;
			if (Application.isBatchMode)
			{
				BrushPrefab = val;
				return;
			}
			Transform val3 = val.transform.Find("attach");
			if (!Object.op_Implicit((Object)(object)val3))
			{
				throw new InvalidOperationException("Hammer attach transform missing");
			}
			Material val4 = (from r in ((Component)val3).GetComponentsInChildren<Renderer>(true)
				select r.sharedMaterial).FirstOrDefault((Func<Material, bool>)((Material m) => Object.op_Implicit((Object)(object)m) && Object.op_Implicit((Object)(object)m.shader)));
			if (!Object.op_Implicit((Object)(object)val4))
			{
				throw new InvalidOperationException("No supported material on Hammer");
			}
			Renderer[] componentsInChildren = ((Component)val3).GetComponentsInChildren<Renderer>(true);
			for (int num = 0; num < componentsInChildren.Length; num++)
			{
				componentsInChildren[num].enabled = false;
			}
			Material material = ColoredMaterial(val4, new Color(0.6f, 0.4f, 0.2f), "BrushWood");
			Material material2 = ColoredMaterial(val4, new Color(0.4f, 0.26f, 0.13f), "BrushBinding");
			Material material3 = ColoredMaterial(val4, new Color(0.68f, 0.55f, 0.36f), "BrushBristles");
			MakePart(val3, "Handle", new Vector3(0f, 0.1f, 0f), new Vector3(0.035f, 0.17f, 0.035f), material);
			MakePart(val3, "Binding", new Vector3(0f, 0.285f, 0f), new Vector3(0.067f, 0.04f, 0.067f), material2);
			for (int num2 = 0; num2 < 7; num2++)
			{
				float num3 = (float)num2 * (float)Math.PI * 2f / 7f;
				MakePart(val3, "Bristle" + num2, new Vector3(Mathf.Cos(num3) * 0.023f, 0.355f, Mathf.Sin(num3) * 0.023f), new Vector3(0.019f, 0.055f + (float)(num2 % 3) * 0.003f, 0.019f), material3);
			}
			BrushPrefab = val;
		}
		catch
		{
			Object.Destroy((Object)(object)val);
			throw;
		}
	}

	private Material ColoredMaterial(Material source, Color color, string name)
	{
		//IL_0001: Unknown result type (might be due to invalid IL or missing references)
		//IL_0006: Unknown result type (might be due to invalid IL or missing references)
		//IL_000e: Expected O, but got Unknown
		//IL_003e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0057: Unknown result type (might be due to invalid IL or missing references)
		Material val = new Material(source)
		{
			name = name
		};
		if (val.HasProperty("_MainTex"))
		{
			val.SetTexture("_MainTex", (Texture)(object)Texture2D.whiteTexture);
		}
		if (val.HasProperty("_Color"))
		{
			val.SetColor("_Color", color);
		}
		if (val.HasProperty("_BaseColor"))
		{
			val.SetColor("_BaseColor", color);
		}
		_materials.Add(val);
		return val;
	}

	private static void MakePart(Transform parent, string name, Vector3 position, Vector3 scale, Material material)
	{
		//IL_0020: Unknown result type (might be due to invalid IL or missing references)
		//IL_002c: Unknown result type (might be due to invalid IL or missing references)
		GameObject obj = GameObject.CreatePrimitive((PrimitiveType)2);
		((Object)obj).name = name;
		obj.transform.SetParent(parent, false);
		obj.transform.localPosition = position;
		obj.transform.localScale = scale;
		Collider component = obj.GetComponent<Collider>();
		component.enabled = false;
		Object.Destroy((Object)(object)component);
		obj.GetComponent<Renderer>().sharedMaterial = material;
		obj.layer = ((Component)parent).gameObject.layer;
	}

	private Sprite LoadIcon()
	{
		//IL_004d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0053: Expected O, but got Unknown
		//IL_0098: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a7: Unknown result type (might be due to invalid IL or missing references)
		if (Object.op_Implicit((Object)(object)_icon))
		{
			return _icon;
		}
		using Stream stream = typeof(NaturalPaintPlugin).Assembly.GetManifestResourceStream("PaintYourBuild.NaturalPaintIcon.png");
		if (stream == null)
		{
			throw new InvalidOperationException("Embedded brush icon missing");
		}
		using MemoryStream memoryStream = new MemoryStream();
		stream.CopyTo(memoryStream);
		Texture2D val = new Texture2D(2, 2, (TextureFormat)4, false);
		if (!ImageConversion.LoadImage(val, memoryStream.ToArray(), true))
		{
			Object.Destroy((Object)(object)val);
			throw new InvalidOperationException("Invalid brush PNG");
		}
		((Object)val).name = "NaturalPaintIcon";
		_icon = Sprite.Create(val, new Rect(0f, 0f, (float)((Texture)val).width, (float)((Texture)val).height), new Vector2(0.5f, 0.5f), 128f);
		return _icon;
	}

	private void ReleaseAssets()
	{
		foreach (Material material in _materials)
		{
			if (Object.op_Implicit((Object)(object)material))
			{
				Object.Destroy((Object)(object)material);
			}
		}
		if (Object.op_Implicit((Object)(object)_icon))
		{
			Object.Destroy((Object)(object)_icon.texture);
			Object.Destroy((Object)(object)_icon);
		}
		if (Object.op_Implicit((Object)(object)_prefabRoot))
		{
			Object.Destroy((Object)(object)_prefabRoot);
		}
		if (Object.op_Implicit((Object)(object)BrushRecipe))
		{
			Object.Destroy((Object)(object)BrushRecipe);
		}
		BrushRecipe = null;
		BrushPrefab = null;
	}

	private void BindCameraConfig()
	{
		//IL_001b: 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)
		_cameraKey = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("Camera controls", "ToggleKey", new KeyboardShortcut((KeyCode)287, Array.Empty<KeyCode>()), "Enter/leave painting free camera; only while Paintbrush is equipped.");
		_cameraResetKey = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("Camera controls", "ResetViewKey", new KeyboardShortcut((KeyCode)288, Array.Empty<KeyCode>()), "Reset the active painting camera.");
		_cameraController = ((BaseUnityPlugin)this).Config.Bind<bool>("Camera controls", "ControllerStickToggle", true, "Press both stick buttons together to toggle. Release both before toggling again.");
		_cameraHideBody = ((BaseUnityPlugin)this).Config.Bind<bool>("Camera view", "HideLocalPlayer", true, "Hide the local character on this client during painting camera. Your body remains in the world.");
		_cameraExitDamage = ((BaseUnityPlugin)this).Config.Bind<bool>("Camera safety", "ExitOnDamage", true, "Return to the normal camera when your body takes damage.");
		_cameraSpeed = CameraFloat("MoveSpeed", 6f, 1f, 15f, "Flight speed in metres per second.");
		_cameraFast = CameraFloat("FastMultiplier", 2f, 1f, 3f, "Hold Run to fly faster.");
		_cameraRadius = CameraFloat("CameraRadius", 25f, 8f, 25f, "Base radius around your body. Multiplied by FreeCameraRangeMultiplier.");
		_cameraReach = CameraFloat("PaintReach", 25f, 5f, 25f, "Base painting reach from your body, only in painting free camera. Normal reach is unchanged.");
		_cameraMultiplier = CameraFloat("FreeCameraRangeMultiplier", 2f, 1f, 2f, "Default camera radius and painting reach: 25 x 2 = 50 metres. Matches Build Camera 0.1.13.");
		ConfigEntry<bool> val = ((BaseUnityPlugin)this).Config.Bind<bool>("Painting camera", "Range50mUpgradeApplied", false, "Tracks the one-time 0.1.4 upgrade to a 50-metre camera radius and painting reach.");
		if (!val.Value)
		{
			bool saveOnConfigSet = ((BaseUnityPlugin)this).Config.SaveOnConfigSet;
			((BaseUnityPlugin)this).Config.SaveOnConfigSet = false;
			try
			{
				_cameraRadius.Value = 25f;
				_cameraReach.Value = 25f;
				_cameraMultiplier.Value = 2f;
				val.Value = true;
				((BaseUnityPlugin)this).Config.Save();
			}
			finally
			{
				((BaseUnityPlugin)this).Config.SaveOnConfigSet = saveOnConfigSet;
			}
			((BaseUnityPlugin)this).Logger.LogInfo((object)"[paint.camera] Range upgraded: camera radius=50m; paint reach=50m.");
		}
		_cameraPitch = CameraFloat("StartingPitch", 50f, 20f, 75f, "Initial downward viewing angle.");
		_cameraFov = CameraFloat("FieldOfView", 45f, 25f, 75f, "Initial FOV. Page Up/Down zoom outside the palette.");
	}

	private ConfigEntry<float> CameraFloat(string key, float value, float min, float max, string description)
	{
		//IL_001c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0026: Expected O, but got Unknown
		return ((BaseUnityPlugin)this).Config.Bind<float>("Painting camera", key, value, new ConfigDescription(description, (AcceptableValueBase)(object)new AcceptableValueRange<float>(min, max), Array.Empty<object>()));
	}

	internal bool EnsureBrushSession()
	{
		if (Application.isBatchMode)
		{
			return false;
		}
		Player localPlayer = Player.m_localPlayer;
		bool flag = ((Behaviour)this).isActiveAndEnabled && BrushSessionRules.CanStay(BrushSelected(localPlayer), Object.op_Implicit((Object)(object)localPlayer) && !((Character)localPlayer).IsDead(), Object.op_Implicit((Object)(object)localPlayer) && (((Character)localPlayer).IsTeleporting() || ((Character)localPlayer).InCutscene() || ((Character)localPlayer).IsAttached() || localPlayer.GetDoodadController() != null));
		if (flag && _paintCamera)
		{
			flag = (Object)(object)localPlayer == (Object)(object)_cameraPlayer && Object.op_Implicit((Object)(object)_paintGameCamera) && (Object)(object)_paintGameCamera == (Object)(object)GameCamera.instance && Object.op_Implicit((Object)(object)_paintLens);
		}
		if (flag)
		{
			return true;
		}
		ClosePalette();
		StopBrushCamera("Brush unequipped or player/camera changed");
		ClearHover();
		_cameraToggle.Reset(waitForRelease: true);
		_cameraChord = false;
		_cameraSuppressUntil = 0f;
		return false;
	}

	internal void TickBrushCamera()
	{
		//IL_00d1: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d6: Unknown result type (might be due to invalid IL or missing references)
		//IL_0156: Unknown result type (might be due to invalid IL or missing references)
		//IL_015b: Unknown result type (might be due to invalid IL or missing references)
		TickClearBuildingView();
		if (!Ready || Application.isBatchMode || _cameraFrame == Time.frameCount)
		{
			return;
		}
		_cameraFrame = Time.frameCount;
		try
		{
			if (!EnsureBrushSession())
			{
				return;
			}
			ReleaseExternalBuildCamera();
			if (ZInput.instance == null || !Application.isFocused)
			{
				return;
			}
			bool flag = ZInput.GetButton("JoyLStick");
			bool flag2 = ZInput.GetButton("JoyRStick");
			Gamepad current = Gamepad.current;
			if (current != null)
			{
				flag |= current.leftStickButton.isPressed;
				flag2 |= current.rightStickButton.isPressed;
			}
			bool flag3 = CameraModal() || OverlayBlocksInput;
			bool flag4 = _cameraController.Value && !flag3;
			_cameraChord = flag4 && flag && flag2;
			CameraToggleInput cameraToggle = _cameraToggle;
			int frameCount = Time.frameCount;
			KeyboardShortcut value = _cameraKey.Value;
			if (cameraToggle.Step(frameCount, ((KeyboardShortcut)(ref value)).IsDown(), !flag3, flag, flag2, flag4, Time.unscaledDeltaTime))
			{
				if (_paintCamera)
				{
					StopBrushCamera("Toggled off");
				}
				else
				{
					StartBrushCamera(Player.m_localPlayer);
				}
			}
			else
			{
				if (!_paintCamera)
				{
					return;
				}
				if (!flag3 && !BuildingClearActive && ZInput.GetKeyDown((KeyCode)27, true))
				{
					StopBrushCamera("Escape");
					return;
				}
				KeepPaintBodyStill();
				if (!flag3)
				{
					value = _cameraResetKey.Value;
					if (((KeyboardShortcut)(ref value)).IsDown())
					{
						ResetPaintCamera();
					}
				}
				if (!flag3 && !CameraInputSuppressed)
				{
					MovePaintCamera();
				}
				ApplyPaintCameraPose();
			}
		}
		catch (Exception ex)
		{
			StopBrushCamera("Camera error");
			ClosePalette();
			ReportOnce("paint.camera", ex);
		}
	}

	private static bool CameraModal()
	{
		try
		{
			return !CanUseTool(Player.m_localPlayer) || UnifiedPopup.IsVisible() || (Object.op_Implicit((Object)(object)TextViewer.instance) && TextViewer.instance.IsVisible()) || PlayerCustomizaton.IsBarberGuiVisible() || (Object.op_Implicit((Object)(object)ZNet.instance) && Object.op_Implicit((Object)(object)Player.m_localPlayer) && Game.IsPaused());
		}
		catch (NullReferenceException)
		{
			return true;
		}
	}

	private void StartBrushCamera(Player player)
	{
		//IL_0093: Unknown result type (might be due to invalid IL or missing references)
		//IL_0098: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a9: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ae: Unknown result type (might be due to invalid IL or missing references)
		//IL_00bf: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c4: Unknown result type (might be due to invalid IL or missing references)
		//IL_0126: Unknown result type (might be due to invalid IL or missing references)
		if (!BrushSelected(player) || CameraModal() || OverlayBlocksInput)
		{
			return;
		}
		if (!((Character)player).IsOnGround() || ((Character)player).IsSwimming())
		{
			Tell(player, "Stand on solid ground to enter the painting camera.");
			return;
		}
		_paintGameCamera = GameCamera.instance;
		_paintLens = (Object.op_Implicit((Object)(object)_paintGameCamera) ? ((Component)_paintGameCamera).GetComponent<Camera>() : null);
		if (Object.op_Implicit((Object)(object)_paintLens) && !GameCamera.InFreeFly())
		{
			_paintSky = _paintGameCamera.m_skyCamera;
			_cameraPlayer = player;
			_bodyAnchor = ((Component)player).transform.position;
			_previousCameraPosition = ((Component)_paintGameCamera).transform.position;
			_previousCameraRotation = ((Component)_paintGameCamera).transform.rotation;
			_previousFov = _paintLens.fieldOfView;
			_previousNearClip = _paintLens.nearClipPlane;
			_previousSkyFov = (Object.op_Implicit((Object)(object)_paintSky) ? _paintSky.fieldOfView : _previousFov);
			_paintCamera = true;
			player.m_autoRun = false;
			((Character)player).StopMovement();
			player.SetControls(Vector3.zero, false, false, false, false, false, false, false, false, false, false, false);
			((Character)player).m_onDamaged = (Action<float, Character>)Delegate.Combine(((Character)player).m_onDamaged, new Action<float, Character>(PaintCameraDamaged));
			ResetPaintCamera();
			HidePaintBody();
			_cameraSuppressUntil = Time.unscaledTime + 0.2f;
			ApplyPaintCameraPose();
			((BaseUnityPlugin)this).Logger.LogInfo((object)("[paint.camera] ON; radius=" + CameraRadius + "; paint reach=" + CameraPaintReach));
		}
	}

	private void ResetPaintCamera()
	{
		//IL_000c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0011: Unknown result type (might be due to invalid IL or missing references)
		//IL_0014: Unknown result type (might be due to invalid IL or missing references)
		//IL_007f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0084: Unknown result type (might be due to invalid IL or missing references)
		//IL_008a: Unknown result type (might be due to invalid IL or missing references)
		//IL_009f: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a9: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b3: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b8: Unknown result type (might be due to invalid IL or missing references)
		//IL_00bd: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c2: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c7: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c9: 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_00d5: 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_00e4: Unknown result type (might be due to invalid IL or missing references)
		//IL_00e9: Unknown result type (might be due to invalid IL or missing references)
		Quaternion rotation = ((Character)_cameraPlayer).m_eye.rotation;
		_flightYaw = ((Quaternion)(ref rotation)).eulerAngles.y;
		_flightPitch = CameraMath.Clamp(_cameraPitch.Value, 50f, 20f, 75f);
		_flightFov = CameraMath.Clamp(_cameraFov.Value, 45f, 25f, 75f);
		_flightRotation = Quaternion.Euler(_flightPitch, _flightYaw, 0f);
		Vector3 val = _bodyAnchor + Quaternion.Euler(0f, _flightYaw, 0f) * Vector3.forward * 5f + Vector3.up;
		_flightPosition = val - _flightRotation * Vector3.forward * 12f;
		ClampPaintCamera();
	}

	private void MovePaintCamera()
	{
		//IL_001a: Unknown result type (might be due to invalid IL or missing references)
		//IL_001f: 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_00cf: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ec: Unknown result type (might be due to invalid IL or missing references)
		//IL_007a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0084: Unknown result type (might be due to invalid IL or missing references)
		//IL_008a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0094: Unknown result type (might be due to invalid IL or missing references)
		//IL_0099: Unknown result type (might be due to invalid IL or missing references)
		//IL_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_0176: Unknown result type (might be due to invalid IL or missing references)
		//IL_017b: Unknown result type (might be due to invalid IL or missing references)
		//IL_02a7: Unknown result type (might be due to invalid IL or missing references)
		//IL_02c1: Unknown result type (might be due to invalid IL or missing references)
		//IL_02c8: Unknown result type (might be due to invalid IL or missing references)
		//IL_02ce: Unknown result type (might be due to invalid IL or missing references)
		//IL_02d3: Unknown result type (might be due to invalid IL or missing references)
		//IL_02d8: Unknown result type (might be due to invalid IL or missing references)
		float num = CameraMath.Clamp(Time.unscaledDeltaTime, 0f, 0f, 0.05f);
		Vector2 val = Vector2.zero;
		if (ZInput.IsMouseActive())
		{
			val = ZInput.GetMouseDelta() * (ZInput.IsGamepadMouseActive() ? PlayerController.m_switchMouseSens : PlayerController.m_mouseSens);
			if (PlayerController.m_invertMouse)
			{
				val.y *= -1f;
			}
		}
		else if (ZInput.IsGamepadActive())
		{
			val = new Vector2(StickInput.RightStickX(), 0f - StickInput.RightStickY()) * 110f * num * PlayerController.m_gamepadSens;
			if (PlayerController.m_invertCameraX)
			{
				val.x *= -1f;
			}
			if (PlayerController.m_invertCameraY)
			{
				val.y *= -1f;
			}
		}
		_flightYaw = Mathf.Repeat(_flightYaw + val.x, 360f);
		_flightPitch = Mathf.Clamp(_flightPitch - val.y, -80f, 85f);
		if (ZInput.GetKey((KeyCode)280, true))
		{
			_flightFov -= 25f * num;
		}
		if (ZInput.GetKey((KeyCode)281, true))
		{
			_flightFov += 25f * num;
		}
		_flightFov = Mathf.Clamp(_flightFov, 25f, 75f);
		_flightRotation = Quaternion.Euler(_flightPitch, _flightYaw, 0f);
		float x = (float)((ZInput.GetButton("Right") ? 1 : 0) - (ZInput.GetButton("Left") ? 1 : 0)) + StickInput.LeftStickX();
		float z = (float)((ZInput.GetButton("Forward") ? 1 : 0) - (ZInput.GetButton("Backward") ? 1 : 0)) - StickInput.LeftStickY();
		float y = ((ZInput.GetButton("Jump") || ZInput.GetButton("JoyJump")) ? 1 : 0) - ((ZInput.GetButton("Crouch") || ZInput.GetButton("JoyCrouch")) ? 1 : 0);
		CameraMath.NormalizeInput(ref x, ref y, ref z);
		MovementVector movementVector = CameraMath.ViewRelativeMovement(_flightYaw, _flightPitch, x, y, z);
		CameraMath.NormalizeInput(ref movementVector.X, ref movementVector.Y, ref movementVector.Z);
		float num2 = CameraMath.Clamp(_cameraSpeed.Value, 6f, 1f, 15f);
		if (ZInput.GetButton("Run") || ZInput.GetButton("JoyRun"))
		{
			num2 *= CameraMath.Clamp(_cameraFast.Value, 2f, 1f, 3f);
		}
		_flightPosition += new Vector3(movementVector.X, movementVector.Y, movementVector.Z) * num2 * num;
		ClampPaintCamera();
	}

	private void ClampPaintCamera()
	{
		//IL_0001: Unknown result type (might be due to invalid IL or missing references)
		//IL_0006: Unknown result type (might be due to invalid IL or missing references)
		//IL_000b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0010: Unknown result type (might be due to invalid IL or missing references)
		//IL_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)
		//IL_001d: Unknown result type (might be due to invalid IL or missing references)
		//IL_003f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0040: Unknown result type (might be due to invalid IL or missing references)
		//IL_0041: Unknown result type (might be due to invalid IL or missing references)
		//IL_0046: Unknown result type (might be due to invalid IL or missing references)
		Vector3 val = _bodyAnchor + Vector3.up;
		Vector3 val2 = _flightPosition - val;
		CameraMath.ClampToSphere(ref val2.x, ref val2.y, ref val2.z, CameraRadius);
		_flightPosition = val + val2;
	}

	private void KeepPaintBodyStill()
	{
		//IL_0019: Unknown result type (might be due to invalid IL or missing references)
		//IL_001f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0024: Unknown result type (might be due to invalid IL or missing references)
		//IL_0029: 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.op_Implicit((Object)(object)_cameraPlayer))
		{
			Vector3 val = ((Component)_cameraPlayer).transform.position - _bodyAnchor;
			if (((Vector3)(ref val)).sqrMagnitude > 0.0001f)
			{
				((Component)_cameraPlayer).transform.position = _bodyAnchor;
				Physics.SyncTransforms();
			}
			((Character)_cameraPlayer).StopMovement();
		}
	}

	internal void ApplyPaintCameraPose()
	{
		//IL_0030: Unknown result type (might be due to invalid IL or missing references)
		//IL_0035: Unknown result type (might be due to invalid IL or missing references)
		//IL_0039: 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)
		//IL_0063: Unknown result type (might be due to invalid IL or missing references)
		//IL_0046: Unknown result type (might be due to invalid IL or missing references)
		//IL_004b: Unknown result type (might be due to invalid IL or missing references)
		//IL_004f: Unknown result type (might be due to invalid IL or missing references)
		if (!_paintCamera || !Object.op_Implicit((Object)(object)_paintLens) || !Object.op_Implicit((Object)(object)_paintGameCamera))
		{
			return;
		}
		Transform transform = ((Component)_paintGameCamera).transform;
		Vector3 position = transform.position;
		if (((Vector3)(ref position)).Equals(_flightPosition))
		{
			Quaternion rotation = transform.rotation;
			if (((Quaternion)(ref rotation)).Equals(_flightRotation))
			{
				goto IL_006d;
			}
		}
		transform.SetPositionAndRotation(_flightPosition, _flightRotation);
		goto IL_006d;
		IL_006d:
		if (_paintLens.fieldOfView != _flightFov)
		{
			_paintLens.fieldOfView = _flightFov;
		}
		float num = Mathf.Min(_previousNearClip, 0.1f);
		if (_paintLens.nearClipPlane != num)
		{
			_paintLens.nearClipPlane = num;
		}
		if (Object.op_Implicit((Object)(object)_paintSky) && _paintSky.fieldOfView != _flightFov)
		{
			_paintSky.fieldOfView = _flightFov;
		}
	}

	private void HidePaintBody()
	{
		if (!_cameraHideBody.Value)
		{
			RestorePaintBody();
			return;
		}
		GameObject val = (Object.op_Implicit((Object)(object)_cameraPlayer) ? ((Character)_cameraPlayer).GetVisual() : null);
		if (!Object.op_Implicit((Object)(object)val))
		{
			return;
		}
		_bodyRenderers.Clear();
		val.GetComponentsInChildren<Renderer>(true, _bodyRenderers);
		foreach (Renderer bodyRenderer in _bodyRenderers)
		{
			if (Object.op_Implicit((Object)(object)bodyRenderer))
			{
				if (!_hiddenBody.ContainsKey(bodyRenderer))
				{
					_hiddenBody.Add(bodyRenderer, bodyRenderer.forceRenderingOff);
				}
				bodyRenderer.forceRenderingOff = true;
			}
		}
		_retiredBody.Clear();
		foreach (KeyValuePair<Renderer, bool> item in _hiddenBody)
		{
			if (!Object.op_Implicit((Object)(object)item.Key))
			{
				_retiredBody.Add(item.Key);
			}
			else if ((Object)(object)((Component)item.Key).transform != (Object)(object)val.transform && !((Component)item.Key).transform.IsChildOf(val.transform))
			{
				item.Key.forceRenderingOff = item.Value;
				_retiredBody.Add(item.Key);
			}
		}
		foreach (Renderer item2 in _retiredBody)
		{
			_hiddenBody.Remove(item2);
		}
		_retiredBody.Clear();
		_bodyRenderers.Clear();
	}

	private void RestorePaintBody()
	{
		foreach (KeyValuePair<Renderer, bool> item in _hiddenBody)
		{
			try
			{
				if (Object.op_Implicit((Object)(object)item.Key))
				{
					item.Key.forceRenderingOff = item.Value;
				}
			}
			catch (Exception ex)
			{
				ReportOnce("camera.renderer.restore", ex);
			}
		}
		_hiddenBody.Clear();
		_bodyRenderers.Clear();
		_retiredBody.Clear();
	}

	internal void StopBrushCamera(string reason)
	{
		//IL_0122: Unknown result type (might be due to invalid IL or missing references)
		//IL_0128: Unknown result type (might be due to invalid IL or missing references)
		//IL_012d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0132: Unknown result type (might be due to invalid IL or missing references)
		//IL_0173: Unknown result type (might be due to invalid IL or missing references)
		//IL_0179: Unknown result type (might be due to invalid IL or missing references)
		ClearBuildingView.Stop();
		_cameraToggle.CancelPending();
		if (!_paintCamera)
		{
			return;
		}
		_paintCamera = false;
		try
		{
			if (Object.op_Implicit((Object)(object)_cameraPlayer))
			{
				Player cameraPlayer = _cameraPlayer;
				((Character)cameraPlayer).m_onDamaged = (Action<float, Character>)Delegate.Remove(((Character)cameraPlayer).m_onDamaged, new Action<float, Character>(PaintCameraDamaged));
			}
		}
		catch (Exception ex)
		{
			ReportOnce("camera.damage.restore", ex);
		}
		RestorePaintBody();
		try
		{
			if (Object.op_Implicit((Object)(object)_paintLens))
			{
				_paintLens.fieldOfView = _previousFov;
				_paintLens.nearClipPlane = _previousNearClip;
			}
			if (Object.op_Implicit((Object)(object)_paintSky))
			{
				_paintSky.fieldOfView = _previousSkyFov;
			}
			if (Object.op_Implicit((Object)(object)_cameraPlayer) && (Object)(object)_cameraPlayer == (Object)(object)Player.m_localPlayer && !((Character)_cameraPlayer).IsTeleporting() && !((Character)_cameraPlayer).InCutscene() && !((Character)_cameraPlayer).IsAttached() && _cameraPlayer.GetDoodadController() == null)
			{
				Vector3 val = ((Component)_cameraPlayer).transform.position - _bodyAnchor;
				if (((Vector3)(ref val)).sqrMagnitude < 1f && Object.op_Implicit((Object)(object)_paintGameCamera) && (Object)(object)_paintGameCamera == (Object)(object)GameCamera.instance && !GameCamera.InFreeFly())
				{
					((Component)_paintGameCamera).transform.SetPositionAndRotation(_previousCameraPosition, _previousCameraRotation);
				}
			}
		}
		catch (Exception ex2)
		{
			ReportOnce("camera.pose.restore", ex2);
		}
		_cameraPlayer = null;
		_paintGameCamera = null;
		_paintLens = null;
		_paintSky = null;
		_cameraSuppressUntil = Time.unscaledTime + 0.2f;
		ClearHover();
		((BaseUnityPlugin)this).Logger.LogInfo((object)("[paint.camera] OFF: " + reason));
	}

	private void PaintCameraDamaged(float damage, Character attacker)
	{
		if (_paintCamera && _cameraExitDamage.Value && damage > 0f)
		{
			ClosePalette();
			StopBrushCamera("Player damaged");
		}
	}

	private void LateUpdate()
	{
		if (!Ready || Application.isBatchMode)
		{
			return;
		}
		try
		{
			if (EnsureBrushSession() && _paintCamera)
			{
				KeepPaintBodyStill();
				ApplyPaintCameraPose();
				if (Time.unscaledTime >= _nextBodyScan)
				{
					HidePaintBody();
					_nextBodyScan = Time.unscaledTime + 0.25f;
				}
			}
		}
		catch (Exception ex)
		{
			StopBrushCamera("Camera error");
			ReportOnce("paint.camera.late", ex);
		}
	}

	private void OnDisable()
	{
		CancelBuildingClear();
		ClosePalette();
		StopBrushCamera("Plugin disabled");
		_cameraToggle.Reset(waitForRelease: true);
		_releaseGuard.Reset();
		_cameraChord = false;
		_cameraSuppressUntil = 0f;
	}

	private void OnApplicationFocus(bool focused)
	{
		ClearBuildingView.FocusChanged(focused);
		if (!focused)
		{
			CancelBuildingClear();
			ClosePalette();
			StopBrushCamera("Focus lost");
			_cameraToggle.Reset(waitForRelease: true);
		}
	}

	private void ReleaseExternalBuildCamera()
	{
		//IL_00d4: Unknown result type (might be due to invalid IL or missing references)
		//IL_00e1: Expected O, but got Unknown
		if (!Object.op_Implicit((Object)(object)_externalBuildCamera))
		{
			if (!Chainloader.PluginInfos.TryGetValue("valheim.buildcamera", out var value) || !Object.op_Implicit((Object)(object)value.Instance))
			{
				return;
			}
			_externalBuildCamera = value.Instance;
			Type type = ((object)_externalBuildCamera).GetType();
			_externalCameraActive = AccessTools.Field(type, "_active");
			_externalCameraStop = AccessTools.Method(type, "StopCamera", new Type[2]
			{
				typeof(string),
				typeof(bool)
			}, (Type[])null);
			MethodInfo methodInfo = AccessTools.Method(type, "StartCamera", new Type[1] { typeof(Player) }, (Type[])null);
			if (methodInfo != null && _harmony != null)
			{
				_harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(NaturalPaintPlugin), "AllowStandaloneBuildCameraStart", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			}
		}
		if (_externalCameraActive != null && (bool)_externalCameraActive.GetValue(_externalBuildCamera))
		{
			if (_externalCameraStop == null)
			{
				throw new InvalidOperationException("Exit the standalone build camera before using the paintbrush.");
			}
			_externalCameraStop.Invoke(_externalBuildCamera, new object[2] { "Paintbrush selected", false });
		}
	}

	private static bool AllowStandaloneBuildCameraStart()
	{
		return !BrushSelected(Player.m_localPlayer);
	}

	private void BindFeedbackConfig()
	{
		//IL_002f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0039: Expected O, but got Unknown
		_soundVolume = ((BaseUnityPlugin)this).Config.Bind<float>("Audio", "BrushVolume", 0.65f, new ConfigDescription("Brushing sound volume; follows the game's effects volume.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>()));
	}

	internal static bool BrushSelected(Player player)
	{
		if (Ready && Object.op_Implicit((Object)(object)player) && (Object)(object)player == (Object)(object)Player.m_localPlayer)
		{
			return IsBrush(((Humanoid)player).RightItem);
		}
		return false;
	}

	private static bool CanShowBrush(Player player)
	{
		if (BrushSelected(player))
		{
			return CanUseTool(player);
		}
		return false;
	}

	internal void UpdateBrushHud(Hud hud, Player player)
	{
		//IL_0190: Unknown result type (might be due to invalid IL or missing references)
		//IL_02b4: Unknown result type (might be due to invalid IL or missing references)
		//IL_02b9: 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_0238: Unknown result type (might be due to invalid IL or missing references)
		try
		{
			_brushHud = hud;
			bool flag = CanShowBrush(player) && !PaintModalOpen;
			hud.m_buildHud.SetActive(flag);
			if (!flag)
			{
				if (!PaletteOpen)
				{
					ClearPreview();
				}
				return;
			}
			if (Object.op_Implicit((Object)(object)hud.m_buildUi) && ((Component)hud.m_buildUi).gameObject.activeSelf)
			{
				hud.m_buildUi.Close();
			}
			if (Object.op_Implicit((Object)(object)hud.m_closePieceSelectionButton))
			{
				((Component)hud.m_closePieceSelectionButton).gameObject.SetActive(false);
			}
			object? value = SnappingIcon.GetValue(hud);
			Image val = (Image)((value is Image) ? value : null);
			if (Object.op_Implicit((Object)(object)val))
			{
				((Behaviour)val).enabled = false;
			}
			hud.m_buildSelection.text = (PaintCameraActive ? "Paint camera · " : "Paint · ") + SelectedFinish.Name;
			((Behaviour)hud.m_buildIcon).enabled = true;
			hud.m_buildIcon.sprite = _icon;
			GameObject[] requirementItems;
			if (BuildingClearActive)
			{
				ClearHover();
				hud.m_buildSelection.text = (_buildingMultiPaint ? ("Multi-paint · " + _buildingSelection.Count + " selected") : "Clear building colours");
				hud.m_pieceDescription.text = BuildingSelectionHint;
				requirementItems = hud.m_requirementItems;
				for (int i = 0; i < requirementItems.Length; i++)
				{
					requirementItems[i].SetActive(false);
				}
				return;
			}
			if (TryGetBrushTarget(player, out var target))
			{
				_hoverTarget = target;
				if (PrivateArea.CheckAccess(((Component)target.Piece).transform.position, 0f, false, false))
				{
					bool flag2 = MaterialRules.Accepts(target.Kind, SelectedFinish.Id);
					SetPreview(target.Piece, SelectedFinish.Id, _selectedOpacity.Value);
					hud.m_pieceDescription.text = (flag2 ? "Preview · Click / attack to apply" : ("Select a " + target.Kind.ToString().ToLowerInvariant() + " finish"));
					TMP_Text pieceDescription = hud.m_pieceDescription;
					pieceDescription.text = pieceDescription.text + "\nRight-click / P: palette · " + ((object)_cameraKey.Value/*cast due to .constrained prefix*/).ToString() + ": free camera";
				}
				else
				{
					ClearPreview();
					hud.m_pieceDescription.text = "Protected by a ward";
				}
			}
			else
			{
				ClearHover();
				hud.m_pieceDescription.text = (PaintCameraActive ? ("Aim at a building piece within " + CameraPaintReach + " metres of your body") : "Aim at a building piece or furniture within 5 metres") + "\nRight-click / P: palette · " + ((object)_cameraKey.Value/*cast due to .constrained prefix*/).ToString() + ": free camera";
			}
			requirementItems = hud.m_requirementItems;
			for (int i = 0; i < requirementItems.Length; i++)
			{
				requirementItems[i].SetActive(false);
			}
		}
		catch (Exception ex)
		{
			ReportOnce("paint.hud", ex);
		}
	}

	internal void UpdateBrushCrosshair(Hud hud, Player player)
	{
		//IL_00ff: Unknown result type (might be due to invalid IL or missing references)
		if (CanShowBrush(player) && !PaintModalOpen && !BuildingClearActive && Object.op_Implicit((Object)(object)_hoverTarget.Piece))
		{
			string name = ReadFinish(_hoverTarget.Piece).Name;
			bool flag = MaterialRules.Accepts(_hoverTarget.Kind, SelectedFinish.Id);
			((TMP_Text)hud.m_hoverName).text = Localization.instance.Localize(_hoverTarget.Piece.m_name) + "\n<color=#F3DCAD>" + name + "</color> · " + (flag ? ("Preview: " + SelectedFinish.Name) : ("Requires " + _hoverTarget.Kind.ToString().ToLowerInvariant() + " paint"));
			((Graphic)hud.m_crosshair).color = new Color(0.95f, 0.86f, 0.68f);
			((Component)hud.m_pieceHealthRoot).gameObject.SetActive((Object)(object)_hoverTarget.Wear != (Object)null);
			if (Object.op_Implicit((Object)(object)_hoverTarget.Wear))
			{
				hud.m_pieceHealthBar.SetValue(_hoverTarget.Wear.GetHealthPercentage());
			}
		}
	}

	internal void ClearHover()
	{
		_hoverTarget = default(BrushTarget);
		ClearPreview();
	}

	internal void PlayBrushSound(Vector3 point)
	{
		//IL_01e3: Unknown result type (might be due to invalid IL or missing references)
		//IL_0176: Unknown result type (might be due to invalid IL or missing references)
		//IL_017b: Unknown result type (might be due to invalid IL or missing references)
		if (Application.isBatchMode || _soundVolume.Value <= 0f)
		{
			return;
		}
		try
		{
			if (!Object.op_Implicit((Object)(object)_brushSound))
			{
				using Stream stream = typeof(NaturalPaintPlugin).Assembly.GetManifestResourceStream("PaintYourBuild.BrushRub.wav");
				PcmWave pcmWave = PcmWave.Read(stream);
				_brushSound = AudioClip.Create("NaturalPaint_ResinRub", pcmWave.Samples.Length / pcmWave.Channels, pcmWave.Channels, pcmWave.SampleRate, false);
				if (!_brushSound.SetData(pcmWave.Samples, 0))
				{
					Object.Destroy((Object)(object)_brushSound);
					_brushSound = null;
					throw new InvalidDataException("AudioClip.SetData failed");
				}
			}
			if (!Object.op_Implicit((Object)(object)_brushMixer) && Object.op_Implicit((Object)(object)AudioMan.instance))
			{
				AudioMixer masterMixer = AudioMan.instance.m_masterMixer;
				if (Object.op_Implicit((Object)(object)masterMixer))
				{
					_brushMixer = ((IEnumerable<AudioMixerGroup>)masterMixer.FindMatchingGroups("")).FirstOrDefault((Func<AudioMixerGroup, bool>)((AudioMixerGroup group) => string.Equals(((Object)group).name, "SFX", StringComparison.OrdinalIgnoreCase)));
				}
				if (!Object.op_Implicit((Object)(object)_brushMixer))
				{
					_brushMixer = AudioMan.instance.m_guiMixer;
				}
			}
			int num = _nextBrushSource++ % _brushSources.Length;
			AudioSource val = _brushSources[num];
			if (!Object.op_Implicit((Object)(object)val))
			{
				GameObject val2 = new GameObject("NaturalPaint_RubAudio" + num);
				val2.transform.SetParent(((Component)this).transform, false);
				val = val2.AddComponent<AudioSource>();
				val.playOnAwake = false;
				val.loop = false;
				val.spatialBlend = 1f;
				val.minDistance = 4f;
				val.maxDistance = 18f;
				val.rolloffMode = (AudioRolloffMode)1;
				val.dopplerLevel = 0f;
				_brushSources[num] = val;
			}
			((Component)val).transform.position = point;
			val.outputAudioMixerGroup = _brushMixer;
			val.pitch = Random.Range(0.96f, 1.04f);
			val.PlayOneShot(_brushSound, _soundVolume.Value * (Object.op_Implicit((Object)(object)_brushMixer) ? 1f : AudioMan.GetSFXVolume()));
		}
		catch (Exception ex)
		{
			ReportOnce("brush.audio", ex);
		}
	}

	private void ReleaseFeedback()
	{
		ClearHover();
		if (Object.op_Implicit((Object)(object)_brushHud) && Object.op_Implicit((Object)(object)Player.m_localPlayer) && IsBrush(((Humanoid)Player.m_localPlayer).RightItem))
		{
			_brushHud.m_buildHud.SetActive(false);
		}
		AudioSource[] brushSources = _brushSources;
		foreach (AudioSource val in brushSources)
		{
			if (Object.op_Implicit((Object)(object)val))
			{
				Object.Destroy((Object)(object)((Component)val).gameObject);
			}
		}
		if (Object.op_Implicit((Object)(object)_brushSound))
		{
			Object.Destroy((Object)(object)_brushSound);
		}
	}

	private static void ResolveHotbarApis()
	{
		UpdateHotbarIcons = Method(typeof(HotkeyBar), "UpdateIcons", typeof(Player));
		HotbarElements = Field(typeof(HotkeyBar), "m_elements");
		HotbarElementEquipped = Field(HotbarElements.FieldType.GetGenericArguments()[0], "m_equiped");
	}

	internal static void RefreshBlockedHotbar(HotkeyBar bar)
	{
		if (!Ready || !Object.op_Implicit((Object)(object)Instance))
		{
			return;
		}
		try
		{
			UpdateHotbarIcons.Invoke(bar, new object[1] { Player.m_localPlayer });
		}
		catch (Exception ex)
		{
			Instance.ReportOnce("hotbar.refresh", ex);
		}
	}

	internal void UpdateBrushHotbar(HotkeyBar bar, Player player, int selectedSlot)
	{
		//IL_004b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0052: Expected O, but got Unknown
		try
		{
			if (!Object.op_Implicit((Object)(object)player))
			{
				return;
			}
			IList list = (IList)HotbarElements.GetValue(bar);
			Inventory inventory = ((Humanoid)player).GetInventory();
			for (int i = 0; i < list.Count; i++)
			{
				ItemData itemAt = inventory.GetItemAt(i, 0);
				if (IsBrush(itemAt))
				{
					GameObject val = (GameObject)HotbarElementEquipped.GetValue(list[i]);
					if (Object.op_Implicit((Object)(object)val))
					{
						val.SetActive(!((Character)player).IsDead() && ((Humanoid)player).RightItem == itemAt);
					}
				}
			}
		}
		catch (Exception ex)
		{
			ReportOnce("hotbar.indicator", ex);
		}
	}

	private void StartBuildingSelection()
	{
		//IL_009d: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a2: Unknown result type (might be due to invalid IL or missing references)
		//IL_0130: Unknown result type (might be due to invalid IL or missing references)
		Piece paletteTarget = _paletteTarget;
		if (!Object.op_Implicit((Object)(object)paletteTarget))
		{
			_paletteCaption.text = "Aim at a house wall or floor, then reopen the palette";
			return;
		}
		_buildingClearMaterial = ClassifyPiece(paletteTarget);
		if (_buildingClearMaterial == NaturalPaintLogic.PaintMaterial.Unsupported)
		{
			_paletteCaption.text = "Aim at a supported building material first";
			return;
		}
		ClosePalette();
		ClearHover();
		_buildingPlayer = Player.m_localPlayer;
		_buildingIsUndo = false;
		_buildingMultiPaint = false;
		_buildingSelection.Clear();
		_buildingCandidates.Clear();
		_buildingNodes.Clear();
		_buildingNodeBounds.Clear();
		_buildingSeedPosition = ((Component)paletteTarget).transform.position;
		_buildingOutsideScope = 0;
		_buildingInvalidBounds = 0;
		foreach (PaintedPiece value in _painted.Values)
		{
			if (Object.op_Implicit((Object)(object)value.Piece) && ((Component)value.Piece).gameObject.activeInHierarchy && Object.op_Implicit((Object)(object)value.View) && value.View.IsValid() && BatchPaintRules.SameClearMaterial(_buildingClearMaterial, ClassifyPiece(value.Piece)))
			{
				if (!InBuildingScope(((Component)value.Piece).transform.position))
				{
					_buildingOutsideScope++;
				}
				else
				{
					_buildingCandidates.Add(value);
				}
			}
		}
		((BaseUnityPlugin)this).Logger.LogInfo((object)("[paint.batch.detect] material=" + _buildingClearMaterial.ToString() + "; candidates=" + _buildingCandidates.Count + "; outsideScope=" + _buildingOutsideScope));
		if (_buildingCandidates.Count > 20000)
		{
			FailBuilding("Too many nearby loaded pieces. Use Multi-paint with Restore original to select specific pieces.");
			return;
		}
		_buildingSeed = paletteTarget;
		_buildingSeedIndex = -1;
		_buildingScanIndex = 0;
		_buildingGraph = new BuildingConnectivity();
		_buildingGraphStarted = false;
		_buildingHasBounds = false;
		_buildingCollectIndex = 0;
		_buildingProtected = 0;
		if (!Object.op_Implicit((Object)(object)_buildingCanvas))
		{
			CreateBuildingWindow();
		}
		_buildingStage = BuildingStage.Detecting;
		_buildingMessage.text = "Finding connected " + MaterialRules.DisplayName(_buildingClearMaterial) + " pieces…\nOther material types are excluded.\nEsc / B: cancel";
		_buildingTitle.text = "FINDING HOUSE PIECES";
		((Component)_buildingConfirm).gameObject.SetActive(false);
		((Component)_buildingEdit).gameObject.SetActive(false);
		((Component)_buildingCancel).gameObject.SetActive(true);
		_buildingCanvas.SetActive(true);
	}

	private bool TryBuildingBounds(Piece piece, out Bounds bounds)
	{
		//IL_0001: Unknown result type (might be due to invalid IL or missing references)
		//IL_0037: Unknown result type (might be due to invalid IL or missing references)
		//IL_003c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0040: 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_0065: Unknown result type (might be due to invalid IL or missing references)
		//IL_0059: Unknown result type (might be due to invalid IL or missing references)
		//IL_005b: Unknown result type (might be due to invalid IL or missing references)
		bounds = default(Bounds);
		bool flag = false;
		Collider[] componentsInChildren = ((Component)piece).GetComponentsInChildren<Collider>();
		foreach (Collider val in componentsInChildren)
		{
			if (!val.enabled || val.isTrigger || (Object)(object)((Component)val).GetComponentInParent<Piece>() != (Object)(object)piece)
			{
				continue;
			}
			Bounds bounds2 = val.bounds;
			Vector3 size = ((Bounds)(ref bounds2)).size;
			if (!(((Vector3)(ref size)).sqrMagnitude < 0.0001f))
			{
				if (!flag)
				{
					bounds = bounds2;
					flag = true;
				}
				else
				{
					((Bounds)(ref bounds)).Encapsulate(bounds2);
				}
			}
		}
		return flag;
	}

	private void TickBuildingDetection()
	{
		//IL_008d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0092: Unknown result type (might be due to invalid IL or missing references)
		//IL_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_00a3: Unknown result type (might be due to invalid IL or missing references)
		//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b1: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b8: Unknown result type (might be due to invalid IL or missing references)
		//IL_00bf: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c6: Unknown result type (might be due to invalid IL or missing references)
		//IL_00fe: Unknown result type (might be due to invalid IL or missing references)
		//IL_027d: Unknown result type (might be due to invalid IL or missing references)
		//IL_02a4: Unknown result type (might be due to invalid IL or missing references)
		//IL_02a9: Unknown result type (might be due to invalid IL or missing references)
		//IL_02ca: Unknown result type (might be due to invalid IL or missing references)
		//IL_02b4: Unknown result type (might be due to invalid IL or missing references)
		//IL_02b6: Unknown result type (might be due to invalid IL or missing references)
		//IL_0321: Unknown result type (might be due to invalid IL or missing references)
		//IL_035c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0361: Unknown result type (might be due to invalid IL or missing references)
		Stopwatch stopwatch = Stopwatch.StartNew();
		int num = 0;
		while (_buildingScanIndex < _buildingCandidates.Count && num++ < 64 && stopwatch.ElapsedMilliseconds < 2)
		{
			PaintedPiece paintedPiece = _buildingCandidates[_buildingScanIndex++];
			if (Object.op_Implicit((Object)(object)paintedPiece.Piece) && Object.op_Implicit((Object)(object)paintedPiece.View) && paintedPiece.View.IsValid() && BatchPaintRules.SameClearMaterial(_buildingClearMaterial, ClassifyPiece(paintedPiece.Piece)) && TryBuildingBounds(paintedPiece.Piece, out var bounds))
			{
				Vector3 min = ((Bounds)(ref bounds)).min;
				Vector3 max = ((Bounds)(ref bounds)).max;
				int buildingSeedIndex;
				try
				{
					buildingSeedIndex = _buildingGraph.Add(new BuildingBox(min.x, min.y, min.z, max.x, max.y, max.z));
				}
				catch (InvalidOperationException)
				{
					_buildingInvalidBounds++;
					continue;
				}
				_buildingNodes.Add(paintedPiece);
				_buildingNodeBounds.Add(bounds);
				if ((Object)(object)paintedPiece.Piece == (Object)(object)_buildingSeed)
				{
					_buildingSeedIndex = buildingSeedIndex;
				}
			}
		}
		if (_buildingScanIndex < _buildingCandidates.Count)
		{
			return;
		}
		if (!_buildingGraphStarted)
		{
			if (!Object.op_Implicit((Object)(object)_buildingSeed) || _buildingSeedIndex < 0)
			{
				FailBuilding("Aim at a solid house wall or floor and try again.");
				return;
			}
			_buildingGraph.Start(_buildingSeedIndex);
			_buildingSeed = null;
			_buildingGraphStarted = true;
		}
		num = 0;
		while (!_buildingGraph.Complete && num++ < 2048 && stopwatch.ElapsedMilliseconds < 2)
		{
			_buildingGraph.Step();
		}
		if (!_buildingGraph.Complete)
		{
			return;
		}
		num = 0;
		while (_buildingCollectIndex < _buildingGraph.Selected.Count && num++ < 64 && stopwatch.ElapsedMilliseconds < 2)
		{
			int index = _buildingGraph.Selected[_buildingCollectIndex++];
			PaintedPiece paintedPiece2 = _buildingNodes[index];
			if (!Object.op_Implicit((Object)(object)paintedPiece2.Piece) || !Object.op_Implicit((Object)(object)paintedPiece2.View) || !paintedPiece2.View.IsValid() || !BatchPaintRules.SameClearMaterial(_buildingClearMaterial, ClassifyPiece(paintedPiece2.Piece)))
			{
				continue;
			}
			if (!InBuildingScope(((Component)paintedPiece2.Piece).transform.position))
			{
				_buildingOutsideScope++;
				continue;
			}
			Bounds val = _buildingNodeBounds[index];
			if (!_buildingHasBounds)
			{
				_buildingBounds = val;
				_buildingHasBounds = true;
			}
			else
			{
				((Bounds)(ref _buildingBounds)).Encapsulate(val);
			}
			int finish = paintedPiece2.View.GetZDO().GetInt(PaintHash, 0);
			float opacity = paintedPiece2.View.GetZDO().GetFloat(CrystalOpacityHash, -1f);
			if (BuildingClearRules.Changed(finish, opacity))
			{
				if (!PrivateArea.CheckAccess(((Component)paintedPiece2.Piece).transform.position, 0f, false, false))
				{
					_buildingProtected++;
					continue;
				}
				_buildingSelection.Add(new BuildingRecord
				{
					Id = paintedPiece2.View.GetZDO().m_uid,
					Piece = paintedPiece2.Piece,
					View = paintedPiece2.View,
					Finish = finish,
					Opacity = opacity
				});
				HighlightBuildingPiece(paintedPiece2.Piece);
			}
		}
		if (_buildingCollectIndex >= _buildingGraph.Selected.Count)
		{
			if (_buildingSelection.Count > 10000)
			{
				FailBuilding("Too many coloured pieces. Use Multi-paint with Restore original to select specific pieces.");
				return;
			}
			if (!_buildingHasBounds)
			{
				FailBuilding("The selected house is no longer loaded. Nothing changed.");
				return;
			}
			ShowBuildingReview("Clear colours from " + _buildingSelection.Count + " connected " + MaterialRules.DisplayName(_buildingClearMaterial) + " pieces?\nGREEN surfaces = selected pieces. Hidden pieces are included in the count.\nOnly " + MaterialRules.DisplayName(_buildingClearMaterial) + ". Other material types stay unchanged.\n" + _buildingProtected + " protected pieces skipped. Unhighlighted pieces stay unchanged.\nWithin 64 m of target and 96 m of you. Undo last batch is available.");
			((BaseUnityPlugin)this).Logger.LogInfo((object)("[paint.batch.preview] material=" + _buildingClearMaterial.ToString() + "; selected=" + _buildingSelection.Count + "; protected=" + _buildingProtected + "; invalidBounds=" + _buildingInvalidBounds));
			_buildingGraph = null;
			_buildingCandidates.Clear();
			_buildingNodes.Clear();
			_buildingNodeBounds.Clear();
		}
	}

	private void PreviewBuildingUndo()
	{
		//IL_00c3: Unknown result type (might be due to invalid IL or missing references)
		if ((Object)(object)_undoPlayer != (Object)(object)Player.m_localPlayer)
		{
			_buildingUndo.Clear();
		}
		if (_buildingUndo.Count == 0)
		{
			_paletteCaption.text = "No batch to undo in this session";
			return;
		}
		ClosePalette();
		ClearHover();
		_buildingPlayer = Player.m_localPlayer;
		_buildingIsUndo = true;
		_buildingMultiPaint = false;
		_buildingSelection.Clear();
		foreach (BuildingRecord item in _buildingUndo)
		{
			ResolveBuildingRecord(item);
			if (Object.op_Implicit((Object)(object)item.Piece) && Object.op_Implicit((Object)(object)item.View) && item.View.IsValid() && PrivateArea.CheckAccess(((Component)item.Piece).transform.position, 0f, false, false) && BuildingClearRules.Matches(item.View.GetZDO().GetInt(PaintHash, 0), item.View.GetZDO().GetFloat(CrystalOpacityHash, -1f), item.AppliedFinish, item.AppliedOpacity))
			{
				_buildingSelection.Add(item);
				HighlightBuildingPiece(item.Piece);
			}
		}
		ShowBuildingReview("Restore colours on up to " + _buildingSelection.Count + " pieces?\nGREEN surfaces = previous colours will be restored.\nRepainted, unloaded, missing or protected pieces are skipped.\nUndo is available only in this play session. B / Esc cancels.");
	}

	private void ShowBuildingReview(string message)
	{
		if (!Object.op_Implicit((Object)(object)_buildingCanvas))
		{
			CreateBuildingWindow();
		}
		_buildingStage = BuildingStage.Review;
		_buildingReviewFrame = Time.frameCount;
		_buildingTitle.text = (_buildingIsUndo ? "UNDO LAST BATCH" : (_buildingMultiPaint ? "APPLY MULTI-PAINT" : "CLEAR HOUSE COLOURS"));
		_buildingMessage.text = message;
		((Component)_buildingEdit).gameObject.SetActive(_buildingMultiPaint && !_buildingIsUndo);
		((Component)_buildingConfirm).gameObject.SetActive(true);
		((Selectable)_buildingConfirm).interactable = _buildingSelection.Count != 0;
		((Component)_buildingCancel).gameObject.SetActive(true);
		_buildingCanvas.SetActive(true);
		_releaseGuard.Arm(Time.frameCount);
		ZCursor.LockState = (CursorLockMode)0;
		ZCursor.Show();
	}

	private void CreateBuildingWindow()
	{
		//IL_0040: Unknown result type (might be due to invalid IL or missing references)
		//IL_004a: Expected O, but got Unknown
		//IL_009a: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c9: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d8: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f1: Unknown result type (might be due to invalid IL or missing references)
		//IL_0113: Unknown result type (might be due to invalid IL or missing references)
		//IL_0122: Unknown result type (might be due to invalid IL or missing references)
		//IL_014d: Unknown result type (might be due to invalid IL or missing references)
		//IL_015c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0182: Unknown result type (might be due to invalid IL or missing references)
		//IL_0191: Unknown result type (might be due to invalid IL or missing references)
		//IL_01be: Unknown result type (might be due to invalid IL or missing references)
		//IL_01cd: Unknown result type (might be due to invalid IL or missing references)
		//IL_020b: Unknown result type (might be due to invalid IL or missing references)
		//IL_021a: Unknown result type (might be due to invalid IL or missing references)
		_buildingCanvas = new GameObject("PaintYourBuild_ClearBuilding", new Type[4]
		{
			typeof(RectTransform),
			typeof(Canvas),
			typeof(CanvasScaler),
			typeof(GraphicRaycaster)
		});
		_buildingCanvas.transform.SetParent(((Component)this).transform, false);
		Canvas component = _buildingCanvas.GetComponent<Canvas>();
		component.renderMode = (RenderMode)0;
		component.sortingOrder = 30001;
		CanvasScaler component2 = _buildingCanvas.GetComponent<CanvasScaler>();
		component2.uiScaleMode = (ScaleMode)1;
		component2.referenceResolution = new Vector2(1600f, 900f);
		component2.matchWidthOrHeight = 0.5f;
		RectTransform parent = Box(_buildingCanvas.transform, "Clear building colours", new Vector2(900f, 290f), new Vector2(0f, -35f), new Color(0.09f, 0.085f, 0.072f, 0.97f));
		_buildingTitle = Label((Transform)(object)parent, "CLEAR HOUSE COLOURS", 28f, new Vector2(840f, 42f), new Vector2(0f, -14f), (TextAlignmentOptions)514);
		_buildingMessage = Label((Transform)(object)parent, "", 21f, new Vector2(840f, 166f), new Vector2(0f, -60f), (TextAlignmentOptions)514);
		_buildingConfirm = ActionButton((Transform)(object)parent, "Confirm · Enter / A", new Vector2(260f, 46f), new Vector2(-285f, -230f), BeginBuildingOperation);
		_buildingEdit = ActionButton((Transform)(object)parent, "Edit selection · Y", new Vector2(260f, 46f), new Vector2(0f, -230f), ReturnToMultiSelection);
		((Component)_buildingEdit).gameObject.SetActive(false);
		_buildingCancel = ActionButton((Transform)(object)parent, "Cancel / stop · Esc / B", new Vector2(260f, 46f), new Vector2(285f, -230f), CancelBuildingClear);
	}

	private void BeginBuildingOperation()
	{
		if (_buildingStage != BuildingStage.Review || _buildingSelection.Count == 0 || _releaseGuard.Blocked || Time.frameCount <= _buildingReviewFrame)
		{
			return;
		}
		ClearBuildingPreview();
		_buildingStartedUndoRecord = false;
		_buildingQueue.Clear();
		foreach (BuildingRecord item in _buildingSelection)
		{
			item.OwnershipDeadline = 0f;
			_buildingQueue.Enqueue(item);
		}
		_buildingChanged = (_buildingSkipped = 0);
		_buildingStage = BuildingStage.Running;
		((Component)_buildingEdit).gameObject.SetActive(false);
		((BaseUnityPlugin)this).Logger.LogInfo((object)("[paint.batch.start] mode=" + (_buildingIsUndo ? "undo" : (_buildingMultiPaint ? "paint" : "clear")) + "; count=" + _buildingSelection.Count));
		((Component)_buildingConfirm).gameObject.SetActive(false);
		if (Object.op_Implicit((Object)(object)EventSystem.current))
		{
			EventSystem.current.SetSelectedGameObject((GameObject)null);
		}
		ClearPreview();
	}

	private void TickBuildingClear()
	{
		//IL_0084: 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)
		if (_undoPlayer != null && _undoPlayer != Player.m_localPlayer)
		{
			_buildingUndo.Clear();
			_undoPlayer = null;
		}
		if (!BuildingClearActive)
		{
			return;
		}
		try
		{
			Player localPlayer = Player.m_localPlayer;
			if (!Object.op_Implicit((Object)(object)localPlayer) || (Object)(object)localPlayer != (Object)(object)_buildingPlayer || !BrushSelected(localPlayer) || !CanUseTool(localPlayer))
			{
				CancelBuildingClear();
				return;
			}
			if (!ZInput.GetKeyDown((KeyCode)27, true) && !ZInput.GetButtonDown("JoyButtonB"))
			{
				KeyboardShortcut value = _paletteKey.Value;
				if (!((KeyboardShortcut)(ref value)).IsDown())
				{
					if (_buildingStage == BuildingStage.Selecting)
					{
						if (!_releaseGuard.Blocked && Time.unscaledTime >= _nextUse && (ZInput.GetKeyDown((KeyCode)13, true) || ZInput.GetKeyDown((KeyCode)271, true) || ZInput.GetButtonDown("JoyButtonY") || ZInput.GetButtonDown("BuildMenu") || ZInput.GetButtonDown("JoyBuildMenu")))
						{
							ReviewMultiPaint();
						}
						return;
					}
					if (_buildingStage == BuildingStage.Review && _buildingMultiPaint && !_releaseGuard.Blocked && ZInput.GetButtonDown("JoyButtonY"))
					{
						ReturnToMultiSelection();
						return;
					}
					if (_buildingStage == BuildingStage.Detecting)
					{
						TickBuildingDetection();
						return;
					}
					if (_buildingStage == BuildingStage.Review && !_releaseGuard.Blocked && Time.frameCount > _buildingReviewFrame && (ZInput.GetKeyDown((KeyCode)13, true) || ZInput.GetKeyDown((KeyCode)271, true) || ZInput.GetButtonDown("JoyButtonA")))
					{
						BeginBuildingOperation();
					}
					if (_buildingStage != BuildingStage.Running)
					{
						return;
					}
					Stopwatch stopwatch = Stopwatch.StartNew();
					int num = Math.Min(8, _buildingQueue.Count);
					while (num-- > 0 && stopwatch.ElapsedMilliseconds < 2 && _buildingQueue.Count > 0)
					{
						BuildingRecord record = _buildingQueue.Dequeue();
						try
						{
							ProcessBuildingRecord(record);
						}
						catch (Exception ex)
						{
							_buildingSkipped++;
							ReportOnce("paint.building.piece", ex);
						}
					}
					_buildingMessage.text = (_buildingIsUndo ? "Restoring colours…" : (_buildingMultiPaint ? "Painting selected pieces…" : "Clearing colours…")) + "\nChanged: " + _buildingChanged + " · Skipped: " + _buildingSkipped + " · Remaining: " + _buildingQueue.Count + "\nYou can stop now; completed changes remain undoable.";
					if (_buildingQueue.Count == 0)
					{
						CancelBuildingClear();
					}
					return;
				}
			}
			CancelBuildingClear();
		}
		catch (Exception ex2)
		{
			ReportOnce("paint.building", ex2);
			FailBuilding("Batch stopped: " + ex2.GetType().Name + ". Details are in LogOutput.log; completed changes can be undone.");
		}
	}

	private void ResolveBuildingRecord(BuildingRecord record)
	{
		//IL_0039: Unknown result type (might be due to invalid IL or missing references)
		if ((!Object.op_Implicit((Object)(object)record.Piece) || !Object.op_Implicit((Object)(object)record.View) || !record.View.IsValid()) && Object.op_Implicit((Object)(object)ZNetScene.instance))
		{
			GameObject val = ZNetScene.instance.FindInstance(record.Id);
			if (Object.op_Implicit((Object)(object)val))
			{
				record.Piece = val.GetComponent<Piece>();
				record.View = val.GetComponent<ZNetView>();
			}
		}
	}

	private void ProcessBuildingRecord(BuildingRecord record)
	{
		//IL_0039: 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_0065: Unknown result type (might be due to invalid IL or missing references)
		ResolveBuildingRecord(record);
		if (!Object.op_Implicit((Object)(object)record.Piece) || !Object.op_Implicit((Object)(object)record.View) || !record.View.IsValid() || Vector3.Distance(((Component)record.Piece).transform.position, ((Component)_buildingPlayer).transform.position) > 96f || !PrivateArea.CheckAccess(((Component)record.Piece).transform.position, 0f, false, false))
		{
			_buildingSkipped++;
			return;
		}
		if (!_buildingIsUndo && (!_buildingMultiPaint || _multiFinish == 0) && !BatchPaintRules.SameClearMaterial(_buildingClearMaterial, ClassifyPiece(record.Piece)))
		{
			_buildingSkipped++;
			return;
		}
		ZDO zDO = record.View.GetZDO();
		int num = (_buildingIsUndo ? record.AppliedFinish : record.Finish);
		float num2 = (_buildingIsUndo ? record.AppliedOpacity : record.Opacity);
		if (!BuildingClearRules.Matches(zDO.GetInt(PaintHash, 0), zDO.GetFloat(CrystalOpacityHash, -1f), num, num2))
		{
			_buildingSkipped++;
			return;
		}
		if (_buildingMultiPaint && !MaterialRules.Accepts(ClassifyPiece(record.Piece), _multiFinish))
		{
			_buildingSkipped++;
			return;
		}
		if (!record.View.IsOwner())
		{
			if (record.OwnershipDeadline == 0f)
			{
				record.OwnershipDeadline = Time.unscaledTime + 2f;
				record.View.ClaimOwnership();
			}
			if (!record.View.IsOwner())
			{
				if (Time.unscaledTime < record.OwnershipDeadline)
				{
					_buildingQueue.Enqueue(record);
				}
				else
				{
					_buildingSkipped++;
				}
				return;
			}
		}
		int num3 = (_buildingIsUndo ? record.Finish : (_buildingMultiPaint ? _multiFinish : 0));
		float num4 = (_buildingIsUndo ? record.Opacity : (_buildingMultiPaint ? BatchPaintRules.Opacity(_multiFinish, ClassifyPiece(record.Piece) == NaturalPaintLogic.PaintMaterial.Crystal, _multiOpacity, record.Opacity) : (-1f)));
		if (BuildingClearRules.Matches(num, num2, num3, num4))
		{
			_buildingSkipped++;
			return;
		}
		zDO.Set(PaintHash, num3, false);
		zDO.Set(CrystalOpacityHash, num4);
		if (!_buildingIsUndo)
		{
			record.AppliedFinish = num3;
			record.AppliedOpacity = num4;
		}
		if (_buildingIsUndo)
		{
			_buildingUndo.Remove(record);
		}
		else
		{
			if (!_buildingStartedUndoRecord)
			{
				_buildingUndo.Clear();
				_undoPlayer = _buildingPlayer;
				_buildingStartedUndoRecord = true;
			}
			_buildingUndo.Add(record);
		}
		_buildingChanged++;
		RefreshSaved(record.Piece);
	}

	private void CancelBuildingClear()
	{
		if (_buildingStage == BuildingStage.Running)
		{
			((BaseUnityPlugin)this).Logger.LogInfo((object)("[paint.batch.done] changed=" + _buildingChanged + "; skipped=" + _buildingSkipped + "; stopped=" + _buildingQueue.Count));
			Tell(Player.m_localPlayer, (_buildingIsUndo ? "Restore" : (_buildingMultiPaint ? "Paint" : "Clear")) + ": " + _buildingChanged + " changed, " + _buildingSkipped + " skipped, " + _buildingQueue.Count + " stopped. Undo remaining: " + _buildingUndo.Count + ".");
		}
		bool buildingClearActive = BuildingClearActive;
		_buildingStage = BuildingStage.None;
		_buildingMultiPaint = false;
		_buildingSelection.Clear();
		_buildingQueue.Clear();
		_buildingPlayer = null;
		_buildingGraph = null;
		_buildingCandidates.Clear();
		_buildingNodes.Clear();
		_buildingNodeBounds.Clear();
		_buildingSeed = null;
		if (Object.op_Implicit((Object)(object)_buildingCanvas))
		{
			_buildingCanvas.SetActive(false);
		}
		ClearBuildingPreview();
		if (buildingClearActive)
		{
			_releaseGuard.Arm(Time.frameCount);
			_nextUse = Time.unscaledTime + 0.3f;
		}
		if (Object.op_Implicit((Object)(object)EventSystem.current) && Object.op_Implicit((Object)(object)EventSystem.current.currentSelectedGameObject) && Object.op_Implicit((Object)(object)_buildingCanvas) && EventSystem.current.currentSelectedGameObject.transform.IsChildOf(_buildingCanvas.transform))
		{
			EventSystem.current.SetSelectedGameObject((GameObject)null);
		}
	}

	private void HighlightBuildingPiece(Piece piece)
	{
		if (Object.op_Implicit((Object)(object)piece) && _painted.TryGetValue(piece, out var value) && _buildingPreviewPieces.Add(piece))
		{
			BuildSurfaces(value);
			ApplyPiece(value);
		}
	}

	private void RemoveBuildingHighlight(Piece piece)
	{
		_buildingPreviewPieces.Remove(piece);
		if (Object.op_Implicit((Object)(object)piece))
		{
			RefreshSaved(piece);
		}
	}

	private bool ApplyBuildingPreview(Surface surface)
	{
		//IL_00be: 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)
		//IL_00c3: Unknown result type (might be due to invalid IL or missing references)
		//IL_00cd: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d2: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d6: Unknown result type (might be due to invalid IL or missing references)
		//IL_00e9: Unknown result type (might be due to invalid IL or missing references)
		//IL_00fc: Unknown result type (might be due to invalid IL or missing references)
		//IL_0175: Unknown result type (might be due to invalid IL or missing references)
		if (!_buildingPreviewPieces.Contains(surface.Piece.Piece))
		{
			return false;
		}
		int saved = surface.Piece.Saved;
		bool flag = saved != 0 && Palette.IsValid(saved) && MaterialRules.Accepts(surface.Piece.Kind, saved);
		Finish finish = Palette.Get(flag ? saved : 0);
		foreach (Slot slot in surface.Slots)
		{
			Color val = Color.Lerp((Color)(flag ? new Color(slot.Base.r * finish.R, slot.Base.g * finish.G, slot.Base.b * finish.B, slot.Base.a) : slot.Base), BuildingPreviewColour, 0.65f);
			val.r = Mathf.Clamp01(val.r);
			val.g = Mathf.Clamp01(val.g);
			val.b = Mathf.Clamp01(val.b);
			val.a = ((surface.Piece.Kind == NaturalPaintLogic.PaintMaterial.Crystal) ? Mathf.Max(0.4f, CrystalOpacity.Alpha(slot.Base.a, surface.Piece.Opacity)) : slot.Base.a);
			surface.Renderer.GetPropertyBlock(surface.Block);
			surface.Block.SetColor(slot.Property, val);
			surface.Renderer.SetPropertyBlock(surface.Block, slot.Index);
		}
		surface.Applied = true;
		return true;
	}

	private void ClearBuildingPreview()
	{
		List<Piece> list = new List<Piece>(_buildingPreviewPieces);
		_buildingPreviewPieces.Clear();
		foreach (Piece item in list)
		{
			if (Object.op_Implicit((Object)(object)item))
			{
				try
				{
					RefreshSaved(item);
				}
				catch (Exception ex)
				{
					ReportOnce("paint.building.preview.restore", ex);
				}
			}
		}
	}

	private bool InBuildingScope(Vector3 position)
	{
		//IL_0000: Unknown result type (might be due to invalid IL or missing references)
		//IL_0002: Unknown result type (might be due to invalid IL or missing references)
		//IL_0007: Unknown result type (might be due to invalid IL or missing references)
		//IL_000c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0014: 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_002a: Unknown result type (might be due to invalid IL or missing references)
		Vector3 val = position - _buildingSeedPosition;
		float sqrMagnitude = ((Vector3)(ref val)).sqrMagnitude;
		val = position - ((Component)_buildingPlayer).transform.position;
		return BatchPaintRules.InScope(sqrMagnitude, ((Vector3)(ref val)).sqrMagnitude);
	}

	private void FailBuilding(string message)
	{
		((BaseUnityPlugin)this).Logger.LogWarning((object)("[paint.batch.stop] " + message));
		CancelBuildingClear();
		Tell(Player.m_localPlayer, message);
	}

	private void StartMultiPaint()
	{
		_multiFinish = SelectedFinish.Id;
		_multiOpacity = CrystalOpacity.Normalize(_selectedOpacity.Value);
		ClosePalette();
		ClearHover();
		_buildingPlayer = Player.m_localPlayer;
		_buildingIsUndo = false;
		_buildingMultiPaint = true;
		_buildingSelection.Clear();
		_buildingClearMaterial = NaturalPaintLogic.PaintMaterial.Unsupported;
		_buildingStage = BuildingStage.Selecting;
		_nextUse = Time.unscaledTime + 0.3f;
		Tell(_buildingPlayer, "Multi-paint: " + Palette.Get(_multiFinish).Name + ". Click pieces to select. Right-click / Enter / Y to review.");
	}

	private void ToggleMultiPiece(Player player)
	{
		//IL_015f: Unknown result type (might be due to invalid IL or missing references)
		//IL_026f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0274: Unknown result type (might be due to invalid IL or missing references)
		if (OverlayBlocksInput || CameraInputSuppressed || Time.unscaledTime < _nextUse || !CanUseTool(player))
		{
			return;
		}
		_nextUse = Time.unscaledTime + 0.25f;
		if (!TryGetBrushTarget(player, out var target))
		{
			Tell(player, "Aim at a building piece within brush reach.");
			return;
		}
		int num = _buildingSelection.FindIndex((BuildingRecord r) => (Object)(object)r.Piece == (Object)(object)target.Piece);
		if (num >= 0)
		{
			_buildingSelection.RemoveAt(num);
			if (_buildingSelection.Count == 0)
			{
				_buildingClearMaterial = NaturalPaintLogic.PaintMaterial.Unsupported;
			}
			RemoveBuildingHighlight(target.Piece);
		}
		else if (_buildingSelection.Count >= 500)
		{
			Tell(player, "Select up to 500 pieces per batch.");
		}
		else if (!MaterialRules.Accepts(target.Kind, _multiFinish))
		{
			Tell(player, "This finish cannot paint " + MaterialRules.DisplayName(target.Kind) + ".");
		}
		else if (_multiFinish == 0 && _buildingSelection.Count > 0 && !BatchPaintRules.SameClearMaterial(_buildingClearMaterial, target.Kind))
		{
			Tell(player, "This removal batch is " + MaterialRules.DisplayName(_buildingClearMaterial) + " only. Clear the selection to choose another material.");
		}
		else
		{
			if (!PrivateArea.CheckAccess(((Component)target.Piece).transform.position, 0f, true, false))
			{
				return;
			}
			if (!HasPaintableSurfaces(target.Piece))
			{
				Tell(player, "This piece has no supported paint surface.");
				return;
			}
			ZNetView component = ((Component)target.Piece).GetComponent<ZNetView>();
			if (!Object.op_Implicit((Object)(object)component) || !component.IsValid())
			{
				return;
			}
			int num2 = component.GetZDO().GetInt(PaintHash, 0);
			float num3 = component.GetZDO().GetFloat(CrystalOpacityHash, -1f);
			float expectedOpacity = BatchPaintRules.Opacity(_multiFinish, target.Kind == NaturalPaintLogic.PaintMaterial.Crystal, _multiOpacity, num3);
			if (BuildingClearRules.Matches(num2, num3, _multiFinish, expectedOpacity))
			{
				Tell(player, "This piece already has the selected finish.");
				return;
			}
			if (_multiFinish == 0 && _buildingSelection.Count == 0)
			{
				_buildingClearMaterial = target.Kind;
			}
			_buildingSelection.Add(new BuildingRecord
			{
				Piece = target.Piece,
				View = component,
				Id = component.GetZDO().m_uid,
				Finish = num2,
				Opacity = num3
			});
			HighlightBuildingPiece(target.Piece);
		}
	}

	private void ReviewMultiPaint()
	{
		if (_buildingSelection.Count == 0)
		{
			Tell(_buildingPlayer, "Select at least one piece first.");
			return;
		}
		ClearHover();
		ShowBuildingReview("Apply " + Palette.Get(_multiFinish).Name + ((_multiFinish == 0) ? (" (" + MaterialRules.DisplayName(_buildingClearMaterial) + " only)") : "") + " to " + _buildingSelection.Count + " selected pieces?\nGREEN marks your selected pieces. It is not the final colour.\nEdit selection / Y: add or remove pieces.\nChanges are applied together after confirmation. Undo last batch is available.");
	}

	private void ReturnToMultiSelection()
	{
		if (_buildingMultiPaint && _buildingStage == BuildingStage.Review)
		{
			if (Object.op_Implicit((Object)(object)EventSystem.current))
			{
				EventSystem.current.SetSelectedGameObject((GameObject)null);
			}
			_buildingCanvas.SetActive(false);
			_buildingStage = BuildingStage.Selecting;
			_releaseGuard.Arm(Time.frameCount);
			_nextUse = Time.unscaledTime + 0.3f;
		}
	}

	internal static bool CanUseTool(Player player)
	{
		if (Object.op_Implicit((Object)(object)player) && !((Character)player).IsDead() && !((Character)player).IsTeleporting() && !((Character)player).InCutscene() && !InventoryGui.IsVisible() && !Hud.InRadial() && !Menu.IsVisible() && !Console.IsVisible() && !StoreGui.IsVisible() && !TextInput.IsVisible() && !Minimap.IsOpen() && (!Object.op_Implicit((Object)(object)Chat.instance) || !Chat.instance.HasFocus()))
		{
			return !GameCamera.InFreeFly();
		}
		return false;
	}

	internal static NaturalPaintLogic.PaintMaterial ClassifyPiece(Piece piece)
	{
		if (!Object.op_Implicit((Object)(object)piece))
		{
			return (NaturalPaintLogic.PaintMaterial)SurfaceSupport.RefineKind(0, piece);
		}
		WearNTear component = ((Component)piece).GetComponent<WearNTear>();
		string support = (Object.op_Implicit((Object)(object)component) ? ((object)Unsafe.As<MaterialType, MaterialType>(ref component.m_materialType)/*cast due to .constrained prefix*/).ToString() : "");
		NaturalPaintLogic.PaintMaterial paintMaterial = MaterialRules.Classify(support, ((Object)piece).name, woodenSurface: false, stoneSurface: false);
		if (paintMaterial != NaturalPaintLogic.PaintMaterial.Unsupported)
		{
			return (NaturalPaintLogic.PaintMaterial)SurfaceSupport.RefineKind((int)paintMaterial, piece);
		}
		bool flag = false;
		bool flag2 = false;
		Renderer[] componentsInChildren = ((Component)piece).GetComponentsInChildren<Renderer>(true);
		foreach (Renderer val in componentsInChildren)
		{
			if ((Object)(object)((Component)val).GetComponentInParent<Piece>() != (Object)(object)piece)
			{
				continue;
			}
			Material[] sharedMaterials = val.sharedMaterials;
			foreach (Material val2 in sharedMaterials)
			{
				if (Object.op_Implicit((Object)(object)val2))
				{
					flag |= MaterialRules.WoodLabel(((Object)val2).name) && MaterialRules.PaintableSlot(NaturalPaintLogic.PaintMaterial.Wood, ((Object)val2).name, ((Object)val).name);
					flag2 |= MaterialRules.StoneLabel(((Object)val2).name) && MaterialRules.PaintableSlot(NaturalPaintLogic.PaintMaterial.Stone, ((Object)val2).name, ((Object)val).name);
				}
			}
		}
		return (NaturalPaintLogic.PaintMaterial)SurfaceSupport.RefineKind((int)MaterialRules.Classify(support, ((Object)piece).name, flag, flag2), piece);
	}

	internal static bool TryGetBrushTarget(Player player, out BrushTarget target)
	{
		//IL_005a: 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_007c: 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_00aa: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b1: Unknown result type (might be due to invalid IL or missing references)
		//IL_0167: Unknown result type (might be due to invalid IL or missing references)
		//IL_016c: Unknown result type (might be due to invalid IL or missing references)
		target = default(BrushTarget);
		GameCamera instance = GameCamera.instance;
		if (!Object.op_Implicit((Object)(object)player) || !Object.op_Implicit((Object)(object)instance))
		{
			return false;
		}
		float num = ((Object.op_Implicit((Object)(object)Instance) && Instance.PaintCameraActive) ? Instance.CameraPaintReach : Math.Min(5f, player.m_maxPlaceDistance));
		float num2 = CameraMath.BuildRayDistance(Vector3.Distance(((Component)instance).transform.position, ((Character)player).GetEyePoint()), num, 0f);
		RaycastHit val = default(RaycastHit);
		if (!Physics.Raycast(((Component)instance).transform.position, ((Component)instance).transform.forward, ref val, num2, (int)RemoveRayMask.GetValue(player), (QueryTriggerInteraction)1))
		{
			return false;
		}
		if (Vector3.Distance(((Character)player).GetEyePoint(), ((RaycastHit)(ref val)).point) > num)
		{
			return false;
		}
		Piece componentInParent = ((Component)((Rayca