Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of ChestGrid v1.0.0
plugins\ChestGrid\ChestGrid.dll
Decompiled 3 days agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Configuration; using HarmonyLib; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("ChestGrid")] [assembly: AssemblyProduct("ChestGrid")] [assembly: AssemblyDescription("Valheim chest placement snapping mod.")] [assembly: AssemblyCompany("LEGIOmods")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyMetadata("AI_Assisted_Creation", "This assembly was partially created with the assistance of OpenAI Codex for code generation, packaging, and documentation.")] [assembly: AssemblyMetadata("AI_Model_Vendor", "OpenAI")] [assembly: AssemblyMetadata("AI_Agent", "OpenAI Codex")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyVersion("1.0.0.0")] namespace ChestGrid; [BepInPlugin("ChestGrid", "ChestGrid", "1.0.0")] public class ChestGridPlugin : BaseUnityPlugin { private struct SnapCandidate { public readonly Vector3 Position; public readonly Quaternion AnchorRotation; public SnapCandidate(Vector3 position, Quaternion anchorRotation) { //IL_0001: 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_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) Position = position; AnchorRotation = anchorRotation; } } public const string GUID = "ChestGrid"; public const string NAME = "ChestGrid"; public const string VERSION = "1.0.0"; private const float DefaultChestCacheInterval = 1f; private static readonly FieldInfo PlacementGhostField = AccessTools.Field(typeof(Player), "m_placementGhost"); private static ChestGridPlugin instance; private ConfigEntry<bool> modEnabled; private ConfigEntry<float> snapRange; private ConfigEntry<float> gridWidth; private ConfigEntry<float> gridDepth; private ConfigEntry<float> gridHeight; private ConfigEntry<float> verticalSnapRange; private ConfigEntry<float> placementYOffset; private ConfigEntry<bool> usePlacementHeightForVerticalSnap; private ConfigEntry<float> occupiedRadius; private ConfigEntry<float> chestCacheInterval; private ConfigEntry<bool> snapRotation; private ConfigEntry<bool> disableWhileShiftHeld; private ConfigEntry<string> normalChestPrefabNames; private ConfigEntry<string> normalChestPieceNames; private readonly Harmony harmony = new Harmony("ChestGrid"); private readonly List<Piece> cachedChestPieces = new List<Piece>(); private HashSet<string> chestPrefabNameSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase); private HashSet<string> chestPieceNameSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase); private float nextChestCacheRefreshTime; private void Awake() { instance = this; modEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Enable ChestGrid."); snapRange = ((BaseUnityPlugin)this).Config.Bind<float>("Snapping", "SnapRange", 1.55f, "Maximum distance from the placement ghost to a chest grid slot before snapping."); gridWidth = ((BaseUnityPlugin)this).Config.Bind<float>("Snapping", "GridWidth", 1.28f, "Local left/right spacing between normal wooden chests."); gridDepth = ((BaseUnityPlugin)this).Config.Bind<float>("Snapping", "GridDepth", 0.74f, "Local front/back spacing between normal wooden chests."); gridHeight = ((BaseUnityPlugin)this).Config.Bind<float>("Snapping", "GridHeight", 1.05f, "Vertical spacing for stacked normal wooden chests."); verticalSnapRange = ((BaseUnityPlugin)this).Config.Bind<float>("Snapping", "VerticalSnapRange", 1.8f, "Maximum distance to vertical chest stack slots. Higher values make stacking easier to hit."); placementYOffset = ((BaseUnityPlugin)this).Config.Bind<float>("Snapping", "PlacementYOffset", 0.08f, "Small upward offset applied to snapped vertical chest positions when placement height is not used."); usePlacementHeightForVerticalSnap = ((BaseUnityPlugin)this).Config.Bind<bool>("Snapping", "UsePlacementHeightForVerticalSnap", true, "For vertical rows, keep the hammer placement height from the wood/floor and only snap horizontal chest grid coordinates."); occupiedRadius = ((BaseUnityPlugin)this).Config.Bind<float>("Snapping", "OccupiedRadius", 0.35f, "Minimum distance to another normal wooden chest before a grid slot is treated as occupied."); chestCacheInterval = ((BaseUnityPlugin)this).Config.Bind<float>("Performance", "ChestCacheInterval", 1f, "Seconds between expensive chest list refreshes while placing a wooden chest."); snapRotation = ((BaseUnityPlugin)this).Config.Bind<bool>("Snapping", "SnapRotation", true, "Rotate the placement ghost to match the chest it snaps to."); normalChestPrefabNames = ((BaseUnityPlugin)this).Config.Bind<string>("Chest Detection", "NormalChestPrefabNames", "piece_chest_wood", "Comma-separated prefab names treated as normal wooden chests."); normalChestPieceNames = ((BaseUnityPlugin)this).Config.Bind<string>("Chest Detection", "NormalChestPieceNames", "$piece_chestwood", "Comma-separated Piece.m_name values treated as normal wooden chests."); disableWhileShiftHeld = ((BaseUnityPlugin)this).Config.Bind<bool>("Controls", "DisableWhileShiftHeld", true, "Disable ChestGrid snapping while Left Shift or Right Shift is held."); RefreshNameSets(); normalChestPrefabNames.SettingChanged += delegate { RefreshNameSets(); ForceRefreshChestCache(); }; normalChestPieceNames.SettingChanged += delegate { RefreshNameSets(); ForceRefreshChestCache(); }; PatchPlacementGhost(); ((BaseUnityPlugin)this).Logger.LogInfo((object)"ChestGrid 1.0.0 loaded. GUID=ChestGrid"); } private void OnDestroy() { harmony.UnpatchSelf(); if (instance == this) { instance = null; } } private void RefreshNameSets() { chestPrefabNameSet = ParseNameSet(normalChestPrefabNames.Value); chestPieceNameSet = ParseNameSet(normalChestPieceNames.Value); } private static HashSet<string> ParseNameSet(string value) { return new HashSet<string>(from name in (value ?? string.Empty).Split(new char[1] { ',' }, StringSplitOptions.RemoveEmptyEntries) select name.Trim() into name where name.Length > 0 select name, StringComparer.OrdinalIgnoreCase); } private void PatchPlacementGhost() { //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Expected O, but got Unknown MethodInfo methodInfo = AccessTools.Method(typeof(Player), "UpdatePlacementGhost", (Type[])null, (Type[])null); MethodInfo methodInfo2 = AccessTools.Method(typeof(ChestGridPlugin), "UpdatePlacementGhostPostfix", (Type[])null, (Type[])null); if (methodInfo == null || methodInfo2 == null) { ((BaseUnityPlugin)this).Logger.LogWarning((object)"Could not patch Player.UpdatePlacementGhost. Chest snapping will be disabled."); } else { harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(methodInfo2), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } private static void UpdatePlacementGhostPostfix(Player __instance) { if (!((Object)(object)instance == (Object)null) && instance.modEnabled.Value) { instance.TrySnapPlacementGhost(__instance); } } private void TrySnapPlacementGhost(Player player) { //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0050: 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_0086: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player == (Object)null || PlacementGhostField == null || IsShiftDisableHeld()) { return; } object? value = PlacementGhostField.GetValue(player); GameObject val = (GameObject)((value is GameObject) ? value : null); if ((Object)(object)val == (Object)null || !IsNormalWoodChest(val)) { return; } Vector3 position = val.transform.position; if (TryFindBestSnap(position, val, out var best)) { val.transform.position = best.Position; if (snapRotation.Value) { Transform transform = val.transform; Quaternion anchorRotation = best.AnchorRotation; transform.rotation = Quaternion.Euler(0f, ((Quaternion)(ref anchorRotation)).eulerAngles.y, 0f); } } } private bool TryFindBestSnap(Vector3 desiredPosition, GameObject ghost, out SnapCandidate best) { //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: 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_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_00e7: 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_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_00db: 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_0108: Unknown result type (might be due to invalid IL or missing references) //IL_011b: Unknown result type (might be due to invalid IL or missing references) //IL_0124: Unknown result type (might be due to invalid IL or missing references) best = default(SnapCandidate); bool result = false; float num = float.MaxValue; List<Piece> list = GetCachedChestPieces(ghost); foreach (Piece item in list) { if ((Object)(object)item == (Object)null || (Object)(object)((Component)item).gameObject == (Object)(object)ghost) { continue; } foreach (Vector3 gridOffset in GetGridOffsets()) { bool flag = Math.Abs(gridOffset.y) > 0.01f; float num2 = Math.Max(0f, flag ? verticalSnapRange.Value : snapRange.Value); Vector3 val = ((Component)item).transform.TransformPoint(gridOffset); if (flag) { val.y = (usePlacementHeightForVerticalSnap.Value ? desiredPosition.y : (val.y + placementYOffset.Value)); } Vector3 val2 = desiredPosition - val; float sqrMagnitude = ((Vector3)(ref val2)).sqrMagnitude; if (!(sqrMagnitude > num2 * num2) && !(sqrMagnitude > num) && !IsOccupied(val, ghost, item, list)) { result = true; num = sqrMagnitude; best = new SnapCandidate(val, ((Component)item).transform.rotation); } } } return result; } private List<Piece> GetCachedChestPieces(GameObject ghost) { cachedChestPieces.RemoveAll((Piece piece) => (Object)(object)piece == (Object)null || (Object)(object)((Component)piece).gameObject == (Object)(object)ghost); if (Time.time < nextChestCacheRefreshTime) { return cachedChestPieces; } nextChestCacheRefreshTime = Time.time + Math.Max(0.1f, chestCacheInterval.Value); cachedChestPieces.Clear(); Piece[] array = Object.FindObjectsOfType<Piece>(); foreach (Piece val in array) { if ((Object)(object)val != (Object)null && (Object)(object)((Component)val).gameObject != (Object)(object)ghost && IsNormalWoodChest(((Component)val).gameObject)) { cachedChestPieces.Add(val); } } return cachedChestPieces; } private void ForceRefreshChestCache() { nextChestCacheRefreshTime = 0f; cachedChestPieces.Clear(); } private bool IsShiftDisableHeld() { if (disableWhileShiftHeld.Value) { if (!Input.GetKey((KeyCode)304)) { return Input.GetKey((KeyCode)303); } return true; } return false; } private IEnumerable<Vector3> GetGridOffsets() { float width = Math.Max(0.01f, gridWidth.Value); float depth = Math.Max(0.01f, gridDepth.Value); float height = Math.Max(0.01f, gridHeight.Value); for (int y = -1; y <= 1; y++) { for (int z = -1; z <= 1; z++) { for (int x = -1; x <= 1; x++) { if (x != 0 || y != 0 || z != 0) { yield return new Vector3((float)x * width, (float)y * height, (float)z * depth); } } } } } private bool IsOccupied(Vector3 candidatePosition, GameObject ghost, Piece anchor, List<Piece> chestPieces) { //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0056: 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) float num = Math.Max(0.01f, occupiedRadius.Value); float num2 = num * num; foreach (Piece chestPiece in chestPieces) { if (!((Object)(object)chestPiece == (Object)null) && !((Object)(object)chestPiece == (Object)(object)anchor) && !((Object)(object)((Component)chestPiece).gameObject == (Object)(object)ghost)) { Vector3 val = candidatePosition - ((Component)chestPiece).transform.position; if (((Vector3)(ref val)).sqrMagnitude <= num2) { return true; } } } return false; } private bool IsNormalWoodChest(GameObject gameObject) { if ((Object)(object)gameObject == (Object)null) { return false; } string item = NormalizeObjectName(((Object)gameObject).name); if (chestPrefabNameSet.Contains(item)) { return true; } Piece component = gameObject.GetComponent<Piece>(); if ((Object)(object)component != (Object)null) { return chestPieceNameSet.Contains(component.m_name ?? string.Empty); } return false; } private static string NormalizeObjectName(string name) { if (string.IsNullOrEmpty(name)) { return string.Empty; } int num = name.IndexOf("(Clone)", StringComparison.OrdinalIgnoreCase); if (num >= 0) { name = name.Substring(0, num); } return name.Trim(); } }