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 Sinka v1.3.0
plugins/Sinka/Sinka.dll
Decompiled 6 days agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.6.2", FrameworkDisplayName = ".NET Framework 4.6.2")] [assembly: AssemblyCompany("Thijssen Software")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyCopyright("Copyright (c) 2026 Robbin Thijssen")] [assembly: AssemblyDescription("Snap points for chests, fences and anything else that will not line up.")] [assembly: AssemblyFileVersion("1.3.0.0")] [assembly: AssemblyInformationalVersion("1.3.0+6444bd05d2b7b048aeee30a6e5c1777b2ee00dbd")] [assembly: AssemblyProduct("Sinka")] [assembly: AssemblyTitle("Sinka")] [assembly: AssemblyVersion("1.3.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace Sinka { internal static class Buildable { private static ObjectDB _builtFrom; private static HashSet<string> _names; private const int MaxWaitFrames = 900; private static int _waited; private static bool _gaveUp; public static bool Ready() { if (!SinkaConfig.BuildablePiecesOnly.Value) { return true; } if (Build()) { return true; } if (_gaveUp) { return true; } if (++_waited < 900) { return false; } _gaveUp = true; SinkaPlugin.Log.LogWarning((object)("No piece tables found after " + 900 + " frames, so BuildablePiecesOnly cannot be applied. Snapping everything with a Piece component instead, which includes dungeon loot chests and pots.")); return true; } public static bool Includes(GameObject prefab) { if (!SinkaConfig.BuildablePiecesOnly.Value) { return true; } if (_names == null) { return true; } return _names.Contains(((Object)prefab).name); } private static bool Build() { ObjectDB instance = ObjectDB.instance; if ((Object)(object)instance == (Object)null || instance.m_items == null || instance.m_items.Count == 0) { return false; } if ((Object)(object)_builtFrom == (Object)(object)instance && _names != null) { return true; } HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase); int num = 0; foreach (GameObject item in instance.m_items) { if ((Object)(object)item == (Object)null) { continue; } ItemDrop component = item.GetComponent<ItemDrop>(); if ((Object)(object)component == (Object)null || component.m_itemData == null || component.m_itemData.m_shared == null) { continue; } PieceTable buildPieces = component.m_itemData.m_shared.m_buildPieces; if ((Object)(object)buildPieces == (Object)null || buildPieces.m_pieces == null) { continue; } num++; foreach (GameObject piece in buildPieces.m_pieces) { if ((Object)(object)piece != (Object)null) { hashSet.Add(((Object)piece).name); } } } if (num == 0 || hashSet.Count == 0) { return false; } _names = hashSet; _builtFrom = instance; SinkaPlugin.Log.LogInfo((object)("Buildable set: " + hashSet.Count + " piece(s) across " + num + " piece table(s).")); return true; } } internal static class CentrePoints { internal const string BaseName = "snap_centre-base"; private const string Tag = "snappoint"; private const float TopTolerance = 0.1f; private const float ReachSlack = 0.05f; private static readonly HashSet<string> PropPieceNames = new HashSet<string>(StringComparer.Ordinal); private static readonly Dictionary<string, Bounds?> Footprints = new Dictionary<string, Bounds?>(StringComparer.OrdinalIgnoreCase); private static Piece _aimedPiece; private static Vector3 _aimedCentre; private static Transform _point; private static bool _searching; private static int _ghostLayer = -1; internal static void Reset() { PropPieceNames.Clear(); Footprints.Clear(); _aimedPiece = null; Release(); } internal static bool Wanted(GameObject prefab) { if (SinkaConfig.CentreOnSurface.Value) { return SinkaConfig.IsCentreProp(((Object)prefab).name); } return false; } internal static bool AddBase(GameObject prefab) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_003d: 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 (!SnapPoints.Footprint(prefab, out var bounds)) { return false; } Vector3 val = default(Vector3); ((Vector3)(ref val))..ctor(((Bounds)(ref bounds)).center.x, ((Bounds)(ref bounds)).min.y, ((Bounds)(ref bounds)).center.z); SnapPoints.Create(prefab, "snap_centre-base", val); Register(prefab, val); return true; } internal static bool Readopt(GameObject prefab) { //IL_002b: Unknown result type (might be due to invalid IL or missing references) Transform val = prefab.transform.Find("snap_centre-base"); if ((Object)(object)val == (Object)null || !((Component)val).CompareTag("snappoint")) { return false; } Register(prefab, val.localPosition); return true; } private static void Register(GameObject prefab, Vector3 local) { Piece component = prefab.GetComponent<Piece>(); if ((Object)(object)component != (Object)null && !string.IsNullOrEmpty(component.m_name)) { PropPieceNames.Add(component.m_name); } if (SinkaConfig.Verbose.Value) { SinkaPlugin.Log.LogInfo((object)(((Object)prefab).name + ": centre point at its base " + ((Vector3)(ref local)).ToString("F2"))); } } internal static void ReportProps() { List<string> list = new List<string>(); foreach (string item in SinkaConfig.ConfiguredCentreProps()) { GameObject prefab = ZNetScene.instance.GetPrefab(item); if (!((Object)(object)prefab == (Object)null) && !Buildable.Includes(prefab)) { list.Add(item); } } if (list.Count > 0) { SinkaPlugin.Log.LogWarning((object)("CentreProps pieces that are in no build menu: " + string.Join(", ", list.ToArray()))); } } private static string PrefabName(string name) { int num = name.IndexOf("(Clone)", StringComparison.Ordinal); if (num >= 0) { return name.Substring(0, num); } return name; } private static bool Measure(string prefabName, out Bounds footprint) { //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) if (!Footprints.TryGetValue(prefabName, out var value)) { value = null; GameObject val = (((Object)(object)ZNetScene.instance != (Object)null) ? ZNetScene.instance.GetPrefab(prefabName) : null); try { if ((Object)(object)val != (Object)null && SnapPoints.Footprint(val, out var bounds)) { value = bounds; } } catch (Exception ex) { SinkaPlugin.Log.LogWarning((object)("Could not measure " + prefabName + " to centre on: " + ex.Message)); } Footprints[prefabName] = value; } footprint = value.GetValueOrDefault(); return value.HasValue; } private static void Release() { _searching = false; if (!((Object)(object)_point == (Object)null)) { ((Component)_point).tag = "Untagged"; _point.SetParent((Transform)null, false); } } [HarmonyPostfix] [HarmonyPatch(typeof(Player), "PieceRayTest")] private static void Aimed(bool __result, ref Vector3 point, ref Vector3 normal, ref Piece piece) { //IL_0051: 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_0069: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_007d: 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_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) _aimedPiece = null; if (PropPieceNames.Count != 0 && __result && !((Object)(object)piece == (Object)null) && !(normal.y < 0.8f) && Measure(PrefabName(((Object)((Component)piece).gameObject).name), out var footprint)) { Vector3 val = ((Component)piece).transform.TransformPoint(new Vector3(((Bounds)(ref footprint)).center.x, ((Bounds)(ref footprint)).max.y, ((Bounds)(ref footprint)).center.z)); if (!(Mathf.Abs(point.y - val.y) > 0.1f)) { _aimedPiece = piece; _aimedCentre = val; } } } [HarmonyPostfix] [HarmonyPatch(typeof(Piece), "GetSnapPoints", new Type[] { typeof(List<Transform>) })] private static void Listed(Piece __instance, List<Transform> points) { if (PropPieceNames.Count == 0 || points == null || !PropPieceNames.Contains(__instance.m_name)) { return; } if (_ghostLayer < 0) { _ghostLayer = LayerMask.NameToLayer("ghost"); } if ((Object)(object)_aimedPiece != (Object)null && ((Component)__instance).gameObject.layer == _ghostLayer) { return; } Transform transform = ((Component)__instance).transform; int num = points.Count - 1; while (num >= 0) { Transform val = points[num]; if (!((Object)(object)val == (Object)null) && !((Object)(object)val.parent != (Object)(object)transform)) { if (((Object)val).name == "snap_centre-base") { points.RemoveAt(num); } num--; continue; } break; } } [HarmonyPrefix] [HarmonyPatch(typeof(Player), "FindClosestSnapPoints")] private static void Searching(Transform ghost, ref float maxSnapDistance) { //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_0061: 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) Release(); Piece val = default(Piece); if ((Object)(object)_aimedPiece == (Object)null || (Object)(object)ghost == (Object)null || !((Component)ghost).TryGetComponent<Piece>(ref val) || !PropPieceNames.Contains(val.m_name)) { return; } Transform val2 = ghost.Find("snap_centre-base"); if (!((Object)(object)val2 == (Object)null)) { if ((Object)(object)_point == (Object)null) { _point = new GameObject("snap_centre-top") { hideFlags = (HideFlags)61 }.transform; } _point.SetParent(((Component)_aimedPiece).transform, false); _point.position = _aimedCentre; ((Component)_point).tag = "snappoint"; _searching = true; float num = Vector3.Distance(val2.position, _aimedCentre) + 0.05f; if (num > maxSnapDistance) { maxSnapDistance = num; } } } [HarmonyPostfix] [HarmonyPatch(typeof(Player), "FindClosestSnapPoints")] private static void Searched() { Release(); } [HarmonyPostfix] [HarmonyPatch(typeof(Piece), "GetSnapPoints", new Type[] { typeof(Vector3), typeof(float), typeof(List<Transform>), typeof(List<Piece>) })] private static void Gathered(List<Transform> points) { if (!_searching || (Object)(object)_point == (Object)null || points == null) { return; } for (int num = points.Count - 1; num >= 0; num--) { if ((Object)(object)points[num] != (Object)(object)_point) { points.RemoveAt(num); } } } } internal static class SinkaConfig { internal sealed class Socket { internal HashSet<string> Targets; internal float StickOut; } private const string DefaultFences = "wood_fence, piece_sharpstakes, piece_stakewall_blackwood, piece_dvergr_sharpstakes, piece_dvergr_stake_wall"; private const string DefaultPoles = "wood_pole, wood_pole2, wood_pole_log, darkwood_pole, darkwood_pole4, ashwood_pole_1m, ashwood_pole_2m"; private const string DefaultSockets = "piece_groundtorch_wood : wood_pole, wood_pole2, wood_pole_log, darkwood_pole, darkwood_pole4, ashwood_pole_1m, ashwood_pole_2m : 0.25 ; piece_groundtorch, piece_groundtorch_blue, piece_groundtorch_green : wood_pole, wood_pole2, wood_pole_log, darkwood_pole, darkwood_pole4, ashwood_pole_1m, ashwood_pole_2m ; piece_groundtorch_mist : wood_pole, wood_pole2, wood_pole_log, darkwood_pole, darkwood_pole4, ashwood_pole_1m, ashwood_pole_2m : 0.27"; private const string DefaultCentreProps = "piece_brazierfloor01, piece_brazierfloor02"; public static ConfigEntry<bool> SnapContainers; public static ConfigEntry<bool> SnapFences; public static ConfigEntry<bool> SnapUnsnappedPieces; public static ConfigEntry<bool> BuildablePiecesOnly; public static ConfigEntry<string> FencePrefabs; public static ConfigEntry<string> ExcludePrefabs; public static ConfigEntry<string> PointOverrides; public static ConfigEntry<float> Gap; public static ConfigEntry<string> GapOverrides; public static ConfigEntry<float> FenceLadderStep; public static ConfigEntry<float> FenceLadderBelow; public static ConfigEntry<bool> SnapSockets; public static ConfigEntry<string> Sockets; public static ConfigEntry<Protection> ProtectFrom; public static ConfigEntry<bool> CentreOnSurface; public static ConfigEntry<string> CentreProps; public static ConfigEntry<bool> StackContainers; public static ConfigEntry<bool> Verbose; private static HashSet<string> _fences; private static HashSet<string> _excluded; private static HashSet<string> _centreProps; internal const float Measured = -1f; private static Dictionary<string, Socket> _sockets; private static HashSet<string> _targets; private static Dictionary<string, float> _gaps; private static Dictionary<string, Vector3[]> _overrides; public static void Bind(ConfigFile config) { SnapContainers = config.Bind<bool>("Snapping", "SnapContainers", true, "Snap anything buildable that holds items. Matched on components, so modded chests are covered without naming them."); SnapFences = config.Bind<bool>("Snapping", "SnapFences", true, "Snap the fences and stake walls listed in FencePrefabs."); SnapUnsnappedPieces = config.Bind<bool>("Snapping", "SnapUnsnappedPieces", false, "Snap every buildable piece the game never gave snap points to. Catches modded pieces for free, but also chairs, banners and item stands, where snapping tends to fight you rather than help."); BuildablePiecesOnly = config.Bind<bool>("Snapping", "BuildablePiecesOnly", true, "Only snap pieces that actually appear in a build menu. Off means anything with a Piece component qualifies, which includes dungeon loot chests and pots you cannot place and probably do not want to snap to."); FencePrefabs = config.Bind<string>("Snapping", "FencePrefabs", "wood_fence, piece_sharpstakes, piece_stakewall_blackwood, piece_dvergr_sharpstakes, piece_dvergr_stake_wall", "Comma-separated prefab names treated as fences. Names that do not exist are reported in the log at startup."); ExcludePrefabs = config.Bind<string>("Snapping", "ExcludePrefabs", "", "Comma-separated prefab names to leave alone, whatever else matches."); PointOverrides = config.Bind<string>("Snapping", "PointOverrides", "", "Exact snap points for named prefabs, replacing anything derived. Format:\n# prefab: x,y,z | x,y,z ; other_prefab: x,y,z\n# Semicolons separate prefabs, a colon follows the name, pipes separate\n# points, commas separate the three coordinates of one point. Naming a\n# prefab here is enough to get it snapped - it does not also have to be a\n# container or a listed fence. Decimals must use a dot, never a comma,\n# because a comma already separates coordinates. Gap and the fence ladder\n# do not apply: a point given here is used exactly as written."); Gap = config.Bind<float>("Snapping", "Gap", 0f, "Metres of space left between two chained pieces. 0 places them flush. Negative values are ignored - overlapping pieces just clip."); GapOverrides = config.Bind<string>("Snapping", "GapOverrides", "", "Gap for named prefabs, overriding Gap for those and leaving the rest alone.\n# Format: prefab: metres ; other_prefab: metres\n# Semicolons separate prefabs and a colon follows the name. Decimals must\n# use a dot. Negative values are ignored, as with Gap, and a name that\n# matches no prefab is reported in the log at startup. Points given by hand\n# through PointOverrides are used exactly as written and take no gap at all."); FenceLadderStep = config.Bind<float>("Snapping", "FenceLadderStep", 0.2f, "Vertical spacing of the snap points up each end of a fence, in metres. Smaller follows sloping ground more closely and costs more points per piece. 0 turns the ladder off and gives fences plain corners like a chest."); FenceLadderBelow = config.Bind<float>("Snapping", "FenceLadderBelow", 0.2f, "How far below its own base a fence's lowest rung sits, in metres. This is what lets the next panel step down rather than only up."); SnapSockets = config.Bind<bool>("Sockets", "SnapSockets", true, "Aim a piece listed in Sockets at the top of one of its targets and it snaps down into it, centred, with only its head standing above the top - a torch on a post. It snaps only while aimed at the target's top face, to that one target and nothing else, and nothing ever snaps to the piece afterwards. Hold the place-without-snapping key to set a torch on a pole top without sinking it."); Sockets = config.Bind<string>("Sockets", "Sockets", "piece_groundtorch_wood : wood_pole, wood_pole2, wood_pole_log, darkwood_pole, darkwood_pole4, ashwood_pole_1m, ashwood_pole_2m : 0.25 ; piece_groundtorch, piece_groundtorch_blue, piece_groundtorch_green : wood_pole, wood_pole2, wood_pole_log, darkwood_pole, darkwood_pole4, ashwood_pole_1m, ashwood_pole_2m ; piece_groundtorch_mist : wood_pole, wood_pole2, wood_pole_log, darkwood_pole, darkwood_pole4, ashwood_pole_1m, ashwood_pole_2m : 0.27", "What sinks into what, and how much of it still shows. Format:\n# piece, piece : target, target : metres showing ; next entry\n# Semicolons separate entries, colons separate an entry's three fields, and\n# commas separate names within a field. Decimals must use a dot.\n#\n# The third field is optional. Left out, the depth is measured off the piece\n# itself: as deep as it can sink while the zone a burning torch spreads fire\n# into stays clear of its own pole, which in the Ashlands is what stops the\n# pole burning out from under it. A typed value below that floor is raised to\n# it with a warning, and one past the piece's own length is capped, since a\n# socket below its tip would leave it floating above the pole instead.\n#\n# A target needs snap points of its own for any of this to work, because the\n# highest of them is what the piece lands on - which is why a pole of any\n# length is covered without naming its length. The startup log names any\n# target that has none, is in no build menu, or matches no prefab at all."); ProtectFrom = config.Bind<Protection>("Sockets", "ProtectFrom", Protection.All, "What a torch snapped into a pole keeps that pole safe from, for as long as the\n# torch stands in it. Removing the torch removes the protection.\n# All - every hit on the pole is ignored: creatures, players, fire, the\n# Ashlands cinders. The pole can then only be taken down by hand.\n# Fire - only fire is ignored: Ashlands cinders, burning, and the fire\n# share of a mixed hit. Creatures and players still break the pole.\n# Off - no protection.\n# It applies to every pole named as a target in Sockets. Only a torch Sinka\n# snapped counts: one set on top with the place-without-snapping key does not.\n# The wear that brings down a pole with nothing under it is never prevented.\n# The game's owner of the pole decides, so a player running without Sinka who\n# owns a pole at that moment does not honour it."); CentreOnSurface = config.Bind<bool>("Centring", "CentreOnSurface", true, "Aim a piece listed in CentreProps at the top face of any piece and it lands centred on that face. It centres only while the crosshair is on an upward face level with the target's top, and nothing ever snaps to the prop afterwards. Hold the place-without-snapping key to place it where you aim."); CentreProps = config.Bind<string>("Centring", "CentreProps", "piece_brazierfloor01, piece_brazierfloor02", "Comma-separated prefab names of the standing props that centre. Kept to the two floor braziers rather than every fire in the game: the bonfire and hearth names have not been confirmed against a running game, and a name that matches no prefab is reported in the log at startup, so adding one is safe to try."); StackContainers = config.Bind<bool>("Stacking", "StackContainers", true, "Let a chest be placed on top of another chest, and stay there.\n# A chest holds up a chest and nothing else: a wall, a beam or a torch on a\n# chest is still refused, which is vanilla's answer rather than a limit of\n# this setting.\n#\n# ON A SERVER, READ THIS FIRST. Whether a piece is supported is worked out\n# by whichever player's game owns it, and ownership follows whoever is\n# nearby. A player who does not have Sinka works it out the vanilla way,\n# finds a chest standing on nothing, and DESTROYS IT WITH EVERYTHING IN IT.\n# There is no fix for that from here, because the rule runs on their\n# machine. A stack is safe in single player, and on a server where every\n# player has this mod - a modpack that carries it, for instance. On a server\n# where some players do not, turn this off and lose nothing but the feature.\n#\n# The build-integrity colours still show a stacked chest as unsupported.\n# That is only the colour: the chest is exempt from the wear that colour\n# usually warns about."); Verbose = config.Bind<bool>("Diagnostics", "Verbose", false, "Log the measured footprint of every piece that gets snap points, and the colliders it was measured from."); } public static bool IsFence(string prefabName) { if (_fences == null) { _fences = Split(FencePrefabs.Value); } return _fences.Contains(prefabName); } public static bool IsExcluded(string prefabName) { if (_excluded == null) { _excluded = Split(ExcludePrefabs.Value); } if (_excluded.Count > 0) { return _excluded.Contains(prefabName); } return false; } public static IEnumerable<string> ConfiguredFences() { if (_fences == null) { _fences = Split(FencePrefabs.Value); } return _fences; } public static bool IsCentreProp(string prefabName) { return ConfiguredCentreProps().Contains(prefabName); } public static HashSet<string> ConfiguredCentreProps() { if (_centreProps == null) { _centreProps = Split(CentreProps.Value); } return _centreProps; } public static Socket SocketFor(string prefabName) { if (!ParsedSockets().TryGetValue(prefabName, out var value)) { return null; } return value; } public static bool IsSocketTarget(string prefabName) { return ConfiguredTargets().Contains(prefabName); } public static IEnumerable<string> ConfiguredSockets() { return ParsedSockets().Keys; } public static HashSet<string> ConfiguredTargets() { if (_targets != null) { return _targets; } _targets = new HashSet<string>(StringComparer.OrdinalIgnoreCase); foreach (Socket value in ParsedSockets().Values) { foreach (string target in value.Targets) { _targets.Add(target); } } return _targets; } private static Dictionary<string, Socket> ParsedSockets() { if (_sockets != null) { return _sockets; } _sockets = new Dictionary<string, Socket>(StringComparer.OrdinalIgnoreCase); if (string.IsNullOrEmpty(Sockets.Value)) { return _sockets; } string[] array = Sockets.Value.Split(new char[1] { ';' }); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (text.Length == 0) { continue; } string[] array2 = text.Split(new char[1] { ':' }); if (array2.Length < 2 || array2.Length > 3) { Warn("Sockets entry is not 'piece : target : metres' and was ignored: " + text); continue; } HashSet<string> hashSet = Split(array2[0]); HashSet<string> hashSet2 = Split(array2[1]); if (hashSet.Count == 0 || hashSet2.Count == 0) { Warn("Sockets entry names no piece or no target and was ignored: " + text); continue; } float result = -1f; if (array2.Length == 3 && array2[2].Trim().Length > 0 && !float.TryParse(array2[2].Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out result)) { Warn("Sockets: " + array2[0].Trim() + " has a depth that does not parse as a number, so it is measured off the piece instead: " + array2[2].Trim()); result = -1f; } foreach (string item in hashSet) { _sockets[item] = new Socket { Targets = hashSet2, StickOut = result }; } } return _sockets; } public static float GapFor(string prefabName) { if (!Gaps().TryGetValue(prefabName, out var value)) { return Gap.Value; } return value; } public static IEnumerable<string> ConfiguredGaps() { return Gaps().Keys; } private static Dictionary<string, float> Gaps() { if (_gaps != null) { return _gaps; } _gaps = new Dictionary<string, float>(StringComparer.OrdinalIgnoreCase); if (string.IsNullOrEmpty(GapOverrides.Value)) { return _gaps; } string[] array = GapOverrides.Value.Split(new char[1] { ';' }); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (text.Length == 0) { continue; } int num = text.IndexOf(':'); if (num <= 0) { Warn("GapOverrides entry has no 'prefab:' part and was ignored: " + text); continue; } string text2 = text.Substring(0, num).Trim(); if (text2.Length == 0 || !float.TryParse(text.Substring(num + 1).Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out var result)) { Warn("GapOverrides: " + text2 + " has a gap that does not parse as a number and was ignored: " + text); } else { _gaps[text2] = result; } } return _gaps; } public static bool HasPointOverride(string prefabName) { if (Overrides().Count > 0) { return Overrides().ContainsKey(prefabName); } return false; } public static Vector3[] PointOverride(string prefabName) { if (!Overrides().TryGetValue(prefabName, out var value)) { return null; } return value; } public static IEnumerable<string> ConfiguredOverrides() { return Overrides().Keys; } private static Dictionary<string, Vector3[]> Overrides() { if (_overrides != null) { return _overrides; } _overrides = new Dictionary<string, Vector3[]>(StringComparer.OrdinalIgnoreCase); if (string.IsNullOrEmpty(PointOverrides.Value)) { return _overrides; } string[] array = PointOverrides.Value.Split(new char[1] { ';' }); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (text.Length == 0) { continue; } int num = text.IndexOf(':'); if (num <= 0) { Warn("PointOverrides entry has no 'prefab:' part and was ignored: " + text); continue; } string text2 = text.Substring(0, num).Trim(); Vector3[] array2 = ParsePoints(text.Substring(num + 1), text2); if (text2.Length != 0 && array2 != null) { _overrides[text2] = array2; } } return _overrides; } private static Vector3[] ParsePoints(string text, string prefabName) { //IL_00d8: Unknown result type (might be due to invalid IL or missing references) List<Vector3> list = new List<Vector3>(); string[] array = text.Split(new char[1] { '|' }); for (int i = 0; i < array.Length; i++) { string text2 = array[i].Trim(); if (text2.Length != 0) { string[] array2 = text2.Split(new char[1] { ',' }); float result; float result2; float result3; if (array2.Length != 3) { Warn("PointOverrides: " + prefabName + " has a point that is not three numbers and was ignored: " + text2); } else if (!float.TryParse(array2[0].Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out result) || !float.TryParse(array2[1].Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out result2) || !float.TryParse(array2[2].Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out result3)) { Warn("PointOverrides: " + prefabName + " has a point that does not parse as numbers and was ignored: " + text2); } else { list.Add(new Vector3(result, result2, result3)); } } } if (list.Count != 0) { return list.ToArray(); } Warn("PointOverrides: " + prefabName + " listed no usable points, so it is snapped the derived way instead."); return null; } private static void Warn(string message) { if (SinkaPlugin.Log != null) { SinkaPlugin.Log.LogWarning((object)message); } } private static HashSet<string> Split(string value) { HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase); if (string.IsNullOrEmpty(value)) { return hashSet; } string[] array = value.Split(new char[1] { ',' }); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (text.Length > 0) { hashSet.Add(text); } } return hashSet; } } [BepInPlugin("ezomic.valheim.sinka", "Sinka", "1.3.0")] public class SinkaPlugin : BaseUnityPlugin { public const string PluginGuid = "ezomic.valheim.sinka"; public const string PluginName = "Sinka"; public const string PluginVersion = "1.3.0"; public const string PluginAuthor = "Robbin Thijssen"; internal static ManualLogSource Log; private Harmony _harmony; private void Awake() { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; SinkaConfig.Bind(((BaseUnityPlugin)this).Config); _harmony = new Harmony("ezomic.valheim.sinka"); _harmony.PatchAll(typeof(ScenePatches)); _harmony.PatchAll(typeof(SocketPoints)); _harmony.PatchAll(typeof(CentrePoints)); _harmony.PatchAll(typeof(Stacking)); _harmony.PatchAll(typeof(TorchGuard)); Log.LogInfo((object)"Sinka 1.3.0 by Robbin Thijssen - ready."); } private void OnDestroy() { if (_harmony != null) { _harmony.UnpatchSelf(); } } private void Update() { if (!((Object)(object)ZNetScene.instance == (Object)null)) { SnapPoints.Apply(); } } } internal static class ScenePatches { [HarmonyPostfix] [HarmonyPatch(typeof(ZNetScene), "Awake")] private static void AddSnapPointsOnScene() { SnapPoints.Apply(); } } internal static class SnapPoints { private const string Tag = "snappoint"; private static ZNetScene _appliedTo; private static int _pieceMask; private const int MaxRungs = 24; private static int PieceMask { get { if (_pieceMask == 0) { _pieceMask = LayerMask.GetMask(new string[2] { "piece", "piece_nonsolid" }); } return _pieceMask; } } public static bool Apply() { ZNetScene instance = ZNetScene.instance; if ((Object)(object)instance == (Object)null) { return false; } if ((Object)(object)_appliedTo == (Object)(object)instance) { return true; } if (!Buildable.Ready()) { return false; } int num = 0; int num2 = 0; int num3 = 0; int num4 = 0; int num5 = 0; int num6 = 0; List<string> list = new List<string>(); SocketPoints.Reset(); CentrePoints.Reset(); Stacking.Reset(); foreach (GameObject prefab in instance.m_prefabs) { if ((Object)(object)prefab == (Object)null || !Eligible(prefab)) { continue; } Vector3[] array = SinkaConfig.PointOverride(((Object)prefab).name); bool flag = array == null && SocketPoints.Wanted(prefab); bool flag2 = array == null && !flag && CentrePoints.Wanted(prefab); if (flag && SocketPoints.Readopt(prefab)) { num++; num5++; continue; } if (flag2 && CentrePoints.Readopt(prefab)) { num++; num6++; continue; } if (HasSnapPoints(prefab.transform)) { num2++; continue; } try { bool flag3 = array == null && !flag && !flag2 && UseLadder(prefab); if ((array != null) ? AddExact(prefab, array) : (flag ? SocketPoints.AddSocket(prefab) : (flag2 ? CentrePoints.AddBase(prefab) : (flag3 ? AddLadder(prefab) : AddCorners(prefab))))) { num++; if (array != null) { num4++; } else if (flag) { num5++; } else if (flag2) { num6++; } else if (flag3) { num3++; } } } catch (Exception ex) { SinkaPlugin.Log.LogWarning((object)("Could not snap " + ((Object)prefab).name + ": " + ex.Message)); } if (!flag && !flag2 && !ReachableAsTarget(prefab)) { list.Add(((Object)prefab).name); } } if (list.Count > 0) { SinkaPlugin.Log.LogWarning((object)("These have snap points, but none of their colliders are on the piece or piece_nonsolid layer, which is where the game looks for something to snap to: " + string.Join(", ", list.ToArray()) + ". Placing one still snaps it to its neighbours; what will not work is snapping anything to it once it is standing. That is the piece's own layer setup, not something this mod will change for it.")); } _appliedTo = instance; SinkaPlugin.Log.LogInfo((object)("Added snap points to " + num + " piece(s): " + num3 + " fence ladder, " + num4 + " from PointOverrides, " + num5 + " socket, " + num6 + " centring, " + (num - num3 - num4 - num5 - num6) + " corners. " + num2 + " already had their own.")); ReportMissingNames(); return true; } private static bool ReachableAsTarget(GameObject prefab) { bool flag = false; Collider[] componentsInChildren = prefab.GetComponentsInChildren<Collider>(true); foreach (Collider val in componentsInChildren) { if (!val.isTrigger) { flag = true; if ((PieceMask & (1 << ((Component)val).gameObject.layer)) != 0) { return true; } } } return !flag; } private static bool AddExact(GameObject prefab, Vector3[] points) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) for (int i = 0; i < points.Length; i++) { Create(prefab, "snap_custom" + (i + 1), points[i]); } if (SinkaConfig.Verbose.Value) { SinkaPlugin.Log.LogInfo((object)(((Object)prefab).name + ": " + points.Length + " point(s) from PointOverrides")); } return true; } private static void ReportMissingNames() { if (SinkaConfig.SnapFences.Value) { Report("FencePrefabs", SinkaConfig.ConfiguredFences()); } Report("PointOverrides", SinkaConfig.ConfiguredOverrides()); Report("GapOverrides", SinkaConfig.ConfiguredGaps()); if (SinkaConfig.CentreOnSurface.Value) { Report("CentreProps", SinkaConfig.ConfiguredCentreProps()); CentrePoints.ReportProps(); } if (SinkaConfig.SnapSockets.Value) { Report("Sockets pieces", SinkaConfig.ConfiguredSockets()); Report("Sockets targets", SinkaConfig.ConfiguredTargets()); SocketPoints.ReportTargets(); } } private static void Report(string setting, IEnumerable<string> names) { List<string> list = new List<string>(); foreach (string name in names) { if ((Object)(object)ZNetScene.instance.GetPrefab(name) == (Object)null) { list.Add(name); } } if (list.Count != 0) { SinkaPlugin.Log.LogWarning((object)(setting + " names that match no prefab: " + string.Join(", ", list.ToArray()))); } } private static bool Eligible(GameObject prefab) { if ((Object)(object)prefab.GetComponent<Piece>() == (Object)null) { return false; } if ((Object)(object)prefab.GetComponent<Ship>() != (Object)null) { return false; } if (SinkaConfig.IsExcluded(((Object)prefab).name)) { return false; } bool flag = SinkaConfig.HasPointOverride(((Object)prefab).name); if (!flag && !Buildable.Includes(prefab)) { return false; } if (flag) { return true; } if (SinkaConfig.SnapContainers.Value && (Object)(object)prefab.GetComponent<Container>() != (Object)null) { return true; } if (SinkaConfig.SnapFences.Value && SinkaConfig.IsFence(((Object)prefab).name)) { return true; } if (SocketPoints.Wanted(prefab) || CentrePoints.Wanted(prefab)) { return true; } return SinkaConfig.SnapUnsnappedPieces.Value; } private static bool HasSnapPoints(Transform root) { for (int i = 0; i < root.childCount; i++) { if (((Component)root.GetChild(i)).CompareTag("snappoint")) { return true; } } return false; } private static bool AddCorners(GameObject prefab) { //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_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_0197: Unknown result type (might be due to invalid IL or missing references) //IL_019c: Unknown result type (might be due to invalid IL or missing references) //IL_01b7: Unknown result type (might be due to invalid IL or missing references) //IL_01bc: Unknown result type (might be due to invalid IL or missing references) //IL_009a: 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_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_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_012f: Unknown result type (might be due to invalid IL or missing references) if (!Footprint(prefab, out var bounds)) { return false; } float num = Mathf.Max(0f, SinkaConfig.GapFor(((Object)prefab).name)) * 0.5f; Vector3 val = ((Bounds)(ref bounds)).extents + new Vector3(num, num, num); int[] array = new int[2] { -1, 1 }; foreach (int num2 in array) { int[] array2 = new int[2] { -1, 1 }; foreach (int num3 in array2) { int[] array3 = new int[2] { -1, 1 }; foreach (int num4 in array3) { Vector3 localPosition = ((Bounds)(ref bounds)).center + new Vector3(val.x * (float)num2, val.y * (float)num3, val.z * (float)num4); string text = ((num3 < 0) ? "bottom" : "top") + "-" + ((num4 < 0) ? "back" : "front") + "-" + ((num2 < 0) ? "left" : "right"); Create(prefab, "snap_" + text, localPosition); } } } if (SinkaConfig.Verbose.Value) { ManualLogSource log = SinkaPlugin.Log; string[] obj = new string[7] { ((Object)prefab).name, ": footprint ", null, null, null, null, null }; Vector3 val2 = ((Bounds)(ref bounds)).size; obj[2] = ((Vector3)(ref val2)).ToString("F2"); obj[3] = " centred "; val2 = ((Bounds)(ref bounds)).center; obj[4] = ((Vector3)(ref val2)).ToString("F2"); obj[5] = ", gap "; obj[6] = SinkaConfig.GapFor(((Object)prefab).name).ToString("F2"); log.LogInfo((object)string.Concat(obj)); } return true; } private static bool UseLadder(GameObject prefab) { if (SinkaConfig.SnapFences.Value && SinkaConfig.FenceLadderStep.Value > 0f) { return SinkaConfig.IsFence(((Object)prefab).name); } return false; } private static bool AddLadder(GameObject prefab) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_002f: 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) //IL_0094: 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_00f0: Unknown result type (might be due to invalid IL or missing references) //IL_0143: Unknown result type (might be due to invalid IL or missing references) //IL_0148: Unknown result type (might be due to invalid IL or missing references) //IL_0107: Unknown result type (might be due to invalid IL or missing references) //IL_010c: Unknown result type (might be due to invalid IL or missing references) //IL_01de: Unknown result type (might be due to invalid IL or missing references) //IL_01e3: Unknown result type (might be due to invalid IL or missing references) //IL_01fe: Unknown result type (might be due to invalid IL or missing references) //IL_0203: Unknown result type (might be due to invalid IL or missing references) if (!Footprint(prefab, out var bounds)) { return false; } bool flag = ((Bounds)(ref bounds)).size.x >= ((Bounds)(ref bounds)).size.z; float reach = (flag ? ((Bounds)(ref bounds)).extents.x : ((Bounds)(ref bounds)).extents.z) + Mathf.Max(0f, SinkaConfig.GapFor(((Object)prefab).name)) * 0.5f; float value = SinkaConfig.FenceLadderStep.Value; float num = ((Bounds)(ref bounds)).min.y - Mathf.Max(0f, SinkaConfig.FenceLadderBelow.Value); int num2 = Mathf.FloorToInt((((Bounds)(ref bounds)).max.y - num) / value) + 1; bool flag2 = num2 > 24; if (flag2) { num2 = 24; } for (int i = 0; i < num2; i++) { AddRung(prefab, bounds, flag, reach, num + (float)i * value); } float num3 = num + (float)(num2 - 1) * value; if (!flag2 && ((Bounds)(ref bounds)).max.y - num3 > value * 0.25f) { AddRung(prefab, bounds, flag, reach, ((Bounds)(ref bounds)).max.y); } Vector3 val; if (flag2) { ManualLogSource log = SinkaPlugin.Log; string[] obj = new string[10] { ((Object)prefab).name, " is ", null, null, null, null, null, null, null, null }; val = ((Bounds)(ref bounds)).size; obj[2] = val.y.ToString("F2"); obj[3] = "m tall and FenceLadderStep is "; obj[4] = value.ToString(); obj[5] = "m, which wants more than "; obj[6] = 24.ToString(); obj[7] = " rungs. The ladder stops at "; obj[8] = num3.ToString("F2"); obj[9] = "m - raise the step to cover the whole piece."; log.LogWarning((object)string.Concat(obj)); } if (SinkaConfig.Verbose.Value) { ManualLogSource log2 = SinkaPlugin.Log; string[] obj2 = new string[11] { ((Object)prefab).name, ": footprint ", null, null, null, null, null, null, null, null, null }; val = ((Bounds)(ref bounds)).size; obj2[2] = ((Vector3)(ref val)).ToString("F2"); obj2[3] = " centred "; val = ((Bounds)(ref bounds)).center; obj2[4] = ((Vector3)(ref val)).ToString("F2"); obj2[5] = ", gap "; obj2[6] = SinkaConfig.GapFor(((Object)prefab).name).ToString("F2"); obj2[7] = ", ladder of "; obj2[8] = num2.ToString(); obj2[9] = " along "; obj2[10] = (flag ? "x" : "z"); log2.LogInfo((object)string.Concat(obj2)); } return true; } private static void AddRung(GameObject prefab, Bounds bounds, bool alongX, float reach, float y) { //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0068: 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_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Unknown result type (might be due to invalid IL or missing references) float[] array = new float[2] { -1f, 1f }; foreach (float num in array) { Vector3 localPosition = (alongX ? new Vector3(((Bounds)(ref bounds)).center.x + reach * num, y, ((Bounds)(ref bounds)).center.z) : new Vector3(((Bounds)(ref bounds)).center.x, y, ((Bounds)(ref bounds)).center.z + reach * num)); string text = (alongX ? ((num < 0f) ? "left" : "right") : ((num < 0f) ? "back" : "front")); Create(prefab, "snap_" + text + "-y" + y.ToString("F2"), localPosition); } } internal static void Create(GameObject prefab, string name, Vector3 localPosition) { //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_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject(name) { tag = "snappoint" }; val.transform.SetParent(prefab.transform, false); val.transform.localPosition = localPosition; } internal static bool Footprint(GameObject prefab, out Bounds bounds) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0063: 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) //IL_0059: 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_01f1: Unknown result type (might be due to invalid IL or missing references) //IL_01f6: Unknown result type (might be due to invalid IL or missing references) //IL_01fb: Unknown result type (might be due to invalid IL or missing references) //IL_020d: Unknown result type (might be due to invalid IL or missing references) //IL_0201: Unknown result type (might be due to invalid IL or missing references) //IL_0203: Unknown result type (might be due to invalid IL or missing references) //IL_0133: Unknown result type (might be due to invalid IL or missing references) //IL_0135: Unknown result type (might be due to invalid IL or missing references) //IL_013a: Unknown result type (might be due to invalid IL or missing references) //IL_014c: Unknown result type (might be due to invalid IL or missing references) //IL_0140: Unknown result type (might be due to invalid IL or missing references) //IL_0142: Unknown result type (might be due to invalid IL or missing references) //IL_017d: 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) bounds = default(Bounds); bool flag = false; Transform transform = prefab.transform; Transform val = LiveGeometry(prefab); Collider[] componentsInChildren = ((Component)val).GetComponentsInChildren<Collider>(true); Vector3 size; foreach (Collider val2 in componentsInChildren) { if (!Skip(val2, transform) && LocalBounds(val2, out var local)) { Bounds val3 = ToRoot(transform, ((Component)val2).transform, local); if (!flag) { bounds = val3; flag = true; } else { ((Bounds)(ref bounds)).Encapsulate(val3); } if (SinkaConfig.Verbose.Value) { ManualLogSource log = SinkaPlugin.Log; string text = PathTo(((Component)val2).transform, transform); size = ((Bounds)(ref val3)).size; log.LogInfo((object)(" " + text + " " + ((Vector3)(ref size)).ToString("F2"))); } } } if (flag) { return true; } if ((Object)(object)val != (Object)(object)transform) { componentsInChildren = prefab.GetComponentsInChildren<Collider>(true); foreach (Collider val4 in componentsInChildren) { if (!Skip(val4, transform) && (PieceMask & (1 << ((Component)val4).gameObject.layer)) != 0 && !InDamageState(((Component)val4).transform, prefab) && LocalBounds(val4, out var local2)) { Bounds val5 = ToRoot(transform, ((Component)val4).transform, local2); if (!flag) { bounds = val5; flag = true; } else { ((Bounds)(ref bounds)).Encapsulate(val5); } if (SinkaConfig.Verbose.Value) { ManualLogSource log2 = SinkaPlugin.Log; string text2 = PathTo(((Component)val4).transform, transform); size = ((Bounds)(ref val5)).size; log2.LogInfo((object)(" (outside m_new) " + text2 + " " + ((Vector3)(ref size)).ToString("F2"))); } } } if (flag) { return true; } } MeshFilter[] componentsInChildren2 = ((Component)val).GetComponentsInChildren<MeshFilter>(true); foreach (MeshFilter val6 in componentsInChildren2) { if (!((Object)(object)val6.sharedMesh == (Object)null) && !IsDisabled(((Component)val6).transform, transform)) { Bounds val7 = ToRoot(transform, ((Component)val6).transform, val6.sharedMesh.bounds); if (!flag) { bounds = val7; flag = true; } else { ((Bounds)(ref bounds)).Encapsulate(val7); } } } if (flag && SinkaConfig.Verbose.Value) { SinkaPlugin.Log.LogInfo((object)(" (no collider found; measured from meshes) " + ((Object)prefab).name)); } return flag; } private static bool InDamageState(Transform transform, GameObject prefab) { WearNTear component = prefab.GetComponent<WearNTear>(); if ((Object)(object)component == (Object)null) { return false; } if (Under(transform, component.m_worn) || Under(transform, component.m_broken)) { return true; } if (component.m_fragmentRoots != null) { GameObject[] fragmentRoots = component.m_fragmentRoots; foreach (GameObject ancestor in fragmentRoots) { if (Under(transform, ancestor)) { return true; } } } return false; } private static bool Under(Transform transform, GameObject ancestor) { if ((Object)(object)ancestor == (Object)null) { return false; } Transform val = transform; while ((Object)(object)val != (Object)null) { if ((Object)(object)((Component)val).gameObject == (Object)(object)ancestor) { return true; } val = val.parent; } return false; } internal static Transform LiveGeometry(GameObject prefab) { WearNTear component = prefab.GetComponent<WearNTear>(); if ((Object)(object)component == (Object)null || (Object)(object)component.m_new == (Object)null || (Object)(object)component.m_new == (Object)(object)prefab) { return prefab.transform; } return component.m_new.transform; } private static bool Skip(Collider collider, Transform root) { if (collider.isTrigger) { return true; } if (IsDisabled(((Component)collider).transform, root)) { return true; } Rigidbody attachedRigidbody = collider.attachedRigidbody; if ((Object)(object)attachedRigidbody != (Object)null && (Object)(object)((Component)attachedRigidbody).transform != (Object)(object)root) { return true; } return false; } internal static bool IsDisabled(Transform transform, Transform root) { Transform val = transform; while ((Object)(object)val != (Object)null && (Object)(object)val != (Object)(object)root) { if (!((Component)val).gameObject.activeSelf) { return true; } val = val.parent; } return false; } private static string PathTo(Transform transform, Transform root) { string text = ((Object)transform).name; Transform parent = transform.parent; while ((Object)(object)parent != (Object)null && (Object)(object)parent != (Object)(object)root) { text = ((Object)parent).name + "/" + text; parent = parent.parent; } return text; } private static bool LocalBounds(Collider collider, out Bounds local) { //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: 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_0069: 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_0092: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00c1: 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_00cb: Unknown result type (might be due to invalid IL or missing references) BoxCollider val = (BoxCollider)(object)((collider is BoxCollider) ? collider : null); if (val == null) { MeshCollider val2 = (MeshCollider)(object)((collider is MeshCollider) ? collider : null); if (val2 == null) { CapsuleCollider val3 = (CapsuleCollider)(object)((collider is CapsuleCollider) ? collider : null); if (val3 != null) { float num = val3.radius * 2f; local = new Bounds(val3.center, new Vector3(num, Mathf.Max(val3.height, num), num)); return true; } SphereCollider val4 = (SphereCollider)(object)((collider is SphereCollider) ? collider : null); if (val4 != null) { local = new Bounds(val4.center, Vector3.one * val4.radius * 2f); return true; } } else if ((Object)(object)val2.sharedMesh != (Object)null) { local = val2.sharedMesh.bounds; return true; } local = default(Bounds); return false; } local = new Bounds(val.center, val.size); return true; } internal static Bounds ToRoot(Transform root, Transform child, Bounds local) { //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_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000f: 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_0015: Unknown result type (might be due to invalid IL or missing references) //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_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_0063: 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) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0074: 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_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) Vector3 center = ((Bounds)(ref local)).center; Vector3 extents = ((Bounds)(ref local)).extents; Bounds result = default(Bounds); ((Bounds)(ref result))..ctor(root.InverseTransformPoint(child.TransformPoint(center)), Vector3.zero); for (int i = 0; i < 8; i++) { Vector3 val = center + new Vector3(((i & 1) == 0) ? (0f - extents.x) : extents.x, ((i & 2) == 0) ? (0f - extents.y) : extents.y, ((i & 4) == 0) ? (0f - extents.z) : extents.z); ((Bounds)(ref result)).Encapsulate(root.InverseTransformPoint(child.TransformPoint(val))); } return result; } } internal static class SocketPoints { internal const string SocketName = "snap_into-pole"; private const string Tag = "snappoint"; private const float FaceSlack = 0.3f; private const float VanillaReach = 0.5f; private const float TopTolerance = 0.1f; private const float IgniteMargin = 0.05f; private const float DefaultStickOut = 0.25f; private static readonly Dictionary<string, float> Reach = new Dictionary<string, float>(StringComparer.OrdinalIgnoreCase); private static readonly List<string> Depths = new List<string>(); private static readonly HashSet<string> SocketPieceNames = new HashSet<string>(StringComparer.Ordinal); private static Transform _aimedTop; private static string _aimedTarget; private static Transform _ghost; private static int _ghostLayer = -1; internal static void Reset() { Reach.Clear(); Depths.Clear(); SocketPieceNames.Clear(); _aimedTop = null; _aimedTarget = null; } internal static bool Wanted(GameObject prefab) { if (SinkaConfig.SnapSockets.Value) { return SinkaConfig.SocketFor(((Object)prefab).name) != null; } return false; } internal static bool AddSocket(GameObject prefab) { //IL_001e: 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_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_00ec: Unknown result type (might be due to invalid IL or missing references) //IL_00f8: Unknown result type (might be due to invalid IL or missing references) //IL_0107: Unknown result type (might be due to invalid IL or missing references) //IL_011c: 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) //IL_0125: Unknown result type (might be due to invalid IL or missing references) if (!SnapPoints.Footprint(prefab, out var bounds)) { return false; } SinkaConfig.Socket socket = SinkaConfig.SocketFor(((Object)prefab).name); if (socket == null) { return false; } Bounds visual = Visual(prefab, bounds); float num = StickOutFloor(prefab, visual); bool flag = socket.StickOut >= 0f; float num2 = (flag ? socket.StickOut : ((num > 0f) ? num : 0.25f)); float num3 = Mathf.Min(Mathf.Max(new float[3] { num2, num, 0f }), ((Bounds)(ref visual)).size.y); if (flag && num3 > num2 + 0.001f) { SinkaPlugin.Log.LogWarning((object)(((Object)prefab).name + ": a depth of " + num2.ToString("F3") + " would put the piece's fire-spread zone inside its own pole, so " + num3.ToString("F3") + " is used. Below that a torch can set the pole alight in the Ashlands.")); } Vector3 val = default(Vector3); ((Vector3)(ref val))..ctor(((Bounds)(ref bounds)).center.x, ((Bounds)(ref visual)).max.y - num3, ((Bounds)(ref bounds)).center.z); SnapPoints.Create(prefab, "snap_into-pole", val); Register(prefab, bounds, val); Depths.Add(((Object)prefab).name + " " + num3.ToString("F2") + (flag ? "" : " measured")); return true; } private static float StickOutFloor(GameObject prefab, Bounds visual) { //IL_0048: Unknown result type (might be due to invalid IL or missing references) Fireplace component = prefab.GetComponent<Fireplace>(); if ((Object)(object)component == (Object)null || component.m_igniteCapsuleRadius <= 0f) { return 0f; } float num = Mathf.Min(component.m_igniteCapsuleStart.y, component.m_igniteCapsuleEnd.y) - component.m_igniteCapsuleRadius; return ((Bounds)(ref visual)).max.y - num + 0.05f; } internal static bool Readopt(GameObject prefab) { //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0043: 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_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004c: 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) Transform val = prefab.transform.Find("snap_into-pole"); if ((Object)(object)val == (Object)null || !((Component)val).CompareTag("snappoint")) { return false; } if (!SnapPoints.Footprint(prefab, out var bounds)) { return false; } Register(prefab, bounds, val.localPosition); Bounds val2 = Visual(prefab, bounds); float num = ((Bounds)(ref val2)).max.y - val.localPosition.y; Depths.Add(((Object)prefab).name + " " + num.ToString("F2")); return true; } private static void Register(GameObject prefab, Bounds footprint, Vector3 socket) { //IL_0000: 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_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: 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) float num = Mathf.Abs(socket.y - ((Bounds)(ref footprint)).min.y); float value = Mathf.Max(0.5f, num + 0.3f); Reach[((Object)prefab).name] = value; Piece component = prefab.GetComponent<Piece>(); if ((Object)(object)component != (Object)null && !string.IsNullOrEmpty(component.m_name)) { SocketPieceNames.Add(component.m_name); } if (SinkaConfig.Verbose.Value) { ManualLogSource log = SinkaPlugin.Log; string[] obj = new string[10] { ((Object)prefab).name, ": socket at ", ((Vector3)(ref socket)).ToString("F2"), ", footprint ", null, null, null, null, null, null }; Vector3 val = ((Bounds)(ref footprint)).size; obj[4] = ((Vector3)(ref val)).ToString("F2"); obj[5] = " from y "; val = ((Bounds)(ref footprint)).min; obj[6] = val.y.ToString("F2"); obj[7] = ", reaches "; obj[8] = value.ToString("F2"); obj[9] = "m to the top of the target aimed at"; log.LogInfo((object)string.Concat(obj)); } } private static Bounds Visual(GameObject prefab, Bounds footprint) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000a: 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_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) Transform transform = prefab.transform; bool flag = false; Bounds result = footprint; MeshFilter[] componentsInChildren = ((Component)SnapPoints.LiveGeometry(prefab)).GetComponentsInChildren<MeshFilter>(true); foreach (MeshFilter val in componentsInChildren) { if (!((Object)(object)val.sharedMesh == (Object)null) && !SnapPoints.IsDisabled(((Component)val).transform, transform)) { Bounds val2 = SnapPoints.ToRoot(transform, ((Component)val).transform, val.sharedMesh.bounds); if (!flag) { result = val2; flag = true; } else { ((Bounds)(ref result)).Encapsulate(val2); } } } return result; } internal static void ReportTargets() { List<string> list = new List<string>(); List<string> list2 = new List<string>(); foreach (string item in SinkaConfig.ConfiguredTargets()) { GameObject prefab = ZNetScene.instance.GetPrefab(item); if (!((Object)(object)prefab == (Object)null)) { if ((Object)(object)TopPoint(prefab.transform) == (Object)null) { list.Add(item); } if (!Buildable.Includes(prefab)) { list2.Add(item); } } } if (list.Count > 0) { SinkaPlugin.Log.LogWarning((object)("Sockets targets with no snap points of their own, so nothing can sink into them: " + string.Join(", ", list.ToArray()))); } if (list2.Count > 0) { SinkaPlugin.Log.LogWarning((object)("Sockets targets that are in no build menu: " + string.Join(", ", list2.ToArray()))); } if (Depths.Count > 0) { SinkaPlugin.Log.LogInfo((object)("Sockets, metres left showing: " + string.Join(", ", Depths.ToArray()))); } } internal static Transform TopPoint(Transform piece) { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) Transform val = null; for (int i = 0; i < piece.childCount; i++) { Transform child = piece.GetChild(i); if (((Component)child).CompareTag("snappoint") && ((Object)(object)val == (Object)null || child.localPosition.y > val.localPosition.y)) { val = child; } } return val; } private static string PrefabName(string name) { int num = name.IndexOf("(Clone)", StringComparison.Ordinal); if (num >= 0) { return name.Substring(0, num); } return name; } private static bool PairedWithAim(string prefabName) { if (_aimedTarget == null) { return false; } return SinkaConfig.SocketFor(prefabName)?.Targets.Contains(_aimedTarget) ?? false; } [HarmonyPostfix] [HarmonyPatch(typeof(Player), "PieceRayTest")] private static void Aimed(bool __result, ref Vector3 point, ref Vector3 normal, ref Piece piece) { //IL_006b: Unknown result type (might be due to invalid IL or missing references) _aimedTop = null; _aimedTarget = null; if (Reach.Count == 0 || !__result || (Object)(object)piece == (Object)null || normal.y < 0.8f) { return; } string text = PrefabName(((Object)((Component)piece).gameObject).name); if (SinkaConfig.IsSocketTarget(text)) { Transform val = TopPoint(((Component)piece).transform); if (!((Object)(object)val == (Object)null) && !(Mathf.Abs(point.y - val.position.y) > 0.1f)) { _aimedTop = val; _aimedTarget = text; } } } [HarmonyPostfix] [HarmonyPatch(typeof(Piece), "GetSnapPoints", new Type[] { typeof(List<Transform>) })] private static void Listed(Piece __instance, List<Transform> points) { if (SocketPieceNames.Count == 0 || points == null || !SocketPieceNames.Contains(__instance.m_name)) { return; } if (_ghostLayer < 0) { _ghostLayer = LayerMask.NameToLayer("ghost"); } if ((Object)(object)_aimedTop != (Object)null && ((Component)__instance).gameObject.layer == _ghostLayer && PairedWithAim(PrefabName(((Object)((Component)__instance).gameObject).name))) { return; } Transform transform = ((Component)__instance).transform; int num = points.Count - 1; while (num >= 0) { Transform val = points[num]; if (!((Object)(object)val == (Object)null) && !((Object)(object)val.parent != (Object)(object)transform)) { if (((Object)val).name == "snap_into-pole") { points.RemoveAt(num); } num--; continue; } break; } } [HarmonyPrefix] [HarmonyPatch(typeof(Player), "FindClosestSnapPoints")] private static void Searching(Transform ghost, ref float maxSnapDistance) { _ghost = null; if ((Object)(object)_aimedTop == (Object)null || (Object)(object)ghost == (Object)null) { return; } string name = ((Object)ghost).name; if (Reach.TryGetValue(name, out var value) && PairedWithAim(name)) { _ghost = ghost; if (value > maxSnapDistance) { maxSnapDistance = value; } } } [HarmonyPostfix] [HarmonyPatch(typeof(Player), "FindClosestSnapPoints")] private static void Searched() { _ghost = null; } [HarmonyPostfix] [HarmonyPatch(typeof(Piece), "GetSnapPoints", new Type[] { typeof(Vector3), typeof(float), typeof(List<Transform>), typeof(List<Piece>) })] private static void Gathered(List<Transform> points) { if ((Object)(object)_ghost == (Object)null || (Object)(object)_aimedTop == (Object)null || points == null) { return; } for (int num = points.Count - 1; num >= 0; num--) { if ((Object)(object)points[num] != (Object)(object)_aimedTop) { points.RemoveAt(num); } } } } internal static class Stacking { private const float ProbeDepth = 0.12f; private const float ProbeHalfWidth = 0.35f; private static readonly Collider[] Probe = (Collider[])(object)new Collider[16]; private static WearNTear _lent; private static bool _lentWas; private static int _pieceMask; private static string _ghostName; private static bool _ghostIsContainer; private static int PieceMask { get { if (_pieceMask == 0) { _pieceMask = LayerMask.GetMask(new string[2] { "piece", "piece_nonsolid" }); } return _pieceMask; } } internal static void Reset() { Restore(); } private static void Restore() { if ((Object)(object)_lent == (Object)null) { _lent = null; return; } _lent.m_supports = _lentWas; _lent = null; } private static bool IsContainer(GameObject piece) { if ((Object)(object)piece == (Object)null) { return false; } if ((Object)(object)piece.GetComponentInChildren<Container>() != (Object)null) { return (Object)(object)piece.GetComponent<Ship>() == (Object)null; } return false; } private static bool GhostIsContainer(GameObject ghost) { string name = ((Object)ghost).name; if (name == _ghostName) { return _ghostIsContainer; } _ghostName = name; ZNetScene instance = ZNetScene.instance; GameObject val = (((Object)(object)instance == (Object)null) ? null : instance.GetPrefab(name)); _ghostIsContainer = IsContainer(((Object)(object)val == (Object)null) ? ghost : val); return _ghostIsContainer; } [HarmonyPostfix] [HarmonyPatch(typeof(Player), "PieceRayTest")] private static void Aimed(Player __instance, bool __result, ref Piece piece, GameObject ___m_placementGhost) { Restore(); if (SinkaConfig.StackContainers.Value && __result && !((Object)(object)piece == (Object)null) && !((Object)(object)___m_placementGhost == (Object)null) && !((Object)(object)__instance != (Object)(object)Player.m_localPlayer) && GhostIsContainer(___m_placementGhost) && IsContainer(((Component)piece).gameObject)) { WearNTear component = ((Component)piece).GetComponent<WearNTear>(); if (!((Object)(object)component == (Object)null) && !component.m_supports) { _lent = component; _lentWas = component.m_supports; component.m_supports = true; } } } [HarmonyPostfix] [HarmonyPatch(typeof(Player), "UpdatePlacementGhost")] private static void Placed() { Restore(); } [HarmonyPostfix] [HarmonyPatch(typeof(WearNTear), "HaveSupport")] private static void Supported(WearNTear __instance, ref bool __result) { if (!__result && SinkaConfig.StackContainers.Value && IsContainer(((Component)__instance).gameObject)) { __result = RestsOnContainer(((Component)__instance).transform); } } private static bool RestsOnContainer(Transform chest) { //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_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) Vector3 val = chest.position + Vector3.down * 0.12f; Vector3 val2 = default(Vector3); ((Vector3)(ref val2))..ctor(0.35f, 0.12f, 0.35f); int num = Physics.OverlapBoxNonAlloc(val, val2, Probe, Quaternion.identity, PieceMask); for (int i = 0; i < num; i++) { Collider val3 = Probe[i]; if (!((Object)(object)val3 == (Object)null)) { Piece componentInParent = ((Component)val3).GetComponentInParent<Piece>(); if (!((Object)(object)componentInParent == (Object)null) && !((Object)(object)((Component)componentInParent).transform == (Object)(object)chest) && IsContainer(((Component)componentInParent).gameObject)) { return true; } } } return false; } } internal enum Protection { All, Fire, Off } internal static class TorchGuard { private const float Seated = 0.1f; private const float Search = 1.5f; private static bool _failed; private static readonly List<Piece> Nearby = new List<Piece>(); [HarmonyPrefix] [HarmonyPatch(typeof(WearNTear), "ApplyDamage")] private static bool Damaged(WearNTear __instance, ref float damage, HitData hitData) { try { return Decide(__instance, ref damage, hitData); } catch (Exception ex) { if (!_failed) { _failed = true; SinkaPlugin.Log.LogWarning((object)("Torch protection failed, damage proceeds: " + ex)); } return true; } } private static bool Decide(WearNTear __instance, ref float damage, HitData hitData) { //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Invalid comparison between Unknown and I4 //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Invalid comparison between Unknown and I4 Protection value = SinkaConfig.ProtectFrom.Value; if (value == Protection.Off || hitData == null || damage <= 0f) { return true; } if (!Protected(__instance)) { return true; } if (value == Protection.All) { return false; } if ((int)hitData.m_hitType == 20 || (int)hitData.m_hitType == 5) { return false; } damage = Mathf.Max(0f, damage - hitData.m_damage.m_fire); return damage > 0f; } private static bool Protected(WearNTear pole) { //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) Piece val = default(Piece); if (!((Component)pole).TryGetComponent<Piece>(ref val)) { return false; } string text = PrefabName(((Object)((Component)pole).gameObject).name); if (!SinkaConfig.IsSocketTarget(text)) { return false; } Transform val2 = SocketPoints.TopPoint(((Component)pole).transform); if ((Object)(object)val2 == (Object)null) { return false; } Nearby.Clear(); Piece.GetAllPiecesInRadius(val2.position, 1.5f, Nearby); foreach (Piece item in Nearby) { if (!((Object)(object)item == (Object)(object)val) && Seats(item, text, val2.position)) { return true; } } return false; } private static bool Seats(Piece torch, string poleName, Vector3 topPoint) { //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_005e: 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) SinkaConfig.Socket socket = SinkaConfig.SocketFor(PrefabName(((Object)((Component)torch).gameObject).name)); if (socket == null || !socket.Targets.Contains(poleName)) { return false; } ZNetView val = default(ZNetView); if (!((Component)torch).TryGetComponent<ZNetView>(ref val) || !val.IsValid()) { return false; } Transform val2 = ((Component)torch).transform.Find("snap_into-pole"); if ((Object)(object)val2 != (Object)null) { Vector3 val3 = val2.position - topPoint; return ((Vector3)(ref val3)).sqrMagnitude <= 0.010000001f; } return false; } private static string PrefabName(string name) { int num = name.IndexOf("(Clone)", StringComparison.Ordinal); if (num >= 0) { return name.Substring(0, num); } return name; } } }