Decompiled source of Customer Loyalty v0.6.1

BepInEx/plugins/CustomerLoyalty/CustomerLoyalty.dll

Decompiled 8 hours ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Xml;
using System.Xml.Serialization;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using CustomerLoyalty.Ui;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using TMPro;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("Customer Loyalty")]
[assembly: AssemblyDescription("Loyal regulars who come back with a growing shopping list.")]
[assembly: AssemblyCompany("Dimwit's")]
[assembly: AssemblyProduct("Customer Loyalty")]
[assembly: AssemblyCopyright("Copyright (c) 2026 Dimwit's")]
[assembly: AssemblyVersion("0.4.3.0")]
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;
		}
	}
}
namespace CustomerLoyalty
{
	internal static class Art
	{
		private static readonly Dictionary<string, Sprite?> Cache = new Dictionary<string, Sprite>();

		private static readonly string[] TierFiles = new string[4] { "tier_1_hole_in_the_wall", "tier_2_neighborhood_shop", "tier_3_destination_store", "tier_4_collectors_paradise" };

		public static Sprite? Tier(int tier)
		{
			return Get(TierFiles[Math.Max(0, Math.Min(tier, TierFiles.Length - 1))]);
		}

		public static Sprite? Get(string name)
		{
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			//IL_005f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			//IL_007f: Expected O, but got Unknown
			//IL_00ac: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bb: Unknown result type (might be due to invalid IL or missing references)
			if (Cache.TryGetValue(name, out Sprite value))
			{
				return value;
			}
			Sprite val = null;
			try
			{
				string path = Path.Combine(Path.Combine(Path.GetDirectoryName(((BaseUnityPlugin)Plugin.Instance).Info.Location) ?? "", "art"), name + ".png");
				if (File.Exists(path))
				{
					Texture2D val2 = new Texture2D(2, 2, (TextureFormat)4, true)
					{
						filterMode = (FilterMode)2,
						wrapMode = (TextureWrapMode)1,
						name = "CustomerLoyalty_" + name
					};
					if (ImageConversion.LoadImage(val2, File.ReadAllBytes(path)))
					{
						Object.DontDestroyOnLoad((Object)(object)val2);
						val = Sprite.Create(val2, new Rect(0f, 0f, (float)((Texture)val2).width, (float)((Texture)val2).height), new Vector2(0.5f, 0.5f), 100f);
						((Object)val).name = "CustomerLoyalty_" + name;
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Couldn't load art/" + name + ".png: " + ex.Message));
			}
			Cache[name] = val;
			return val;
		}
	}
	internal static class Lines
	{
		public static readonly string[] Arrive = new string[3] { "I love being here!", "Back again!", "My favorite shop!" };

		public static readonly string[] FoundEverything = new string[3] { "You always have exactly what I am after!", "This is my favorite store!", "Exactly what I came for!" };

		public static readonly string[] Disappointed = new string[3] { "You were out of my usual...", "Hope you restock soon.", "Missing a few things today." };

		public static readonly string[] GaveUp = new string[2] { "I think I'll shop somewhere else.", "This place isn't what it used to be." };

		public static readonly string[] Joined = new string[2] { "This is my favorite store!", "I'll be back for sure!" };

		public static readonly string[] SignedUp = new string[2] { "Count me in for the tournament!", "See you at the tournament!" };

		public static readonly string[] Champion = new string[2] { "Champion! I knew this was my shop!", "I won! Best shop in town!" };

		public static readonly string[] WatchFinal = new string[2] { "Wouldn't miss this final!", "I came to watch the final!" };

		public static string[] TooPricey(string product)
		{
			return new string[2]
			{
				product + " costs more than I usually pay.",
				"Pricey " + product + " today..."
			};
		}

		public static void Say(Customer customer, string[] lines)
		{
			if (!Plugin.ShowLines.Value || lines.Length == 0 || !Object.op_Implicit((Object)(object)customer))
			{
				return;
			}
			try
			{
				PricePopupSpawner instance = CSingleton<PricePopupSpawner>.Instance;
				if (instance != null)
				{
					instance.ShowTextPopup(lines[Random.Range(0, lines.Length)], 1.8f, ((Component)customer).transform);
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Couldn't show a regular's line: " + ex.Message));
			}
		}
	}
	public static class Links
	{
		public const int Version = 1;

		public static float Following()
		{
			if (!Plugin.Enabled.Value)
			{
				return 0f;
			}
			return LoyaltyRules.Following(Session.Regulars, Plugin.MaxRegulars());
		}

		public static bool IsRegular(Customer c)
		{
			if ((Object)(object)c != (Object)null)
			{
				return Regulars.IsRegularHere(c);
			}
			return false;
		}

		public static string NameForFace(bool female, int model)
		{
			if (model < 0)
			{
				return "";
			}
			return Session.Regulars.FirstOrDefault((Regular x) => x.Playing && x.IsFemale == female && x.Model == model)?.FirstName ?? "";
		}
	}
	public sealed class Regular
	{
		private string name = "";

		public string Id { get; set; } = "";

		public string Name
		{
			get
			{
				if (!string.IsNullOrWhiteSpace(name))
				{
					return name;
				}
				return LoyaltyRules.NameFor(Id);
			}
			set
			{
				name = value ?? "";
			}
		}

		public List<string> NextList { get; set; } = new List<string>();

		public bool IsFemale { get; set; }

		public int Model { get; set; } = -1;

		public int Visits { get; set; }

		public int JoinedDay { get; set; }

		public int NextVisitDay { get; set; }

		public int Tournaments { get; set; }

		public int Titles { get; set; }

		public bool Entered { get; set; }

		public bool Playing { get; set; }

		public string FirstName => LoyaltyRules.FirstName(Name);

		public string Record
		{
			get
			{
				if (Tournaments > 0)
				{
					return string.Format("{0} tournament{1}", Tournaments, (Tournaments == 1) ? "" : "s") + ((Titles > 0) ? string.Format(", {0} title{1}", Titles, (Titles == 1) ? "" : "s") : "");
				}
				return "";
			}
		}

		public Regular With(List<string> list, bool countVisit)
		{
			return new Regular
			{
				Id = Id,
				Name = Name,
				NextList = list,
				IsFemale = IsFemale,
				Model = Model,
				Visits = (countVisit ? (Visits + 1) : Visits),
				JoinedDay = JoinedDay,
				NextVisitDay = NextVisitDay,
				Tournaments = Tournaments,
				Titles = Titles,
				Entered = Entered,
				Playing = Playing
			};
		}
	}
	public enum VisitOutcome
	{
		BoughtEverything,
		MissingStock,
		Interrupted,
		Completed
	}
	public static class LoyaltyRules
	{
		private static readonly string[] FirstNames = new string[32]
		{
			"Alex", "Jordan", "Taylor", "Morgan", "Casey", "Jamie", "Riley", "Avery", "Cameron", "Quinn",
			"Robin", "Sam", "Charlie", "Drew", "Reese", "Skyler", "Rowan", "Harper", "Sage", "Blair",
			"Emerson", "Finley", "Parker", "Hayden", "Dakota", "Kendall", "Remy", "Ellis", "Jules", "Frankie",
			"Shawn", "Sidney"
		};

		private static readonly string[] LastNames = new string[32]
		{
			"Bennett", "Brooks", "Carter", "Chen", "Collins", "Davis", "Ellis", "Foster", "Garcia", "Hayes",
			"Hughes", "Kim", "Lane", "Lee", "Lewis", "Martin", "Miller", "Morgan", "Nguyen", "Parker",
			"Patel", "Reed", "Rivera", "Ross", "Scott", "Shah", "Singh", "Taylor", "Turner", "Walker",
			"Ward", "Wright"
		};

		public const int FullAfterVisits = 8;

		public const int FirstAppointment = 60;

		public const int LastAppointment = 690;

		public static string NameFor(string id)
		{
			uint num = 2166136261u;
			string text = id ?? "";
			foreach (char c in text)
			{
				num = (num ^ c) * 16777619;
			}
			return FirstNames[num % (uint)FirstNames.Length] + " " + LastNames[num / (uint)FirstNames.Length % (uint)LastNames.Length];
		}

		public static Regular Enroll(Regular profile, IEnumerable<string> completedList, int cap, Func<string> pickItem)
		{
			if (profile == null || string.IsNullOrWhiteSpace(profile.Id))
			{
				throw new ArgumentException("A stable customer identity is required.", "profile");
			}
			List<string> list = Validate(completedList);
			if (list.Count == 0)
			{
				throw new ArgumentException("An empty visit cannot earn loyalty.");
			}
			return Finish(profile.With(list, countVisit: false), VisitOutcome.BoughtEverything, cap, pickItem);
		}

		public static Regular Enroll(string id, IEnumerable<string> completedList, int cap, Func<string> pickItem)
		{
			return Enroll(new Regular
			{
				Id = id
			}, completedList, cap, pickItem);
		}

		public static Regular? Finish(Regular regular, VisitOutcome outcome, int cap, Func<string> pickItem)
		{
			if (regular == null || string.IsNullOrWhiteSpace(regular.Id))
			{
				throw new ArgumentException("Invalid regular.");
			}
			if (cap < 1)
			{
				throw new ArgumentOutOfRangeException("cap");
			}
			if (!Enum.IsDefined(typeof(VisitOutcome), outcome))
			{
				throw new ArgumentOutOfRangeException("outcome");
			}
			List<string> list = Validate(regular.NextList);
			switch (outcome)
			{
			case VisitOutcome.Interrupted:
				return regular.With(list, countVisit: false);
			case VisitOutcome.MissingStock:
				list.RemoveRange(Math.Max(0, list.Count - 2), Math.Min(2, list.Count));
				break;
			default:
				if (list.Count < cap)
				{
					string text = pickItem();
					if (string.IsNullOrWhiteSpace(text))
					{
						throw new ArgumentException("The next product must be valid.");
					}
					list.Add(text);
				}
				break;
			}
			if (list.Count > cap)
			{
				list.RemoveRange(cap, list.Count - cap);
			}
			if (list.Count != 0)
			{
				return regular.With(list, countVisit: true);
			}
			return null;
		}

		public static string FirstName(string name)
		{
			string text = (name ?? "").Trim();
			int num = text.IndexOf(' ');
			if (num <= 0)
			{
				return text;
			}
			return text.Substring(0, num);
		}

		public static float Following(IEnumerable<Regular> roster, int maxRegulars)
		{
			if (roster == null || maxRegulars <= 0)
			{
				return 0f;
			}
			float num = 0f;
			foreach (Regular item in roster)
			{
				float num2 = Math.Min(1f, (float)Math.Max(0, item.Visits) / 8f);
				num += ((item.Titles > 0) ? (num2 * 2f) : num2);
			}
			return Math.Min(1f, num / (float)maxRegulars);
		}

		public static int ContinueChance(int visits)
		{
			return 25 + Math.Min(13, Math.Max(0, visits - 1)) * 5;
		}

		public static bool CanEnroll(bool paid, bool missingStock)
		{
			if (paid)
			{
				return !missingStock;
			}
			return false;
		}

		public static int NextVisitDay(int today, int minDays, int maxDays, Func<int, int, int> randomInclusive)
		{
			int num = Math.Max(1, minDays);
			int arg = Math.Max(num, maxDays);
			return today + randomInclusive(num, arg);
		}

		public static bool Due(Regular regular, int today)
		{
			return regular.NextVisitDay <= today;
		}

		public static int JoinedOn(IEnumerable<Regular> roster, int day)
		{
			return roster.Count((Regular r) => r.JoinedDay == day);
		}

		public static bool RoomToJoinToday(IEnumerable<Regular> roster, int day, int maxPerDay)
		{
			if (maxPerDay > 0)
			{
				return JoinedOn(roster, day) < maxPerDay;
			}
			return false;
		}

		public static VisitOutcome Outcome(bool paid, bool missingStock, bool refusedPrice)
		{
			if (!(missingStock || refusedPrice))
			{
				if (!paid)
				{
					return VisitOutcome.Interrupted;
				}
				return VisitOutcome.Completed;
			}
			return VisitOutcome.MissingStock;
		}

		public static bool BoughtEverything(IEnumerable<string> wanted, IEnumerable<string> purchased)
		{
			List<string> list = Validate(purchased);
			List<string> list2 = Validate(wanted);
			if (list2.Count == 0)
			{
				return false;
			}
			foreach (string item in list2)
			{
				if (!list.Remove(item))
				{
					return false;
				}
			}
			return true;
		}

		public static List<string> StillNeeded(IEnumerable<string> wanted, IEnumerable<string> purchased)
		{
			List<string> list = Validate(purchased);
			List<string> list2 = new List<string>();
			foreach (string item in Validate(wanted))
			{
				if (!list.Remove(item))
				{
					list2.Add(item);
				}
			}
			return list2;
		}

		public static decimal RequiredWallet(IEnumerable<string> list, Func<string, decimal> price)
		{
			decimal result = default(decimal);
			foreach (string item in Validate(list))
			{
				decimal num = price(item);
				if (num < 0m)
				{
					throw new ArgumentOutOfRangeException("price");
				}
				result += num;
			}
			return result;
		}

		public static int MaxRegulars(int tier, IReadOnlyList<int> byTier, int standalone)
		{
			if (tier < 0 || byTier == null || byTier.Count == 0)
			{
				return Math.Max(0, standalone);
			}
			return Math.Max(0, byTier[Math.Min(tier, byTier.Count - 1)]);
		}

		public static List<int> ParseCounts(string text, IReadOnlyList<int> fallback)
		{
			List<int> list = new List<int>();
			string[] array = (text ?? "").Split(new char[4] { ',', ';', '/', ' ' }, StringSplitOptions.RemoveEmptyEntries);
			for (int i = 0; i < array.Length; i++)
			{
				if (!int.TryParse(array[i], out var result) || result < 0)
				{
					return fallback.ToList();
				}
				list.Add(result);
			}
			if (list.Count != 0)
			{
				return list;
			}
			return fallback.ToList();
		}

		private static List<string> Validate(IEnumerable<string> items)
		{
			if (items == null)
			{
				throw new ArgumentNullException("items");
			}
			List<string> list = items.ToList();
			if (list.Any(string.IsNullOrWhiteSpace))
			{
				throw new ArgumentException("Invalid product key.");
			}
			return list;
		}
	}
	public sealed class SaveMark
	{
		public int Index { get; set; }

		public int Cycle { get; set; }

		public int Day { get; set; }

		public bool Matches(SaveMark? other)
		{
			if (other != null && other.Index == Index && other.Cycle == Cycle)
			{
				return other.Day == Day;
			}
			return false;
		}

		public override string ToString()
		{
			return $"save {Cycle}:{Index}, day {Day}";
		}
	}
	public sealed class LoyaltySave
	{
		public int Version { get; set; } = 1;

		public SaveMark Mark { get; set; } = new SaveMark();

		public List<Regular> Regulars { get; set; } = new List<Regular>();
	}
	public sealed class LoyaltyStore
	{
		private readonly string directory;

		private readonly XmlSerializer serializer = new XmlSerializer(typeof(LoyaltySave));

		public LoyaltyStore(string directory)
		{
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Expected O, but got Unknown
			if (string.IsNullOrWhiteSpace(directory))
			{
				throw new ArgumentException("A folder is required.", "directory");
			}
			this.directory = directory;
		}

		public string MainPath(int slot)
		{
			return Path.Combine(directory, "Slot" + CheckSlot(slot) + ".xml");
		}

		public string BackupPath(int slot)
		{
			return Path.Combine(directory, "Slot" + CheckSlot(slot) + ".backup.xml");
		}

		public LoyaltySave? Read(string path)
		{
			//IL_000c: 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_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Expected O, but got Unknown
			if (!File.Exists(path))
			{
				return null;
			}
			LoyaltySave loyaltySave;
			try
			{
				XmlReader val = XmlReader.Create(path, new XmlReaderSettings
				{
					DtdProcessing = (DtdProcessing)0,
					XmlResolver = null
				});
				try
				{
					loyaltySave = (LoyaltySave)serializer.Deserialize(val);
				}
				finally
				{
					((IDisposable)val)?.Dispose();
				}
			}
			catch (Exception ex) when (((ex is InvalidOperationException || ex is XmlException) ? 1 : 0) != 0)
			{
				throw new InvalidDataException("Damaged loyalty file: " + Path.GetFileName(path), ex);
			}
			Validate(loyaltySave);
			return loyaltySave;
		}

		public LoyaltySave? Find(int slot, SaveMark mark, List<string> notes)
		{
			string[] array = new string[2]
			{
				MainPath(slot),
				BackupPath(slot)
			};
			foreach (string path in array)
			{
				try
				{
					LoyaltySave loyaltySave = Read(path);
					if (loyaltySave != null && loyaltySave.Mark.Matches(mark))
					{
						return loyaltySave;
					}
				}
				catch (InvalidDataException ex)
				{
					notes.Add(ex.Message);
				}
			}
			return null;
		}

		public void Save(int slot, LoyaltySave data)
		{
			Validate(data);
			Directory.CreateDirectory(directory);
			string text = MainPath(slot);
			string text2 = BackupPath(slot);
			string text3 = text + "." + Guid.NewGuid().ToString("N") + ".tmp";
			try
			{
				using (FileStream fileStream = new FileStream(text3, FileMode.CreateNew, FileAccess.Write, FileShare.None))
				{
					serializer.Serialize((Stream)fileStream, (object)data);
					fileStream.Flush(flushToDisk: true);
				}
				if (File.Exists(text) && !Readable(text))
				{
					File.Move(text, SetAsidePath(text));
				}
				if (File.Exists(text))
				{
					if (File.Exists(text2) && !Readable(text2))
					{
						File.Move(text2, SetAsidePath(text2));
					}
					File.Replace(text3, text, text2);
				}
				else
				{
					File.Move(text3, text);
				}
			}
			finally
			{
				if (File.Exists(text3))
				{
					File.Delete(text3);
				}
			}
		}

		public void CopySlot(int from, int to)
		{
			if (CheckSlot(from) == CheckSlot(to))
			{
				return;
			}
			Directory.CreateDirectory(directory);
			string[][] array = new string[2][]
			{
				new string[2]
				{
					MainPath(from),
					MainPath(to)
				},
				new string[2]
				{
					BackupPath(from),
					BackupPath(to)
				}
			};
			foreach (string[] obj in array)
			{
				string text = obj[0];
				string text2 = obj[1];
				if (File.Exists(text2) && !Readable(text2))
				{
					File.Move(text2, SetAsidePath(text2));
				}
				if (File.Exists(text))
				{
					File.Copy(text, text2, overwrite: true);
				}
				else if (File.Exists(text2))
				{
					File.Delete(text2);
				}
			}
		}

		private bool Readable(string path)
		{
			try
			{
				Read(path);
				return true;
			}
			catch (InvalidDataException)
			{
				return false;
			}
		}

		private static string SetAsidePath(string path)
		{
			return Path.Combine(Path.GetDirectoryName(path), Path.GetFileNameWithoutExtension(path) + ".damaged-" + DateTime.Now.ToString("yyyyMMdd-HHmmss-fff") + ".xml");
		}

		private static int CheckSlot(int slot)
		{
			if (slot < 0 || slot > 99)
			{
				throw new ArgumentOutOfRangeException("slot");
			}
			return slot;
		}

		private static void Validate(LoyaltySave? data)
		{
			if (data == null || data.Version != 1 || data.Mark == null || data.Regulars == null)
			{
				throw new InvalidDataException("Unsupported loyalty file.");
			}
			HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
			foreach (Regular regular in data.Regulars)
			{
				if (regular == null || string.IsNullOrWhiteSpace(regular.Id) || !hashSet.Add(regular.Id) || regular.NextList == null || regular.NextList.Count == 0 || regular.NextList.Any(string.IsNullOrWhiteSpace))
				{
					throw new InvalidDataException("Invalid or duplicate regular in loyalty file.");
				}
			}
		}
	}
	internal static class Others
	{
		private const BindingFlags Flags = BindingFlags.Static | BindingFlags.Public;

		private static Func<int>? spreeNow;

		private static Func<int>? tierNow;

		private static Action? refresh;

		private static Action<string, string>? notice;

		private static Func<bool>? finalNow;

		private static MethodInfo? spree;

		private static MethodInfo? tier;

		private static PropertyInfo? score;

		private static Func<Customer, bool>? isWhale;

		private static Func<Customer, bool>? isWealthy;

		private static HashSet<int>? bigSpenders;

		public static bool PrestigeLinked
		{
			get
			{
				if (spreeNow == null)
				{
					return spree != null;
				}
				return true;
			}
		}

		public static bool TournamentsLinked => finalNow != null;

		public static void Resolve()
		{
			if (Chainloader.PluginInfos.TryGetValue("dimwits.shopprestige", out var value) && (Object)(object)value.Instance != (Object)null)
			{
				Assembly assembly = ((object)value.Instance).GetType().Assembly;
				Type type = assembly.GetType("ShopPrestige.Links");
				if (type != null)
				{
					spreeNow = Make<Func<int>>(type.GetMethod("SpreeSize", BindingFlags.Static | BindingFlags.Public, null, Type.EmptyTypes, null));
					tierNow = Make<Func<int>>(type.GetMethod("Tier", BindingFlags.Static | BindingFlags.Public, null, Type.EmptyTypes, null));
					isWhale = Make<Func<Customer, bool>>(type.GetMethod("IsWhale", BindingFlags.Static | BindingFlags.Public, null, new Type[1] { typeof(Customer) }, null));
					isWealthy = Make<Func<Customer, bool>>(type.GetMethod("IsWealthy", BindingFlags.Static | BindingFlags.Public, null, new Type[1] { typeof(Customer) }, null));
					refresh = Make<Action>(type.GetMethod("Refresh", BindingFlags.Static | BindingFlags.Public, null, Type.EmptyTypes, null));
					notice = Make<Action<string, string>>(type.GetMethod("Notice", BindingFlags.Static | BindingFlags.Public, null, new Type[2]
					{
						typeof(string),
						typeof(string)
					}, null));
					if (spreeNow == null || tierNow == null)
					{
						spreeNow = (tierNow = null);
						Plugin.Log.LogWarning((object)"Shop Prestige link unavailable: using the standalone list limit and regular count.");
					}
					else
					{
						Plugin.Log.LogInfo((object)"Linked to Shop Prestige: list limit and regular count follow Prestige; regulars add to Prestige.");
					}
				}
				else
				{
					Type type2 = assembly.GetType("ShopPrestige.Whales");
					Type type3 = assembly.GetType("ShopPrestige.Prestige");
					spree = type2?.GetMethod("SpreeSize", BindingFlags.Static | BindingFlags.Public, null, new Type[1] { typeof(float) }, null);
					tier = type3?.GetMethod("Tier", BindingFlags.Static | BindingFlags.Public, null, new Type[1] { typeof(float) }, null);
					score = type3?.GetProperty("Active", BindingFlags.Static | BindingFlags.Public);
					isWhale = Make<Func<Customer, bool>>(type2?.GetMethod("Is", BindingFlags.Static | BindingFlags.Public, null, new Type[1] { typeof(Customer) }, null));
					isWealthy = Make<Func<Customer, bool>>(assembly.GetType("ShopPrestige.Wealthy")?.GetMethod("Is", BindingFlags.Static | BindingFlags.Public, null, new Type[1] { typeof(Customer) }, null));
					if (spree?.ReturnType != typeof(int) || tier?.ReturnType != typeof(int) || score?.PropertyType != typeof(float))
					{
						spree = (tier = null);
						score = null;
						Plugin.Log.LogWarning((object)"Shop Prestige link unavailable: using the standalone list limit and regular count.");
					}
					else
					{
						Plugin.Log.LogInfo((object)"Linked to Shop Prestige (older version): list limit and regular count follow Prestige. Update Shop Prestige for regulars to add to Prestige.");
					}
				}
				if (isWhale == null || isWealthy == null)
				{
					Plugin.Log.LogWarning((object)"Shop Prestige whale and wealthy checks unavailable: those customers may become regulars.");
				}
			}
			if (Chainloader.PluginInfos.TryGetValue("dimwits.tournamentformats", out var value2) && (Object)(object)value2.Instance != (Object)null)
			{
				finalNow = Make<Func<bool>>(((object)value2.Instance).GetType().Assembly.GetType("TournamentFormats.Links")?.GetProperty("FinalNow", BindingFlags.Static | BindingFlags.Public)?.GetGetMethod());
				if (finalNow != null)
				{
					Plugin.Log.LogInfo((object)"Linked to Tournament Formats: regulars come to watch finals.");
				}
				else
				{
					Plugin.Log.LogInfo((object)"Tournament Formats is too old to link with Customer Loyalty; update it for the extras.");
				}
			}
			if (Chainloader.PluginInfos.TryGetValue("yaka.weeklytraffic", out var value3) && (Object)(object)value3.Instance != (Object)null)
			{
				bigSpenders = (((object)value3.Instance).GetType().Assembly.GetType("WeeklyTraffic.BigSpenderCustomerPatch")?.GetField("BigSpenderCustomerIds", BindingFlags.Static | BindingFlags.Public))?.GetValue(null) as HashSet<int>;
				if (bigSpenders == null)
				{
					Plugin.Log.LogWarning((object)"Weekly Traffic big-spender check unavailable: big spenders may become regulars.");
				}
			}
		}

		private static T? Make<T>(MethodInfo? method) where T : Delegate
		{
			if (method == null)
			{
				return null;
			}
			try
			{
				return (T)Delegate.CreateDelegate(typeof(T), method);
			}
			catch (ArgumentException)
			{
				return null;
			}
		}

		public static bool IsSpecial(Customer c)
		{
			try
			{
				Func<Customer, bool>? func = isWhale;
				int result;
				if (func == null || !func(c))
				{
					Func<Customer, bool>? func2 = isWealthy;
					if (func2 == null || !func2(c))
					{
						result = ((bigSpenders?.Contains(((Object)c).GetInstanceID()) ?? false) ? 1 : 0);
						goto IL_0042;
					}
				}
				result = 1;
				goto IL_0042;
				IL_0042:
				return (byte)result != 0;
			}
			catch (Exception ex)
			{
				isWhale = (isWealthy = null);
				bigSpenders = null;
				Plugin.Log.LogWarning((object)("Special-customer checks failed and are off for this session: " + ex.Message));
				return false;
			}
		}

		private static float Score()
		{
			return Math.Max(0f, (float)score.GetValue(null, null));
		}

		public static int? SpreeLimit()
		{
			try
			{
				if (spreeNow != null)
				{
					return Math.Max(1, spreeNow());
				}
				if (spree == null || score == null)
				{
					return null;
				}
				return Math.Max(1, (int)spree.Invoke(null, new object[1] { Score() }));
			}
			catch (Exception)
			{
				Off();
				return null;
			}
		}

		public static int Tier()
		{
			try
			{
				if (tierNow != null)
				{
					return Math.Max(0, tierNow());
				}
				if (tier == null || score == null)
				{
					return -1;
				}
				return Math.Max(0, (int)tier.Invoke(null, new object[1] { Score() }));
			}
			catch (Exception)
			{
				Off();
				return -1;
			}
		}

		public static void RefreshPrestige()
		{
			try
			{
				refresh?.Invoke();
			}
			catch (Exception ex)
			{
				refresh = null;
				Plugin.Log.LogWarning((object)("Shop Prestige refresh failed and is off: " + ex.Message));
			}
		}

		public static void Notice(string icon, string text)
		{
			try
			{
				notice?.Invoke(icon, text);
			}
			catch (Exception ex)
			{
				notice = null;
				Plugin.Log.LogWarning((object)("Shop Prestige notices failed and are off: " + ex.Message));
			}
		}

		public static bool FinalNow()
		{
			try
			{
				return finalNow?.Invoke() ?? false;
			}
			catch (Exception ex)
			{
				finalNow = null;
				Plugin.Log.LogWarning((object)("Tournament Formats link failed and is off: " + ex.Message));
				return false;
			}
		}

		private static void Off()
		{
			spree = (tier = null);
			score = null;
			spreeNow = (tierNow = null);
			Plugin.Log.LogWarning((object)"Shop Prestige link failed: using the standalone limits for this session.");
		}
	}
	internal sealed class Panel
	{
		private const float W = 782f;

		private const float H = 986f;

		private const float U = 17f;

		private GameObject? canvas;

		private RectTransform? root;

		private RectTransform? content;

		private IUiKit? kit;

		private Palette pal = Palette.Phone;

		private bool hosted;

		private bool holding;

		private float nextRefresh;

		private int view;

		private int page;

		private int generation;

		private string? selected;

		private string snapshot = "";

		public bool IsOpen
		{
			get
			{
				if ((Object)(object)canvas != (Object)null)
				{
					return canvas.activeSelf;
				}
				return false;
			}
		}

		public void Update()
		{
			//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_006c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0076: 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)
			try
			{
				KeyboardShortcut value = Plugin.PanelKey.Value;
				if (((KeyboardShortcut)(ref value)).IsDown())
				{
					if (IsOpen)
					{
						Close();
					}
					else
					{
						Open();
					}
				}
				else
				{
					if (!IsOpen)
					{
						return;
					}
					if (!hosted && Input.GetKeyDown((KeyCode)27))
					{
						Close();
						return;
					}
					if ((Object)(object)root != (Object)null)
					{
						((Transform)root).localScale = Vector3.one * 17f * Math.Min(1f, (float)Screen.width * 1080f / (float)Math.Max(1, Screen.height) / 830f);
					}
					Cursor.visible = true;
					Cursor.lockState = (CursorLockMode)0;
					if (Time.unscaledTime < nextRefresh)
					{
						return;
					}
					nextRefresh = Time.unscaledTime + 1f;
					if ((Object)(object)Object.FindObjectOfType<InteractionPlayerController>() == (Object)null)
					{
						Close();
						return;
					}
					if (generation != Session.Generation)
					{
						generation = Session.Generation;
						selected = null;
						page = 0;
					}
					if (Snapshot() != snapshot)
					{
						Build();
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Regulars panel: " + ex.Message));
				Close();
			}
		}

		private string Snapshot()
		{
			return Session.Generation + ":" + Plugin.Enabled.Value + ":" + Regulars.Active + ":" + Plugin.MaxRegulars() + ":" + Plugin.ListLimit() + ":" + Plugin.ShowShoppingLists.Value + ":" + Plugin.JoinChance.Value + ":" + Plugin.MinDaysBetweenVisits.Value + ":" + Plugin.MaxDaysBetweenVisits.Value + ":" + Plugin.MaxNewPerDay.Value + ":" + string.Join(",", Regulars.RegularsInShop()) + ":" + string.Join(";", Session.Regulars.Select((Regular r) => r.Id + ":" + r.Name + ":" + r.Visits + ":" + r.JoinedDay + ":" + r.Record + ":" + string.Join(",", r.NextList)));
		}

		public void Open()
		{
			if ((Object)(object)Object.FindObjectOfType<InteractionPlayerController>() == (Object)null)
			{
				return;
			}
			try
			{
				EnsureCanvas();
				generation = Session.Generation;
				Build();
				canvas.SetActive(true);
				if (!hosted && !holding)
				{
					PlayerHold.Begin();
					holding = true;
				}
				nextRefresh = Time.unscaledTime + 1f;
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Could not open regulars: " + ex.Message));
				Close();
			}
		}

		public bool OpenHosted()
		{
			if (IsOpen)
			{
				Close();
			}
			hosted = true;
			Open();
			if (!IsOpen)
			{
				hosted = false;
			}
			return IsOpen;
		}

		public void HostClosed()
		{
			if (hosted)
			{
				hosted = false;
				if ((Object)(object)canvas != (Object)null)
				{
					canvas.SetActive(false);
				}
			}
		}

		public void Close()
		{
			if ((Object)(object)canvas != (Object)null)
			{
				canvas.SetActive(false);
			}
			if (holding)
			{
				PlayerHold.End();
				holding = false;
			}
			if (hosted)
			{
				hosted = false;
				ShopOSApp.CloseScreen();
			}
		}

		public void Destroy()
		{
			Close();
			if ((Object)(object)canvas != (Object)null)
			{
				Object.Destroy((Object)(object)canvas);
			}
			canvas = null;
			root = null;
			kit = null;
		}

		public void ResetLook()
		{
			Destroy();
		}

		private void EnsureCanvas()
		{
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Expected O, but got Unknown
			//IL_0092: 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_0156: Unknown result type (might be due to invalid IL or missing references)
			//IL_015c: Unknown result type (might be due to invalid IL or missing references)
			//IL_015d: Unknown result type (might be due to invalid IL or missing references)
			//IL_015e: 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_017a: Unknown result type (might be due to invalid IL or missing references)
			//IL_018a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0194: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c2: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)canvas != (Object)null))
			{
				pal = Looks.Palette();
				kit = new BuiltInUiKit(pal);
				canvas = new GameObject("CustomerLoyaltyPanel", new Type[1] { typeof(RectTransform) });
				Object.DontDestroyOnLoad((Object)(object)canvas);
				Canvas obj = canvas.AddComponent<Canvas>();
				obj.renderMode = (RenderMode)0;
				obj.sortingOrder = 506;
				CanvasScaler obj2 = canvas.AddComponent<CanvasScaler>();
				obj2.uiScaleMode = (ScaleMode)1;
				obj2.referenceResolution = new Vector2(1920f, 1080f);
				obj2.matchWidthOrHeight = 1f;
				canvas.AddComponent<GraphicRaycaster>();
				Image val = kit.Panel(canvas.transform, "Dim", new Color(0f, 0f, 0f, 0.6f), 0f, raycast: true);
				kit.Stretch(((Graphic)val).rectTransform, 0f, 0f, 0f, 0f);
				root = kit.Node(canvas.transform, "Root");
				RectTransform? obj3 = root;
				RectTransform? obj4 = root;
				RectTransform? obj5 = root;
				Vector2 val2 = default(Vector2);
				((Vector2)(ref val2))..ctor(0.5f, 0.5f);
				obj5.pivot = val2;
				Vector2 anchorMin = (obj4.anchorMax = val2);
				obj3.anchorMin = anchorMin;
				root.sizeDelta = new Vector2(46f, 58f);
				((Transform)root).localScale = Vector3.one * 17f * Math.Min(1f, (float)Screen.width * 1080f / (float)Math.Max(1, Screen.height) / 830f);
				canvas.SetActive(false);
			}
		}

		private void Go(int target)
		{
			view = target;
			selected = null;
			page = 0;
			Build();
		}

		private void Build()
		{
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_0078: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c0: 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_026e: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)root == (Object)null || kit == null)
			{
				return;
			}
			foreach (Transform item in (Transform)root)
			{
				((Component)item).gameObject.SetActive(false);
				Object.Destroy((Object)(object)((Component)item).gameObject);
			}
			Image val2 = kit.Panel((Transform)(object)root, "Frame", pal.Bg, 1.6f, raycast: true);
			kit.Place(((Graphic)val2).rectTransform, 0f, 0f, 46f, 58f);
			kit.Border(((Component)val2).transform, pal.Line, 1.6f);
			content = kit.Node(((Component)val2).transform, "Content");
			kit.Stretch(content, 0f, 0f, 0f, 0f);
			Icon("loyalty_app", 28f, 24f, 46f);
			Text("Customer Loyalty", 88f, 24f, 560f, 48f, 28f, pal.Text);
			Button("X", 710f, 26f, 44f, 44f, Close);
			Button("Overview", 28f, 90f, 236f, 52f, delegate
			{
				Go(0);
			}, view == 0);
			Button("Regulars", 273f, 90f, 236f, 52f, delegate
			{
				Go(1);
			}, view == 1);
			Button("How it works", 518f, 90f, 236f, 52f, delegate
			{
				Go(2);
			}, view == 2);
			if (view == 0)
			{
				Overview();
			}
			else if (view == 1)
			{
				Roster();
			}
			else
			{
				Details();
			}
			Text("Keep their favourites on the shelves.", 28f, 922f, 590f, 36f, 17f, pal.Muted);
			Button("Close", 646f, 920f, 108f, 40f, Close);
			snapshot = Snapshot();
		}

		private void Overview()
		{
			//IL_002c: 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_00a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ea: Unknown result type (might be due to invalid IL or missing references)
			//IL_0178: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d4: Unknown result type (might be due to invalid IL or missing references)
			//IL_0204: Unknown result type (might be due to invalid IL or missing references)
			int count = Session.Regulars.Count;
			int num = Plugin.MaxRegulars();
			Box(28f, 170f, 726f, 270f, pal.Surface);
			Icon("loyalty_app", 58f, 196f, 76f);
			Text(count.ToString(), 154f, 182f, 550f, 128f, 84f, pal.Accent);
			Text("loyal regulars", 158f, 302f, 500f, 42f, 28f, pal.Text);
			Text($"Room for {Math.Max(0, num - count)} more", 58f, 374f, 620f, 40f, 22f, pal.Muted);
			Tile(28f, 462f, 351f, "Here now", Regulars.RegularsInShop().Count().ToString(), "visiting your shop", "customer_arrival");
			Tile(403f, 462f, 351f, "Shopping lists", Plugin.ListLimit().ToString(), "items at most", "shopping_list");
			Box(28f, 686f, 726f, 180f, pal.Surface);
			string value = ((!Plugin.Enabled.Value) ? "Customer Loyalty is switched off." : ((!Regulars.Active) ? "Regulars are unavailable. Check the mod log." : ((count == 0) ? "Your first regular is still to come." : "A familiar face. A growing shopping list.")));
			Text(value, 54f, 710f, 674f, 48f, 25f, pal.Text);
			Text("Customers can become regulars without buying their entire list.", 54f, 762f, 674f, 68f, 20f, pal.Muted, wrap: true);
			Button("Meet your regulars", 430f, 376f, 294f, 44f, delegate
			{
				Go(1);
			}, primary: true);
		}

		private void Roster()
		{
			//IL_00b8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f0: Unknown result type (might be due to invalid IL or missing references)
			//IL_0181: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d7: Unknown result type (might be due to invalid IL or missing references)
			//IL_027b: Unknown result type (might be due to invalid IL or missing references)
			//IL_02ea: Unknown result type (might be due to invalid IL or missing references)
			//IL_02dd: Unknown result type (might be due to invalid IL or missing references)
			Regular regular = Session.Regulars.FirstOrDefault((Regular regular2) => regular2.Id == selected);
			if (regular != null && Plugin.ShowShoppingLists.Value)
			{
				ShoppingList(regular);
				return;
			}
			selected = null;
			List<Regular> list = (from regular2 in Session.Regulars
				orderby regular2.Visits descending, regular2.Id
				select regular2).ToList();
			HashSet<string> hashSet = new HashSet<string>(Regulars.RegularsInShop());
			Text("Your familiar faces", 28f, 164f, 600f, 44f, 28f, pal.Text);
			if (list.Count == 0)
			{
				Text("No regulars yet. Keep the shop stocked and give them a reason to return.", 48f, 270f, 686f, 130f, 26f, pal.Muted, wrap: true);
				return;
			}
			int num = Math.Max(1, (list.Count + 4 - 1) / 4);
			page = Math.Max(0, Math.Min(page, num - 1));
			for (int num2 = 0; num2 < Math.Min(4, list.Count - page * 4); num2++)
			{
				Regular r = list[page * 4 + num2];
				float num3 = 226 + num2 * 152;
				Box(28f, num3, 726f, 134f, pal.Surface);
				Icon("customer_happiness", 50f, num3 + 28f, 64f);
				Text(r.Name, 138f, num3 + 14f, 570f, 42f, 25f, pal.Text);
				Text($"{r.Visits} visits · regular since day {r.JoinedDay}" + ((r.Record.Length > 0) ? (" · " + r.Record) : ""), 138f, num3 + 54f, Plugin.ShowShoppingLists.Value ? 420 : 590, 34f, 18f, pal.Muted);
				Text(hashSet.Contains(r.Id) ? "In the shop now" : "See you next time", 138f, num3 + 88f, 380f, 28f, 17f, hashSet.Contains(r.Id) ? pal.Teal : pal.Muted);
				if (Plugin.ShowShoppingLists.Value)
				{
					Button($"{r.NextList.Count} items  >", 558f, num3 + 42f, 172f, 48f, delegate
					{
						selected = r.Id;
						page = 0;
						Build();
					}, primary: true);
				}
			}
			Pager(num);
		}

		private void ShoppingList(Regular r)
		{
			//IL_006c: 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_0141: Unknown result type (might be due to invalid IL or missing references)
			//IL_0187: Unknown result type (might be due to invalid IL or missing references)
			//IL_0199: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c5: Unknown result type (might be due to invalid IL or missing references)
			if (!Plugin.ShowShoppingLists.Value)
			{
				selected = null;
				Roster();
				return;
			}
			Button("< Regulars", 28f, 164f, 160f, 44f, delegate
			{
				selected = null;
				page = 0;
				Build();
			});
			Text(r.Name, 212f, 164f, 530f, 44f, 28f, pal.Text);
			Text("Next shopping list", 28f, 228f, 630f, 42f, 26f, pal.Accent);
			List<IGrouping<string, string>> list = (from k in r.NextList
				group k by k).ToList();
			int num = Math.Max(1, (list.Count + 7 - 1) / 7);
			page = Math.Max(0, Math.Min(page, num - 1));
			for (int num2 = 0; num2 < Math.Min(7, list.Count - page * 7); num2++)
			{
				IGrouping<string, string> grouping = list[page * 7 + num2];
				float num3 = 290 + num2 * 74;
				Box(28f, num3, 726f, 62f, pal.Surface);
				Text(grouping.Count() + "x", 48f, num3 + 10f, 64f, 42f, 24f, pal.Accent);
				Text(Products.Name(Products.Parse(grouping.Key)), 126f, num3 + 6f, 602f, 50f, 20f, pal.Text, wrap: true);
			}
			Pager(num);
		}

		private void Details()
		{
			//IL_0025: 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_00cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fc: Unknown result type (might be due to invalid IL or missing references)
			//IL_0199: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c9: Unknown result type (might be due to invalid IL or missing references)
			Text("Give them a reason to return", 28f, 170f, 710f, 48f, 28f, pal.Text);
			Tile(28f, 240f, 351f, "No disappointment", "+1", "item on their next list", "customer_happiness");
			Tile(403f, 240f, 351f, "Missing or too pricey", "-2", "items on their next list", "shopping_list_removed");
			Box(28f, 468f, 726f, 370f, pal.Surface);
			Text("A list worth coming back for", 54f, 490f, 670f, 44f, 26f, pal.Text);
			Text("An empty list ends their loyalty. Closing time or a smell interrupting a visit leaves it unchanged.", 54f, 548f, 670f, 94f, 22f, pal.Muted, wrap: true);
			Text($"Join chance: {Plugin.JoinChance.Value:0.#}% of qualifying customers, {Plugin.MaxNewPerDay.Value} new a day at most\nBack every {Plugin.MinDaysBetweenVisits.Value}-{Plugin.MaxDaysBetweenVisits.Value} days\nRoom for {Plugin.MaxRegulars()} regulars · up to {Plugin.ListLimit()} items each", 54f, 668f, 670f, 128f, 21f, pal.Text, wrap: true);
			Text("Partial lists can earn loyalty. Repeat visits encourage more shopping.", 28f, 858f, 710f, 44f, 20f, pal.Muted, wrap: true);
		}

		private void Pager(int pages)
		{
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			if (page > 0)
			{
				Button("< Previous", 28f, 852f, 164f, 44f, delegate
				{
					page--;
					Build();
				});
			}
			Text($"{page + 1} / {pages}", 318f, 852f, 140f, 44f, 20f, pal.Muted);
			if (page + 1 < pages)
			{
				Button("Next >", 590f, 852f, 164f, 44f, delegate
				{
					page++;
					Build();
				});
			}
		}

		private void Tile(float x, float y, float width, string label, string value, string help, string icon)
		{
			//IL_000f: 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)
			//IL_008f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c2: Unknown result type (might be due to invalid IL or missing references)
			Box(x, y, width, 200f, pal.Raised);
			Icon(icon, x + 20f, y + 18f, 44f);
			Text(label, x + 78f, y + 20f, width - 96f, 40f, 21f, pal.Muted);
			Text(value, x + 22f, y + 70f, width - 44f, 72f, 48f, pal.Accent);
			Text(help, x + 22f, y + 150f, width - 44f, 38f, 18f, pal.Text);
		}

		private void Box(float x, float y, float w, float h, Color color)
		{
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			Image val = kit.Panel((Transform)(object)content, "Card", color, 1.0588236f, raycast: false);
			kit.Place(((Graphic)val).rectTransform, x / 17f, y / 17f, w / 17f, h / 17f);
		}

		private void Text(string value, float x, float y, float w, float h, float size, Color color, bool wrap = false)
		{
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			TextMeshProUGUI val = kit.Label((Transform)(object)content, "Text", value, size / 17f, color, (TextAlignmentOptions)4097);
			((TMP_Text)val).richText = false;
			((TMP_Text)val).enableWordWrapping = wrap;
			kit.Place(((TMP_Text)val).rectTransform, x / 17f, y / 17f, w / 17f, Math.Max(h, size * 1.5f) / 17f);
		}

		private void Button(string label, float x, float y, float w, float h, Action click, bool primary = false)
		{
			RectTransform rt = kit.Button((Transform)(object)content, label, primary, 1.0588236f, click);
			kit.Place(rt, x / 17f, y / 17f, w / 17f, h / 17f);
		}

		private void Icon(string name, float x, float y, float size)
		{
			Sprite val = Art.Get(name);
			if (!((Object)(object)val == (Object)null))
			{
				Image val2 = ((Component)kit.Node((Transform)(object)content, "Icon")).gameObject.AddComponent<Image>();
				val2.sprite = val;
				val2.preserveAspect = true;
				((Graphic)val2).raycastTarget = false;
				kit.Place(((Graphic)val2).rectTransform, x / 17f, y / 17f, size / 17f, size / 17f);
			}
		}
	}
	internal static class PlayerHold
	{
		private static InteractionPlayerController? _player;

		public static void Begin()
		{
			if ((Object)(object)_player != (Object)null)
			{
				return;
			}
			try
			{
				_player = Object.FindObjectOfType<InteractionPlayerController>();
				if ((Object)(object)_player != (Object)null)
				{
					_player.EnterUIMode();
					_player.EnterLockMoveMode();
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Could not hold the player still: " + ex.Message));
			}
		}

		public static void End()
		{
			try
			{
				if ((Object)(object)_player != (Object)null)
				{
					_player.ExitLockMoveMode();
					_player.ExitUIMode();
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Could not give control back to the player: " + ex.Message));
			}
			_player = null;
		}
	}
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInPlugin("dimwits.customerloyalty", "Customer Loyalty", "0.6.1")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public sealed class Plugin : BaseUnityPlugin
	{
		public const string Id = "dimwits.customerloyalty";

		public const string Version = "0.6.1";

		public const string PrestigeId = "dimwits.shopprestige";

		public const string WeeklyTrafficId = "yaka.weeklytraffic";

		public const string TournamentsId = "dimwits.tournamentformats";

		private static readonly int[] DefaultTierCounts = new int[4] { 3, 6, 10, 15 };

		internal static ManualLogSource Log = null;

		internal static ConfigEntry<bool> Enabled = null;

		internal static ConfigEntry<bool> ShowLines = null;

		internal static ConfigEntry<bool> ShowShoppingLists = null;

		internal static ConfigEntry<float> JoinChance = null;

		internal static ConfigEntry<int> MinDaysBetweenVisits = null;

		internal static ConfigEntry<int> MaxDaysBetweenVisits = null;

		internal static ConfigEntry<int> MaxNewPerDay = null;

		internal static ConfigEntry<string> MaxRegularsByTier = null;

		internal static ConfigEntry<int> MaxRegularsWithoutPrestige = null;

		internal static ConfigEntry<int> ListLimitWithoutPrestige = null;

		internal static ConfigEntry<KeyboardShortcut> PanelKey = null;

		internal static ConfigEntry<bool> RegularsEnterTournaments = null;

		internal static ConfigEntry<float> TournamentJoinChance = null;

		internal static ConfigEntry<int> FinalWatchers = null;

		internal static Plugin Instance = null;

		internal static ConfigEntry<string> PanelLook = null;

		private Panel panel;

		private bool shopOsTried;

		private float nextShopOs;

		private static List<int> tierCounts = new List<int>(DefaultTierCounts);

		private void Awake()
		{
			//IL_005f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0069: Expected O, but got Unknown
			//IL_0091: Unknown result type (might be due to invalid IL or missing references)
			//IL_009b: Expected O, but got Unknown
			//IL_00c3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cd: Expected O, but got Unknown
			//IL_00f5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ff: Expected O, but got Unknown
			//IL_014b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0155: Expected O, but got Unknown
			//IL_0180: Unknown result type (might be due to invalid IL or missing references)
			//IL_018a: Expected O, but got Unknown
			//IL_01ef: Unknown result type (might be due to invalid IL or missing references)
			//IL_0251: Unknown result type (might be due to invalid IL or missing references)
			//IL_025b: Expected O, but got Unknown
			//IL_0283: Unknown result type (might be due to invalid IL or missing references)
			//IL_028d: Expected O, but got Unknown
			//IL_030a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0314: Expected O, but got Unknown
			//IL_033f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0344: Unknown result type (might be due to invalid IL or missing references)
			//IL_034a: Expected O, but got Unknown
			//IL_034f: Expected O, but got Unknown
			Instance = this;
			Log = ((BaseUnityPlugin)this).Logger;
			Enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("1. Regulars", "Enabled", true, "Customers can become loyal regulars who come back with a growing shopping list.");
			JoinChance = ((BaseUnityPlugin)this).Config.Bind<float>("1. Regulars", "JoinChancePercent", 4f, new ConfigDescription("Chance that a customer who checks out without an unresolved missing-stock complaint becomes a regular. Example: 4 means about one in twenty-five of those customers.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 100f), Array.Empty<object>()));
			MinDaysBetweenVisits = ((BaseUnityPlugin)this).Config.Bind<int>("1. Regulars", "MinDaysBetweenVisits", 2, new ConfigDescription("After a visit, a regular stays away at least this many days. Visits follow the calendar, not how busy the shop is. Example: 2 and 4 means each regular comes back every two to four days, at a random time of day.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 30), Array.Empty<object>()));
			MaxDaysBetweenVisits = ((BaseUnityPlugin)this).Config.Bind<int>("1. Regulars", "MaxDaysBetweenVisits", 4, new ConfigDescription("After a visit, a regular comes back within this many days (if a customer like them walks in that day).", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 30), Array.Empty<object>()));
			MaxNewPerDay = ((BaseUnityPlugin)this).Config.Bind<int>("1. Regulars", "MaxNewRegularsPerDay", 1, new ConfigDescription("Most customers who can become regulars in one day, however busy the shop is. 0 = nobody new joins.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 10), Array.Empty<object>()));
			MaxRegularsByTier = ((BaseUnityPlugin)this).Config.Bind<string>("1. Regulars", "MaxRegularsByPrestigeTier", "3, 6, 10, 15", "Most regulars the shop can have at each Shop Prestige tier, lowest tier first. Example: \"3, 6, 10, 15\". Regulars already won are never removed when the tier drops.");
			MaxRegularsWithoutPrestige = ((BaseUnityPlugin)this).Config.Bind<int>("1. Regulars", "MaxRegularsWithoutShopPrestige", 6, new ConfigDescription("Most regulars when Shop Prestige isn't installed.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 100), Array.Empty<object>()));
			ListLimitWithoutPrestige = ((BaseUnityPlugin)this).Config.Bind<int>("1. Regulars", "ListLimitWithoutShopPrestige", 4, new ConfigDescription("Longest shopping list when Shop Prestige isn't installed (with it, a whale's spree size is the limit). Example: 4 means at most four products, counting duplicates.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 1000), Array.Empty<object>()));
			ShowLines = ((BaseUnityPlugin)this).Config.Bind<bool>("2. Lines", "ShowLines", true, "Regulars say a line when they arrive, check out, join or give up on the shop.");
			ShowShoppingLists = ((BaseUnityPlugin)this).Config.Bind<bool>("3. Interface", "ShowShoppingLists", false, "Show each regular's shopping list and item count in the Regulars panel. Off by default; does not change their shopping behavior.");
			PanelKey = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("3. Interface", "PanelKey", new KeyboardShortcut((KeyCode)108, (KeyCode[])(object)new KeyCode[1] { (KeyCode)306 }), "Opens and closes the list of regulars.");
			RegularsEnterTournaments = ((BaseUnityPlugin)this).Config.Bind<bool>("4. Tournaments", "RegularsEnterTournaments", true, "Regulars can sign up for a tournament you host, the normal way: when they happen to be in the shop during registration. On the day they play as themselves, with their name on the bracket (Tournament Formats), and playing counts as a good visit.");
			TournamentJoinChance = ((BaseUnityPlugin)this).Config.Bind<float>("4. Tournaments", "TopPlayerJoinChancePercent", 25f, new ConfigDescription("Chance that a tournament player who finished in the top 4 and then shops and pays at the till becomes a regular (on top of the one-a-day limit, still within the room for regulars). Example: 25 means one in four. 0 = off.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 100f), Array.Empty<object>()));
			FinalWatchers = ((BaseUnityPlugin)this).Config.Bind<int>("4. Tournaments", "RegularsWatchingTheFinal", 3, new ConfigDescription("With Tournament Formats and Shop Prestige: on a final, up to this many regulars come in to watch (on top of their usual visits), then do their shopping. 0 = off.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 15), Array.Empty<object>()));
			MaxRegularsByTier.SettingChanged += delegate
			{
				ReadTierCounts();
			};
			ReadTierCounts();
			PanelLook = ((BaseUnityPlugin)this).Config.Bind<string>("3. Interface", "PanelLook", "Auto", new ConfigDescription("Vanilla uses blue panels, yellow actions and the game font. ShopOS uses navy, gold and its rounded font. Phone uses dark teal and orange. Auto selects ShopOS when installed, then Phone - Overhaul, otherwise Vanilla.", (AcceptableValueBase)(object)new AcceptableValueList<string>(new string[4] { "Auto", "Vanilla", "ShopOS", "Phone" }), Array.Empty<object>()));
			panel = new Panel();
			PanelLook.SettingChanged += delegate
			{
				panel.ResetLook();
			};
			Harmony val = new Harmony("dimwits.customerloyalty");
			SaveLink.Apply(val);
			Regulars.Apply(val);
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Customer Loyalty 0.6.1 by Dimwit's loaded.");
		}

		private void Start()
		{
			Others.Resolve();
		}

		private void Update()
		{
			panel.Update();
			if (!shopOsTried && Time.unscaledTime >= nextShopOs)
			{
				nextShopOs = Time.unscaledTime + 2f;
				shopOsTried = ShopOSApp.TryRegister(this);
			}
		}

		public void OpenPanel()
		{
			panel.Open();
		}

		public bool OpenPanelHosted()
		{
			return panel.OpenHosted();
		}

		public void HostedPanelClosed()
		{
			panel.HostClosed();
		}

		private void OnDestroy()
		{
			panel?.Destroy();
		}

		private static void ReadTierCounts()
		{
			tierCounts = LoyaltyRules.ParseCounts(MaxRegularsByTier.Value, DefaultTierCounts);
		}

		internal static int ListLimit()
		{
			return Others.SpreeLimit() ?? ListLimitWithoutPrestige.Value;
		}

		internal static int MaxRegulars()
		{
			return LoyaltyRules.MaxRegulars(Others.Tier(), tierCounts, MaxRegularsWithoutPrestige.Value);
		}
	}
	internal static class Products
	{
		public static bool TryParse(string key, out EItemType type)
		{
			type = (EItemType)(-1);
			if (!string.IsNullOrWhiteSpace(key) && !char.IsDigit(key[0]) && Enum.TryParse<EItemType>(key, out type) && Enum.IsDefined(typeof(EItemType), type))
			{
				return (int)type != -1;
			}
			return false;
		}

		public static EItemType Parse(string key)
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			if (!TryParse(key, out var type))
			{
				return (EItemType)(-1);
			}
			return type;
		}

		public unsafe static string Name(EItemType type)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				ItemData itemData = InventoryBase.GetItemData(type);
				return ((itemData != null) ? itemData.GetName() : null) ?? ((object)(*(EItemType*)(&type))/*cast due to .constrained prefix*/).ToString();
			}
			catch (Exception)
			{
				return ((object)(*(EItemType*)(&type))/*cast due to .constrained prefix*/).ToString();
			}
		}

		public static string Describe(IEnumerable<string> keys)
		{
			return string.Join(", ", from k in keys
				group k by k into g
				select $"{g.Count()}x {Name(Parse(g.Key))}");
		}

		public static List<EItemType> Licensed()
		{
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: 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_0050: Invalid comparison between Unknown and I4
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			List<EItemType> list = new List<EItemType>();
			List<RestockData> list2 = CSingleton<InventoryBase>.Instance?.m_StockItemData_SO?.m_RestockDataList;
			List<bool> isItemLicenseUnlocked = CPlayerData.m_IsItemLicenseUnlocked;
			if (list2 == null || isItemLicenseUnlocked == null)
			{
				return list;
			}
			for (int i = 0; i < list2.Count && i < isItemLicenseUnlocked.Count; i++)
			{
				EItemType itemType = list2[i].itemType;
				if (isItemLicenseUnlocked[i] && (int)itemType != -1 && !list.Contains(itemType))
				{
					list.Add(itemType);
				}
			}
			return list;
		}

		public static Func<string> Picker(IReadOnlyList<string> fallback)
		{
			return delegate
			{
				//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)
				List<EItemType> list = Licensed();
				if (list.Count > 0)
				{
					return ((object)list[Random.Range(0, list.Count)]/*cast due to .constrained prefix*/).ToString();
				}
				return (fallback.Count <= 0) ? "" : fallback[Random.Range(0, fallback.Count)];
			};
		}
	}
	internal sealed class Visit
	{
		public Regular? Regular;

		public List<string> Wanted = new List<string>();

		public readonly HashSet<string> Refused = new HashSet<string>();

		public readonly HashSet<string> Missing = new HashSet<string>();

		public bool Judged;

		public bool Watching;

		public int Redirects;

		public int Generation;
	}
	internal static class Regulars
	{
		internal sealed class TakeState
		{
			public ShelfCompartment? Slot;

			public int BagBefore;
		}

		private static FieldRef<Customer, ShelfCompartment>? slotRef;

		private static FieldRef<Customer, Shelf>? shelfRef;

		private static FieldRef<Customer, int>? failRef;

		private static FieldRef<Customer, bool>? cardOnlyRef;

		private static FieldRef<Customer, bool>? checkedOutRef;

		private static MethodInfo? thinkWantToBuyItem;

		private static bool redirecting;

		private static readonly Dictionary<Customer, Visit> visits = new Dictionary<Customer, Visit>();

		private static readonly List<string> bag = new List<string>();

		private static readonly Dictionary<string, int> appointments = new Dictionary<string, int>();

		private static int appointmentsDay = -1;

		private static readonly HashSet<string> watchedFinal = new HashSet<string>();

		private static int watchedDay = -1;

		public static bool Active { get; private set; }

		public static void ForgetVisits()
		{
			visits.Clear();
		}

		public static IEnumerable<string> RegularsInShop()
		{
			return from v in visits.Values
				where v.Regular != null
				select v.Regular.Id;
		}

		public static bool IsRegularHere(Customer c)
		{
			if (visits.TryGetValue(c, out Visit value))
			{
				return value.Regular != null;
			}
			return false;
		}

		public static string? RegularIdOf(Customer c)
		{
			if (!visits.TryGetValue(c, out Visit value))
			{
				return null;
			}
			return value.Regular?.Id;
		}

		public static void Apply(Harmony harmony)
		{
			if (!SaveLink.Ready)
			{
				return;
			}
			try
			{
				slotRef = AccessTools.FieldRefAccess<Customer, ShelfCompartment>("m_CurrentItemCompartment");
				shelfRef = AccessTools.FieldRefAccess<Customer, Shelf>("m_CurrentShelf");
				failRef = AccessTools.FieldRefAccess<Customer, int>("m_FailFindItemAttemptCount");
				cardOnlyRef = AccessTools.FieldRefAccess<Customer, bool>("m_LookingForCardOnly");
				checkedOutRef = AccessTools.FieldRefAccess<Customer, bool>("m_HasCheckedOut");
				thinkWantToBuyItem = AccessTools.Method(typeof(Customer), "ThinkWantToBuyItem", (Type[])null, (Type[])null);
				Dictionary<string, MethodInfo> dictionary = new string[7] { "ActivateCustomer", "OnCustomerReachInsideShop", "TakeItemFromShelf", "ThinkWantToPay", "OnPayingDone", "DeactivateCustomer", "AddReviewData" }.ToDictionary((string n) => n, (string n) => AccessTools.Method(typeof(Customer), n, (Type[])null, (Type[])null));
				List<string> list = (from t in dictionary
					where t.Value == null
					select t.Key).ToList();
				if (thinkWantToBuyItem == null)
				{
					list.Add("ThinkWantToBuyItem");
				}
				if (list.Count > 0)
				{
					Plugin.Log.LogError((object)("The game's customer code changed (" + string.Join(", ", list) + " not found): regulars are switched off."));
					return;
				}
				harmony.Patch((MethodBase)dictionary["ActivateCustomer"], (HarmonyMethod)null, Hook("AfterActivate", 200), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				harmony.Patch((MethodBase)dictionary["OnCustomerReachInsideShop"], (HarmonyMethod)null, Hook("AfterReachInside"), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				harmony.Patch((MethodBase)dictionary["TakeItemFromShelf"], Hook("BeforeTake", 0), Hook("AfterTake"), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				harmony.Patch((MethodBase)dictionary["ThinkWantToPay"], Hook("KeepShopping"), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				harmony.Patch((MethodBase)dictionary["OnPayingDone"], (HarmonyMethod)null, Hook("AfterPaid"), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				harmony.Patch((MethodBase)dictionary["DeactivateCustomer"], Hook("BeforeDeactivate"), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				harmony.Patch((MethodBase)dictionary["AddReviewData"], (HarmonyMethod)null, Hook("AfterReview"), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				Active = true;
				Tournaments.Apply(harmony);
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("Regulars failed to start and are switched off: " + ex.Message));
			}
		}

		private static HarmonyMethod Hook(string name, int priority = 400)
		{
			//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_001f: Expected O, but got Unknown
			return new HarmonyMethod(AccessTools.Method(typeof(Regulars), name, (Type[])null, (Type[])null))
			{
				priority = priority
			};
		}

		private static void Warn(string where, Exception e)
		{
			Plugin.Log.LogWarning((object)("Regulars (" + where + "): " + e.Message));
		}

		private static List<string> BagKeys(Customer c)
		{
			//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)
			bag.Clear();
			foreach (Item itemInBag in c.m_ItemInBagList)
			{
				if (Object.op_Implicit((Object)(object)itemInBag))
				{
					bag.Add(((object)itemInBag.GetItemType()/*cast due to .constrained prefix*/).ToString());
				}
			}
			return bag;
		}

		private unsafe static void AfterActivate(Customer __instance)
		{
			try
			{
				visits.Remove(__instance);
				if (!Plugin.Enabled.Value || __instance.IsSmelly())
				{
					return;
				}
				CustomerTournamentData customerTournamentData = __instance.GetCustomerTournamentData();
				if ((customerTournamentData == null || !customerTournamentData.m_IsTournamentCustomer) && !LightManager.GetHasDayEnded() && !Others.IsSpecial(__instance) && !TryWatchFinal(__instance) && !TryReturn(__instance) && !cardOnlyRef.Invoke(__instance) && __instance.m_TargetBuyItemList.Count > 0)
				{
					visits[__instance] = new Visit
					{
						Wanted = __instance.m_TargetBuyItemList.Select((EItemType t) => ((object)(*(EItemType*)(&t))/*cast due to .constrained prefix*/).ToString()).ToList(),
						Generation = Session.Generation
					};
				}
			}
			catch (Exception e)
			{
				Warn("arrival", e);
			}
		}

		private static int Appointment(Regular r)
		{
			if (!appointments.TryGetValue(r.Id, out var value))
			{
				value = Random.Range(60, 691);
				appointments[r.Id] = value;
			}
			return value;
		}

		private static bool TryReturn(Customer c)
		{
			List<Regular> regulars = Session.Regulars;
			if (regulars.Count == 0)
			{
				return false;
			}
			int today = CPlayerData.m_CurrentDay;
			if (appointmentsDay != today)
			{
				appointments.Clear();
				appointmentsDay = today;
			}
			int now = (LightManager.GetTimeHour() - 8) * 60 + LightManager.GetTimeMinute();
			HashSet<string> here = new HashSet<string>(RegularsInShop());
			Regular regular = regulars.Where((Regular r) => !here.Contains(r.Id) && !r.Playing && r.IsFemale == c.m_IsFemale && LoyaltyRules.Due(r, today) && Appointment(r) <= now).OrderBy(Appointment).FirstOrDefault();
			if (regular == null)
			{
				return false;
			}
			if (!TakeOver(c, regular, out List<string> wanted))
			{
				return false;
			}
			regular.NextVisitDay = LoyaltyRules.NextVisitDay(today, Plugin.MinDaysBetweenVisits.Value, Plugin.MaxDaysBetweenVisits.Value, (int a, int b) => Random.Range(a, b + 1));
			visits[c] = new Visit
			{
				Regular = regular,
				Wanted = wanted,
				Generation = Session.Generation
			};
			Plugin.Log.LogInfo((object)$"Regular {Short(regular)} is coming back for {wanted.Count} thing(s): {Products.Describe(wanted)}. Next visit from day {regular.NextVisitDay}.");
			return true;
		}

		private static bool TryWatchFinal(Customer c)
		{
			if (Plugin.FinalWatchers.Value <= 0 || !Others.PrestigeLinked || !Others.FinalNow())
			{
				return false;
			}
			int currentDay = CPlayerData.m_CurrentDay;
			if (watchedDay != currentDay)
			{
				watchedFinal.Clear();
				watchedDay = currentDay;
			}
			if (watchedFinal.Count >= Plugin.FinalWatchers.Value)
			{
				return false;
			}
			HashSet<string> here = new HashSet<string>(RegularsInShop());
			Regular regular = (from r in Session.Regulars
				where !here.Contains(r.Id) && !r.Playing && !watchedFinal.Contains(r.Id) && r.IsFemale == c.m_IsFemale
				orderby r.Visits descending
				select r).FirstOrDefault();
			if (regular == null || !TakeOver(c, regular, out List<string> wanted))
			{
				return false;
			}
			watchedFinal.Add(regular.Id);
			visits[c] = new Visit
			{
				Regular = regular,
				Wanted = wanted,
				Generation = Session.Generation,
				Watching = true
			};
			Plugin.Log.LogInfo((object)$"Regular {Short(regular)} came in to watch the final ({watchedFinal.Count} of {Plugin.FinalWatchers.Value}).");
			return true;
		}

		private static bool TakeOver(Customer c, Regular regular, out List<string> wanted)
		{
			//IL_00b3: Unknown result type (might be due to invalid IL or missing references)
			wanted = regular.NextList.Where((string k) => Products.TryParse(k, out var _)).ToList();
			if (wanted.Count == 0)
			{
				Plugin.Log.LogWarning((object)("Regular " + Short(regular) + " wants products this game version doesn't have; skipped."));
				return false;
			}
			if (regular.Model >= 0 && c.GetCustomerModelIndex() != regular.Model)
			{
				c.UpdateCharacterModel(regular.Model);
			}
			cardOnlyRef.Invoke(c) = false;
			c.m_TargetBuyItemList.Clear();
			foreach (string item in wanted.Distinct())
			{
				c.m_TargetBuyItemList.Add(Products.Parse(item));
			}
			float num = (float)LoyaltyRules.RequiredWallet(wanted, (string k) => (decimal)Math.Max(0f, CPlayerData.GetItemPrice(Products.Parse(k), true)));
			c.m_MaxMoney = Math.Max(0f, c.m_MaxMoney) + num * 1.25f + 10f;
			return true;
		}

		private static void AfterReachInside(Customer __instance)
		{
			if (visits.TryGetValue(__instance, out Visit value) && value.Regular != null)
			{
				Lines.Say(__instance, value.Watching ? Lines.WatchFinal : Lines.Arrive);
			}
		}

		private unsafe static void AfterReview(Customer __instance, ECustomerReviewType reviewType, EItemType itemType, int higherStarChanceAdd)
		{
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Invalid comparison between Unknown and I4
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Invalid comparison between Unknown and I4
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: Invalid comparison between Unknown and I4
			if (!visits.TryGetValue(__instance, out Visit value) || value.Judged || (int)itemType == -1)
			{
				return;
			}
			string item = ((object)(*(EItemType*)(&itemType))/*cast due to .constrained prefix*/).ToString();
			if ((int)reviewType == 1)
			{
				if (higherStarChanceAdd < 0)
				{
					value.Missing.Add(item);
				}
				else if (higherStarChanceAdd > 0)
				{
					value.Missing.Remove(item);
				}
			}
			else if ((int)reviewType == 2 && higherStarChanceAdd == -40)
			{
				value.Refused.Add(item);
			}
		}

		private static void BeforeTake(Customer __instance, out TakeState? __state)
		{
			__state = null;
			if (visits.TryGetValue(__instance, out Visit value) && value.Regular != null && !value.Judged)
			{
				__state = new TakeState
				{
					Slot = slotRef.Invoke(__instance),
					BagBefore = __instance.m_ItemInBagList.Count
				};
			}
		}

		private static void AfterTake(Customer __instance, TakeState? __state, bool __runOriginal)
		{
			//IL_006f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0074: Unknown result type (might be due to invalid IL or missing references)
			//IL_02f4: Unknown result type (might be due to invalid IL or missing references)
			//IL_0141: Unknown result type (might be due to invalid IL or missing references)
			//IL_0147: Unknown result type (might be due to invalid IL or missing references)
			//IL_0152: 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_0195: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d5: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)__state?.Slot == (Object)null || !__runOriginal)
			{
				return;
			}
			try
			{
				if ((Object)(object)shelfRef.Invoke(__instance) == (Object)null || !visits.TryGetValue(__instance, out Visit value) || value.Regular == null || value.Judged)
				{
					return;
				}
				ShelfCompartment slot = __state.Slot;
				if (slot.IsSettingPrice())
				{
					return;
				}
				EItemType type = slot.GetItemType();
				string key = ((object)Unsafe.As<EItemType, EItemType>(ref type)/*cast due to .constrained prefix*/).ToString();
				if (!value.Wanted.Contains(key))
				{
					return;
				}
				int num = LoyaltyRules.StillNeeded(value.Wanted, BagKeys(__instance)).Count((string k) => k == key);
				bool flag = __instance.m_ItemInBagList.Count > __state.BagBefore;
				bool flag2 = false;
				if (num > 0 && !value.Refused.Contains(key))
				{
					CustomerManager instance = CSingleton<CustomerManager>.Instance;
					while (num > 0 && slot.GetItemCount() > 0 && Random.Range(0, 100) < LoyaltyRules.ContinueChance(value.Regular.Visits))
					{
						Item lastItem = slot.GetLastItem();
						if (!Object.op_Implicit((Object)(object)lastItem) || lastItem.GetItemType() != type)
						{
							break;
						}
						float itemPrice = CPlayerData.GetItemPrice(type, true);
						int customerBuyItemChance = instance.GetCustomerBuyItemChance(itemPrice, CPlayerData.GetItemMarketPrice(type));
						if (Random.Range(0, 100) >= customerBuyItemChance)
						{
							value.Refused.Add(key);
							Lines.Say(__instance, Lines.TooPricey(Products.Name(type)));
							Plugin.Log.LogInfo((object)("Regular " + Short(value.Regular) + " refused the price of " + Products.Name(type) + "."));
							break;
						}
						slot.RemoveItem(lastItem);
						((Renderer)lastItem.m_Mesh).enabled = true;
						__instance.m_ItemInBagList.Add(lastItem);
						lastItem.SetCurrentPrice(itemPrice);
						lastItem.LerpToTransform(__instance.m_ShoppingBagTransform, __instance.m_ShoppingBagTransform, false);
						lastItem.SetHideItemAfterFinishLerp();
						__instance.m_CurrentCostTotal += itemPrice;
						num--;
						flag2 = true;
					}
				}
				if (flag2)
				{
					((Component)__instance.m_ShoppingBagTransform).gameObject.SetActive(true);
					__instance.m_Anim.SetBool("HoldingBag", true);
					if (!flag)
					{
						__instance.m_Anim.SetTrigger("GrabItem");
						failRef.Invoke(__instance) = Math.Max(0, failRef.Invoke(__instance) - 1);
					}
				}
				__instance.m_TargetBuyItemList.RemoveAll((EItemType t) => t == type);
				if (num > 0 && !value.Refused.Contains(key))
				{
					__instance.m_TargetBuyItemList.Add(type);
				}
			}
			catch (Exception e)
			{
				Warn("shelf", e);
			}
		}

		private static bool KeepShopping(Customer __instance)
		{
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Invalid comparison between Unknown and I4
			//IL_013d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0142: Unknown result type (might be due to invalid IL or missing references)
			//IL_014a: 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)
			if (redirecting)
			{
				return true;
			}
			try
			{
				if (!visits.TryGetValue(__instance, out Visit v) || v.Regular == null || v.Judged)
				{
					return true;
				}
				ECustomerState currentState = __instance.m_CurrentState;
				bool flag = currentState - 4 <= 3;
				if (flag || checkedOutRef.Invoke(__instance) || LightManager.GetHasDayEnded() || !CPlayerData.m_IsShopOpen)
				{
					return true;
				}
				List<string> list = (from k in LoyaltyRules.StillNeeded(v.Wanted, BagKeys(__instance))
					where !v.Refused.Contains(k)
					select k).Distinct().ToList();
				if (list.Count == 0 || failRef.Invoke(__instance) > 4 || v.Redirects >= v.Wanted.Count + 8)
				{
					return true;
				}
				if (Random.Range(0, 100) >= LoyaltyRules.ContinueChance(v.Regular.Visits))
				{
					return true;
				}
				v.Redirects++;
				foreach (string item2 in list)
				{
					EItemType item = Products.Parse(item2);
					if (!__instance.m_TargetBuyItemList.Contains(item))
					{
						__instance.m_TargetBuyItemList.Add(item);
					}
				}
				redirecting = true;
				try
				{
					thinkWantToBuyItem.Invoke(__instance, null);
				}
				finally
				{
					redirecting = false;
				}
				return false;
			}
			catch (Exception e)
			{
				Warn("keep shopping", e);
				return true;
			}
		}

		private static void AfterPaid(Customer __instance)
		{
			try
			{
				if (Tournaments.TakeCandidate(__instance, out var place))
				{
					TryEnrollPlayer(__instance, place);
				}
				if (visits.TryGetValue(__instance, out Visit value) && !value.Judged)
				{
					value.Judged = true;
					List<string> bought = BagKeys(__instance).ToList();
					if (value.Regular != null)
					{
						Judge(__instance, value, bought, paid: true);
					}
					else
					{
						TryEnroll(__instance, value, bought);
					}
				}
			}
			catch (Exception e)
			{
				Warn("checkout", e);
			}
		}

		private static void BeforeDeactivate(Customer __instance)
		{
			try
			{
				Tournaments.Forget(__instance);
				if (visits.TryGetValue(__instance, out Visit value))
				{
					visits.Remove(__instance);
					if (!value.Judged && value.Regular != null)
					{
						value.Judged = true;
						Judge(__instance, value, new List<string>(), paid: false);
					}
				}
			}
			catch (Exception e)
			{
				Warn("leaving", e);
			}
		}

		private static void Judge(Customer c, Visit v, List<string> bought, bool paid)
		{
			if (v.Generation != Session.Generation)
			{
				return;
			}
			Regular regular = v.Regular;
			VisitOutcome visitOutcome = LoyaltyRules.Outcome(paid, v.Missing.Count > 0, v.Refused.Count > 0);
			Regular regular2 = LoyaltyRules.Finish(regular, visitOutcome, Plugin.ListLimit(), Products.Picker(regular.NextList));
			Session.Replace(regular.Id, regular2);
			Others.RefreshPrestige();
			switch (visitOutcome)
			{
			case VisitOutcome.BoughtEverything:
			case VisitOutcome.Completed:
				Lines.Say(c, LoyaltyRules.BoughtEverything(v.Wanted, bought) ? Lines.FoundEverything : Lines.Joined);
				Plugin.Log.LogInfo((object)$"Regular {Short(regular)} completed a visit ({bought.Count} bought of {v.Wanted.Count} wanted). Next time: {Products.Describe(regular2.NextList)}.");
				break;
			case VisitOutcome.MissingStock:
			{
				if (regular2 == null)
				{
					Lines.Say(c, Lines.GaveUp);
					Plugin.Log.LogInfo((object)("Regular " + Short(regular) + " is no longer loyal: their list ran out."));
					break;
				}
				Lines.Say(c, Lines.Disappointed);
				List<string> keys = LoyaltyRules.StillNeeded(v.Wanted, bought);
				Plugin.Log.LogInfo((object)("Regular " + Short(regular) + " missed " + Products.Describe(keys) + ". Next time: " + Products.Describe(regular2.NextList) + "."));
				break;
			}
			default:
				Plugin.Log.LogInfo((object)("Regular " + Short(regular) + "'s visit was cut short; their list stays the same."));
				break;
			}
		}

		private static void TryEnroll(Customer c, Visit v, List<string> bought)
		{
			if (v.Generation != Session.Generation || v.Wanted.Count == 0 || !LoyaltyRules.CanEnroll(paid: true, v.Missing.Count > 0) || Session.Regulars.Count >= Plugin.MaxRegulars() || !LoyaltyRules.RoomToJoinToday(Session.Regulars, CPlayerData.m_CurrentDay, Plugin.MaxNewPerDay.Value) || Random.value * 100f >= Plugin.JoinChance.Value)
			{
				return;
			}
			int model = c.GetCustomerModelIndex();
			if (!Session.Regulars.Any((Regular r) => r.IsFemale == c.m_IsFemale && r.Model == model))
			{
				Regular regular = LoyaltyRules.Enroll(new Regular
				{
					Id = Guid.NewGuid().ToString("N"),
					IsFemale = c.m_IsFemale,
					Model = model,
					JoinedDay = CPlayerData.m_CurrentDay,
					NextVisitDay = LoyaltyRules.NextVisitDay(CPlayerData.m_CurrentDay, Plugin.MinDaysBetweenVisits.Value, Plugin.MaxDaysBetweenVisits.Value, (int a, int b) => Random.Range(a, b + 1))
				}, v.Wanted, Plugin.ListLimit(), Products.Picker(v.Wanted));
				Session.Regulars.Add(regular);
				Lines.Say(c, Lines.Joined);
				Others.RefreshPrestige();
				Plugin.Log.LogInfo((object)$"New regular {Short(regular)} ({Session.Regulars.Count} of {Plugin.MaxRegulars()}). Next time: {Products.Describe(regular.NextList)}.");
			}
		}

		private static void TryEnrollPlayer(Customer c, int place)
		{
			if (!Plugin.Enabled.Value || Random.value * 100f >= Plugin.TournamentJoinChance.Value || Session.Regulars.Count >= Plugin.MaxRegulars())
			{
				return;
			}
			EItemType type;
			List<string> list = (from k in BagKeys(c)
				where Products.TryParse(k, out type)
				select k).ToList();
			if (list.Count == 0)
			{
				return;
			}
			int model = c.GetCustomerModelIndex();
			if (!Session.Regulars.Any((Regular r) => r.IsFemale == c.m_IsFemale && r.Model == model))
			{
				Regular regular = LoyaltyRules.Enroll(new Regular
				{
					Id = Guid.NewGuid().ToString("N"),
					IsFemale = c.m_IsFemale,
					Model = model,
					JoinedDay = CPlayerData.m_CurrentDay,
					NextVisitDay = LoyaltyRules.NextVisitDay(CPlayerData.m_CurrentDay, Plugin.MinDaysBetweenVisits.Value, Plugin.MaxDaysBetweenVisits.Value, (int a, int b) => Random.Range(a, b + 1)),
					Tournaments = 1,
					Titles = ((place == 0) ? 1 : 0)
				}, list, Plugin.ListLimit(), Products.Picker(list));
				Session.Regulars.Add(regular);
				Lines.Say(c, Lines.Joined);
				Others.RefreshPrestige();
				Others.Notice("src_regulars", regular.FirstName + ", " + Ordinal(place + 1) + " in your tournament, is now a regular.");
				Plugin.Log.LogInfo((object)$"Tournament player ({Ordinal(place + 1)}) became regular {Short(regular)} ({Session.Regulars.Count} of {Plugin.MaxRegulars()}).");
			}
		}

		internal static string Ordinal(int n)
		{
			string text = n.ToString();
			int num = n % 100;
			bool flag = (uint)(num - 11) <= 2u;
			string text2 = ((!flag) ? ((n % 10) switch
			{
				1 => "st", 
				2 => "nd", 
				3 => "rd", 
				_ => "th", 
			}) : "th");
			return text + text2;
		}

		internal static string Short(Regular r)
		{
			if (r.Id.Length < 4)
			{
				return "#" + r.Id;
			}
			return "#" + r.Id.Substring(0, 4);
		}
	}
	internal static class Session
	{
		public static List<Regular> Regulars { get; set; } = new List<Regular>();

		public static int Generation { get; private set; }

		public static void Reset(List<Regular>? regulars = null)
		{
			Regulars = regulars ?? new List<Regular>();
			Generation++;
			CustomerLoyalty.Regulars.ForgetVisits();
		}

		public static void Replace(string id, Regular? updated)
		{
			int num = Regulars.FindIndex((Regular r) => r.Id == id);
			if (num >= 0)
			{
				if (updated == null)
				{
					Regulars.RemoveAt(num);
				}
				else
				{
					Regulars[num] = updated;
				}
			}
		}
	}
	internal static class SaveLink
	{
		private static LoyaltyStore? store;

		private static DateTime savedBefore;

		private static readonly bool[] slotExisted = new bool[4];

		public static bool Ready => store != null;

		public static void Apply(Harmony harmony)
		{
			try
			{
				store = new LoyaltyStore(Path.Combine(Application.persistentDataPath, "CustomerLoyalty"));
				MethodInfo methodInfo = AccessTools.Method(typeof(CGameData), "SaveGameData", new Type[1] { typeof(int) }, (Type[])null);
				MethodInfo methodInfo2 = AccessTools.Method(typeof(CSaveLoad), "Load", new Type[1] { typeof(int) }, (Type[])null);
				MethodInfo methodInfo3 = AccessTools.Method(typeof(CSaveLoad), "LoadBackup", new Type[1] { typeof(int) }, (Type[])null);
				MethodInfo methodInfo4 = AccessTools.Method(typeof(CSaveLoad), "AutoSaveMoveToEmptySaveSlot", (Type[])null, (Type[])null);
				MethodInfo methodInfo5 = AccessTools.Method(typeof(CGameManager), "LoadMainLevelAsync", new Type[2]
				{
					typeof(string),
					typeof(int)
				}, (Type[])null);
				if (methodInfo == null || methodInfo2 == null || methodInfo3 == null || methodInfo4 == null || methodInfo5 == null)
				{
					Plugin.Log.LogError((object)"The game's save system changed: regulars can't be saved, so they are switched off.");
					store = null;
					return;
				}
				harmony.Patch((MethodBase)methodInfo, Hook("BeforeSave"), Hook("AfterSave"), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, Hook("AfterLoad"), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				harmony.Patch((MethodBase)methodInfo3, (HarmonyMethod)null, Hook("AfterLoadBackup"), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				harmony.Patch((MethodBase)methodInfo4, Hook("BeforeMoveAutosave"), Hook("AfterMoveAutosave"), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				harmony.Patch((MethodBase)methodInfo5, Hook("BeforeLevel"), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			}
			catch (Exception ex)
			{
				store = null;
				Plugin.Log.LogError((object)("Save link failed to start, regulars are switched off: " + ex.Message));
			}
		}

		private static HarmonyMethod Hook(string name)
		{
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Expected O, but got Unknown
			return new HarmonyMethod(AccessTools.Method(typeof(SaveLink), name, (Type[])null, (Type[])null));
		}

		private static string GameSavePath(int slot)
		{
			return Application.persistentDataPath + "/savedGames_Release" + slot + ".json";
		}

		private static DateTime WriteTime(string path)
		{
			if (!File.Exists(path))
			{
				return DateTime.MinValue;
			}
			return File.GetLastWriteTimeUtc(path);
		}

		private static void BeforeLevel()
		{
			Session.Reset();
		}

		private static void BeforeSave(int saveSlotIndex)
		{
			try
			{
				savedBefore = WriteTime(GameSavePath(saveSlotIndex));
			}
			catch (Exception)
			{
				savedBefore = DateTime.MaxValue;
			}
		}

		private static void AfterSave(int saveSlotIndex)
		{
			if (store == null)
			{
				return;
			}
			try
			{
				if (WriteTime(GameSavePath(saveSlotIndex)) <= savedBefore)
				{
					Plugin.Log.LogWarning((object)$"The game save to slot {saveSlotIndex} didn't complete; regulars weren't saved either.");
					return;
				}
				SaveMark mark = new SaveMark
				{
					Index = CPlayerData.m_SaveIndex,
					Cycle = CPlayerData.m_SaveCycle,
					Day = CPlayerData.m_CurrentDay
				};
				store.Save(saveSlotIndex, new LoyaltySave
				{
					Mark = mark,
					Regulars = Session.Regulars.ToList()
				});
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)$"Couldn't save regulars for slot {saveSlotIndex}: {ex.Message}");
			}
		}

		private static void AfterLoad(int slotIndex, bool __result)
		{
			Loaded(slotIndex, __result, "save");
		}

		private static void AfterLoadBackup(int slotIndex, bool __result)
		{
			Loaded(slotIndex, __result, "backup save");
		}

		private static void Loaded(int slot, bool ok, string what)
		{
			if (!ok || store == null)
			{
				return;
			}
			try
			{
				CGameData savedGame = CSaveLoad.m_SavedGame;
				if (savedGame == null)
				{
					return;
				}
				SaveMark mark = new SaveMark
				{
					Index = savedGame.m_SaveIndex,
					Cycle = savedGame.m_SaveCycle,
					Day = savedGame.m_CurrentDay
				};
				List<string> list = new List<string>();
				LoyaltySave loyaltySave = store.Find(slot, mark, list);
				foreach (string item in list)
				{
					Plugin.Log.LogWarning((object)(item + " It was left in place and not used."));
				}
				Session.Reset(loyaltySave?.Regulars ?? new List<Regular>());
				Plugin.Log.LogInfo((object)((loyaltySave == null) ? $"Loaded {what} in slot {slot}: no regulars saved for this point of the game." : $"Loaded {what} in slot {slot}: {loyaltySave.Regulars.Count} regular(s)."));
			}
			catch (Exception ex)
			{
				Session.Reset();
				Plugin.Log.LogError((object)$"Couldn't read regulars for slot {slot}, starting with none: {ex.Message}");
			}
		}

		private static void BeforeMoveAutosave()
		{
			for (int i = 1; i < slotExisted.Length; i++)
			{
				slotExisted[i] = File.Exists(GameSavePath(i)) || File.Exists(Path.ChangeExtension(GameSavePath(i), ".gd"));
			}
		}

		private static void AfterMoveAutosave()
		{
			if (store == null)
			{
				return;
			}
			for (int i = 1; i < slotExisted.Length; i++)
			{
				if (!slotExisted[i] && File.Exists(GameSavePath(i)))
				{
					try
					{
						store.CopySlot(0, i);
						Plugin.Log.LogInfo((object)$"Autosave copied to slot {i}; its regulars went with it.");
						break;
					}
					catch (Exception ex)
					{
						Plugin.Log.LogError((object)$"Couldn't copy regulars to slot {i}: {ex.Message}");
						break;
					}
				}
			}
		}
	}
	internal static class ShopOSApp
	{
		internal sealed class LoyaltyScreen : UIScreenBase
		{
			protected override void OnOpenScreen()
			{
				((UIScreenBase)this).OnOpenScreen();
				bool flag = false;
				try
				{
					flag = (Object)(object)_plugin != (Object)null && _plugin.OpenPanelHosted();
				}
				catch (Exception ex)
				{
					Plugin.Log.LogWarning((object)("The Loyalty app couldn't open: " + ex.Message));
				}
				if (!flag)
				{
					((UIScreenBase)this).OnCloseScreen();
				}
			}

			protected override void OnCloseScreen()
			{
				((UIScreenBase)this).OnCloseScreen();
				try
				{
					_plugin?.HostedPanelClosed();
				}
				catch (Exception ex)
				{
					Plugin.Log.LogWarning((object)("The Loyalty app didn't close cleanly: " + ex.Message));
				}
			}
		}

		public const string ShopOSGuid = "com.qolmods.shopos";

		private static LoyaltyScreen? _screen;

		private static Plugin? _plugin;

		public static bool TryRegister(Plugin plugin)
		{
			if (!Chainloader.PluginInfos.TryGetValue("com.qolmods.shopos", out var value))
			{
				return true;
			}
			if ((Object)(object)value.Instance == (Object)null)
			{
				return false;
			}
			_plugin = plugin;
			try
			{
				Type type = ((object)value.Instance).GetType().Assembly.GetType("ShopOS.ShopOSApi", throwOnError: false);
				if (type == null)
				{
					Plugin.Log.LogWarning((object)"This version of QOL ShopOS has no app API; use the panel key instead.");
					return true;
				}
				object obj = type.GetProperty("IsReady")?.GetValue(null);
				if (obj is bool && !(bool)obj)
				{
					return false;
				}
				Sprite val = Art.Get("loyalty_app");
				MethodInfo method = type.GetMethod("RegisterApp", BindingFlags.Static | BindingFlags.Public, null, new Type[5]
				{
					typeof(string),
					typeof(string),
					typeof(string),
					typeof(Sprite),
					typeof(Func<UIScreenBase>)
				}, null);
				if (method != null)
				{
					Func<UIScreenBase> func = Screen;
					obj = method.Invoke(null, new object[5] { "dimwits.customerloyalty", "loyalty", "Regulars", val, func });
					bool flag = obj is bool && (bool)obj;
					Plugin.Log.LogInfo((object)(flag ? "QOL ShopOS app added (opens full screen in the tablet)." : "QOL ShopOS refused the app."));
					return true;
				}
				MethodInfo method2 = type.GetMethod("RegisterApp", BindingFlags.Static | BindingFlags.Public, null, new Type[5]
				{
					typeof(string),
					typeof(string),
					typeof(string),
					typeof(Sprite),
					typeof(Action)
				}, null);
				if (method2 == null)
				{
					Plugin.Log.LogWarning((object)"This version of QOL ShopOS has no app API; use the panel key instead.");
					return true;
				}
				Action action = plugin.OpenPanel;
				obj = method2.Invoke(null, new object[5] { "dimwits.customerloyalty", "loyalty", "Regulars", val, action });
				bool flag2 = obj is bool && (bool)obj;
				Plugin.Log.LogInfo((object)(flag2 ? "QOL ShopOS app added." : "QOL ShopOS refused the app."));
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Could not add the QOL ShopOS app: " + (ex.InnerException ?? ex).Message));
			}
			return true;
		}

		private static UIScreenBase Screen()
		{
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Expected O, but got Unknown
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Expected O, but got Unknown
			if ((Object)(object)_screen == (Object)null)
			{
				GameObject val = new GameObject("CustomerLoyaltyShopOSScreen");
				Object.DontDestroyOnLoad((Object)(object)val);
				GameObject val2 = new GameObject("Group");
				val2.transform.SetParent(val.transform, false);
				_screen = val.AddComponent<LoyaltyScreen>();
				((UIScreenBase)_screen).m_ScreenGroup = val2;
			}
			return (UIScreenBase)(object)_screen;
		}

		public static void CloseScreen()
		{
			try
			{
				if ((Object)(object)_screen != (Object)null && ((UIScreenBase)_screen).IsScreenOpened())
				{
					((UIScreenBase)_screen).CloseScreen();
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Could not go back to the ShopOS tablet: " + ex.Message));
			}
		}
	}
	internal static class Tournaments
	{
		private static readonly Dictionary<Customer, int> candidates = new Dictionary<Customer, int>();

		public static void Apply(Harmony harmony)
		{
			//IL_0098: 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
			//IL_00ba: Expected O, but got Unknown
			//IL_00ce: Unknown result type (might be due to invalid IL or missing references)
			//IL_00db: Expected O, but got Unknown
			//IL_00ef: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f4: Unknown result type (might be due to invalid IL or missing references)
			//IL_0107: Expected O, but got Unknown
			try
			{
				MethodInfo methodInfo = AccessTools.Method(typeof(Customer), "EvaluateJoinTournament", (Type[])null, (Type[])null);
				MethodInfo methodInfo2 = AccessTools.Method(typeof(Customer), "OnTournamentEnded", new Type[1] { typeof(int) }, (Type[])null);
				MethodInfo methodInfo3 = AccessTools.Method(typeof(CustomerManager), "SpawnGameStartCustomer", (Type[])null, (Type[])null);
				if (methodInfo == null || methodInfo2 == null || methodInfo3 == null)
				{
					Plugin.Log.LogWarning((object)"The game's tournament code changed: regulars won't enter tournaments.");
					return;
				}
				harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(Tournaments), "BeforeJoin", (Type[])null), new HarmonyMethod(typeof(Tournaments), "AfterJoin", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, new HarmonyMethod(typeof(Tournaments), "AfterEnded", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				harmony.Patch((MethodBase)methodInfo3, (HarmonyMethod)null, new HarmonyMethod(typeof(Tournaments), "AfterMorning", (Type[])null)
				{
					priority = 200
				}, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Regulars won't enter tournaments: " + ex.Message));
			}
		}

		private static void BeforeJoin(out int __state)
		{
			__state = CPlayerData.m_TournamentData?.m_TournamentSignedUpCustomerCount ?? 0;
		}

		private static void AfterJoin(Customer __instance, int __state)
		{
			try
			{
				if (!Plugin.Enabled.Value || !Plugin.RegularsEnterTournaments.Value)
				{
					return;
				}
				TournamentData tournamentData = CPlayerData.m_TournamentData;
				if (tournamentData != null && tournamentData.m_TournamentSignedUpCustomerCount > __state)
				{
					string id = Regulars.RegularIdOf(__instance);
					Regular regular = ((id == null) ? null : Session.Regulars.FirstOrDefault((Regular r) => r.Id == id));
					if (regular != null && !regular.Entered)
					{
						regular.Entered = true;
						Lines.Say(__instance, Lines.SignedUp);
						Plugin.Log.LogInfo((object)("Regular " + Regulars.Short(regular) + " signed up for the tournament."));
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Regulars (sign-up): " + ex.Message));
			}
		}

		private static void AfterMorning(CustomerManager __instance)
		{
			try
			{
				TournamentData tournamentData = CPlayerData.m_TournamentData;
				if (tournamentData == null)
				{
					return;
				}
				if (!tournamentData.m_IsTournamentDay)
				{
					foreach (Regular regular2 in Session.Regulars)
					{
						regular2.Playing = false;
						if (!tournamentData.m_IsHostingTournament)
						{
							regular2.Entered = false;
						}
					}
					return;
				}
				List<Customer> source = (__instance.m_TournamentCustomerList ?? new List<Customer>()).Where((Customer c) => (Object)(object)c != (Object)null && c.m_IsActive).ToList();
				foreach (Regular r in Session.Regulars.Where((Regular regular2) => regular2.Playing))
				{
					if (!source.Any((Customer c) => HasFace(c, r)))
					{
						r.Playing = false;
					}
				}
				HashSet<Customer> seated = new HashSet<Customer>(source.Where((Customer c) => Session.Regulars.Any((Regular regular2) => regular2.Playing && HasFace(c, regular2))));
				int num = 0;
				foreach (Regular r2 in Session.Regulars.Where((Regular regular2) => regular2.Entered).ToList())
				{
					r2.Entered = false;
					if (r2.Model < 0 || !Plugin.Enabled.Value)
					{
						continue;
					}
					Customer seat = ((IEnumerable<Customer>)source).FirstOrDefault((Func<Customer, bool>)((Customer c) => !seated.Contains(c) && HasFace(c, r2))) ?? ((IEnumerable<Customer>)source).FirstOrDefault((Func<Customer, bool>)((Customer c) => !seated.Contains(c) && c.m_IsFemale == r2.IsFemale));
					if ((Object)(object)seat == (Object)null)
					{
						Plugin.Log.LogInfo((object)("Regular " + Regulars.Short(r2) + " signed up, but no seat fits them today."));
						continue;
					}
					int customerModelIndex = seat.GetCustomerModelIndex();
					if (customerModelIndex != r2.Model)
					{
						Customer val = ((IEnumerable<Customer>)source).FirstOrDefault((Func<Customer, bool>)((Customer c) => (Object)(object)c != (Object)(object)seat && HasFace(c, r2)));
						seat.UpdateCharacterModel(r2.Model);
						if ((Object)(object)val != (Object)null)
						{
							val.UpdateCharacterModel(customerModelIndex);
						}
					}
					seated.Add(seat);
					num++;
					Regular regular = LoyaltyRules.Finish(r2, VisitOutcome.Completed, Plugin.ListLimit(), Products.Picker(r2.NextList)) ?? r2;
					regular.Tournaments = r2.Tournaments + 1;
					regular.Playing = true;
					regular.Entered = false;
					Session.Replace(r2.Id, regular);
					Plugin.Log.LogInfo((object)("Regular " + Regulars.Short(r2) + " is playing in today's tournament."));
				}
				if (num <= 0)
				{
					return;
				}
				try
				{
					TournamentPairingScreen tournamentPairingScreen = __instance.m_TournamentPairingScreen;
					if (tournamentPairingScreen != null)
					{
						tournamentPairingScreen.RefreshAllCustomerData(__instance.m_TournamentSortedCustomerList);
					}
				}
				catch (Exception)
				{
				}
				Others.RefreshPrestige();
			}
			catch (Exception ex2)
			{
				Plugin.Log.LogWarning((object)("Regulars (tournament morning): " + ex2.Message));
			}
			static bool HasFace(Customer c, Regular regular2)
			{
				if (c.m_IsFemale == regular2.IsFemale)
				{
					return c.GetCustomerModelIndex() == regular2.Model;
				}
				return false;
			}
		}

		private static void AfterEnded(Customer __instance, int tournamentPlacementIndex)
		{
			try
			{
				if ((Object)(object)__instance == (Object)null)
				{
					return;
				}
				int model = __instance.GetCustomerModelIndex();
				Regular regular = Session.Regulars.FirstOrDefault((Regular r) => r.Playing && r.IsFemale == __instance.m_IsFemale && r.Model == model);
				if (regular != null)
				{
					if (tournamentPlacementIndex == 0)
					{
						regular.Titles++;
						Lines.Say(__instance, Lines.Champion);
						Others.Notice("src_regulars", "Your regular " + regular.FirstName + " won the tournament!");
						Others.RefreshPrestige();
					}
					Plugin.Log.LogInfo((object)("Regular " + Regulars.Short(regular) + " finished " + Regulars.Ordinal(tournamentPlacementIndex + 1) + " (" + regular.Record + ")."));
				}
				else if (tournamentPlacementIndex >= 0 && tournamentPlacementIndex < 4 && Plugin.TournamentJoinChance.Value > 0f && !Others.IsSpecial(__instance))
				{
					candidates[__instance] = tournamentPlacementIndex;
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Regulars (tournament end): " + ex.Message));
			}
		}

		public static bool TakeCandidate(Customer c, out int place)
		{