Wink.dll

Decompiled 2 weeks ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Photon.Pun;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: IgnoresAccessChecksTo("Assembly-CSharp")]
[assembly: AssemblyCompany("Vippy")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.2.0")]
[assembly: AssemblyInformationalVersion("1.0.2+01db9ef756c60cd14a297857aa1ca7c2c21b7bf1")]
[assembly: AssemblyProduct("Wink")]
[assembly: AssemblyTitle("Wink")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.2.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[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 Wink
{
	internal static class BuildInfo
	{
		public const string Version = "1.0.2";
	}
	internal static class WinkState
	{
		internal static EyeSettings? Closed;

		internal static void EnsureClosed(List<ExpressionSettings> expressions)
		{
			if (Closed != null || expressions == null)
			{
				return;
			}
			float num = float.NegativeInfinity;
			EyeSettings val = null;
			foreach (ExpressionSettings expression in expressions)
			{
				if (expression == null)
				{
					continue;
				}
				EyeSettings[] array = (EyeSettings[])(object)new EyeSettings[2] { expression.leftEye, expression.rightEye };
				foreach (EyeSettings val2 in array)
				{
					if (val2 != null && val2.upperLidClosedPercent > num)
					{
						num = val2.upperLidClosedPercent;
						val = val2;
					}
				}
			}
			if (val != null)
			{
				Closed = Copy(val);
			}
		}

		private static EyeSettings Copy(EyeSettings s)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0005: 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_001d: 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_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: 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_0065: 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_007d: 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_0095: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ad: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ba: Expected O, but got Unknown
			return new EyeSettings
			{
				upperLidAngle = s.upperLidAngle,
				upperLidClosedPercent = s.upperLidClosedPercent,
				upperLidClosedPercentJitterAmount = s.upperLidClosedPercentJitterAmount,
				upperLidClosedPercentJitterSpeed = s.upperLidClosedPercentJitterSpeed,
				lowerLidAngle = s.lowerLidAngle,
				lowerLidClosedPercent = s.lowerLidClosedPercent,
				lowerLidClosedPercentJitterAmount = s.lowerLidClosedPercentJitterAmount,
				lowerLidClosedPercentJitterSpeed = s.lowerLidClosedPercentJitterSpeed,
				pupilSize = s.pupilSize,
				pupilSizeJitterAmount = s.pupilSizeJitterAmount,
				pupilSizeJitterSpeed = s.pupilSizeJitterSpeed,
				pupilPositionJitter = s.pupilPositionJitter,
				pupilPositionJitterAmount = s.pupilPositionJitterAmount,
				pupilOffsetRotationX = s.pupilOffsetRotationX,
				pupilOffsetRotationY = s.pupilOffsetRotationY
			};
		}
	}
	internal static class WinkAnim
	{
		private const float PreviewUpThreshold = 0.95f;

		private static readonly Dictionary<PlayerExpression, WinkClock> _clocks = new Dictionary<PlayerExpression, WinkClock>();

		private static readonly List<PlayerExpression> _dead = new List<PlayerExpression>();

		public static void Start(PlayerExpression pe, WinkStyle style, float leadIn = 0f, float hold = 0f, bool waitForPreviewUp = false)
		{
			foreach (PlayerExpression key in _clocks.Keys)
			{
				if ((Object)(object)key == (Object)null)
				{
					_dead.Add(key);
				}
			}
			foreach (PlayerExpression item in _dead)
			{
				_clocks.Remove(item);
			}
			_dead.Clear();
			float duration = Mathf.Max(0.05f, (style == WinkStyle.Seductive) ? WinkConfig.SeductiveDuration.Value : WinkConfig.NormalDuration.Value);
			_clocks[pe] = new WinkClock(style, duration, Mathf.Max(0f, leadIn), Mathf.Max(0f, hold), waitForPreviewUp);
		}

		public static void Tick(PlayerExpression pe)
		{
			if (_clocks.TryGetValue(pe, out WinkClock value))
			{
				ExpressionSettings val = ((pe.expressions != null && 4 < pe.expressions.Count) ? pe.expressions[4] : null);
				if (!value.Advance(Time.deltaTime, PreviewIsUp(), val?.weight ?? 0f, val?.stopExpressing ?? false))
				{
					_clocks.Remove(pe);
				}
			}
		}

		public static bool TryGet(PlayerExpression pe, out float amount, out WinkStyle style)
		{
			if (_clocks.TryGetValue(pe, out WinkClock value))
			{
				amount = value.Amount;
				style = value.Style;
				return true;
			}
			amount = 0f;
			style = WinkStyle.Normal;
			return false;
		}

		private static bool PreviewIsUp()
		{
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			PlayerExpressionsUI instance = PlayerExpressionsUI.instance;
			if (!((Object)(object)instance == (Object)null) && !((Object)(object)instance.rectShrink == (Object)null))
			{
				return ((Transform)instance.rectShrink).localScale.x >= 0.95f;
			}
			return true;
		}
	}
	[HarmonyPatch(typeof(PlayerExpression), "Start")]
	internal static class WinkSetupPatch
	{
		private static bool _logged;

		[HarmonyPostfix]
		public static void Postfix(PlayerExpression __instance)
		{
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			List<ExpressionSettings> expressions = __instance.expressions;
			if (expressions != null)
			{
				WinkState.EnsureClosed(expressions);
				if (!_logged)
				{
					_logged = true;
					WinkPlugin.Log.LogInfo((object)$"[Wink] {expressions.Count} expressions, wink on {WinkConfig.WinkKey.Value}");
				}
				float value = WinkConfig.EyelidSpeed.Value;
				SetSpeed(__instance.leftUpperLidClosedAngle, value);
				SetSpeed(__instance.leftLowerLidClosedAngle, value);
				SetSpeed(__instance.rightUpperLidClosedAngle, value);
				SetSpeed(__instance.rightLowerLidClosedAngle, value);
			}
		}

		private static void SetSpeed(SpringFloat? spring, float spd)
		{
			if (spring != null)
			{
				spring.speed = spd;
			}
		}
	}
	[HarmonyPatch(typeof(PlayerExpression), "Update")]
	internal static class WinkUpdatePatch
	{
		private const float HeldFaceMinWeight = 5f;

		private const float KeepAliveTimer = 0.1f;

		[HarmonyPostfix]
		public static void Postfix(PlayerExpression __instance)
		{
			//IL_008f: Unknown result type (might be due to invalid IL or missing references)
			List<ExpressionSettings> expressions = __instance.expressions;
			if (expressions == null || expressions.Count == 0)
			{
				return;
			}
			PlayerAvatarVisuals playerVisuals = __instance.playerVisuals;
			bool flag = __instance.isLocal && (Object)(object)playerVisuals != (Object)null && !playerVisuals.isMenuAvatar && (Object)(object)__instance.playerAvatar != (Object)null && (Object)(object)__instance == (Object)(object)__instance.playerAvatar.playerExpression;
			if (flag && (Object)(object)MenuManager.instance != (Object)null && !Object.op_Implicit((Object)(object)MenuManager.instance.currentMenuPage) && SemiFunc.NoTextInputsActive() && Input.GetKeyDown(WinkConfig.WinkKey.Value))
			{
				WinkStyle style = ((WinkConfig.Style.Value == "Seductive") ? WinkStyle.Seductive : WinkStyle.Normal);
				WinkAnim.Start(__instance, style);
				PlayerExpressionsUI instance = PlayerExpressionsUI.instance;
				if ((Object)(object)instance != (Object)null)
				{
					instance.ShrinkReset();
					if ((Object)(object)instance.playerExpression != (Object)null)
					{
						WinkAnim.Start(instance.playerExpression, style, WinkConfig.PreviewLeadIn.Value, WinkConfig.PreviewHold.Value, waitForPreviewUp: true);
					}
				}
				if (!__instance.onlyVisualRepresentation)
				{
					WinkNet.Send(__instance.playerAvatar, style);
				}
			}
			WinkAnim.Tick(__instance);
			float amount;
			WinkStyle style2;
			bool flag2 = WinkAnim.TryGet(__instance, out amount, out style2);
			bool flag3 = false;
			PlayerExpressionsUI instance2 = PlayerExpressionsUI.instance;
			if ((Object)(object)instance2 != (Object)null && (Object)(object)instance2.playerExpression != (Object)null && WinkAnim.TryGet(instance2.playerExpression, out var _, out var _))
			{
				if ((Object)(object)instance2.playerExpression == (Object)(object)__instance || flag)
				{
					instance2.ShrinkReset();
				}
				flag3 = flag;
			}
			if (!flag2 && !flag3)
			{
				return;
			}
			expressions[0].timer = Mathf.Max(expressions[0].timer, 0.1f);
			float num = 0f;
			for (int i = 1; i < expressions.Count; i++)
			{
				if (i != 4)
				{
					num += expressions[i].weight;
				}
			}
			if (num < 5f)
			{
				expressions[0].weight = 100f;
			}
			if (flag2 && (Object)(object)playerVisuals != (Object)null && style2 == WinkStyle.Seductive)
			{
				playerVisuals.HeadTiltOverride(WinkConfig.HeadTilt.Value * amount);
			}
		}
	}
	[HarmonyPatch(typeof(PlayerExpression), "BlendEyeSettings")]
	internal static class WinkBlendPatch
	{
		[HarmonyPostfix]
		public static void Postfix(PlayerExpression __instance, bool isLeft, EyeSettings __result)
		{
			if (!WinkAnim.TryGet(__instance, out var amount, out var style))
			{
				return;
			}
			EyeSettings closed = WinkState.Closed;
			if (closed == null || __result == null)
			{
				return;
			}
			List<ExpressionSettings> expressions = __instance.expressions;
			int num = 4;
			if (expressions != null && num < expressions.Count)
			{
				float num2 = 0f;
				for (int i = 0; i < expressions.Count; i++)
				{
					num2 += expressions[i].weight;
				}
				EyeSettings val = ((!isLeft) ? expressions[num]?.rightEye : expressions[num]?.leftEye);
				float blinkWeight = ((val != null) ? expressions[num].weight : 0f);
				LidPose pose = Read(__result);
				WinkBlend.Apply(ref pose, (val != null) ? Read(val) : default(LidPose), blinkWeight, num2, Read(closed), amount, isLeft == WinkConfig.WinkLeftEye.Value, style == WinkStyle.Seductive, WinkConfig.SultryLid.Value);
				Write(in pose, __result);
			}
		}

		private static LidPose Read(EyeSettings e)
		{
			return new LidPose
			{
				UpperClosed = e.upperLidClosedPercent,
				LowerClosed = e.lowerLidClosedPercent,
				UpperAngle = e.upperLidAngle,
				LowerAngle = e.lowerLidAngle,
				Pupil = e.pupilSize
			};
		}

		private static void Write(in LidPose p, EyeSettings e)
		{
			e.upperLidClosedPercent = p.UpperClosed;
			e.lowerLidClosedPercent = p.LowerClosed;
			e.upperLidAngle = p.UpperAngle;
			e.lowerLidAngle = p.LowerAngle;
			e.pupilSize = p.Pupil;
		}
	}
	[HarmonyPatch(typeof(PunManager), "Awake")]
	internal static class PunManagerWinkPatch
	{
		[HarmonyPostfix]
		public static void Postfix(PunManager __instance)
		{
			if ((Object)(object)((Component)__instance).GetComponent<WinkNet>() == (Object)null)
			{
				((Component)__instance).gameObject.AddComponent<WinkNet>();
			}
		}
	}
	[BepInPlugin("Vippy.Wink", "Wink", "1.0.2")]
	public class WinkPlugin : BaseUnityPlugin
	{
		internal static ManualLogSource Log { get; private set; }

		private void Awake()
		{
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			Log = ((BaseUnityPlugin)this).Logger;
			((Component)this).gameObject.transform.parent = null;
			((Object)((Component)this).gameObject).hideFlags = (HideFlags)61;
			WinkConfig.Init(((BaseUnityPlugin)this).Config);
			new Harmony("Vippy.Wink").PatchAll();
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Wink v1.0.2 loaded");
		}
	}
	internal struct LidPose
	{
		public float UpperClosed;

		public float LowerClosed;

		public float UpperAngle;

		public float LowerAngle;

		public float Pupil;
	}
	internal static class WinkBlend
	{
		private const float MinWeight = 0.5f;

		public static void Apply(ref LidPose pose, in LidPose blink, float blinkWeight, float totalWeight, in LidPose closed, float amount, bool winkEye, bool seductive, float sultryLid)
		{
			SubtractBlink(ref pose, in blink, blinkWeight, totalWeight);
			if (!(amount <= 0f))
			{
				Wink(ref pose, in closed, amount, winkEye, seductive, sultryLid);
			}
		}

		public static bool BlinkStillFading(float blinkWeight, float previousWeight, bool blinkStopping)
		{
			if (blinkStopping && blinkWeight > 0.5f)
			{
				return blinkWeight < previousWeight;
			}
			return false;
		}

		public static void SubtractBlink(ref LidPose pose, in LidPose blink, float blinkWeight, float totalWeight)
		{
			float num = totalWeight - blinkWeight;
			if (!(blinkWeight <= 0.5f) && !(num <= 0.5f))
			{
				pose.UpperClosed = (pose.UpperClosed * totalWeight - blink.UpperClosed * blinkWeight) / num;
				pose.LowerClosed = (pose.LowerClosed * totalWeight - blink.LowerClosed * blinkWeight) / num;
				pose.UpperAngle = (pose.UpperAngle * totalWeight - blink.UpperAngle * blinkWeight) / num;
				pose.LowerAngle = (pose.LowerAngle * totalWeight - blink.LowerAngle * blinkWeight) / num;
				pose.Pupil = (pose.Pupil * totalWeight - blink.Pupil * blinkWeight) / num;
			}
		}

		public static void Wink(ref LidPose pose, in LidPose closed, float amount, bool winkEye, bool seductive, float sultryLid)
		{
			float num = (winkEye ? amount : (seductive ? (amount * sultryLid) : 0f));
			float num2 = (winkEye ? amount : 0f);
			pose.UpperClosed = Mathf.Lerp(pose.UpperClosed, closed.UpperClosed, num);
			pose.LowerClosed = Mathf.Lerp(pose.LowerClosed, closed.LowerClosed, num);
			pose.UpperAngle = Mathf.Lerp(pose.UpperAngle, closed.UpperAngle, num2);
			pose.LowerAngle = Mathf.Lerp(pose.LowerAngle, closed.LowerAngle, num2);
			pose.Pupil = Mathf.Lerp(pose.Pupil, closed.Pupil, num2);
		}
	}
	internal enum WinkStyle
	{
		Normal,
		Seductive
	}
	internal class WinkClock
	{
		private const float FadeCap = 2f;

		private bool _waitUp;

		private float _leadIn;

		private float _timer;

		private readonly float _duration;

		private float _hold;

		private float _fade;

		private float _prevBlink = float.MaxValue;

		public WinkStyle Style { get; }

		public float Amount { get; private set; }

		public WinkClock(WinkStyle style, float duration, float leadIn, float hold, bool waitForPreviewUp)
		{
			Style = style;
			_duration = duration;
			_timer = duration;
			_leadIn = leadIn;
			_hold = hold;
			_waitUp = waitForPreviewUp;
		}

		public bool Advance(float dt, bool previewUp, float blinkWeight, bool blinkStopping)
		{
			float prevBlink = _prevBlink;
			_prevBlink = blinkWeight;
			if (_waitUp)
			{
				if (!previewUp)
				{
					Amount = 0f;
					return true;
				}
				_waitUp = false;
			}
			if (_leadIn > 0f)
			{
				_leadIn -= dt;
				Amount = 0f;
				return true;
			}
			if (_timer > 0f)
			{
				_timer -= dt;
				if (_timer > 0f)
				{
					Amount = Mathf.Sin(MathF.PI * (1f - _timer / _duration));
					return true;
				}
				Amount = 0f;
			}
			if (_hold > 0f)
			{
				_hold -= dt;
				Amount = 0f;
				return true;
			}
			Amount = 0f;
			_fade += dt;
			if (_fade < 2f)
			{
				return WinkBlend.BlinkStillFading(blinkWeight, prevBlink, blinkStopping);
			}
			return false;
		}
	}
	internal class WinkNet : MonoBehaviour
	{
		private const float BlinkSeconds = 0.3f;

		private static float _blinkStopAt;

		public static void Send(PlayerAvatar avatar, WinkStyle style)
		{
			if (PhotonNetwork.InRoom && PhotonNetwork.CurrentRoom.PlayerCount > 1)
			{
				PunManager.instance.photonView.RPC("WinkRPC", (RpcTarget)1, new object[2]
				{
					SemiFunc.PhotonViewIDPlayerAvatarLocal(),
					(int)style
				});
				if (WinkConfig.BlinkForOthers.Value && !HoldingBlink(avatar))
				{
					avatar.PlayerExpressionSet(4, 100f);
					_blinkStopAt = Time.time + 0.3f;
				}
			}
		}

		private static bool HoldingBlink(PlayerAvatar avatar)
		{
			PlayerExpression playerExpression = avatar.playerExpression;
			if ((Object)(object)playerExpression == (Object)null || playerExpression.expressions == null || 4 >= playerExpression.expressions.Count)
			{
				return false;
			}
			return playerExpression.expressions[4].isExpressing;
		}

		[PunRPC]
		public void WinkRPC(int _avatarViewID, int _style, PhotonMessageInfo _info = default(PhotonMessageInfo))
		{
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			PlayerAvatar val = SemiFunc.PlayerAvatarGetFromPhotonID(_avatarViewID);
			if (!((Object)(object)val == (Object)null) && !((Object)(object)val.playerExpression == (Object)null) && _info.Sender != null && !((Object)(object)val.photonView == (Object)null) && val.photonView.Owner == _info.Sender)
			{
				WinkAnim.Start(val.playerExpression, (_style == 1) ? WinkStyle.Seductive : WinkStyle.Normal);
			}
		}

		private void Update()
		{
			if (_blinkStopAt > 0f && Time.time >= _blinkStopAt)
			{
				_blinkStopAt = 0f;
				PlayerAvatar instance = PlayerAvatar.instance;
				if ((Object)(object)instance != (Object)null && !HoldingBlink(instance))
				{
					instance.PlayerExpressionStop(4);
				}
			}
		}
	}
	internal static class WinkConfig
	{
		public const int BlinkSlot = 4;

		public const string StyleNormal = "Normal";

		public const string StyleSeductive = "Seductive";

		public static ConfigEntry<KeyCode> WinkKey;

		public static ConfigEntry<string> Style;

		public static ConfigEntry<bool> BlinkForOthers;

		public static ConfigEntry<bool> WinkLeftEye;

		public static ConfigEntry<float> NormalDuration;

		public static ConfigEntry<float> SeductiveDuration;

		public static ConfigEntry<float> EyelidSpeed;

		public static ConfigEntry<float> SultryLid;

		public static ConfigEntry<float> HeadTilt;

		public static ConfigEntry<float> PreviewLeadIn;

		public static ConfigEntry<float> PreviewHold;

		public static void Init(ConfigFile config)
		{
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			//IL_005b: Expected O, but got Unknown
			//IL_00bf: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c9: Expected O, but got Unknown
			//IL_00f7: Unknown result type (might be due to invalid IL or missing references)
			//IL_0101: Expected O, but got Unknown
			//IL_012f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0139: Expected O, but got Unknown
			//IL_0167: Unknown result type (might be due to invalid IL or missing references)
			//IL_0171: Expected O, but got Unknown
			//IL_019f: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a9: Expected O, but got Unknown
			//IL_01d7: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e1: Expected O, but got Unknown
			//IL_020f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0219: Expected O, but got Unknown
			WinkKey = config.Bind<KeyCode>("Wink", "Wink key", (KeyCode)52, "Key that plays the wink. This is a NEW expression on its own key, nothing the game ships with is changed: the inventory keys (1-3) and the six vanilla expression keys (5-0) stay exactly as they are. Default is Alpha4, which is Unity's name for the 4 key on the number row (not the numpad). Pick which wink it plays with the 'Wink style' setting.");
			Style = config.Bind<string>("Wink", "Wink style", "Normal", new ConfigDescription("Normal is a quick close-and-reopen. Seductive is slower - the other eye goes half-lidded and the head tilts.", (AcceptableValueBase)(object)new AcceptableValueList<string>(new string[2] { "Normal", "Seductive" }), Array.Empty<object>()));
			BlinkForOthers = config.Bind<bool>("Wink", "Blink for unmodded players", true, "Also flash the vanilla closed-eyes face for a split second so players WITHOUT the mod see a quick blink instead of nothing. Players with the mod always see the real one-eyed wink.");
			WinkLeftEye = config.Bind<bool>("Wink", "Wink the left eye", false, "Close the LEFT eye instead of the right.");
			NormalDuration = config.Bind<float>("Wink", "Normal wink duration", 0.35f, new ConfigDescription("Seconds for the normal wink's close-and-reopen.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.15f, 1f), Array.Empty<object>()));
			SeductiveDuration = config.Bind<float>("Wink", "Seductive wink duration", 0.7f, new ConfigDescription("Seconds for the seductive wink's close-and-reopen. Higher = slower, more deliberate.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.2f, 2f), Array.Empty<object>()));
			EyelidSpeed = config.Bind<float>("Wink", "Eyelid speed", 18f, new ConfigDescription("Eyelid spring smoothing (vanilla 20). Lower is softer.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(8f, 60f), Array.Empty<object>()));
			SultryLid = config.Bind<float>("Wink", "Sultry lid", 0.4f, new ConfigDescription("Seductive wink only: how far the OTHER eye half-closes (bedroom eyes). 0 = wide open.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 0.85f), Array.Empty<object>()));
			HeadTilt = config.Bind<float>("Wink", "Head tilt", 10f, new ConfigDescription("Seductive wink only: head tilt in degrees (sign flips direction). 0 = none.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(-25f, 25f), Array.Empty<object>()));
			PreviewLeadIn = config.Bind<float>("Wink", "Preview lead-in", 0.5f, new ConfigDescription("Seconds the bottom preview holds your face open AFTER it has fully risen, before the wink plays. The wink already waits for the preview to finish rising; this is the extra beat on top of that. Preview only - the wink others see in-world stays instant.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 3f), Array.Empty<object>()));
			PreviewHold = config.Bind<float>("Wink", "Preview hold", 0.9f, new ConfigDescription("Seconds the bottom preview lingers after the wink finishes, before it settles back. Preview only - your in-world wink is unaffected.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 3f), Array.Empty<object>()));
		}
	}
}