using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using CameraTweaks.Configs;
using CameraTweaks.Extensions;
using HarmonyLib;
using UnityEngine;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("CameraTweaks")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("CameraTweaks")]
[assembly: AssemblyCopyright("Copyright © 2023")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("e3243d22-4307-4008-ba36-9f326008cde5")]
[assembly: AssemblyFileVersion("1.2.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.6.2", FrameworkDisplayName = ".NET Framework 4.6.2")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.2.0.0")]
[module: UnverifiableCode]
namespace CameraTweaks
{
[BepInPlugin("Searica.Valheim.CameraTweaks", "CameraTweaks", "1.2.0")]
internal sealed class CameraTweaks : BaseUnityPlugin
{
internal const string Author = "Searica";
public const string PluginName = "CameraTweaks";
public const string PluginGUID = "Searica.Valheim.CameraTweaks";
public const string PluginVersion = "1.2.0";
internal static ConfigEntry<float> MaxDistance;
internal static ConfigEntry<float> MaxDistanceBoat;
internal static ConfigEntry<float> CameraFoV;
internal static ConfigEntry<bool> AlwaysFaceCamera;
private static bool ShouldUpdateCamera;
private static readonly string MainSection = ConfigManager.SetStringPriority("Global", 1);
private static readonly string CameraSection = "Camera";
public void Awake()
{
Log.Init(((BaseUnityPlugin)this).Logger);
ConfigManager.Init("Searica.Valheim.CameraTweaks", ((BaseUnityPlugin)this).Config);
Log.Verbosity = ConfigManager.BindConfig(MainSection, "Verbosity", LogLevel.Low, "Low will log basic information about the mod. Medium will log information that is useful for troubleshooting. High will log a lot of information, do not set it to this without good reason as it will slow Down your game.");
CameraFoV = ConfigManager.BindConfig(CameraSection, "Field of View", 65f, "Camera field of view in degrees. Vanilla default is 65.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(60f, 120f));
MaxDistance = ConfigManager.BindConfig(CameraSection, "Max Distance", 6f, "Maximum distance you can zoom out to while on foot. Vanilla default is 6.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(6f, 20f));
MaxDistanceBoat = ConfigManager.BindConfig(CameraSection, "Max Distance (Boat)", 12f, "Maximum distance you can zoom out to while in a boat. Vanilla default is 6.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(6f, 20f));
AlwaysFaceCamera = ConfigManager.BindConfig(CameraSection, "Always Face Camera", value: false, "Controls whether the player character will always face in the direction of the crosshairs.");
MaxDistance.SettingChanged += SetShouldUpdateCamera;
MaxDistance.SettingChanged += SetShouldUpdateCamera;
CameraFoV.SettingChanged += SetShouldUpdateCamera;
ConfigManager.Save();
Harmony.CreateAndPatchAll(Assembly.GetExecutingAssembly(), "Searica.Valheim.CameraTweaks");
Game.isModded = true;
ConfigManager.SetupWatcher();
ConfigManager.CheckForConfigManager();
ConfigManager.OnConfigFileReloaded += UpdateCameraSettings;
ConfigManager.OnConfigWindowClosed += UpdateCameraSettings;
}
public void OnDestroy()
{
ConfigManager.Save();
}
private static void SetShouldUpdateCamera(object obj, EventArgs e)
{
ShouldUpdateCamera |= !ShouldUpdateCamera;
}
private static void UpdateCameraSettings()
{
if (ShouldUpdateCamera && (Object)(object)GameCamera.instance != (Object)null)
{
GameCamera.instance.m_maxDistance = MaxDistance.Value;
GameCamera.instance.m_maxDistanceBoat = MaxDistanceBoat.Value;
GameCamera.instance.m_fov = CameraFoV.Value;
GameCamera.instance.UpdateCamera(Time.unscaledDeltaTime);
ShouldUpdateCamera = false;
ConfigManager.Save();
}
}
}
[HarmonyPatch]
internal static class GameCameraPatches
{
[HarmonyPostfix]
[HarmonyPatch(typeof(GameCamera), "Awake")]
private static void GameCameraAwakePostfix(GameCamera __instance)
{
__instance.m_maxDistance = CameraTweaks.MaxDistance.Value;
__instance.m_maxDistanceBoat = CameraTweaks.MaxDistanceBoat.Value;
__instance.m_fov = CameraTweaks.CameraFoV.Value;
}
[HarmonyPrefix]
[HarmonyPatch(typeof(Player), "AlwaysRotateCamera")]
public static bool AlwaysRotateCameraPrefix(Player __instance, ref bool __result)
{
if (CameraTweaks.AlwaysFaceCamera.Value && ((Humanoid)__instance).GetCurrentWeapon() != null && !((Character)__instance).InEmote())
{
__result = true;
return false;
}
__result = false;
return true;
}
}
internal enum LogLevel
{
Low,
Medium,
High
}
internal static class Log
{
private static ManualLogSource _logSource;
internal static ConfigEntry<LogLevel> Verbosity { get; set; }
internal static LogLevel VerbosityLevel => Verbosity.Value;
internal static void Init(ManualLogSource logSource)
{
_logSource = logSource;
}
internal static void LogDebug(object data)
{
_logSource.LogDebug(data);
}
internal static void LogError(object data)
{
_logSource.LogError(data);
}
internal static void LogFatal(object data)
{
_logSource.LogFatal(data);
}
internal static void LogInfo(object data, LogLevel level = LogLevel.Low)
{
if (VerbosityLevel >= level)
{
_logSource.LogInfo(data);
}
}
internal static void LogMessage(object data)
{
_logSource.LogMessage(data);
}
internal static void LogWarning(object data)
{
_logSource.LogWarning(data);
}
internal static void LogGameObject(GameObject prefab, bool includeChildren = false)
{
//IL_006b: Unknown result type (might be due to invalid IL or missing references)
//IL_0071: Expected O, but got Unknown
LogInfo("***** " + ((Object)prefab).name + " *****");
Component[] components = prefab.GetComponents<Component>();
for (int i = 0; i < components.Length; i++)
{
LogComponent(components[i]);
}
if (!includeChildren)
{
return;
}
LogInfo("***** " + ((Object)prefab).name + " (children) *****");
foreach (Transform item in prefab.transform)
{
Transform val = item;
LogInfo(" - " + ((Object)((Component)val).gameObject).name);
components = ((Component)val).gameObject.GetComponents<Component>();
for (int i = 0; i < components.Length; i++)
{
LogComponent(components[i]);
}
}
}
internal static void LogComponent(Component compo)
{
LogInfo("--- " + ((object)compo).GetType().Name + ": " + ((Object)compo).name + " ---");
PropertyInfo[] properties = ((object)compo).GetType().GetProperties(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.GetField | BindingFlags.SetField | BindingFlags.GetProperty | BindingFlags.SetProperty);
foreach (PropertyInfo propertyInfo in properties)
{
LogInfo($" - {propertyInfo.Name} = {propertyInfo.GetValue(compo)}");
}
FieldInfo[] fields = ((object)compo).GetType().GetFields(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.GetField | BindingFlags.SetField | BindingFlags.GetProperty | BindingFlags.SetProperty);
foreach (FieldInfo fieldInfo in fields)
{
LogInfo($" - {fieldInfo.Name} = {fieldInfo.GetValue(compo)}");
}
}
}
}
namespace CameraTweaks.Extensions
{
internal static class EventExtensions
{
public static void SafeInvoke(this Action events)
{
if (events == null)
{
return;
}
Delegate[] invocationList = events.GetInvocationList();
for (int i = 0; i < invocationList.Length; i++)
{
Action action = (Action)invocationList[i];
try
{
action();
}
catch (Exception ex)
{
Log.LogWarning($"Exception thrown at event {new StackFrame(1).GetMethod().Name} in {action.Method.DeclaringType.Name}.{action.Method.Name}:\n{ex}");
}
}
}
public static void SafeInvoke<TArg1>(this Action<TArg1> events, TArg1 arg1)
{
if (events == null)
{
return;
}
Delegate[] invocationList = events.GetInvocationList();
for (int i = 0; i < invocationList.Length; i++)
{
Action<TArg1> action = (Action<TArg1>)invocationList[i];
try
{
action(arg1);
}
catch (Exception ex)
{
Log.LogWarning($"Exception thrown at event {new StackFrame(1).GetMethod().Name} in {action.Method.DeclaringType.Name}.{action.Method.Name}:\n{ex}");
}
}
}
public static void SafeInvoke<TArg1, TArg2>(this Action<TArg1, TArg2> events, TArg1 arg1, TArg2 arg2)
{
if (events == null)
{
return;
}
Delegate[] invocationList = events.GetInvocationList();
for (int i = 0; i < invocationList.Length; i++)
{
Action<TArg1, TArg2> action = (Action<TArg1, TArg2>)invocationList[i];
try
{
action(arg1, arg2);
}
catch (Exception ex)
{
Log.LogWarning($"Exception thrown at event {new StackFrame(1).GetMethod().Name} in {action.Method.DeclaringType.Name}.{action.Method.Name}:\n{ex}");
}
}
}
public static void SafeInvoke<TEventArg>(this EventHandler<TEventArg> events, object sender, TEventArg arg1)
{
if (events == null)
{
return;
}
Delegate[] invocationList = events.GetInvocationList();
for (int i = 0; i < invocationList.Length; i++)
{
EventHandler<TEventArg> eventHandler = (EventHandler<TEventArg>)invocationList[i];
try
{
eventHandler(sender, arg1);
}
catch (Exception ex)
{
Log.LogWarning($"Exception thrown at event {new StackFrame(1).GetMethod().Name} in {eventHandler.Method.DeclaringType.Name}.{eventHandler.Method.Name}:\n{ex}");
}
}
}
}
internal static class GameObjectExtensions
{
internal static GameObject DeepCopy(this GameObject obj)
{
bool activeSelf = obj.activeSelf;
obj.SetActive(false);
GameObject result = Object.Instantiate<GameObject>(obj);
obj.SetActive(activeSelf);
return result;
}
public static bool HasAnyComponent(this GameObject gameObject, params Type[] components)
{
foreach (Type type in components)
{
if (Object.op_Implicit((Object)(object)gameObject.GetComponent(type)))
{
return true;
}
}
return false;
}
public static bool HasAnyComponent(this GameObject gameObject, params string[] componentNames)
{
foreach (string text in componentNames)
{
if (Object.op_Implicit((Object)(object)gameObject.GetComponent(text)))
{
return true;
}
}
return false;
}
public static bool HasAllComponents(this GameObject gameObject, params string[] componentNames)
{
foreach (string text in componentNames)
{
if (!Object.op_Implicit((Object)(object)gameObject.GetComponent(text)))
{
return false;
}
}
return true;
}
public static bool HasAllComponents(this GameObject gameObject, params Type[] components)
{
foreach (Type type in components)
{
if (!Object.op_Implicit((Object)(object)gameObject.GetComponent(type)))
{
return false;
}
}
return true;
}
public static bool HasAnyComponentInChildren(this GameObject gameObject, bool includeInactive = false, params Type[] components)
{
foreach (Type type in components)
{
if (Object.op_Implicit((Object)(object)gameObject.GetComponentInChildren(type, includeInactive)))
{
return true;
}
}
return false;
}
public static Transform FindDeepChild(this GameObject gameObject, string childName, IterativeSearchType searchType = 1)
{
//IL_0007: Unknown result type (might be due to invalid IL or missing references)
return gameObject.transform.FindDeepChild(childName, searchType);
}
public static void DestroyComponentsInChildren<T>(this GameObject gameObject, bool includeInactive = false) where T : Component
{
T[] componentsInChildren = gameObject.GetComponentsInChildren<T>(includeInactive);
for (int i = 0; i < componentsInChildren.Length; i++)
{
Object.DestroyImmediate((Object)(object)componentsInChildren[i]);
}
}
internal static Mesh GetMesh(this GameObject gameObject, string meshName)
{
MeshFilter[] componentsInChildren = gameObject.GetComponentsInChildren<MeshFilter>();
for (int i = 0; i < componentsInChildren.Length; i++)
{
Mesh mesh = componentsInChildren[i].mesh;
if (!((Object)(object)mesh == (Object)null) && ((Object)mesh).name.RemoveSuffix("Instance").Trim() == meshName)
{
return mesh;
}
}
Log.LogWarning("Could not find Mesh: " + meshName + " for GameObject: " + ((Object)gameObject).name);
return null;
}
}
internal static class TypeExtensions
{
internal static List<T> GetAllPublicConstantValues<T>(this Type type)
{
return (from fi in type.GetFields(BindingFlags.Static | BindingFlags.Public | BindingFlags.FlattenHierarchy)
where fi.IsLiteral && !fi.IsInitOnly && fi.FieldType == typeof(T)
select fi into x
select (T)x.GetRawConstantValue()).ToList();
}
internal static List<T> GetAllPublicStaticValues<T>(this Type type)
{
return (from fi in type.GetFields(BindingFlags.Static | BindingFlags.Public)
where fi.FieldType == typeof(T)
select fi into x
select (T)x.GetValue(null)).ToList();
}
}
internal static class GenericExtensions
{
private const BindingFlags AllBindings = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.GetField | BindingFlags.SetField | BindingFlags.GetProperty | BindingFlags.SetProperty;
public static void CopyFields(this object target, object source)
{
if (target == null || source == null)
{
throw new Exception("Target or/and Source Objects are null");
}
Type type = source.GetType();
FieldInfo[] fields = target.GetType().GetFields(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.GetField | BindingFlags.SetField | BindingFlags.GetProperty | BindingFlags.SetProperty);
foreach (FieldInfo fieldInfo in fields)
{
FieldInfo field = type.GetField(fieldInfo.Name, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.GetField | BindingFlags.SetField | BindingFlags.GetProperty | BindingFlags.SetProperty);
if (!(field == null) && field.IsInitOnly && field.FieldType.IsAssignableFrom(fieldInfo.FieldType))
{
field.SetValue(source, fieldInfo.GetValue(target));
}
}
}
internal static T Ref<T>(this T o) where T : Object
{
if (!Object.op_Implicit((Object)(object)o))
{
return default(T);
}
return o;
}
}
internal static class IEnumerableExtensions
{
internal static void Dispose(this IEnumerable<IDisposable> collection)
{
foreach (IDisposable item in collection)
{
if (item != null)
{
try
{
item.Dispose();
}
catch (Exception)
{
Log.LogWarning("Could not dispose of item");
}
}
}
}
}
internal static class ReflectionUtils
{
public const BindingFlags AllBindings = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.GetField | BindingFlags.SetField | BindingFlags.GetProperty | BindingFlags.SetProperty;
internal static MethodInfo GetMethod(Type type, string name, Type[] types)
{
MethodInfo[] methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.GetField | BindingFlags.SetField | BindingFlags.GetProperty | BindingFlags.SetProperty);
foreach (MethodInfo methodInfo in methods)
{
if (methodInfo.Name == name && HasMatchingParameterTypes(0, types, methodInfo.GetParameters()))
{
return methodInfo;
}
}
return null;
}
internal static MethodInfo GetGenericMethod(Type type, string name, int genericParameterCount, Type[] types)
{
MethodInfo[] methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.GetField | BindingFlags.SetField | BindingFlags.GetProperty | BindingFlags.SetProperty);
foreach (MethodInfo methodInfo in methods)
{
if (methodInfo.IsGenericMethod && methodInfo.ContainsGenericParameters && methodInfo.Name == name && HasMatchingParameterTypes(genericParameterCount, types, methodInfo.GetParameters()))
{
return methodInfo;
}
}
return null;
}
private static bool HasMatchingParameterTypes(int genericParameterCount, Type[] types, ParameterInfo[] parameters)
{
if (parameters.Length < genericParameterCount || parameters.Length != types.Length)
{
return false;
}
int num = 0;
for (int i = 0; i < parameters.Length; i++)
{
if (parameters[i].ParameterType.IsGenericParameter)
{
num++;
}
else if (types[i] != parameters[i].ParameterType)
{
return false;
}
}
if (num != genericParameterCount)
{
return false;
}
return true;
}
}
internal static class StringExtensions
{
internal static bool ContainsAny(this string str, params string[] substrings)
{
foreach (string value in substrings)
{
if (str.Contains(value))
{
return true;
}
}
return false;
}
internal static bool EndsWithAny(this string str, params string[] suffixes)
{
foreach (string value in suffixes)
{
if (str.EndsWith(value))
{
return true;
}
}
return false;
}
internal static bool StartsWithAny(this string str, params string[] suffixes)
{
foreach (string value in suffixes)
{
if (str.StartsWith(value))
{
return true;
}
}
return false;
}
internal static string RemoveSuffix(this string s, string suffix)
{
if (s.EndsWith(suffix))
{
return s.Substring(0, s.Length - suffix.Length);
}
return s;
}
internal static string RemovePrefix(this string s, string prefix)
{
if (s.StartsWith(prefix))
{
return s.Substring(prefix.Length, s.Length - prefix.Length);
}
return s;
}
internal static string CapitalizeFirstLetter(this string s)
{
if (s.Length == 0)
{
return s;
}
if (s.Length == 1)
{
return $"{char.ToUpper(s[0])}";
}
return char.ToUpper(s[0]) + s.Substring(1);
}
}
public static class TransformExtensions
{
public static Transform FindDeepChild(this Transform transform, string childName, IterativeSearchType searchType = 1)
{
//IL_0002: Unknown result type (might be due to invalid IL or missing references)
return Utils.FindChild(transform, childName, searchType);
}
}
}
namespace CameraTweaks.Configs
{
internal static class ConfigManager
{
private static string ConfigFileName;
private static string ConfigFileFullPath;
private static ConfigFile configFile;
private static BaseUnityPlugin ConfigurationManager;
private const string ConfigManagerGUID = "com.bepis.bepinex.configurationmanager";
private const char ZWS = '\u200b';
internal static event Action OnConfigWindowClosed;
internal static event Action OnConfigFileReloaded;
private static void InvokeOnConfigWindowClosed()
{
ConfigManager.OnConfigWindowClosed?.SafeInvoke();
}
private static void InvokeOnConfigFileReloaded()
{
ConfigManager.OnConfigFileReloaded?.SafeInvoke();
}
internal static ConfigEntry<T> BindConfig<T>(string section, string name, T value, string description, AcceptableValueBase acceptVals = null)
{
//IL_0010: Unknown result type (might be due to invalid IL or missing references)
//IL_001a: Expected O, but got Unknown
return configFile.Bind<T>(section, name, value, new ConfigDescription(description, acceptVals, Array.Empty<object>()));
}
internal static string SetStringPriority(string sectionName, int priority)
{
if (priority == 0)
{
return sectionName;
}
return new string('\u200b', priority) + sectionName;
}
internal static string GetExtendedDescription(string description, bool synchronizedSetting)
{
return description + (synchronizedSetting ? " [Synced with Server]" : " [Not Synced with Server]");
}
internal static void Init(string GUID, ConfigFile config, bool saveOnConfigSet = false)
{
configFile = config;
configFile.SaveOnConfigSet = saveOnConfigSet;
ConfigFileName = GUID + ".cfg";
ConfigFileFullPath = Paths.ConfigPath + Path.DirectorySeparatorChar + ConfigFileName;
}
private static bool DisableSaveOnConfigSet()
{
bool saveOnConfigSet = configFile.SaveOnConfigSet;
configFile.SaveOnConfigSet = false;
return saveOnConfigSet;
}
internal static void SaveOnConfigSet(bool value)
{
configFile.SaveOnConfigSet = value;
}
internal static void Save()
{
configFile.Save();
}
internal static void SetupWatcher()
{
FileSystemWatcher fileSystemWatcher = new FileSystemWatcher(Paths.ConfigPath, ConfigFileName);
fileSystemWatcher.Changed += ReloadConfigFile;
fileSystemWatcher.Created += ReloadConfigFile;
fileSystemWatcher.Renamed += ReloadConfigFile;
fileSystemWatcher.IncludeSubdirectories = true;
fileSystemWatcher.SynchronizingObject = ThreadingHelper.SynchronizingObject;
fileSystemWatcher.EnableRaisingEvents = true;
}
private static void ReloadConfigFile(object sender, FileSystemEventArgs e)
{
if (!File.Exists(ConfigFileFullPath))
{
return;
}
try
{
Log.LogInfo("Reloading config file");
bool value = DisableSaveOnConfigSet();
configFile.Reload();
SaveOnConfigSet(value);
InvokeOnConfigFileReloaded();
}
catch
{
Log.LogError("There was an issue loading your " + ConfigFileName);
Log.LogError("Please check your config entries for spelling and format!");
}
}
internal static void CheckForConfigManager()
{
//IL_0000: Unknown result type (might be due to invalid IL or missing references)
//IL_0006: Invalid comparison between Unknown and I4
if ((int)SystemInfo.graphicsDeviceType != 4 && Chainloader.PluginInfos.TryGetValue("com.bepis.bepinex.configurationmanager", out var value) && Object.op_Implicit((Object)(object)value.Instance))
{
ConfigurationManager = value.Instance;
Log.LogDebug("Configuration manager found, hooking DisplayingWindowChanged");
EventInfo @event = ((object)ConfigurationManager).GetType().GetEvent("DisplayingWindowChanged");
if (@event != null)
{
Action<object, object> action = OnConfigManagerDisplayingWindowChanged;
Delegate handler = Delegate.CreateDelegate(@event.EventHandlerType, action.Target, action.Method);
@event.AddEventHandler(ConfigurationManager, handler);
}
}
}
private static void OnConfigManagerDisplayingWindowChanged(object sender, object e)
{
if (!(bool)((object)ConfigurationManager).GetType().GetProperty("DisplayingWindow").GetValue(ConfigurationManager, null))
{
InvokeOnConfigWindowClosed();
}
}
}
}