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 Odins Hearth v0.1.3
plugins/OdinsHearth.dll
Decompiled 16 hours agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using TMPro; using UnityEngine; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyCompany("OdinsHearth")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.1.3.0")] [assembly: AssemblyInformationalVersion("0.1.3+0490000cb09bb5c32379bd597dd519f70a646626")] [assembly: AssemblyProduct("OdinsHearth")] [assembly: AssemblyTitle("OdinsHearth")] [assembly: AssemblyVersion("0.1.3.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 OdinsHearth { internal enum Verdict { Counted, BeatenInGroup, Duplicate, Unlit } internal sealed class ComfortPiece { public Piece Piece; public string Token; public ComfortGroup Group; public int Comfort; public Verdict Verdict; public ComfortPiece BeatenBy; } internal sealed class Material { public string Token; public string Name; public int Need; public int Have; public int Missing => Math.Max(0, Need - Have); } internal sealed class Suggestion { public Piece Prefab; public string Name; public ComfortGroup Group; public int Gain; public readonly List<Material> Materials = new List<Material>(); public bool Free; public string Station; public bool StationInRange; public bool ReadyToBuild; public bool Known; public int Missing; public int Total; } internal sealed class FireInfo { public string Name; public bool Burning; public bool Endless; public float SecondsLeft; public int ToFill; public string FuelName; } internal sealed class HearthReport { public int Level; public int PersonalBest; public bool Sheltered; public bool NearFire; public bool Resting; public float RestingFor; public float RestDelay; public bool Rested; public float RestedLeft; public float RestedWouldLast; public readonly List<string> NotRestingBecause = new List<string>(); public readonly List<ComfortPiece> Pieces = new List<ComfortPiece>(); public int ShelteredLevel; public readonly List<Suggestion> Suggestions = new List<Suggestion>(); public bool NothingLeftToBuild; public readonly List<FireInfo> Fires = new List<FireInfo>(); public readonly List<Suggestion> Road = new List<Suggestion>(); public int MaxLevel; public readonly List<Material> RoadMaterials = new List<Material>(); public bool BetterSpot; public Vector3 SpotOffset; public int SpotLevel; } internal static class Comfort { private static float _radius = 10f; private static readonly List<Piece> Nearby = new List<Piece>(); private static readonly List<Piece> Candidates = new List<Piece>(); private static int _candidateSource = -1; private static bool _warnedMismatch; private const float SpotRange = 6f; private const float SpotStep = 1.5f; private static float _spotCheckedAt = -100f; private static Vector3 _spotCheckedFrom; private static bool _spotFound; private static Vector3 _spotOffset; private static int _spotLevel; private static int _solidMask; internal static bool Init() { try { if (AccessTools.Method(typeof(SE_Rested), "CalculateComfortLevel", new Type[2] { typeof(bool), typeof(Vector3) }, (Type[])null) == null) { throw new MissingMethodException("SE_Rested.CalculateComfortLevel(bool, Vector3)"); } if (AccessTools.Field(typeof(SE_Rested), "c_ComfortRadius")?.GetRawConstantValue() is float num && num > 0f) { _radius = num; } else { Plugin.Log.LogWarning((object)$"Couldn't read the comfort radius from the game; assuming {_radius} m."); } return true; } catch (Exception ex) { Plugin.Log.LogError((object)("Couldn't find Valheim's comfort code; a game update may have changed it. The hearth is cold. (" + ex.Message + ")")); return false; } } internal static HearthReport Compute() { //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_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: 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_0103: Unknown result type (might be due to invalid IL or missing references) //IL_01a9: Unknown result type (might be due to invalid IL or missing references) Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null || (Object)(object)ObjectDB.instance == (Object)null) { return null; } Vector3 position = ((Component)localPlayer).transform.position; HearthReport hearthReport = new HearthReport { Sheltered = localPlayer.InShelter() }; hearthReport.Level = SE_Rested.CalculateComfortLevel(hearthReport.Sheltered, position); hearthReport.ShelteredLevel = (hearthReport.Sheltered ? hearthReport.Level : SE_Rested.CalculateComfortLevel(true, position)); if ((Object)(object)Game.instance != (Object)null && Game.instance.GetPlayerProfile() != null) { hearthReport.PersonalBest = Math.Max(hearthReport.Level, (int)Game.instance.GetPlayerProfile().GetStat((PlayerStatType)132)); } Nearby.Clear(); Piece.GetAllComfortPiecesInRadius(position, _radius, Nearby); foreach (Piece item in Nearby) { hearthReport.Pieces.Add(new ComfortPiece { Piece = item, Token = item.m_name, Group = item.m_comfortGroup, Comfort = item.GetComfort() }); } int num = Replay(hearthReport.Pieces, sheltered: true); if (num != hearthReport.ShelteredLevel && !_warnedMismatch) { _warnedMismatch = true; Plugin.Log.LogWarning((object)$"Comfort rules may have changed: the game says {hearthReport.ShelteredLevel}, the hearth's replay says {num}. The level shown is the game's; the piece lists may be off."); } ReadRest(localPlayer, hearthReport); ReadFires(hearthReport); if (hearthReport.Sheltered && (Plugin.ShowNextStep.Value || Plugin.ShowRoadToMax.Value)) { Suggest(localPlayer, position, hearthReport); } if (Plugin.ShowBetterSpot.Value) { FindBetterSpot(localPlayer, hearthReport); } return hearthReport; } private static int Sort(ComfortPiece x, ComfortPiece y) { //IL_0001: 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_0015: Unknown result type (might be due to invalid IL or missing references) if (x.Group != y.Group) { ref ComfortGroup reference = ref x.Group; object target = y.Group; return ((Enum)Unsafe.As<ComfortGroup, ComfortGroup>(ref reference)/*cast due to .constrained prefix*/).CompareTo(target); } if (x.Comfort != y.Comfort) { return y.Comfort.CompareTo(x.Comfort); } return string.CompareOrdinal(y.Token, x.Token); } private static int Replay(List<ComfortPiece> pieces, bool sheltered) { //IL_004d: 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_005c: Unknown result type (might be due to invalid IL or missing references) int num = 1; if (!sheltered) { return num; } num++; pieces.Sort(Sort); ComfortPiece beatenBy = null; for (int i = 0; i < pieces.Count; i++) { ComfortPiece comfortPiece = pieces[i]; if (i > 0) { ComfortPiece comfortPiece2 = pieces[i - 1]; bool flag = (int)comfortPiece.Group != 0 && comfortPiece.Group == comfortPiece2.Group; if (flag || comfortPiece.Token == comfortPiece2.Token) { comfortPiece.Verdict = ((comfortPiece.Comfort == 0) ? Verdict.Unlit : (flag ? Verdict.BeatenInGroup : Verdict.Duplicate)); comfortPiece.BeatenBy = beatenBy; continue; } } num += comfortPiece.Comfort; comfortPiece.Verdict = ((comfortPiece.Comfort == 0) ? Verdict.Unlit : Verdict.Counted); beatenBy = comfortPiece; } return num; } private static int LevelWith(List<ComfortPiece> pieces, ComfortPiece extra) { //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) List<ComfortPiece> list = new List<ComfortPiece>(pieces.Count + 1); foreach (ComfortPiece piece in pieces) { list.Add(new ComfortPiece { Piece = piece.Piece, Token = piece.Token, Group = piece.Group, Comfort = piece.Comfort }); } if (extra != null) { list.Add(extra); } return Replay(list, sheltered: true); } private static void ReadRest(Player player, HearthReport r) { //IL_00ce: Unknown result type (might be due to invalid IL or missing references) SEMan sEMan = ((Character)player).GetSEMan(); r.NearFire = sEMan.HaveStatusEffect(SEMan.s_statusEffectCampFire); StatusEffect statusEffect = sEMan.GetStatusEffect(SEMan.s_statusEffectRested); if ((Object)(object)statusEffect != (Object)null) { r.Rested = true; r.RestedLeft = statusEffect.GetRemaningTime(); } StatusEffect statusEffect2 = sEMan.GetStatusEffect(SEMan.s_statusEffectResting); if ((Object)(object)statusEffect2 != (Object)null) { r.Resting = true; r.RestingFor = statusEffect2.GetDuration(); r.RestDelay = ((SE_Cozy)(((statusEffect2 is SE_Cozy) ? statusEffect2 : null)?)).m_delay ?? 0f; } StatusEffect statusEffect3 = ObjectDB.instance.GetStatusEffect(SEMan.s_statusEffectRested); SE_Rested val = (SE_Rested)(object)((statusEffect3 is SE_Rested) ? statusEffect3 : null); if (val != null) { r.RestedWouldLast = val.m_baseTTL + (float)(r.Level - 1) * val.m_TTLPerComfortLevel; } if (!r.Resting) { bool flag = (Object)(object)EffectArea.IsPointInsideArea(((Component)player).transform.position, (Type)64, 1f) != (Object)null; if (!r.NearFire) { r.NotRestingBecause.Add("no fire close by"); } if (!r.Sheltered && !((Character)player).IsSitting()) { r.NotRestingBecause.Add("sit down or get under a roof"); } if (sEMan.HaveStatusEffect(SEMan.s_statusEffectWet) && !flag) { r.NotRestingBecause.Add("you're wet"); } if (!flag && (sEMan.HaveStatusEffect(SEMan.s_statusEffectCold) || sEMan.HaveStatusEffect(SEMan.s_statusEffectFreezing))) { r.NotRestingBecause.Add("you're cold"); } if (sEMan.HaveStatusEffect(SEMan.s_statusEffectBurning)) { r.NotRestingBecause.Add("you're on fire"); } if (player.IsSensed()) { r.NotRestingBecause.Add("something has your scent"); } } } private static void Suggest(Player player, Vector3 pos, HearthReport r) { //IL_00ed: Unknown result type (might be due to invalid IL or missing references) //IL_00f2: Unknown result type (might be due to invalid IL or missing references) //IL_011f: Unknown result type (might be due to invalid IL or missing references) //IL_016e: Unknown result type (might be due to invalid IL or missing references) LoadCandidates(); bool flag = player.NoCostCheat(); Inventory inventory = ((Humanoid)player).GetInventory(); HashSet<string> hashSet = new HashSet<string>(); List<Suggestion> list = new List<Suggestion>(); foreach (Piece candidate in Candidates) { if (!hashSet.Add(candidate.m_name)) { continue; } bool flag2 = flag || player.IsRecipeKnown(candidate.m_name); if ((Plugin.KnownPiecesOnly.Value && !flag2) || (candidate.m_dlc.Length > 0 && ((Object)(object)DLCMan.instance == (Object)null || !DLCMan.instance.IsDLCInstalled(candidate.m_dlc)))) { continue; } int num = LevelWith(r.Pieces, Trial(candidate)) - r.Level; if (num <= 0) { continue; } Suggestion suggestion = new Suggestion { Prefab = candidate, Name = Localize(candidate.m_name), Group = candidate.m_comfortGroup, Gain = num, Known = flag2, Free = (flag || ((Object)(object)ZoneSystem.instance != (Object)null && ZoneSystem.instance.GetGlobalKey(candidate.FreeBuildKey()))) }; if ((Object)(object)candidate.m_craftingStation != (Object)null) { suggestion.Station = Localize(candidate.m_craftingStation.m_name); suggestion.StationInRange = flag || (Object)(object)CraftingStation.HaveBuildStationInRange(candidate.m_craftingStation.m_name, pos) != (Object)null || ((Object)(object)ZoneSystem.instance != (Object)null && ZoneSystem.instance.GetGlobalKey((GlobalKeys)27)); } Requirement[] resources = candidate.m_resources; foreach (Requirement val in resources) { if (!((Object)(object)val.m_resItem == (Object)null) && val.m_amount > 0) { string name = val.m_resItem.m_itemData.m_shared.m_name; Material material = new Material { Token = name, Name = Localize(name), Need = val.m_amount, Have = ((inventory != null) ? inventory.CountItems(name, -1, true) : 0) }; suggestion.Materials.Add(material); if (!suggestion.Free) { suggestion.Missing += material.Missing; suggestion.Total += material.Need; } } } suggestion.ReadyToBuild = ((!suggestion.Free) ? player.HaveRequirements(candidate, (RequirementMode)0) : (suggestion.Station == null || suggestion.StationInRange)); list.Add(suggestion); } list.Sort(Cheapest); r.NothingLeftToBuild = list.Count == 0; for (int j = 0; j < list.Count && j <= Plugin.Alternatives.Value; j++) { r.Suggestions.Add(list[j]); } if (Plugin.ShowRoadToMax.Value) { PlanRoad(r, list); } } private static int Cheapest(Suggestion a, Suggestion b) { if (a.Missing != b.Missing) { return a.Missing.CompareTo(b.Missing); } if (a.Total != b.Total) { return a.Total.CompareTo(b.Total); } if (a.Gain != b.Gain) { return b.Gain.CompareTo(a.Gain); } if (a.StationInRange != b.StationInRange) { return b.StationInRange.CompareTo(a.StationInRange); } return string.CompareOrdinal(a.Name, b.Name); } private static ComfortPiece Trial(Piece prefab) { //IL_0013: 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) return new ComfortPiece { Token = prefab.m_name, Group = prefab.m_comfortGroup, Comfort = prefab.m_comfort }; } private static void PlanRoad(HearthReport r, List<Suggestion> options) { //IL_0021: 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_0060: Unknown result type (might be due to invalid IL or missing references) Dictionary<ComfortGroup, Suggestion> dictionary = new Dictionary<ComfortGroup, Suggestion>(); List<Suggestion> list = new List<Suggestion>(); foreach (Suggestion option in options) { Suggestion value; if ((int)option.Group == 0) { list.Add(option); } else if (!dictionary.TryGetValue(option.Group, out value) || option.Prefab.m_comfort > value.Prefab.m_comfort) { dictionary[option.Group] = option; } } list.AddRange(dictionary.Values); list.Sort(Cheapest); List<ComfortPiece> list2 = new List<ComfortPiece>(r.Pieces); foreach (Suggestion item in list) { list2.Add(Trial(item.Prefab)); } r.MaxLevel = LevelWith(list2, null); if (r.MaxLevel <= r.Level) { return; } Dictionary<string, Material> dictionary2 = new Dictionary<string, Material>(); foreach (Suggestion item2 in list) { r.Road.Add(item2); if (item2.Free) { continue; } foreach (Material material in item2.Materials) { if (!dictionary2.TryGetValue(material.Token, out var value2)) { value2 = new Material { Token = material.Token, Name = material.Name, Have = material.Have }; dictionary2[material.Token] = value2; r.RoadMaterials.Add(value2); } value2.Need += material.Need; } } r.RoadMaterials.Sort((Material a, Material b) => (a.Missing == b.Missing) ? string.CompareOrdinal(a.Name, b.Name) : b.Missing.CompareTo(a.Missing)); } private static void ReadFires(HearthReport r) { foreach (ComfortPiece piece in r.Pieces) { if (piece.Verdict == Verdict.Counted || piece.Verdict == Verdict.Unlit) { Fireplace val = (((Object)(object)piece.Piece != (Object)null) ? ((Component)piece.Piece).GetComponent<Fireplace>() : null); ZNetView val2 = (((Object)(object)val != (Object)null) ? ((Component)val).GetComponent<ZNetView>() : null); ZDO val3 = (((Object)(object)val2 != (Object)null && val2.IsValid()) ? val2.GetZDO() : null); if (val3 != null) { float num = val3.GetFloat(ZDOVars.s_fuel, 0f); r.Fires.Add(new FireInfo { Name = Localize(piece.Token), Burning = val.IsBurning(), Endless = val.m_infiniteFuel, SecondsLeft = Mathf.Max(0f, num) * val.m_secPerFuel, ToFill = (val.m_canRefill ? Mathf.Max(0, Mathf.FloorToInt(val.m_maxFuel - num)) : 0), FuelName = (((Object)(object)val.m_fuelItem != (Object)null) ? Localize(val.m_fuelItem.m_itemData.m_shared.m_name) : null) }); } } } } private static void FindBetterSpot(Player player, HearthReport r) { //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_003d: 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_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_0091: 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_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_009d: Unknown result type (might be due to invalid IL or missing references) //IL_009e: 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_022e: 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_00ee: 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_00fd: Unknown result type (might be due to invalid IL or missing references) //IL_0102: Unknown result type (might be due to invalid IL or missing references) //IL_011f: Unknown result type (might be due to invalid IL or missing references) //IL_0124: 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) //IL_012d: 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_0145: Unknown result type (might be due to invalid IL or missing references) //IL_014f: Unknown result type (might be due to invalid IL or missing references) //IL_0154: Unknown result type (might be due to invalid IL or missing references) //IL_0159: 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) //IL_0165: Unknown result type (might be due to invalid IL or missing references) //IL_016a: Unknown result type (might be due to invalid IL or missing references) //IL_0180: Unknown result type (might be due to invalid IL or missing references) //IL_0181: Unknown result type (might be due to invalid IL or missing references) //IL_0183: Unknown result type (might be due to invalid IL or missing references) //IL_0184: Unknown result type (might be due to invalid IL or missing references) //IL_0195: 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_0198: Unknown result type (might be due to invalid IL or missing references) //IL_01b9: Unknown result type (might be due to invalid IL or missing references) //IL_01d0: Unknown result type (might be due to invalid IL or missing references) //IL_01d2: Unknown result type (might be due to invalid IL or missing references) //IL_01d3: Unknown result type (might be due to invalid IL or missing references) //IL_01d8: Unknown result type (might be due to invalid IL or missing references) Vector3 position = ((Component)player).transform.position; if (Time.time - _spotCheckedAt > 3f || Vector3.Distance(position, _spotCheckedFrom) > 1f) { _spotCheckedAt = Time.time; _spotCheckedFrom = position; _spotFound = false; if (_solidMask == 0) { _solidMask = LayerMask.GetMask(new string[6] { "Default", "static_solid", "Default_small", "piece", "terrain", "vehicle" }); } Vector3 val = ((Character)player).GetCenterPoint() - position; Vector3 val2 = position + val; int num = r.Level; RaycastHit val3 = default(RaycastHit); float num4 = default(float); bool flag = default(bool); for (float num2 = -6f; num2 <= 6f; num2 += 1.5f) { for (float num3 = -6f; num3 <= 6f; num3 += 1.5f) { if (num2 * num2 + num3 * num3 > 36f || (num2 == 0f && num3 == 0f) || !Physics.Raycast(position + new Vector3(num2, 1.2f, num3), Vector3.down, ref val3, 2.5f, _solidMask)) { continue; } Vector3 point = ((RaycastHit)(ref val3)).point; if (Mathf.Abs(point.y - position.y) > 1.2f || Physics.CheckCapsule(point + Vector3.up * 0.5f, point + Vector3.up * 1.6f, 0.3f, _solidMask) || Physics.Linecast(val2, point + val, _solidMask)) { continue; } Cover.GetCoverForPoint(point + val, ref num4, ref flag, 0.5f); if (!(num4 < 0.8f) && flag) { int num5 = SE_Rested.CalculateComfortLevel(true, point); if (num5 > num) { num = num5; _spotFound = true; _spotOffset = point - position; _spotLevel = num5; } } } } } r.BetterSpot = _spotFound && _spotLevel > r.Level; r.SpotOffset = _spotOffset; r.SpotLevel = _spotLevel; } private static void LoadCandidates() { ObjectDB instance = ObjectDB.instance; int num = ((Object)instance).GetInstanceID() ^ instance.m_items.Count; if (num == _candidateSource && Candidates.Count > 0) { return; } _candidateSource = num; Candidates.Clear(); HashSet<PieceTable> hashSet = new HashSet<PieceTable>(); foreach (GameObject item in instance.m_items) { PieceTable val = ((!((Object)(object)item != (Object)null)) ? null : item.GetComponent<ItemDrop>()?.m_itemData?.m_shared?.m_buildPieces); if ((Object)(object)val == (Object)null || !hashSet.Add(val)) { continue; } foreach (GameObject piece in val.m_pieces) { Piece val2 = (((Object)(object)piece != (Object)null) ? piece.GetComponent<Piece>() : null); if ((Object)(object)val2 != (Object)null && val2.m_comfort > 0 && val2.m_enabled) { Candidates.Add(val2); } } } Plugin.Log.LogInfo((object)$"Found {Candidates.Count} comfort pieces across {hashSet.Count} build tools."); } internal static string Localize(string token) { if (Localization.instance == null) { return token; } return Localization.instance.Localize(token); } } internal static class HearthText { private const string Value = "orange"; private const string Good = "#9CCC65"; private const string Bad = "#E57373"; private const string FlavorColor = "#B9B3A8"; private const string NoteColor = "#B0B0B0"; private static readonly StringBuilder Sb = new StringBuilder(); private static int _visit; private static readonly string[] Outdoors = new string[8] { "No roof, no walls, no comfort. Odin has slept in worse. Not much worse.", "The sky is a fine ceiling until it rains. Build a roof.", "You're camping. Odin calls that 'being lost on purpose'.", "Furniture doesn't count out here. The trees don't care how nice your chair is.", "Stars overhead, wolves nearby. Get inside, little warrior.", "A fire with no roof is just a beacon for trolls.", "Even a Greydwarf has a better hut than this.", "Comfort 1. The ground thanks you for your company." }; private static readonly string[] JustARoof = new string[6] { "A roof. That's a start. A cold, bare, echoing start.", "Four walls and nothing in them. Very Norse. Very sad.", "You've built a box. Now put something in it.", "Sheltered, yes. Comfortable? Odin has seen cosier caves.", "The rain stays out. So does any joy. Add a fire.", "Even the draugr decorate. Badly, but they try." }; private static readonly string[] Spartan = new string[8] { "A bench and a fire. Spartan. Odin approves, grudgingly.", "Bed, fire, roof. Everything a Viking needs and nothing he wants.", "It'll do. It won't impress anyone, but it'll do.", "Simple. Honest. A little draughty. Like Thor's jokes.", "Odin once slept like this for nine nights. He was hanging from a tree at the time.", "Enough to rest, not enough to boast about.", "A warrior's den. Smells of smoke and wet boots.", "You've got the basics. Now try owning a chair." }; private static readonly string[] Modest = new string[8] { "Getting homely. Someone's been to the workbench.", "A proper hut. Odin would stop by for a mead.", "Cosy enough that the trolls might be jealous.", "Not a hall, but a home. The difference is a table and a bit of effort.", "Warm fire, soft bed, something to sit on. Civilisation, nearly.", "Huginn says it's nice. Muninn says the rug's crooked.", "Good enough for a long rest and a short boast.", "The kind of place you come back to on purpose." }; private static readonly string[] Comfortable = new string[8] { "Now this is a longhouse. Pull up a stool and stay a while.", "Comfortable. Dangerously comfortable. Don't forget there's a boss to kill.", "Your neighbours, if you had any, would be envious.", "A real hearth. Odin could get used to this.", "Rugs, banners, a proper table. Someone's been busy.", "The mead tastes better here. Odin has checked. Several times.", "Fit for a chieftain. A small, muddy chieftain.", "Rest here and you'll fight like you've had a holiday." }; private static readonly string[] Lavish = new string[7] { "Lavish. Odin hasn't seen this much furniture since Frigg's last renovation.", "This is a hall worth singing about. Badly, after mead.", "Thrones, trophies, banners. You're one goat away from a palace.", "The skalds will mention this place. Probably the rugs.", "If the Elder saw this he'd ask for a room.", "Comfort enough to make a berserker nap.", "You've out-decorated half of Asgard. The other half is Freyja's." }; private static readonly string[] Jarl = new string[6] { "A hall fit for a jarl. Odin may move in. He won't help with the dishes.", "This is a mead hall. All it needs is a feast and a fight.", "The Valkyries have started asking for directions.", "So much comfort the fire's taking a rest too.", "Even Loki would wipe his feet before coming in here.", "Odin's own chair is less comfortable. Don't tell him." }; private static readonly string[] Valhalla = new string[6] { "Valhalla called. It wants its furniture back.", "There is nothing left to sit on that you haven't sat on.", "Odin's hall has a rival. He's pretending not to mind.", "Peak comfort. Any cosier and you'd never leave to fight again.", "The skalds have run out of words. So has Odin. Nearly.", "This isn't a house, it's an argument for staying home." }; private static readonly string[] NoFire = new string[3] { "No fire? No rest. Even Odin needs his feet warm.", "A hearth without a fire is just a room with ambitions.", "Light something. Not the furniture." }; private static readonly string[] Unlit = new string[2] { "That fire's out. Comfort doesn't come from cold ashes.", "Feed the fire. It counts for nothing when it's dark." }; private static readonly string[] FireLow = new string[2] { "The fire's getting hungry. So is Odin, but the fire comes first.", "Embers don't count. Throw on a log before it's too late." }; private static readonly string[] WrongSpot = new string[2] { "You're standing in the draughty bit. Shuffle over.", "Wrong corner. The comfy part of the room is right there." }; private static readonly string[] Wet = new string[2] { "You're dripping on the rugs. Dry off by the fire first.", "Wet warriors don't rest. They sulk." }; private static readonly string[] Doubled = new string[2] { "Two of the same thing doesn't make twice the comfort. Odin has two ravens and still only one opinion.", "The game only counts the best of each kind. The rest is just clutter." }; internal const string ScrollHint = " <size=60%><color=#B0B0B0>(scroll for more)</color></size>"; private static readonly string[] Directions = new string[8] { "ahead", "ahead, to your right", "to your right", "behind you, to the right", "behind you", "behind you, to the left", "to your left", "ahead, to your left" }; internal static void NewVisit() { _visit = Random.Range(0, 1048576); } internal static string Title(HearthReport r) { return $"Comfort level {r.Level}"; } private static string Remark(HearthReport r) { List<string> list = new List<string>(Tier(r)); if (r.Sheltered && !r.NearFire) { list.AddRange(NoFire); } if (r.Pieces.Exists((ComfortPiece p) => p.Verdict == Verdict.Unlit)) { list.AddRange(Unlit); } if (r.Fires.Exists((FireInfo f) => f.Burning && !f.Endless && f.SecondsLeft < 600f)) { list.AddRange(FireLow); } if (r.BetterSpot) { list.AddRange(WrongSpot); } if (r.NotRestingBecause.Contains("you're wet")) { list.AddRange(Wet); } if (r.Pieces.FindAll((ComfortPiece p) => p.Verdict == Verdict.BeatenInGroup || p.Verdict == Verdict.Duplicate).Count >= 3) { list.AddRange(Doubled); } return list[_visit % list.Count]; } private static string[] Tier(HearthReport r) { if (!r.Sheltered) { return Outdoors; } if (r.Level <= 2) { return JustARoof; } if (r.Level <= 4) { return Spartan; } if (r.Level <= 7) { return Modest; } if (r.Level <= 10) { return Comfortable; } if (r.Level <= 14) { return Lavish; } if (r.Level <= 18) { return Jarl; } return Valhalla; } internal static string Body(HearthReport r) { Sb.Clear(); if (Plugin.ShowFlavor.Value) { Sb.Append("<i><color=").Append("#B9B3A8").Append('>') .Append(Remark(r)) .Append("</color></i>\n\n"); } AppendStatus(r); if (Plugin.ShowBetterSpot.Value && r.BetterSpot) { AppendBetterSpot(r); } if (Plugin.ShowFires.Value) { AppendFires(r); } if (Plugin.ShowCounted.Value) { AppendCounted(r); } if (Plugin.ShowIgnored.Value) { AppendIgnored(r); } if (Plugin.ShowNextStep.Value) { AppendNextStep(r); } if (Plugin.ShowRoadToMax.Value) { AppendRoad(r); } return Sb.ToString().TrimEnd(new char[1] { '\n' }); } private static void AppendStatus(HearthReport r) { Line("Sheltered", r.Sheltered ? Yes("yes") : No("no")); Line("Near a fire", r.NearFire ? Yes("yes") : No("no")); string text = ((r.RestedWouldLast > 0f) ? Duration(r.RestedWouldLast) : null); if (r.Resting && r.RestDelay > r.RestingFor && !r.Rested) { Line("Rested", string.Format("resting, rested in <color={0}>{1} s</color>", "orange", Mathf.CeilToInt(r.RestDelay - r.RestingFor)) + For(text)); } else if (r.Rested) { Line("Rested", "<color=orange>" + Clock(r.RestedLeft) + "</color> left" + (r.Resting ? " (topping up)" : ((text != null) ? (", resting here gives " + text) : ""))); } else if (r.Resting) { Line("Rested", Yes("resting") + For(text)); } else { Line("Rested", No("no") + ((text != null) ? (", resting here gives <color=orange>" + text + "</color>") : "")); } if (!r.Resting && r.NotRestingBecause.Count > 0) { Sb.Append(" <color=").Append("#B0B0B0").Append(">Can't rest: ") .Append(string.Join(", ", r.NotRestingBecause)) .Append("</color>\n"); } if (r.PersonalBest > r.Level) { Line("Best ever", string.Format("<color={0}>{1}</color>", "orange", r.PersonalBest)); } } private static string For(string wouldLast) { if (wouldLast == null) { return ""; } return ", for <color=orange>" + wouldLast + "</color>"; } private static void AppendCounted(HearthReport r) { //IL_0187: Unknown result type (might be due to invalid IL or missing references) List<ComfortPiece> list = r.Pieces.FindAll((ComfortPiece p) => p.Verdict == Verdict.Counted); if (!r.Sheltered) { if (list.Count > 0) { Sb.Append("\n<color=").Append("#B0B0B0").Append(">Under a roof, ") .Append(Count(list.Count, "piece")) .Append(" here would count.</color>\n"); } return; } Sb.Append("\nCounting <color=").Append("#B0B0B0").Append(">(base 1, shelter +1)</color>\n"); if (list.Count == 0) { Sb.Append(" <color=").Append("#B0B0B0").Append(">Nothing yet.</color>\n"); return; } int num = 0; foreach (ComfortPiece item in list) { if (num++ == Plugin.MaxListed.Value) { Sb.Append(" <color=").Append("#B0B0B0").Append(">...and ") .Append(list.Count - Plugin.MaxListed.Value) .Append(" more</color>\n"); break; } Sb.Append(" <color=").Append("orange").Append(">+") .Append(item.Comfort) .Append("</color> ") .Append(Comfort.Localize(item.Token)) .Append(GroupTag(item.Group)) .Append('\n'); } } private static void AppendIgnored(HearthReport r) { if (!r.Sheltered) { return; } List<string> list = new List<string>(); Dictionary<string, int> dictionary = new Dictionary<string, int>(); foreach (ComfortPiece piece in r.Pieces) { if (piece.Verdict != Verdict.Counted) { string text = Comfort.Localize(piece.Token); string text2 = text + "\n" + piece.Verdict switch { Verdict.Unlit => "not lit", Verdict.Duplicate => "you've already got one", _ => (piece.BeatenBy != null) ? (Comfort.Localize(piece.BeatenBy.Token) + " counts instead") : "beaten by a better one", }; if (dictionary.ContainsKey(text2)) { dictionary[text2]++; continue; } dictionary[text2] = 1; list.Add(text2); } } if (list.Count == 0) { return; } Sb.Append("\nNot counting\n"); int num = 0; foreach (string item in list) { if (num++ == Plugin.MaxListed.Value) { Sb.Append(" <color=").Append("#B0B0B0").Append(">...and ") .Append(list.Count - Plugin.MaxListed.Value) .Append(" more</color>\n"); break; } string[] array = item.Split(new char[1] { '\n' }); Sb.Append(" ").Append(array[0]); if (dictionary[item] > 1) { Sb.Append(" x").Append(dictionary[item]); } Sb.Append(": <color=").Append("#B0B0B0").Append('>') .Append(array[1]) .Append("</color>\n"); } } private static void AppendNextStep(HearthReport r) { //IL_0144: Unknown result type (might be due to invalid IL or missing references) Sb.Append('\n'); if (!r.Sheltered) { Sb.Append("Next step: <color=").Append("orange").Append(">get under a roof</color>\n"); Sb.Append(" <color=").Append("#B0B0B0").Append(">Shelter is +1, and furniture only counts indoors. With a roof here: comfort ") .Append(r.ShelteredLevel) .Append(".</color>\n"); return; } if (r.Suggestions.Count == 0) { Sb.Append("Next step: <color=").Append("#B0B0B0").Append('>') .Append((!r.NothingLeftToBuild) ? "none found." : (Plugin.KnownPiecesOnly.Value ? "nothing you know how to build would add comfort here. Unlock new pieces to go higher." : "nothing in the game would add comfort here. You've done it.")) .Append("</color>\n"); return; } Suggestion suggestion = r.Suggestions[0]; Sb.Append("Cheapest next step: <color=").Append("orange").Append('>') .Append(suggestion.Name) .Append("</color> <color=") .Append("#9CCC65") .Append(">+") .Append(suggestion.Gain) .Append("</color>") .Append(GroupTag(suggestion.Group)) .Append('\n'); if (!suggestion.Known) { Sb.Append(" <color=").Append("#B0B0B0").Append(">(not unlocked yet)</color>\n"); } if (suggestion.Free) { Sb.Append(" Costs: <color=").Append("#9CCC65").Append(">nothing (free building is on)</color>\n"); } else if (suggestion.Materials.Count > 0) { Sb.Append(" "); for (int i = 0; i < suggestion.Materials.Count; i++) { Material material = suggestion.Materials[i]; if (i > 0) { Sb.Append(", "); } Sb.Append(material.Name).Append(" <color=").Append((material.Missing == 0) ? "#9CCC65" : "#E57373") .Append('>') .Append(material.Have) .Append('/') .Append(material.Need) .Append("</color>"); } Sb.Append('\n'); } if (suggestion.ReadyToBuild) { Sb.Append(" <color=").Append("#9CCC65").Append(">You can build it right now.</color>\n"); } else { List<string> list = new List<string>(); if (suggestion.Missing > 0) { list.Add(string.Format("{0} more material{1}", suggestion.Missing, (suggestion.Missing == 1) ? "" : "s")); } if (suggestion.Station != null && !suggestion.StationInRange) { list.Add("a " + suggestion.Station + " nearby"); } if (list.Count == 0) { list.Add("something the game won't say"); } Sb.Append(" <color=").Append("#E57373").Append(">Needs ") .Append(string.Join(" and ", list)) .Append(".</color>\n"); } if (r.Suggestions.Count <= 1) { return; } Sb.Append(" <color=").Append("#B0B0B0").Append(">Or: "); for (int j = 1; j < r.Suggestions.Count; j++) { Suggestion suggestion2 = r.Suggestions[j]; if (j > 1) { Sb.Append(", "); } Sb.Append(suggestion2.Name).Append(" +").Append(suggestion2.Gain) .Append(suggestion2.ReadyToBuild ? " (ready)" : ((suggestion2.Missing > 0) ? $" ({suggestion2.Missing} to fetch)" : "")); } Sb.Append("</color>\n"); } private static void AppendBetterSpot(HearthReport r) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) Vector2 val = new Vector2(r.SpotOffset.x, r.SpotOffset.z); float magnitude = ((Vector2)(ref val)).magnitude; Sb.Append("Better spot: <color=").Append("orange").Append('>') .Append(Mathf.Max(1, Mathf.RoundToInt(magnitude))) .Append(" m ") .Append(Direction(r.SpotOffset)) .Append("</color>, comfort <color=") .Append("#9CCC65") .Append('>') .Append(r.SpotLevel) .Append("</color>\n"); } private static void AppendFires(HearthReport r) { if (r.Fires.Count == 0) { return; } Sb.Append("\nFires\n"); int num = 0; foreach (FireInfo fire in r.Fires) { if (num++ == Plugin.MaxListed.Value) { break; } Sb.Append(" ").Append(fire.Name).Append(": "); if (fire.Endless) { Sb.Append("<color=").Append("#9CCC65").Append(">burns forever</color>"); } else if (!fire.Burning || fire.SecondsLeft <= 0f) { Sb.Append("<color=").Append("#E57373").Append(">out</color>") .Append((fire.FuelName != null) ? (", add " + fire.FuelName) : ""); } else { Sb.Append("<color=").Append((fire.SecondsLeft < 600f) ? "#E57373" : "orange").Append('>') .Append(Duration(fire.SecondsLeft)) .Append("</color> left"); if (fire.ToFill > 0 && fire.FuelName != null) { Sb.Append(" <color=").Append("#B0B0B0").Append(">(room for ") .Append(fire.ToFill) .Append(' ') .Append(fire.FuelName) .Append(")</color>"); } } Sb.Append('\n'); } } private static void AppendRoad(HearthReport r) { if (!r.Sheltered || r.Road.Count == 0) { return; } Sb.Append("\nRoad to comfort <color=").Append("#9CCC65").Append('>') .Append(r.MaxLevel) .Append("</color> <color=") .Append("#B0B0B0") .Append(">(") .Append(Count(r.Road.Count, "piece")) .Append(", cheapest first)</color>\n "); for (int i = 0; i < r.Road.Count; i++) { if (i > 0) { Sb.Append(", "); } Sb.Append(r.Road[i].Name); } Sb.Append('\n'); if (r.RoadMaterials.Count == 0) { return; } Sb.Append(" All told: "); for (int j = 0; j < r.RoadMaterials.Count && j < 6; j++) { Material material = r.RoadMaterials[j]; if (j > 0) { Sb.Append(", "); } Sb.Append(material.Name).Append(" <color=").Append((material.Missing == 0) ? "#9CCC65" : "#E57373") .Append('>') .Append(material.Have) .Append('/') .Append(material.Need) .Append("</color>"); } if (r.RoadMaterials.Count > 6) { Sb.Append(" <color=").Append("#B0B0B0").Append(">and ") .Append(r.RoadMaterials.Count - 6) .Append(" more</color>"); } Sb.Append('\n'); } private static string Direction(Vector3 offset) { //IL_001c: 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_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0022: 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_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0039: 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) Camera mainCamera = Utils.GetMainCamera(); Vector3 val = (((Object)(object)mainCamera != (Object)null) ? ((Component)mainCamera).transform.forward : Vector3.forward); float num = Vector2.SignedAngle(new Vector2(offset.x, offset.z), new Vector2(val.x, val.z)); return Directions[Mathf.RoundToInt(Mathf.Repeat(num, 360f) / 45f) % 8]; } private static void Line(string label, string value) { Sb.Append(label).Append(": ").Append(value) .Append('\n'); } private static string Yes(string s) { return "<color=#9CCC65>" + s + "</color>"; } private static string No(string s) { return "<color=#E57373>" + s + "</color>"; } private unsafe static string GroupTag(ComfortGroup g) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) if ((int)g != 0) { return " <color=#B0B0B0>(" + ((object)(*(ComfortGroup*)(&g))/*cast due to .constrained prefix*/).ToString().ToLowerInvariant() + ")</color>"; } return ""; } private static string Count(int n, string noun) { if (n != 1) { return $"{n} {noun}s"; } return "1 " + noun; } internal static string Duration(float seconds) { int num = Mathf.Max(1, Mathf.RoundToInt(seconds / 60f)); if (num >= 60) { return $"{num / 60} h {num % 60} min"; } return $"{num} min"; } private static string Clock(float seconds) { int num = Mathf.Max(0, Mathf.CeilToInt(seconds)); return $"{num / 60}:{num % 60:00}"; } } internal class HearthTooltip : MonoBehaviour { private const float RefreshInterval = 0.5f; private GameObject _tooltip; private RectTransform _panel; private TMP_Text _topic; private TMP_Text _text; private readonly Vector3[] _corners = (Vector3[])(object)new Vector3[4]; private CanvasGroup _group; private RectTransform _viewport; private LayoutElement _viewportLayout; private RectTransform _content; private float _scroll; private float _maxScroll; private int _fittedForScreen; internal static bool WheelCaptured; private float _shownAt; private float _fadeOutStart = -1f; private float _fadeOutLength; private float _nextRefresh; private static bool _loggedLayout; private void LateUpdate() { bool num = CanShow(); if (num && ShowKeyDown()) { if (IsOpen() && !IsFading()) { Close(); } else { Open(); } } WheelCaptured = false; if (!num || !IsOpen()) { Hide(); } else { if ((Object)(object)_tooltip == (Object)null && !CreateTooltip()) { return; } if (Time.time >= _nextRefresh) { _nextRefresh = Time.time + 0.5f; HearthReport hearthReport = Comfort.Compute(); if (hearthReport == null) { Hide(); return; } if (!_tooltip.activeSelf) { _tooltip.SetActive(true); _tooltip.transform.SetAsLastSibling(); } string text = HearthText.Title(hearthReport); _topic.text = text; _text.text = HearthText.Body(hearthReport); LayoutRebuilder.ForceRebuildLayoutImmediate(_panel); if ((Object)(object)_viewport == (Object)null) { MakeScrollable(); } Fit(); if (_maxScroll > 0f) { _topic.text = text + " <size=60%><color=#B0B0B0>(scroll for more)</color></size>"; Fit(); } } else if ((Object)(object)_viewport != (Object)null && Screen.height != _fittedForScreen) { Fit(); } if (!_tooltip.activeSelf) { _tooltip.SetActive(true); _tooltip.transform.SetAsLastSibling(); } Scroll(); _group.alpha = Alpha(); Place(); } } private void OnDestroy() { if ((Object)(object)_tooltip != (Object)null) { Object.Destroy((Object)(object)_tooltip); } } private bool IsOpen() { return Time.time < _fadeOutStart + _fadeOutLength; } private bool IsFading() { return Time.time >= _fadeOutStart; } private void Open() { float num = (IsOpen() ? Alpha() : 0f); _shownAt = Time.time - num * Plugin.FadeInSeconds.Value; _fadeOutStart = Time.time + Plugin.ShowSeconds.Value; _fadeOutLength = Plugin.FadeOutSeconds.Value; _nextRefresh = 0f; if (num == 0f) { HearthText.NewVisit(); _scroll = 0f; } } private void Close() { _fadeOutStart = Time.time; _fadeOutLength = Plugin.FadeInSeconds.Value; } private float Alpha() { float value = Plugin.FadeInSeconds.Value; float num = ((value > 0f) ? Mathf.Clamp01((Time.time - _shownAt) / value) : 1f); float num2 = ((_fadeOutLength > 0f) ? Mathf.Clamp01((Time.time - _fadeOutStart) / _fadeOutLength) : (IsFading() ? 1f : 0f)); return num * (1f - num2); } private static bool CanShow() { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null || ((Character)localPlayer).IsDead() || !HudVisible()) { return false; } if (!InventoryGui.IsVisible() && !Menu.IsVisible() && !Minimap.IsOpen() && !TextInput.IsVisible() && !Console.IsVisible()) { if ((Object)(object)Chat.instance != (Object)null) { return !Chat.instance.HasFocus(); } return true; } return false; } private static bool HudVisible() { if ((Object)(object)Hud.instance != (Object)null && !Hud.IsUserHidden()) { return Hud.instance.IsVisible(); } return false; } private static bool ShowKeyDown() { //IL_0005: 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_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0016: 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) KeyboardShortcut value = Plugin.ShowKey.Value; if ((int)((KeyboardShortcut)(ref value)).MainKey == 0 || !ZInput.GetKeyDown(((KeyboardShortcut)(ref value)).MainKey, false)) { return false; } foreach (KeyCode modifier in ((KeyboardShortcut)(ref value)).Modifiers) { if (!ZInput.GetKey(modifier, false)) { return false; } } return true; } private void Place() { //IL_0034: 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_0057: 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_0068: 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_0077: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0089: 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_008f: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_0095: 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) Vector2 val = default(Vector2); ((Vector2)(ref val))..ctor(Plugin.PositionX.Value * (float)Screen.width, Plugin.PositionY.Value * (float)Screen.height); _tooltip.transform.position = Vector2.op_Implicit(val); _panel.GetWorldCorners(_corners); Vector2 val2 = Vector2.op_Implicit((_corners[0] + _corners[2]) / 2f); Transform transform = _tooltip.transform; transform.position += Vector2.op_Implicit(val - val2); Utils.ClampUIToScreen(_panel); } private bool CreateTooltip() { GameObject val = FindTooltipPrefab(); if ((Object)(object)val == (Object)null) { return false; } Canvas componentInParent = ((Component)InventoryGui.instance).GetComponentInParent<Canvas>(); Canvas val2 = ((componentInParent != null) ? componentInParent.rootCanvas : null); if ((Object)(object)val2 == (Object)null) { return false; } _tooltip = Object.Instantiate<GameObject>(val, ((Component)val2).transform); ((Object)_tooltip).name = "OdinsHearth"; ref RectTransform panel = ref _panel; Transform child = _tooltip.transform.GetChild(0); panel = (RectTransform)(object)((child is RectTransform) ? child : null); Transform obj = Utils.FindChild(_tooltip.transform, "Topic", (IterativeSearchType)0); _topic = ((obj != null) ? ((Component)obj).GetComponent<TMP_Text>() : null); Transform obj2 = Utils.FindChild(_tooltip.transform, "Text", (IterativeSearchType)0); _text = ((obj2 != null) ? ((Component)obj2).GetComponent<TMP_Text>() : null); if ((Object)(object)_panel == (Object)null || (Object)(object)_topic == (Object)null || (Object)(object)_text == (Object)null) { Object.Destroy((Object)(object)_tooltip); _tooltip = null; return false; } Graphic[] componentsInChildren = _tooltip.GetComponentsInChildren<Graphic>(true); for (int i = 0; i < componentsInChildren.Length; i++) { componentsInChildren[i].raycastTarget = false; } _group = _tooltip.GetComponent<CanvasGroup>(); if ((Object)(object)_group == (Object)null) { _group = _tooltip.AddComponent<CanvasGroup>(); } _group.interactable = false; _group.blocksRaycasts = false; _group.alpha = 0f; return true; } private void MakeScrollable() { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Expected O, but got Unknown //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_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Expected O, but got Unknown //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Expected O, but got Unknown //IL_009b: 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_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_01de: Unknown result type (might be due to invalid IL or missing references) //IL_01f8: Unknown result type (might be due to invalid IL or missing references) //IL_0212: Unknown result type (might be due to invalid IL or missing references) //IL_0228: Unknown result type (might be due to invalid IL or missing references) //IL_022d: Unknown result type (might be due to invalid IL or missing references) //IL_0236: Unknown result type (might be due to invalid IL or missing references) //IL_0246: Unknown result type (might be due to invalid IL or missing references) RectTransform val = (RectTransform)_text.transform; Rect rect = val.rect; float width = ((Rect)(ref rect)).width; LogLayoutOnce(val); GameObject val2 = new GameObject("Scroll", new Type[3] { typeof(RectTransform), typeof(RectMask2D), typeof(LayoutElement) }); _viewport = (RectTransform)val2.transform; ((Transform)_viewport).SetParent(((Transform)val).parent, false); ((Transform)_viewport).SetSiblingIndex(((Transform)val).GetSiblingIndex()); _viewport.anchorMin = val.anchorMin; _viewport.anchorMax = val.anchorMax; _viewport.pivot = val.pivot; _viewport.anchoredPosition = val.anchoredPosition; _viewport.sizeDelta = val.sizeDelta; _viewportLayout = val2.GetComponent<LayoutElement>(); _viewportLayout.layoutPriority = 10; LayoutElement component = ((Component)_text).GetComponent<LayoutElement>(); _viewportLayout.preferredWidth = (((Object)(object)component != (Object)null && component.preferredWidth > 0f) ? component.preferredWidth : width); _viewportLayout.minWidth = (((Object)(object)component != (Object)null) ? component.minWidth : (-1f)); _viewportLayout.flexibleWidth = (((Object)(object)component != (Object)null) ? component.flexibleWidth : (-1f)); if ((Object)(object)component != (Object)null) { component.ignoreLayout = true; } ContentSizeFitter[] components = ((Component)_text).GetComponents<ContentSizeFitter>(); for (int i = 0; i < components.Length; i++) { ((Behaviour)components[i]).enabled = false; } _content = val; ((Transform)_content).SetParent((Transform)(object)_viewport, false); _content.anchorMin = new Vector2(0f, 1f); _content.anchorMax = new Vector2(1f, 1f); _content.pivot = new Vector2(0.5f, 1f); RectTransform content = _content; rect = val.rect; content.sizeDelta = new Vector2(0f, ((Rect)(ref rect)).height); _content.anchoredPosition = Vector2.zero; } private void Fit() { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0021: 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_0062: 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_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00d7: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_011f: Unknown result type (might be due to invalid IL or missing references) //IL_012f: Unknown result type (might be due to invalid IL or missing references) //IL_0164: Unknown result type (might be due to invalid IL or missing references) //IL_0170: Unknown result type (might be due to invalid IL or missing references) //IL_0146: Unknown result type (might be due to invalid IL or missing references) //IL_014b: Unknown result type (might be due to invalid IL or missing references) _fittedForScreen = Screen.height; LayoutRebuilder.ForceRebuildLayoutImmediate(_panel); Rect rect = _viewport.rect; float width = ((Rect)(ref rect)).width; if (!(width < 1f)) { float y = _text.GetPreferredValues(_text.text, width, 0f).y; _content.sizeDelta = new Vector2(0f, y); Canvas componentInParent = _tooltip.GetComponentInParent<Canvas>(); Canvas val = ((componentInParent != null) ? componentInParent.rootCanvas : null); float num = (((Object)(object)val != (Object)null && val.scaleFactor > 0f) ? val.scaleFactor : 1f); float num2 = (float)Screen.height * Plugin.MaxHeight.Value / num; rect = _panel.rect; float height = ((Rect)(ref rect)).height; rect = _viewport.rect; float num3 = height - ((Rect)(ref rect)).height; float num4 = Mathf.Min(y, Mathf.Max(60f, num2 - num3)); _viewportLayout.minHeight = num4; _viewportLayout.preferredHeight = num4; if (!Mathf.Approximately(_viewport.anchorMin.y, _viewport.anchorMax.y)) { rect = _viewport.rect; num4 = ((Rect)(ref rect)).height; } else { _viewport.sizeDelta = new Vector2(_viewport.sizeDelta.x, num4); } _maxScroll = Mathf.Max(0f, y - num4); ApplyScroll(); LayoutRebuilder.ForceRebuildLayoutImmediate(_panel); } } private void Scroll() { //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) //IL_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_viewport == (Object)null || _maxScroll <= 0f || IsFading()) { return; } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null || ((Character)localPlayer).InPlaceMode()) { return; } WheelCaptured = true; float num = _text.fontSize * 3.6f; float mouseScrollWheel = ZInput.GetMouseScrollWheel(); Rect rect; if (mouseScrollWheel > 0f || ZInput.GetKeyDown((KeyCode)280, false)) { float scroll = _scroll; float num2; if (!(mouseScrollWheel > 0f)) { rect = _viewport.rect; num2 = ((Rect)(ref rect)).height * 0.9f; } else { num2 = num; } _scroll = scroll - num2; } else if (mouseScrollWheel < 0f || ZInput.GetKeyDown((KeyCode)281, false)) { float scroll2 = _scroll; float num3; if (!(mouseScrollWheel < 0f)) { rect = _viewport.rect; num3 = ((Rect)(ref rect)).height * 0.9f; } else { num3 = num; } _scroll = scroll2 + num3; } ApplyScroll(); } private void ApplyScroll() { //IL_002d: Unknown result type (might be due to invalid IL or missing references) _scroll = Mathf.Clamp(_scroll, 0f, _maxScroll); _content.anchoredPosition = new Vector2(0f, _scroll); } private void LogLayoutOnce(RectTransform textRect) { //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) //IL_004b: 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) if (_loggedLayout) { return; } _loggedLayout = true; StringBuilder stringBuilder = new StringBuilder("Tooltip layout:"); Transform val = (Transform)(object)textRect; while ((Object)(object)val != (Object)null && (Object)(object)val != (Object)(object)_tooltip.transform.parent) { StringBuilder stringBuilder2 = stringBuilder.Append("\n ").Append(((Object)val).name).Append(" "); Rect rect = ((RectTransform)val).rect; stringBuilder2.Append(((Rect)(ref rect)).size).Append(":"); Component[] components = ((Component)val).GetComponents<Component>(); foreach (Component val2 in components) { stringBuilder.Append(' ').Append(((object)val2).GetType().Name); } val = val.parent; } Plugin.Log.LogInfo((object)stringBuilder.ToString()); } private static GameObject FindTooltipPrefab() { InventoryGui instance = InventoryGui.instance; if ((Object)(object)instance == (Object)null) { return null; } UITooltip val = (((Object)(object)instance.m_playerGrid != (Object)null && (Object)(object)instance.m_playerGrid.m_elementPrefab != (Object)null) ? instance.m_playerGrid.m_elementPrefab.GetComponentInChildren<UITooltip>(true) : null); if ((Object)(object)val != (Object)null && (Object)(object)val.m_tooltipPrefab != (Object)null) { return val.m_tooltipPrefab; } UITooltip[] componentsInChildren = ((Component)instance).GetComponentsInChildren<UITooltip>(true); foreach (UITooltip val2 in componentsInChildren) { if ((Object)(object)val2.m_tooltipPrefab != (Object)null) { return val2.m_tooltipPrefab; } } return null; } private void Hide() { if ((Object)(object)_tooltip != (Object)null && _tooltip.activeSelf) { _tooltip.SetActive(false); } } } [BepInPlugin("hevyh2o.valheim.odinshearth", "Odin's Hearth", "0.1.3")] public class Plugin : BaseUnityPlugin { [HarmonyPatch] private static class WheelPatches { private static bool _inCameraUpdate; [HarmonyPatch(typeof(GameCamera), "UpdateCamera")] [HarmonyPrefix] private static void CameraStart() { _inCameraUpdate = true; } [HarmonyPatch(typeof(GameCamera), "UpdateCamera")] [HarmonyFinalizer] private static void CameraEnd() { _inCameraUpdate = false; } [HarmonyPatch(typeof(ZInput), "GetMouseScrollWheel")] [HarmonyPostfix] private static void Wheel(ref float __result) { if (_inCameraUpdate && HearthTooltip.WheelCaptured) { __result = 0f; } } } public const string Guid = "hevyh2o.valheim.odinshearth"; public const string Name = "Odin's Hearth"; public const string Version = "0.1.3"; internal static ManualLogSource Log; internal static ConfigEntry<KeyboardShortcut> ShowKey; internal static ConfigEntry<float> ShowSeconds; internal static ConfigEntry<float> FadeInSeconds; internal static ConfigEntry<float> FadeOutSeconds; internal static ConfigEntry<float> PositionX; internal static ConfigEntry<float> PositionY; internal static ConfigEntry<float> MaxHeight; internal static ConfigEntry<bool> ShowCounted; internal static ConfigEntry<bool> ShowIgnored; internal static ConfigEntry<int> MaxListed; internal static ConfigEntry<bool> ShowNextStep; internal static ConfigEntry<int> Alternatives; internal static ConfigEntry<bool> ShowRoadToMax; internal static ConfigEntry<bool> ShowFires; internal static ConfigEntry<bool> ShowBetterSpot; internal static ConfigEntry<bool> KnownPiecesOnly; internal static ConfigEntry<bool> ShowFlavor; private HearthTooltip _tooltip; private Harmony _harmony; private void Awake() { //IL_0022: 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_006e: Expected O, but got Unknown //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Expected O, but got Unknown //IL_00de: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Expected O, but got Unknown //IL_011b: Unknown result type (might be due to invalid IL or missing references) //IL_0125: Expected O, but got Unknown //IL_0158: Unknown result type (might be due to invalid IL or missing references) //IL_0162: Expected O, but got Unknown //IL_0195: Unknown result type (might be due to invalid IL or missing references) //IL_019f: Expected O, but got Unknown //IL_0207: Unknown result type (might be due to invalid IL or missing references) //IL_0211: Expected O, but got Unknown //IL_0258: Unknown result type (might be due to invalid IL or missing references) //IL_0262: Expected O, but got Unknown //IL_0326: Unknown result type (might be due to invalid IL or missing references) //IL_0330: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; ShowKey = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("Display", "ShowKey", new KeyboardShortcut((KeyCode)110, Array.Empty<KeyCode>()), "Key that opens the hearth panel. Press again to close it early, or while it's fading to bring it back."); ShowSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("Display", "ShowSeconds", 20f, new ConfigDescription("How long the panel stays fully visible after pressing ShowKey, before it starts to fade.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(2f, 120f), Array.Empty<object>())); FadeInSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("Display", "FadeInSeconds", 0.3f, new ConfigDescription("How long the panel takes to fade in. Also used when closing it with ShowKey. 0 = instant.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 2f), Array.Empty<object>())); FadeOutSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("Display", "FadeOutSeconds", 5f, new ConfigDescription("How long the panel takes to fade away once ShowSeconds is up. 0 = vanish at once.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 30f), Array.Empty<object>())); PositionX = ((BaseUnityPlugin)this).Config.Bind<float>("Display", "PositionX", 0.75f, new ConfigDescription("Horizontal position of the panel, 0 = left edge, 1 = right edge.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>())); PositionY = ((BaseUnityPlugin)this).Config.Bind<float>("Display", "PositionY", 0.55f, new ConfigDescription("Vertical position of the panel, 0 = bottom, 1 = top.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>())); MaxHeight = ((BaseUnityPlugin)this).Config.Bind<float>("Display", "MaxHeight", 0.5f, new ConfigDescription("Tallest the panel can get, as a fraction of the screen height. Anything longer scrolls: mouse wheel or PageUp/PageDown while the panel is open.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.2f, 0.95f), Array.Empty<object>())); ShowCounted = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "ShowCounted", true, "List the pieces that are counting toward your comfort."); ShowIgnored = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "ShowIgnored", true, "List the comfort pieces nearby that aren't counting, and why (a better piece of the same kind, a duplicate, or an unlit fire)."); MaxListed = ((BaseUnityPlugin)this).Config.Bind<int>("General", "MaxListed", 8, new ConfigDescription("Most pieces to list in each of the counted and ignored lists before summarising the rest.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 30), Array.Empty<object>())); ShowNextStep = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "ShowNextStep", true, "Show the cheapest piece you could build to raise your comfort, with its cost and what you're carrying."); Alternatives = ((BaseUnityPlugin)this).Config.Bind<int>("General", "Alternatives", 2, new ConfigDescription("How many runner-up pieces to mention after the cheapest next step.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 5), Array.Empty<object>())); ShowRoadToMax = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "ShowRoadToMax", true, "Show the highest comfort you could reach here, the pieces that get you there and the materials for all of them."); ShowFires = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "ShowFires", true, "Show how long each comfort fire nearby will keep burning, and how much fuel would fill it."); ShowBetterSpot = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "ShowBetterSpot", true, "Point out a sheltered spot within a few metres where comfort is higher than where you're standing."); KnownPiecesOnly = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "KnownPiecesOnly", true, "Only suggest pieces you've already unlocked. Turn off to see every comfort piece in the game, spoilers and all."); ShowFlavor = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "ShowFlavor", true, "Show Odin's one-line remark about your home."); if (Comfort.Init()) { _tooltip = ((Component)this).gameObject.AddComponent<HearthTooltip>(); _harmony = new Harmony("hevyh2o.valheim.odinshearth"); _harmony.PatchAll(typeof(WheelPatches)); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Odin's Hearth 0.1.3 loaded. Odin has pulled up a chair."); } } private void OnDestroy() { Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } if ((Object)(object)_tooltip != (Object)null) { Object.Destroy((Object)(object)_tooltip); } } } internal static class BuildInfo { public const string Version = "0.1.3"; } }