Decompiled source of ParkStats v0.2.1
plugins/ParkStats.Core.dll
Decompiled 40 minutes agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using System.Text.Json; using System.Text.Json.Serialization; using System.Text.RegularExpressions; using Microsoft.CodeAnalysis; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")] [assembly: AssemblyCompany("ParkStats.Core")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+afeb85d5923f3daee29c3dc1ca473ef5d09b3a69")] [assembly: AssemblyProduct("ParkStats.Core")] [assembly: AssemblyTitle("ParkStats.Core")] [assembly: AssemblyVersion("1.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace ParkStats.Core { public enum Severity { Low, Medium, High } public sealed record Advice(Severity Severity, string Title, string Detail); public static class Advisor { private const double ConditionThreshold = 0.5; private const double NeedMediumBelow = 0.6; private const double NeedHighBelow = 0.4; private const double NearFull = 0.9; private const double TicketTolerance = 0.1; private const double RidePriceTolerance = 0.2; private const double DecorationLowBelow = 0.5; private const double StaffCostShare = 0.5; private const int NamesShown = 3; private const int BusyParkUses = 50; private const int ShortestQueueWorthBuildingFor = 2; private static readonly Dictionary<string, string> NeedHints = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase) { ["Fun"] = "Add more attractions or reopen closed ones.", ["Energy"] = "Add places to rest.", ["Hygiene"] = "Add showers and keep pools clean.", ["Thirst"] = "Add or restock drink stands.", ["Hunger"] = "Add or restock food stands.", ["Toilet"] = "Build more toilets.", ["Trash"] = "Add trash bins or cleaning staff." }; public static IReadOnlyList<Advice> Evaluate(ParkSnapshot park) { List<Advice> list = new List<Advice>(); AddRideCondition(park, list); AddOpenRequests(park, list); AddEmptyStands(park, list); AddQueues(park, list); AddWeakestNeed(park, list); AddCapacity(park, list); AddTicketPrice(park, list); AddRidePricing(park, list); AddUnused(park, list); AddSatisfactionMultiplier(park, list); AddNextStar(park, list); AddVisitorRoom(park, list); AddBuildMore(park, list); AddNeedDemand(park, list); AddUpgrades(park, list); AddDecoration(park, list); AddStaff(park, list); AddTopComplaint(park, list); return list.OrderByDescending((Advice a) => a.Severity).ToList(); } private static void AddRideCondition(ParkSnapshot park, List<Advice> advice) { List<RideRow> list = park.Rides.Where((RideRow r) => r.IsBroken).ToList(); if (list.Count > 0) { advice.Add(new Advice(Severity.High, "Repair broken rides", Names(list) + ": malfunctioning, so no guests can use them.")); } List<RideRow> list2 = park.Rides.Where((RideRow r) => !r.IsOpen && !r.IsBroken).ToList(); if (list2.Count > 0) { advice.Add(new Advice(Severity.Medium, "Reopen closed rides", Names(list2) + ": closed, earning nothing and serving no needs.")); } double cleanLimit = Math.Min(park.CleanlinessThreshold ?? 0.5, 0.5); List<RideRow> list3 = park.Rides.Where((RideRow r) => r.Cleanliness < cleanLimit).ToList(); if (list3.Count > 0) { advice.Add(new Advice(Severity.Medium, "Clean dirty rides", Names(list3) + ": cleanliness below " + Format.Percent(cleanLimit) + ".")); } double wearLimit = Math.Min(park.DurabilityThreshold ?? 0.5, 0.5); List<RideRow> list4 = park.Rides.Where((RideRow r) => r.Durability < wearLimit).ToList(); if (list4.Count > 0) { advice.Add(new Advice(Severity.Medium, "Maintain worn rides", Names(list4) + ": durability below " + Format.Percent(wearLimit) + ", breakdowns are likely.")); } } private static void AddOpenRequests(ParkSnapshot park, List<Advice> advice) { List<List<RideRow>> list = Kinds(park.Rides.Where((RideRow r) => r.HasOpenRequest)); if (list.Count != 0) { advice.Add(new Advice(Severity.High, "Guests have a request open", KindNames(list) + ": a guest inside has asked for different settings. Set them on the control panel; a guest who leaves without them complains of bad service.")); } } private static void AddEmptyStands(ParkSnapshot park, List<Advice> advice) { List<List<RideRow>> list = Kinds(park.Rides.Where((RideRow r) => r.IsOpen && !r.IsBroken && r.StockCapacity > 0 && r.StockLeft == 0)); if (list.Count != 0) { advice.Add(new Advice(Severity.High, "Restock empty stands", KindNames(list) + ": sold out. Every guest who walks up leaves without buying.")); } } private static void AddQueues(ParkSnapshot park, List<Advice> advice) { List<(List<RideRow>, int)> list = (from kind in Kinds(park.Rides) select (Kind: kind, Waiting: kind.Sum((RideRow r) => r.QueueLength.GetValueOrDefault())) into k where k.Waiting >= Math.Max(2, k.Kind.Count) orderby k.Waiting descending select k).Take(3).ToList(); if (list.Count == 0) { return; } IEnumerable<string> values = list.Select<(List<RideRow>, int), string>(delegate((List<RideRow> Kind, int Waiting) k) { string text = $"{KindName(k.Kind)}: {k.Waiting} waiting"; double num = k.Kind.Sum((RideRow r) => r.Earned) / (double)k.Kind.Count; return (!(num > 0.0)) ? text : (text + ", about " + Format.Money(num) + " earned each today"); }); advice.Add(new Advice(Severity.Medium, "Guests are queuing", string.Join("; ", values) + ". Another of these would serve them sooner.")); } private static void AddUnused(ParkSnapshot park, List<Advice> advice) { if (park.Rides.Sum((RideRow r) => r.UsesToday) < 50) { return; } List<List<RideRow>> list = (from kind in Kinds(park.Rides.Where((RideRow r) => r.Price > 0.0 && r.IsOpen && !r.IsBroken)) where kind.All((RideRow r) => r.UsesToday == 0) select kind).ToList(); if (list.Count != 0) { advice.Add(new Advice(Severity.Low, "Unused paid attractions", KindNames(list) + ": no guest has used them today. Check that guests can reach them, or try a lower price.")); } } private static List<List<RideRow>> Kinds(IEnumerable<RideRow> rides) { return (from r in rides group r by r.Name into g select g.ToList()).ToList(); } private static string KindName(IReadOnlyList<RideRow> kind) { if (kind.Count <= 1) { return kind[0].Name; } return $"{kind[0].Name} x{kind.Count}"; } private static string KindNames(IReadOnlyList<List<RideRow>> kinds) { string text = string.Join(", ", kinds.Take(3).Select(KindName)); if (kinds.Count <= 3) { return text; } return $"{text} and {kinds.Count - 3} more"; } private static void AddWeakestNeed(ParkSnapshot park, List<Advice> advice) { NeedStat needStat = park.Needs.Where((NeedStat n) => n.Average.HasValue).MinBy((NeedStat n) => n.Average); double? num = needStat?.Average; if (!num.HasValue) { return; } double valueOrDefault = num.GetValueOrDefault(); if (!(valueOrDefault >= 0.6)) { Severity severity = ((!(valueOrDefault < 0.4)) ? Severity.Medium : Severity.High); string text = needStat.Name + " satisfaction is " + Format.Percent(valueOrDefault); if (needStat.GuestsCounted > 0) { text += $"; {needStat.GuestsLow} of {needStat.GuestsCounted} guests are under half"; } text += "."; if (needStat.Sources.Count > 0) { text = text + " In the park it is raised by: " + string.Join(", ", needStat.Sources) + "."; } if (NeedHints.TryGetValue(needStat.Name, out string value)) { text = text + " " + value; } advice.Add(new Advice(severity, "Weakest need: " + needStat.Name, text)); } } private static void AddCapacity(ParkSnapshot park, List<Advice> advice) { foreach (CapacityLine item in park.Capacity) { if (item.Max > 0 && !((double)item.Current < (double)item.Max * 0.9)) { advice.Add(new Advice(Severity.Medium, item.Name + " at capacity", $"{item.Current}/{item.Max} in use. Build more so guests are not turned away.")); } } } private static void AddTicketPrice(ParkSnapshot park, List<Advice> advice) { double? ticketPrice = park.TicketPrice; if (!ticketPrice.HasValue) { return; } double valueOrDefault = ticketPrice.GetValueOrDefault(); ticketPrice = park.TargetTicketPrice; if (!ticketPrice.HasValue) { return; } double valueOrDefault2 = ticketPrice.GetValueOrDefault(); if (!(valueOrDefault2 <= 0.0)) { if (valueOrDefault < valueOrDefault2 * 0.9) { advice.Add(new Advice(Severity.Medium, "Raise ticket price", $"Ticket price is {Format.Money(valueOrDefault)}, the park supports {Format.Money(valueOrDefault2)}.")); } else if (valueOrDefault > valueOrDefault2 * 1.1) { advice.Add(new Advice(Severity.Medium, "Lower ticket price", $"Ticket price is {Format.Money(valueOrDefault)}, above the {Format.Money(valueOrDefault2)} the park supports. Guests may complain or leave.")); } } } private static void AddRidePricing(ParkSnapshot park, List<Advice> advice) { List<RideRow> source = park.Rides.Where((RideRow r) => r.Price.HasValue && r.IdealPrice > 0.0).ToList(); List<RideRow> list = source.Where((RideRow r) => r.Price < r.IdealPrice * 0.8).ToList(); if (list.Count > 0) { advice.Add(new Advice(Severity.Low, "Underpriced rides", Names(list, PriceLabel) + ": raising prices adds income.")); } List<RideRow> list2 = source.Where((RideRow r) => r.Price > r.IdealPrice * 1.2).ToList(); if (list2.Count > 0) { advice.Add(new Advice(Severity.Low, "Overpriced rides", Names(list2, PriceLabel) + ": guests may skip them.")); } } private static void AddSatisfactionMultiplier(ParkSnapshot park, List<Advice> advice) { double? visitorMultiplier = park.VisitorMultiplier; if (!visitorMultiplier.HasValue) { return; } double valueOrDefault = visitorMultiplier.GetValueOrDefault(); visitorMultiplier = park.BestVisitorMultiplier; if (!visitorMultiplier.HasValue) { return; } double valueOrDefault2 = visitorMultiplier.GetValueOrDefault(); if (!(valueOrDefault >= valueOrDefault2 - 0.005)) { visitorMultiplier = park.RecentSatisfaction; object obj; if (visitorMultiplier.HasValue) { double valueOrDefault3 = visitorMultiplier.GetValueOrDefault(); obj = "Recent satisfaction of " + Format.Percent(valueOrDefault3); } else { obj = "Recent satisfaction"; } string value = (string)obj; string detail = $"{value} puts the game's visitor multiplier at {Format.Multiplier(valueOrDefault)}. The best is {Format.Multiplier(valueOrDefault2)}. Raising the weakest needs lifts it."; advice.Add((valueOrDefault < 1.0) ? new Advice(Severity.Medium, "Low satisfaction is costing visitors", detail) : new Advice(Severity.Low, "Higher satisfaction brings more visitors", detail)); } } private static void AddVisitorRoom(ParkSnapshot park, List<Advice> advice) { int? attractionCapacity = park.AttractionCapacity; if (!attractionCapacity.HasValue) { return; } int valueOrDefault = attractionCapacity.GetValueOrDefault(); PrestigeInfo prestige = park.Prestige; if ((object)prestige == null) { return; } attractionCapacity = prestige.MaxVisitors; if (!attractionCapacity.HasValue) { return; } int valueOrDefault2 = attractionCapacity.GetValueOrDefault(); if (valueOrDefault < valueOrDefault2) { advice.Add(new Advice(Severity.Medium, "Attractions limit your visitors", $"Your open attractions hold {valueOrDefault} visitors; prestige {prestige.Level} allows {valueOrDefault2}. {valueOrDefault2 - valueOrDefault} more places would fill it.{MostRoom(park.BuildOptions)}")); return; } attractionCapacity = prestige.NextLevelMaxVisitors; if (attractionCapacity.HasValue) { int valueOrDefault3 = attractionCapacity.GetValueOrDefault(); if (valueOrDefault < valueOrDefault3) { advice.Add(new Advice(Severity.Low, $"Build room for prestige {prestige.Level + 1}", $"Prestige {prestige.Level + 1} allows {valueOrDefault3} visitors; your attractions hold {valueOrDefault}. {valueOrDefault3 - valueOrDefault} more places are needed to use it.{MostRoom(park.BuildOptions)}")); } } } private static void AddBuildMore(ParkSnapshot park, List<Advice> advice) { IReadOnlyList<KindEconomics> source = AttractionEconomics.For(park); List<KindEconomics> list = (from k in source where k.Busy && k.PaybackDays.HasValue orderby k.PaybackDays select k).Take(3).ToList(); if (list.Count > 0) { IEnumerable<string> values = list.Select(delegate(KindEconomics k) { int num = (int)Math.Ceiling(k.PaybackDays.Value); return $"{k.Name}: nets about {Format.Money(k.NetEach)} a day each and guests are waiting for it. {Format.Money(k.BuildPrice)} to build, paid back in about {num} day{((num == 1) ? "" : "s")}"; }); advice.Add(new Advice(Severity.Medium, "Worth building another", string.Join(". ", values) + ".")); } List<KindEconomics> list2 = (from k in source where k.NetEach < 0.0 orderby k.NetEach select k).Take(3).ToList(); if (list2.Count > 0) { IEnumerable<string> values2 = list2.Select((KindEconomics k) => $"{k.Name}: earned {Format.Money(k.EarnedEach)} a day each against {Format.Money(k.UpkeepEach)} upkeep"); advice.Add(new Advice(Severity.Low, "Earning less than their upkeep", string.Join("; ", values2) + ". They may still be worth keeping for the needs they serve.")); } } private static string MostRoom(IReadOnlyList<BuildOption> options) { List<BuildOption> list = options.Where((BuildOption o) => o.LockedBy == null && o.Capacity > 0).ToList(); if (list.Count == 0) { return ""; } int largest = list.Max((BuildOption o) => o.Capacity); IEnumerable<string> values = from o in (from o in list where o.Capacity * 2 >= largest orderby o.Capacity descending, o.Tier descending select o).Take(3) select $"{o.Name} (+{o.Capacity})"; return " Most room per build: " + string.Join(", ", values) + "."; } private static void AddNeedDemand(ParkSnapshot park, List<Advice> advice) { foreach (NeedDemand item in Demand.ByNeed(park)) { DemandVerdict verdict = item.Verdict; if ((uint)(verdict - 3) > 1u) { continue; } string need = item.Need; string text = $"{item.UsersNow}/{item.Capacity} places in use"; if (item.Waiting > 0) { text += $", {item.Waiting} waiting"; } text += "."; IGrouping<string, RideRow> grouping = (from r in park.Rides where r.Raises.Contains(need) && r.IsOpen && !r.IsBroken group r by r.Name into g orderby g.Sum((RideRow r) => r.QueueLength.GetValueOrDefault()) descending, (double)g.Sum((RideRow r) => r.UsersNow.GetValueOrDefault()) / (double)Math.Max(1, g.Sum((RideRow r) => r.Capacity.GetValueOrDefault())) descending select g).FirstOrDefault(); if (grouping != null) { RideRow rideRow = grouping.First(); int num = grouping.Sum((RideRow r) => r.QueueLength.GetValueOrDefault()); text = text + $" Busiest: {grouping.Key}{Demand.Service(rideRow.Staffed)} ({grouping.Sum((RideRow r) => r.UsersNow.GetValueOrDefault())}/{grouping.Sum((RideRow r) => r.Capacity.GetValueOrDefault())} in use" + ((num > 0) ? $", {num} waiting)." : ")."); } BuildOption buildOption = Demand.BestUnlocked(park.BuildOptions, (BuildOption o) => o.Raises.Contains(need) && o.Category != "Food and drink"); if ((object)buildOption != null) { text += $" Best unlocked for it: {buildOption.Name} ({buildOption.Capacity} at once)."; } List<string> list = (from o in (from o in park.BuildOptions where o.Raises.Contains(need) && o.Category == "Food and drink" && o.LockedBy == null && Demand.NotOwned(park, o) orderby o.Tier descending select o).Take(2) select o.Name + Demand.Service(o.Staffed)).ToList(); if (list.Count > 0) { text = text + " Not built yet for it: " + string.Join(", ", list) + "."; } advice.Add(new Advice(Severity.Medium, "Build more for " + need, text)); } } private static void AddUpgrades(ParkSnapshot park, List<Advice> advice) { IReadOnlyList<(string, int, BuildOption)> readOnlyList = Demand.Upgrades(park); if (readOnlyList.Count == 0) { return; } IEnumerable<string> values = readOnlyList.Take(3).Select<(string, int, BuildOption), string>(delegate((string Owned, int Count, BuildOption Better) u) { object obj; if (u.Count <= 1) { (obj, _, _) = u; } else { obj = $"{u.Owned} x{u.Count}"; } return (string?)obj + ": " + u.Better.Name + " is unlocked"; }); advice.Add(new Advice(Severity.Low, "Better versions unlocked", string.Join("; ", values) + ". Replacing old ones as they wear out raises what each place earns.")); } private static void AddNextStar(ParkSnapshot park, List<Advice> advice) { if (string.IsNullOrWhiteSpace(park.NextStarTask)) { return; } string text = ""; PrestigeInfo prestige = park.Prestige; int? nextLevelMaxVisitors; if ((object)prestige != null) { int? maxVisitors = prestige.MaxVisitors; if (maxVisitors.HasValue) { int valueOrDefault = maxVisitors.GetValueOrDefault(); nextLevelMaxVisitors = prestige.NextLevelMaxVisitors; if (nextLevelMaxVisitors.HasValue) { int valueOrDefault2 = nextLevelMaxVisitors.GetValueOrDefault(); text = $"Prestige {prestige.Level + 1} raises the visitor base from {valueOrDefault} to {valueOrDefault2}. "; } } } text = text + "Still to do: " + park.NextStarTask; nextLevelMaxVisitors = park.Visitors; int num; if (nextLevelMaxVisitors.HasValue) { int valueOrDefault3 = nextLevelMaxVisitors.GetValueOrDefault(); nextLevelMaxVisitors = park.MaxVisitors; if (nextLevelMaxVisitors.HasValue) { int valueOrDefault4 = nextLevelMaxVisitors.GetValueOrDefault(); if (valueOrDefault4 > 0) { num = (((double)valueOrDefault3 >= (double)valueOrDefault4 * 0.9) ? 1 : 0); goto IL_011a; } } } num = 0; goto IL_011a; IL_011a: bool flag = (byte)num != 0; advice.Add(new Advice(flag ? Severity.Medium : Severity.Low, "Next star", text)); } private static void AddDecoration(ParkSnapshot park, List<Advice> advice) { double? num = park.Prestige?.DecorationLevel; if (num.HasValue) { double valueOrDefault = num.GetValueOrDefault(); if (!(valueOrDefault >= 0.5)) { advice.Add(new Advice(Severity.Low, "Decorate the park", "Decoration is at " + Format.Percent(valueOrDefault) + ". Guests notice decoration, and it raises tips.")); } } } private static void AddStaff(ParkSnapshot park, List<Advice> advice) { int? staffCount = park.StaffCount; if (staffCount.HasValue) { int valueOrDefault = staffCount.GetValueOrDefault(); staffCount = park.StaffCapacity; if (staffCount.HasValue) { int valueOrDefault2 = staffCount.GetValueOrDefault(); if (valueOrDefault > valueOrDefault2) { int value = valueOrDefault - valueOrDefault2; string text = $"{valueOrDefault} staff for a capacity of {valueOrDefault2}."; if (park.StaffTax > 0.0) { text += $" The {value} extra cost {Format.Money(park.StaffTax)} a day in staff tax"; if (park.StaffTaxPerExtra > 0.0) { text = text + " (" + Format.Money(park.StaffTaxPerExtra) + " each)"; } text += "."; } text += $" Expand staff capacity or let {value} go."; advice.Add(new Advice(Severity.Medium, "Staff over capacity", text)); } } } double? staffSalary = park.StaffSalary; if (!staffSalary.HasValue) { return; } double valueOrDefault3 = staffSalary.GetValueOrDefault(); staffSalary = park.TypicalDayIncome; if (!staffSalary.HasValue) { return; } double valueOrDefault4 = staffSalary.GetValueOrDefault(); if (!(valueOrDefault4 <= 0.0)) { double num = valueOrDefault3 + park.StaffTax.GetValueOrDefault(); if (!(num <= valueOrDefault4 * 0.5)) { advice.Add(new Advice(Severity.Medium, "Staff cost is high", $"Staff cost {Format.Money(num)} a day is {Format.Percent(num / valueOrDefault4)} of a typical day's income ({Format.Money(valueOrDefault4)}).")); } } } private static void AddTopComplaint(ParkSnapshot park, List<Advice> advice) { CountLine countLine = park.Complaints.Where((CountLine c) => c.Count > 0).MaxBy((CountLine c) => c.Count); if ((object)countLine != null) { string text = $"\"{countLine.Label}\" ({countLine.Count} times)."; if (!string.IsNullOrWhiteSpace(countLine.Note)) { text = text + " " + countLine.Note; } advice.Add(new Advice(Severity.Low, "Top complaint", text)); } } private static string PriceLabel(RideRow ride) { return $"{ride.Name} ({Format.Money(ride.Price)}, ideal {Format.Money(ride.IdealPrice)})"; } private static string Names(IReadOnlyList<RideRow> rides, Func<RideRow, string>? label = null) { if (label == null) { label = (RideRow r) => r.Name; } string text = string.Join(", ", rides.Take(3).Select(label)); if (rides.Count <= 3) { return text; } return $"{text} and {rides.Count - 3} more"; } } public sealed record KindEconomics { public string Name { get; init; } = ""; public int Count { get; init; } public double EarnedEach { get; init; } public double UpkeepEach { get; init; } public bool FullDay { get; init; } public double? NetEach { get; init; } public int? UsersNow { get; init; } public int? Capacity { get; init; } public int Waiting { get; init; } public bool Busy { get; init; } public double? BuildPrice { get; init; } public double? PaybackDays { get; init; } } public static class AttractionEconomics { private const double NearlyFull = 0.8; public static IReadOnlyList<KindEconomics> For(ParkSnapshot park) { List<KindEconomics> list = new List<KindEconomics>(); foreach (IGrouping<string, RideRow> item in from r in park.Rides group r by r.Name) { List<RideRow> list2 = item.ToList(); double num = list2.Average((RideRow r) => r.MaintenancePerDay.GetValueOrDefault()); if (list2.Any((RideRow r) => r.Price > 0.0) || !(num <= 0.0)) { AttractionDay value = null; park.YesterdayAttractions?.TryGetValue(item.Key, out value); bool flag = (object)value != null && value.Count > 0; double num2 = (flag ? (value.Earned / (double)value.Count) : (list2.Sum((RideRow r) => r.Earned) / (double)list2.Count)); double? num3 = (flag ? new double?(num2 - num) : ((double?)null)); double? num4 = list2.Select((RideRow r) => r.BuildPrice).FirstOrDefault((double? p) => p.HasValue); int? num5 = (list2.Any((RideRow r) => r.UsersNow.HasValue) ? new int?(list2.Sum((RideRow r) => r.UsersNow.GetValueOrDefault())) : ((int?)null)); int? num6 = (list2.Any((RideRow r) => r.Capacity.HasValue) ? new int?(list2.Sum((RideRow r) => r.Capacity.GetValueOrDefault())) : ((int?)null)); int num7 = list2.Sum((RideRow r) => r.QueueLength.GetValueOrDefault()); list.Add(new KindEconomics { Name = item.Key, Count = list2.Count, EarnedEach = num2, UpkeepEach = num, FullDay = flag, NetEach = num3, UsersNow = num5, Capacity = num6, Waiting = num7, Busy = (num7 >= list2.Count || (num6 > 0 && (double?)num5 >= (double?)num6 * 0.8)), BuildPrice = num4, PaybackDays = ((!(num3 > 0.0) || !(num4 > 0.0)) ? ((double?)null) : (num4 / num3)) }); } } return (from k in list orderby k.NetEach ?? double.MinValue descending, k.EarnedEach descending select k).ToList(); } } public static class ComplaintHints { private const int PlacesShown = 3; private static readonly Dictionary<string, string> Hints = new Dictionary<string, string> { ["PoorService"] = "A guest in a sauna or hot tub asked for different settings and did not get them. Set what they ask for on its control panel.", ["WaitedTooLong"] = "They gave up waiting to be served. The stand or desk needs more or faster staff.", ["Waiting"] = "They are waiting to be served.", ["Tired"] = "No free lounger when they needed rest.", ["SleptOnFloor"] = "They slept on the floor for lack of a free lounger.", ["LowTrash"] = "They could not find a trash bin in time.", ["LowToilet"] = "They could not reach a toilet in time.", ["LowHygeine"] = "They could not get to a shower.", ["Hungry"] = "They found nothing to eat.", ["Thirsty"] = "They found nothing to drink.", ["Bored"] = "Not enough fun attractions for them.", ["UsedDirtyAttraction"] = "An attraction they used was dirty. It needs cleaning.", ["UsedWornAttraction"] = "An attraction they used was worn. It needs a repair.", ["WaterWasDirty"] = "The pool water was dirty.", ["PoolWasDrained"] = "A pool was drained while they wanted to swim.", ["BadDecorationLevel"] = "The park has too little decoration.", ["Drowning"] = "A guest was drowning. Lifeguards prevent this.", ["Slipped"] = "They slipped on a wet floor.", ["Injured"] = "They were injured on an attraction.", ["Panic"] = "They panicked on an attraction.", ["PhotographedByPervert"] = "A visitor is taking unwanted photos. Catch them.", ["SawVomit"] = "Someone was sick nearby.", ["Disgust"] = "They saw something disgusting nearby." }; private static readonly HashSet<string> AboutAnAttraction = new HashSet<string> { "PoorService", "WaitedTooLong", "Waiting", "UsedDirtyAttraction", "UsedWornAttraction", "WaterWasDirty", "PoolWasDrained", "Drowning", "Injured" }; public static string? For(string thought) { if (!Hints.TryGetValue(thought, out string value)) { return null; } return value; } public static string? Note(string thought, IReadOnlyList<CountLine> places) { List<string> list = new List<string>(); string text = For(thought); if (text != null) { list.Add(text); } List<CountLine> list2 = (AboutAnAttraction.Contains(thought) ? (from p in places where p.Count > 0 orderby p.Count descending select p).Take(3).ToList() : new List<CountLine>()); if (list2.Count > 0) { list.Add("Mostly at: " + string.Join(", ", list2.Select((CountLine p) => $"{p.Label} ({p.Count})")) + "."); } if (list.Count != 0) { return string.Join(" ", list); } return null; } } public enum DemandVerdict { NoneBuilt, Plenty, Fine, NearlyFull, Short } public sealed record NeedDemand { public string Need { get; init; } = ""; public int Units { get; init; } public int UsersNow { get; init; } public int Capacity { get; init; } public int Waiting { get; init; } public DemandVerdict Verdict { get; init; } } public static class Demand { public static readonly string[] Needs = new string[7] { "Fun", "Energy", "Hygiene", "Thirst", "Hunger", "Toilet", "Trash" }; public const string FoodAndDrink = "Food and drink"; private const double NearlyFullFrom = 0.8; private const double PlentyBelow = 0.3; public static IReadOnlyList<NeedDemand> ByNeed(ParkSnapshot park) { List<NeedDemand> list = new List<NeedDemand>(); string[] needs = Needs; foreach (string need in needs) { List<RideRow> list2 = park.Rides.Where((RideRow r) => r.Raises.Contains(need) && r.IsOpen && !r.IsBroken).ToList(); if (list2.Count == 0) { list.Add(new NeedDemand { Need = need, Verdict = DemandVerdict.NoneBuilt }); continue; } int num = list2.Sum((RideRow r) => r.UsersNow.GetValueOrDefault()); int num2 = list2.Sum((RideRow r) => r.Capacity.GetValueOrDefault()); int num3 = list2.Sum((RideRow r) => r.QueueLength.GetValueOrDefault()); DemandVerdict verdict = ((num3 >= list2.Count) ? DemandVerdict.Short : ((num2 > 0 && (double)num >= (double)num2 * 0.8) ? DemandVerdict.NearlyFull : ((num2 > 0 && (double)num < (double)num2 * 0.3 && num3 == 0) ? DemandVerdict.Plenty : DemandVerdict.Fine))); list.Add(new NeedDemand { Need = need, Units = list2.Count, UsersNow = num, Capacity = num2, Waiting = num3, Verdict = verdict }); } return list; } public static string Service(bool? staffed) { if (staffed.HasValue) { if (staffed == true) { return " (staffed)"; } return " (automated)"; } return ""; } public static bool? StaffedFromNames(string internalName, string displayName) { string names = internalName + " " + displayName; if (Says("Machine")) { return false; } if (Says("Stand") || Says("Stall") || Says("Cart") || Says("Kiosk")) { return true; } return null; bool Says(string word) { return names.Contains(word, StringComparison.OrdinalIgnoreCase); } } public static bool NotOwned(ParkSnapshot park, BuildOption option) { if (option.Owned == 0) { return !park.Rides.Any((RideRow r) => r.Name == option.Name); } return false; } public static string Describe(DemandVerdict verdict) { return verdict switch { DemandVerdict.NoneBuilt => "None built", DemandVerdict.Plenty => "Plenty", DemandVerdict.Fine => "Fine", DemandVerdict.NearlyFull => "Nearly full", _ => "Short", }; } public static BuildOption? BestUnlocked(IEnumerable<BuildOption> options, Func<BuildOption, bool> match) { return (from o in options where o.LockedBy == null && match(o) orderby o.Tier descending, o.Capacity descending, o.IdealPrice.GetValueOrDefault() descending select o).FirstOrDefault(); } public static IReadOnlyList<(string Owned, int Count, BuildOption Better)> Upgrades(ParkSnapshot park) { List<(string, int, BuildOption)> list = new List<(string, int, BuildOption)>(); foreach (IGrouping<string, RideRow> item in from r in park.Rides where r.Category != null && r.Category != "Food and drink" && r.Tier.HasValue group r by r.Name) { RideRow sample = item.First(); BuildOption buildOption = BestUnlocked(park.BuildOptions, (BuildOption o) => o.Category == sample.Category); if ((object)buildOption != null && buildOption.Tier > sample.Tier && buildOption.Name != item.Key) { list.Add((item.Key, item.Count(), buildOption)); } } return list.OrderByDescending(((string, int, BuildOption) u) => u.Item2).ToList(); } } public static class Format { public const string Unknown = "n/a"; public static string Money(double? amount) { if (!amount.HasValue) { return "n/a"; } double num = Math.Round(amount.Value, MidpointRounding.AwayFromZero); if (num == 0.0) { return "0"; } return num.ToString("#,0", CultureInfo.InvariantCulture); } public static string Signed(double? amount) { string text = Money(amount); if (!(amount > 0.0) || !(text != "0")) { return text; } return "+" + text; } public static string Percent(double? fraction) { if (!fraction.HasValue) { return "n/a"; } return Math.Round(fraction.Value * 100.0, MidpointRounding.AwayFromZero).ToString("0", CultureInfo.InvariantCulture) + "%"; } public static string Multiplier(double? value) { if (value.HasValue) { return "x" + value.Value.ToString("0.00", CultureInfo.InvariantCulture); } return "n/a"; } public static string Ratio(int? current, int? max) { return Count(current) + "/" + Count(max); } public static string Count(int? value) { return value?.ToString(CultureInfo.InvariantCulture) ?? "n/a"; } public static string Reason(string reason) { if (reason == "Maintance") { return "Maintenance"; } StringBuilder stringBuilder = new StringBuilder(reason.Length + 4); for (int i = 0; i < reason.Length; i++) { char c = reason[i]; if (i > 0 && char.IsUpper(c)) { stringBuilder.Append(' ').Append(char.ToLowerInvariant(c)); } else { stringBuilder.Append(c); } } return stringBuilder.ToString(); } } public sealed record DayRecord { public int Day { get; init; } public int? Visitors { get; init; } public double Income { get; init; } public double Expenses { get; init; } public double? Satisfaction { get; init; } public Dictionary<string, double> Needs { get; init; } = new Dictionary<string, double>(); public Dictionary<string, AttractionDay> Attractions { get; init; } = new Dictionary<string, AttractionDay>(); [JsonIgnore] public double Net => Income + Expenses; public static DayRecord From(ParkSnapshot park) { MoneyBreakdown moneyBreakdown = MoneyBreakdown.From(park.MoneyToday); return new DayRecord { Day = park.Day.GetValueOrDefault(), Visitors = park.VisitorsToday, Income = moneyBreakdown.TotalIncome, Expenses = moneyBreakdown.TotalExpenses, Satisfaction = (park.DaySatisfaction ?? park.Satisfaction), Needs = park.Needs.Where((NeedStat n) => n.Average.HasValue).ToDictionary((NeedStat n) => n.Name, (NeedStat n) => n.Average.Value), Attractions = (from r in park.Rides group r by r.Name).ToDictionary((IGrouping<string, RideRow> g) => g.Key, (IGrouping<string, RideRow> g) => new AttractionDay { Count = g.Count(), Uses = g.Sum((RideRow r) => r.UsesToday), Earned = g.Sum((RideRow r) => r.Earned) }) }; } } public sealed class HistoryStore { private static readonly JsonSerializerOptions JsonOptions = new JsonSerializerOptions { WriteIndented = true }; private readonly string _path; private readonly int _maxDays; private List<DayRecord> _records; public IReadOnlyList<DayRecord> Records => _records; public HistoryStore(string path, int maxDays = 60) { _path = path; _maxDays = maxDays; _records = Load(path); } public void Add(DayRecord record) { _records.RemoveAll((DayRecord r) => r.Day >= record.Day); _records.Add(record); if (_records.Count > _maxDays) { _records.RemoveRange(0, _records.Count - _maxDays); } Save(); } public void FillSatisfaction(int day, double satisfaction) { int num = _records.FindIndex((DayRecord r) => r.Day == day); if (num >= 0) { double? satisfaction2 = _records[num].Satisfaction; if (!satisfaction2.HasValue || !(satisfaction2.GetValueOrDefault() > 0.0)) { _records[num] = _records[num]with { Satisfaction = satisfaction }; Save(); } } } public double? TypicalIncome(int days = 3) { List<DayRecord> list = _records.TakeLast(days).ToList(); if (list.Count != 0) { return list.Average((DayRecord r) => r.Income); } return null; } private void Save() { string directoryName = Path.GetDirectoryName(_path); if (!string.IsNullOrEmpty(directoryName)) { Directory.CreateDirectory(directoryName); } File.WriteAllText(_path, JsonSerializer.Serialize(_records, JsonOptions)); } private static List<DayRecord> Load(string path) { if (!File.Exists(path)) { return new List<DayRecord>(); } try { return JsonSerializer.Deserialize<List<DayRecord>>(File.ReadAllText(path), JsonOptions)?.OrderBy((DayRecord r) => r.Day).ToList() ?? new List<DayRecord>(); } catch (JsonException) { return new List<DayRecord>(); } } } public sealed record MoneyLine(string Reason, double Amount); public sealed record LoanLine(string Label, double Remaining); public sealed record CountLine(string Label, int Count) { public string? Note { get; init; } } public sealed record CapacityLine(string Name, int Current, int Max); public sealed record NeedStat(string Name, double? Average, int GuestsLow, int GuestsCounted) { public IReadOnlyList<string> Sources { get; init; } = Array.Empty<string>(); } public sealed record RideRow { public string Name { get; init; } = ""; public bool IsOpen { get; init; } = true; public bool IsBroken { get; init; } public int UsesToday { get; init; } public double? Price { get; init; } public double? IdealPrice { get; init; } public double? Cleanliness { get; init; } public double? Durability { get; init; } public int? QueueLength { get; init; } public double? MaintenancePerDay { get; init; } public int? StockLeft { get; init; } public int? StockCapacity { get; init; } public string? Category { get; init; } public int? Tier { get; init; } public IReadOnlyList<string> Raises { get; init; } = Array.Empty<string>(); public bool? Staffed { get; init; } public int? UsersNow { get; init; } public int? Capacity { get; init; } public double? BuildPrice { get; init; } public bool HasOpenRequest { get; init; } public double Earned => (double)UsesToday * Price.GetValueOrDefault(); } public sealed record AttractionDay { public int Count { get; init; } public int Uses { get; init; } public double Earned { get; init; } } public sealed record BuildOption { public string Name { get; init; } = ""; public double Price { get; init; } public string? Category { get; init; } public int Tier { get; init; } public bool? Staffed { get; init; } public int Capacity { get; init; } public double? IdealPrice { get; init; } public double? MaintenancePerDay { get; init; } public IReadOnlyList<string> Raises { get; init; } = Array.Empty<string>(); public int Owned { get; init; } public string? LockedBy { get; init; } } public sealed record PrestigeInfo { public int Level { get; init; } public int? MaxVisitors { get; init; } public int? NextLevelMaxVisitors { get; init; } public double? DecorationLevel { get; init; } } public sealed record ParkSnapshot { public int? Day { get; init; } public double? Money { get; init; } public double? TotalEarned { get; init; } public double? TotalSpent { get; init; } public double? TicketPrice { get; init; } public double? TargetTicketPrice { get; init; } public IReadOnlyList<MoneyLine> MoneyToday { get; init; } = Array.Empty<MoneyLine>(); public IReadOnlyList<LoanLine> Loans { get; init; } = Array.Empty<LoanLine>(); public int? Visitors { get; init; } public int? ExpectedVisitors { get; init; } public int? MaxVisitors { get; init; } public int? VisitorsToday { get; init; } public int? RefundedVisitors { get; init; } public int? InjuredVisitors { get; init; } public double? Satisfaction { get; init; } public double? DaySatisfaction { get; init; } public double? RecentSatisfaction { get; init; } public double? AverageGuestCash { get; init; } public IReadOnlyList<NeedStat> Needs { get; init; } = Array.Empty<NeedStat>(); public IReadOnlyList<CountLine> Complaints { get; init; } = Array.Empty<CountLine>(); public IReadOnlyList<CountLine> LeavingReasons { get; init; } = Array.Empty<CountLine>(); public PrestigeInfo? Prestige { get; init; } public int? AttractionCapacity { get; init; } public IReadOnlyList<BuildOption> BuildOptions { get; init; } = Array.Empty<BuildOption>(); public IReadOnlyDictionary<string, AttractionDay>? YesterdayAttractions { get; init; } public string? NextStarTask { get; init; } public double? VisitorMultiplier { get; init; } public double? BestVisitorMultiplier { get; init; } public double? TypicalDayIncome { get; init; } public IReadOnlyList<RideRow> Rides { get; init; } = Array.Empty<RideRow>(); public IReadOnlyList<CapacityLine> Capacity { get; init; } = Array.Empty<CapacityLine>(); public double? CleanlinessThreshold { get; init; } public double? DurabilityThreshold { get; init; } public int? StaffCount { get; init; } public int? StaffCapacity { get; init; } public double? StaffSalary { get; init; } public double? StaffTax { get; init; } public double? StaffTaxPerExtra { get; init; } } public sealed class MoneyBreakdown { public IReadOnlyList<MoneyLine> Income { get; private init; } = Array.Empty<MoneyLine>(); public IReadOnlyList<MoneyLine> Expenses { get; private init; } = Array.Empty<MoneyLine>(); public double TotalIncome { get; private init; } public double TotalExpenses { get; private init; } public double Net => TotalIncome + TotalExpenses; public static MoneyBreakdown From(IEnumerable<MoneyLine> lines) { List<MoneyLine> source = (from l in lines group l by l.Reason into g select new MoneyLine(g.Key, g.Sum((MoneyLine l) => l.Amount))).ToList(); List<MoneyLine> list = (from l in source where l.Amount > 0.0 orderby l.Amount descending select l).ToList(); List<MoneyLine> list2 = (from l in source where l.Amount < 0.0 orderby l.Amount select l).ToList(); return new MoneyBreakdown { Income = list, Expenses = list2, TotalIncome = list.Sum((MoneyLine l) => l.Amount), TotalExpenses = list2.Sum((MoneyLine l) => l.Amount) }; } } public static class Normalize { private const double LargestFraction = 1.5; private static readonly HashSet<string> CostReasons = new HashSet<string> { "Building", "LoanPayment", "StaffSalary", "Maintance", "Fine", "Research", "StaffTax", "TicketRefund" }; public static double? Fraction(double? raw) { if (raw.HasValue) { double num = raw.GetValueOrDefault(); if (num > 1.5) { num /= 100.0; } return Math.Clamp(num, 0.0, 1.0); } return null; } public static IReadOnlyList<MoneyLine> Signs(IReadOnlyList<MoneyLine> lines) { if (lines.Any((MoneyLine l) => l.Amount < 0.0)) { return lines; } return lines.Select((MoneyLine l) => (!CostReasons.Contains(l.Reason)) ? l : l with { Amount = 0.0 - l.Amount }).ToList(); } public static IReadOnlyList<CountLine> Since(IReadOnlyDictionary<string, int> baseline, IReadOnlyDictionary<string, int> current) { List<CountLine> list = new List<CountLine>(); foreach (KeyValuePair<string, int> item in current) { item.Deconstruct(out var key, out var value); string text = key; int num = value; int value2; int num2 = (baseline.TryGetValue(text, out value2) ? value2 : 0); int num3 = ((num >= num2) ? (num - num2) : num); if (num3 > 0) { list.Add(new CountLine(text, num3)); } } return list; } } public enum RowKind { Header, Normal, Good, Bad, Muted } public sealed record Row(RowKind Kind, IReadOnlyList<string> Cells) { public string Text => string.Join(" | ", Cells); public static Row Of(RowKind kind, params string[] cells) { return new Row(kind, cells); } } public static class Pages { private const string NothingToFix = "Nothing needs fixing right now"; private const string Blank = "-"; private const double NeedGoodFrom = 0.7; private const double NeedBadBelow = 0.4; private const int ComplaintsShown = 5; private const int OverviewAdviceShown = 3; private const int LongestRideName = 20; private const int EarnersShown = 8; private const int BuildOptionsShown = 6; private const int EconomicsShown = 12; private const double UnlimitedCashFrom = 500000.0; public static IReadOnlyList<Row> Overview(ParkSnapshot park, IReadOnlyList<Advice> advice) { List<Row> list = new List<Row>(); list.Add(Row.Of(RowKind.Header, "Today")); List<Row> list2 = list; double net = MoneyBreakdown.From(park.MoneyToday).Net; list2.Add(Row.Of(SignKind(net), "Net today", Format.Signed(net))); AddIfKnown(list2, "Balance", park.Money); if (park.Visitors.HasValue) { string text = ((!park.MaxVisitors.HasValue) ? Format.Count(park.Visitors) : Format.Ratio(park.Visitors, park.MaxVisitors)); int? expectedVisitors = park.ExpectedVisitors; if (expectedVisitors.HasValue) { int valueOrDefault = expectedVisitors.GetValueOrDefault(); if (valueOrDefault != park.MaxVisitors) { text += $" (expected {valueOrDefault})"; } } list2.Add(Row.Of(RowKind.Normal, "Visitors", text)); } if (park.Satisfaction.HasValue) { list2.Add(Row.Of(RowKind.Normal, "Satisfaction", Format.Percent(park.Satisfaction))); } PrestigeInfo prestige = park.Prestige; if ((object)prestige != null) { string text2 = Format.Count(prestige.Level); int? expectedVisitors = prestige.MaxVisitors; if (expectedVisitors.HasValue) { int valueOrDefault2 = expectedVisitors.GetValueOrDefault(); text2 += $" (allows {valueOrDefault2} visitors)"; } list2.Add(Row.Of(RowKind.Normal, "Prestige", text2)); if (prestige.DecorationLevel.HasValue) { list2.Add(Row.Of(RowKind.Normal, "Decoration", Format.Percent(prestige.DecorationLevel))); } } list2.Add(Row.Of(RowKind.Header, "Do next")); list2.AddRange(Advisor(advice.Take(3).ToList())); return list2; } public static IReadOnlyList<Row> Money(ParkSnapshot park) { MoneyBreakdown moneyBreakdown = MoneyBreakdown.From(park.MoneyToday); List<Row> list = new List<Row>(); list.Add(Row.Of(RowKind.Header, "Today", Format.Signed(moneyBreakdown.Net))); List<Row> list2 = list; if (moneyBreakdown.Income.Count == 0 && moneyBreakdown.Expenses.Count == 0) { list2.Add(Row.Of(RowKind.Muted, "No money movements today yet")); } else { foreach (MoneyLine item in moneyBreakdown.Income) { list2.Add(Row.Of(RowKind.Good, Format.Reason(item.Reason), Format.Signed(item.Amount))); } foreach (MoneyLine expense in moneyBreakdown.Expenses) { list2.Add(Row.Of(RowKind.Bad, Format.Reason(expense.Reason), Format.Signed(expense.Amount))); } list2.Add(Row.Of(RowKind.Normal, "Income", Format.Signed(moneyBreakdown.TotalIncome))); list2.Add(Row.Of(RowKind.Normal, "Expenses", Format.Signed(moneyBreakdown.TotalExpenses))); } if (park.VisitorsToday > 0) { list2.Add(Row.Of(RowKind.Normal, "Visitors today", Format.Count(park.VisitorsToday))); list2.Add(Row.Of(RowKind.Normal, "Income per visitor", Format.Money(moneyBreakdown.TotalIncome / (double)park.VisitorsToday.Value))); } List<(string, int, double)> list3 = (from r in park.Rides group r by r.Name into g select (Name: g.Key, Count: g.Count(), Earned: g.Sum((RideRow r) => r.Earned)) into k where k.Earned > 0.0 orderby k.Earned descending select k).Take(8).ToList(); if (list3.Count > 0) { list2.Add(Row.Of(RowKind.Header, "Earned by attraction (uses x price)")); foreach (var item2 in list3) { list2.Add(Row.Of(RowKind.Normal, KindLabel(item2.Item1, item2.Item2), Format.Money(item2.Item3))); } } list2.Add(Row.Of(RowKind.Header, "Park")); AddIfKnown(list2, "Balance", park.Money); if (park.TicketPrice.HasValue) { string text = Format.Money(park.TicketPrice); if (park.TargetTicketPrice.HasValue) { text = text + " (target " + Format.Money(park.TargetTicketPrice) + ")"; } list2.Add(Row.Of(RowKind.Normal, "Ticket price", text)); } AddIfKnown(list2, "Lifetime earned", park.TotalEarned); AddIfKnown(list2, "Lifetime spent", park.TotalSpent); if (park.StaffSalary > 0.0) { AddIfKnown(list2, "Staff salaries per day", park.StaffSalary); } if (park.StaffTax > 0.0) { AddIfKnown(list2, "Staff tax", park.StaffTax); } if (park.Loans.Count > 0) { list2.Add(Row.Of(RowKind.Header, "Loans")); foreach (LoanLine loan in park.Loans) { list2.Add(Row.Of(RowKind.Normal, loan.Label, Format.Money(loan.Remaining))); } } return list2; } public static IReadOnlyList<Row> Rides(ParkSnapshot park) { List<Row> list = new List<Row>(); if (park.Rides.Count == 0) { list.Add(Row.Of(RowKind.Muted, "No attractions built yet")); } else { list.Add(Row.Of(RowKind.Header, "Attraction", "State", "Uses", "Price", "Clean", "Durab.", "Wait")); foreach (List<RideRow> item in (from r in park.Rides group r by r.Name into g select g.ToList() into g orderby g.Sum((RideRow r) => r.UsesToday) descending select g).ThenBy<List<RideRow>, string>((List<RideRow> g) => g[0].Name, StringComparer.Ordinal)) { list.Add(RideLine(item)); } List<RideRow> list2 = park.Rides.Where((RideRow r) => r.MaintenancePerDay.HasValue).ToList(); if (list2.Count > 0) { list.Add(Row.Of(RowKind.Normal, "Maintenance per day", Format.Money(list2.Sum((RideRow r) => r.MaintenancePerDay.Value)))); } } if (park.Capacity.Count > 0) { list.Add(Row.Of(RowKind.Header, "Capacity in use")); foreach (CapacityLine item2 in park.Capacity) { list.Add(Row.Of(RowKind.Normal, item2.Name, Format.Ratio(item2.Current, item2.Max))); } } return list; } public static IReadOnlyList<Row> Build(ParkSnapshot park) { List<Row> list = new List<Row>(); if (park.Rides.Any((RideRow r) => r.Raises.Count > 0)) { list.Add(Row.Of(RowKind.Header, "Demand now", "In use", "Waiting", "Verdict")); foreach (NeedDemand item in Demand.ByNeed(park)) { if (item.Verdict == DemandVerdict.NoneBuilt) { list.Add(Row.Of(RowKind.Muted, item.Need, "-", "-", Demand.Describe(item.Verdict))); continue; } DemandVerdict verdict = item.Verdict; bool flag = (uint)(verdict - 3) <= 1u; RowKind kind = ((!flag) ? RowKind.Normal : RowKind.Bad); list.Add(Row.Of(kind, item.Need, $"{item.UsersNow}/{item.Capacity}", Format.Count(item.Waiting), Demand.Describe(item.Verdict))); } } IReadOnlyList<KindEconomics> readOnlyList = AttractionEconomics.For(park); if (readOnlyList.Count > 0) { string text = (readOnlyList.Any((KindEconomics k) => k.FullDay) ? "yesterday" : "today so far"); list.Add(Row.Of(RowKind.Header, "Each one, " + text, "Earned", "Upkeep", "Net", "Busy")); foreach (KindEconomics item2 in readOnlyList.Take(12)) { RowKind kind2 = ((item2.NetEach < 0.0) ? RowKind.Bad : ((!item2.Busy || !(item2.NetEach > 0.0)) ? RowKind.Normal : RowKind.Good)); list.Add(Row.Of(kind2, KindLabel(item2.Name, item2.Count), Format.Money(item2.EarnedEach), Format.Money(item2.UpkeepEach), (!item2.NetEach.HasValue) ? "-" : Format.Signed(item2.NetEach), BusyCell(item2))); } } IOrderedEnumerable<string> orderedEnumerable = (from c in park.BuildOptions.Select((BuildOption o) => o.Category).OfType<string>() where c != "Food and drink" select c).Distinct().OrderBy<string, string>((string c) => c, StringComparer.Ordinal); List<Row> list2 = new List<Row>(); foreach (string category in orderedEnumerable) { BuildOption buildOption = Demand.BestUnlocked(park.BuildOptions, (BuildOption o) => o.Category == category); if ((object)buildOption != null) { bool flag2 = park.Rides.Any((RideRow r) => r.Category == category); list2.Add(Row.Of(RowKind.Normal, category + ": " + buildOption.Name + (flag2 ? "" : " (none built)"))); } } if (list2.Count > 0) { list.Add(Row.Of(RowKind.Header, "Best you can build")); list.AddRange(list2); } List<BuildOption> list3 = (from o in park.BuildOptions where o.Category == "Food and drink" && o.LockedBy == null && Demand.NotOwned(park, o) where o.Capacity > 0 && o.Raises.Count > 0 orderby o.Tier descending select o).Take(6).ToList(); if (list3.Count > 0) { list.Add(Row.Of(RowKind.Header, "Food and drink not built yet")); foreach (BuildOption item3 in list3) { string text2 = ((item3.Raises.Count > 0) ? (", " + string.Join(", ", item3.Raises)) : ""); list.Add(Row.Of(RowKind.Normal, item3.Name + Demand.Service(item3.Staffed) + text2)); } } IReadOnlyList<(string, int, BuildOption)> readOnlyList2 = Demand.Upgrades(park); if (readOnlyList2.Count > 0) { list.Add(Row.Of(RowKind.Header, "Better versions unlocked")); foreach (var (text3, num, buildOption2) in readOnlyList2) { list.Add(Row.Of(RowKind.Normal, ((num > 1) ? $"{text3} x{num}" : text3) + ": " + buildOption2.Name)); } } if (park.AttractionCapacity.HasValue || (park.Prestige?.MaxVisitors).HasValue) { list.Add(Row.Of(RowKind.Header, "Visitor limit")); AddIfKnown(list, "Your attractions allow", park.AttractionCapacity); PrestigeInfo prestige = park.Prestige; if ((object)prestige != null) { AddIfKnown(list, $"Prestige {prestige.Level} caps it at", prestige.MaxVisitors); AddIfKnown(list, $"Prestige {prestige.Level + 1} caps it at", prestige.NextLevelMaxVisitors); } } List<BuildOption> list4 = (from o in park.BuildOptions where o.LockedBy != null && o.Capacity > 0 group o by o.Name into g select (from o in g orderby o.Tier, o.Price select o).First() into o orderby o.Tier, o.Price select o).Take(6).ToList(); if (list4.Count > 0) { list.Add(Row.Of(RowKind.Header, "Locked")); foreach (BuildOption item4 in list4) { list.Add(Row.Of(RowKind.Normal, ShortName(item4.Name), item4.LockedBy)); } } if (list.Count == 0) { list.Add(Row.Of(RowKind.Muted, "No building data available")); } return list; } public static IReadOnlyList<Row> Guests(ParkSnapshot park) { List<Row> list = new List<Row>(); if (park.Needs.Count > 0) { list.Add(Row.Of(RowKind.Header, "Need", "Average", "Under 50%")); foreach (NeedStat need in park.Needs) { string text = ((need.GuestsCounted > 0) ? $"{need.GuestsLow}/{need.GuestsCounted}" : "-"); list.Add(Row.Of(NeedKind(need.Average), need.Name, Format.Percent(need.Average), text)); } } List<Row> list2 = new List<Row>(); if (park.Visitors.HasValue) { list2.Add(Row.Of(RowKind.Normal, "In park", (!park.MaxVisitors.HasValue) ? Format.Count(park.Visitors) : Format.Ratio(park.Visitors, park.MaxVisitors))); } if (park.ExpectedVisitors != park.MaxVisitors) { AddIfKnown(list2, "Expected", park.ExpectedVisitors); } if (park.AverageGuestCash < 500000.0) { AddIfKnown(list2, "Average cash", park.AverageGuestCash); } AddIfKnown(list2, "Refunded", park.RefundedVisitors); AddIfKnown(list2, "Injured", park.InjuredVisitors); if (list2.Count > 0) { list.Add(Row.Of(RowKind.Header, "Guests")); list.AddRange(list2); } List<NeedStat> list3 = park.Needs.Where((NeedStat n) => n.Sources.Count > 0).ToList(); if (list3.Count > 0) { list.Add(Row.Of(RowKind.Header, "Raised by")); foreach (NeedStat item in list3) { list.Add(Row.Of(RowKind.Normal, item.Name + ": " + string.Join(", ", item.Sources))); } } if (park.VisitorMultiplier.HasValue) { list.Add(Row.Of(RowKind.Header, "What satisfaction buys")); if (park.RecentSatisfaction.HasValue) { list.Add(Row.Of(RowKind.Normal, "Recent satisfaction", Format.Percent(park.RecentSatisfaction))); } double? visitorMultiplier = park.VisitorMultiplier; if (visitorMultiplier.HasValue) { double valueOrDefault = visitorMultiplier.GetValueOrDefault(); string text2 = Format.Multiplier(valueOrDefault); visitorMultiplier = park.BestVisitorMultiplier; if (visitorMultiplier.HasValue) { double valueOrDefault2 = visitorMultiplier.GetValueOrDefault(); if (valueOrDefault2 > valueOrDefault) { text2 = text2 + " (" + Format.Multiplier(valueOrDefault2) + " at best)"; } } list.Add(Row.Of(RowKind.Normal, "Visitor multiplier", text2)); } } AddCounts(list, "Top complaints", park.Complaints, 5); AddCounts(list, "Leaving because", park.LeavingReasons, int.MaxValue); if (list.Count == 0) { list.Add(Row.Of(RowKind.Muted, "No guest data available")); } return list; } public static IReadOnlyList<Row> History(IReadOnlyList<DayRecord> days) { if (days.Count == 0) { return new Row[1] { Row.Of(RowKind.Muted, "History starts after the first full day") }; } List<Row> list = new List<Row>(); list.Add(Row.Of(RowKind.Header, "Day", "Visitors", "Net", "Satisfaction")); List<Row> list2 = list; foreach (DayRecord item in days.OrderByDescending((DayRecord d) => d.Day)) { double? fraction = ((item.Satisfaction > 0.0) ? item.Satisfaction : ((double?)null)); list2.Add(Row.Of(SignKind(item.Net), Format.Count(item.Day), Format.Count(item.Visitors), Format.Signed(item.Net), Format.Percent(fraction))); } return list2; } public static IReadOnlyList<Row> Advisor(IReadOnlyList<Advice> advice) { if (advice.Count == 0) { return new Row[1] { Row.Of(RowKind.Good, "Nothing needs fixing right now") }; } List<Row> list = new List<Row>(); foreach (Advice item in advice) { if (list.Count > 0) { list.Add(Row.Of(RowKind.Normal, "")); } list.Add(Row.Of(TitleKind(item), item.Title)); list.Add(Row.Of(RowKind.Normal, item.Detail)); } return list; } private static Row RideLine(IReadOnlyList<RideRow> rides) { int count = rides.Count; int num = rides.Count((RideRow r) => r.IsBroken); int num2 = rides.Count((RideRow r) => r.IsOpen); int num3 = rides.Count((RideRow r) => r.HasOpenRequest); RowKind kind; string text; if (num <= 0) { if (num3 <= 0) { if (num2 != 0) { (kind, text) = ((num2 == count) ? (RowKind.Normal, "Open") : (RowKind.Normal, $"{num2}/{count} open")); } else { kind = RowKind.Muted; text = "Closed"; } } else { string text2 = ((count == 1) ? "Request" : ((num3 == 1) ? "1 request" : $"{num3} requests")); text = text2; kind = RowKind.Bad; } } else { string text2 = ((count == 1) ? "Broken" : $"{num} broken"); text = text2; kind = RowKind.Bad; } string text3 = KindLabel(rides[0].Name, count); List<RideRow> list = rides.Where((RideRow r) => r.QueueLength.HasValue).ToList(); return Row.Of(kind, text3, text, Format.Count(rides.Sum((RideRow r) => r.UsesToday)), PriceCell(rides), Format.Percent(rides.Min((RideRow r) => r.Cleanliness)), Format.Percent(rides.Min((RideRow r) => r.Durability)), (list.Count == 0) ? "-" : Format.Count(list.Sum((RideRow r) => r.QueueLength.Value))); } private static string BusyCell(KindEconomics kind) { string text = ((kind.Waiting > 0) ? $"+{kind.Waiting}" : ""); if (!kind.UsersNow.HasValue || !kind.Capacity.HasValue) { if (text.Length <= 0) { return "-"; } return text; } if (text.Length > 0) { return $"{kind.UsersNow}/{kind.Capacity} {text}"; } return $"{kind.UsersNow}/{kind.Capacity}"; } private static string KindLabel(string name, int count) { name = ShortName(name); if (count <= 1) { return name; } return $"{name} x{count}"; } private static string ShortName(string name) { if (name.Length <= 20) { return name; } return name.Substring(0, 18) + ".."; } private static string PriceCell(IReadOnlyList<RideRow> rides) { List<RideRow> list = rides.Where((RideRow r) => r.Price.HasValue).ToList(); if (list.Count == 0) { return "-"; } double? num = list.Min((RideRow r) => r.Price); double? num2 = list.Max((RideRow r) => r.Price); if (num != num2) { return Format.Money(num) + "-" + Format.Money(num2); } return Format.Money(num); } private static void AddCounts(List<Row> rows, string header, IEnumerable<CountLine> lines, int limit) { List<CountLine> list = (from l in lines where l.Count > 0 orderby l.Count descending select l).Take(limit).ToList(); if (list.Count == 0) { return; } rows.Add(Row.Of(RowKind.Header, header)); foreach (CountLine item in list) { rows.Add(Row.Of(RowKind.Normal, item.Label, Format.Count(item.Count))); if (!string.IsNullOrWhiteSpace(item.Note)) { rows.Add(Row.Of(RowKind.Normal, item.Note)); } } } private static void AddIfKnown(List<Row> rows, string label, double? amount) { if (amount.HasValue) { rows.Add(Row.Of(RowKind.Normal, label, Format.Money(amount))); } } private static void AddIfKnown(List<Row> rows, string label, int? count) { if (count.HasValue) { rows.Add(Row.Of(RowKind.Normal, label, Format.Count(count))); } } private static RowKind SignKind(double amount) { if (!(amount > 0.0)) { if (!(amount < 0.0)) { return RowKind.Normal; } return RowKind.Bad; } return RowKind.Good; } private static RowKind NeedKind(double? average) { if (average.HasValue) { double valueOrDefault = average.GetValueOrDefault(); if (!(valueOrDefault < 0.4)) { if (valueOrDefault >= 0.7) { return RowKind.Good; } return RowKind.Normal; } return RowKind.Bad; } return RowKind.Muted; } private static RowKind TitleKind(Advice advice) { if (advice.Severity != Severity.High) { return RowKind.Header; } return RowKind.Bad; } } public static class TmpMarkup { private static readonly Dictionary<int, int[]> Columns = new Dictionary<int, int[]> { [2] = new int[1] { 62 }, [3] = new int[2] { 50, 75 }, [4] = new int[3] { 30, 52, 72 }, [5] = new int[4] { 40, 55, 69, 83 }, [7] = new int[6] { 34, 48, 57, 68, 79, 91 } }; private const int WideTable = 7; private const string SmallOpen = "<size=80%>"; private const string SmallClose = "</size>"; private static readonly Regex Tag = new Regex("<[^<>]*>", RegexOptions.Compiled); public static string Render(IReadOnlyList<Row> rows) { StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < rows.Count; i++) { Row row = rows[i]; if (i > 0) { stringBuilder.Append('\n'); Row row2 = rows[i - 1]; if (row.Kind == RowKind.Header && !IsBlank(row2) && row2.Kind != RowKind.Header) { stringBuilder.Append('\n'); } } (string Open, string Close) tuple = Wrap(row.Kind); string item = tuple.Open; string item2 = tuple.Close; bool flag = row.Cells.Count >= 7; if (flag) { stringBuilder.Append("<size=80%>"); } stringBuilder.Append(item); for (int j = 0; j < row.Cells.Count; j++) { if (j > 0) { stringBuilder.Append("<pos=").Append(ColumnStart(row.Cells.Count, j)).Append("%>"); } stringBuilder.Append(Plain(row.Cells[j])); } stringBuilder.Append(item2); if (flag) { stringBuilder.Append("</size>"); } } return stringBuilder.ToString(); } private static bool IsBlank(Row row) { return row.Cells.All(string.IsNullOrEmpty); } private static int ColumnStart(int cellCount, int cell) { if (!Columns.TryGetValue(cellCount, out int[] value)) { return cell * 100 / cellCount; } return value[cell - 1]; } private static (string Open, string Close) Wrap(RowKind kind) { return kind switch { RowKind.Header => (Open: "<color=#F2A900><b>", Close: "</b></color>"), RowKind.Good => (Open: "<color=#1FAF5A>", Close: "</color>"), RowKind.Bad => (Open: "<color=#E0433F>", Close: "</color>"), _ => (Open: "", Close: ""), }; } private static string Plain(string cell) { return Tag.Replace(cell, "").Replace("<", "").Replace(">", ""); } } }
plugins/ParkStats.Plugin.dll
Decompiled 40 minutes ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Text; using Assets.Scripts.Attractions; using Assets.Scripts.Systems.NetTypes; using BepInEx; using BepInEx.Configuration; using BepInEx.Core.Logging.Interpolation; using BepInEx.Logging; using BepInEx.Unity.IL2CPP; using CayplayAI; using CayplayMissions; using HarmonyLib; using I2.Loc; using Il2CppInterop.Runtime; using Il2CppInterop.Runtime.InteropTypes; using Il2CppInterop.Runtime.InteropTypes.Arrays; using Il2CppSystem; using Il2CppSystem.Collections.Generic; using Il2CppSystem.Reflection; using Microsoft.CodeAnalysis; using ParkStats.Core; using Settings; using TMPro; using Unity.Collections; using Unity.Netcode; using UnityEngine; using UnityEngine.Events; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")] [assembly: AssemblyCompany("ParkStats.Plugin")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.2.1.0")] [assembly: AssemblyInformationalVersion("0.2.1+afeb85d5923f3daee29c3dc1ca473ef5d09b3a69")] [assembly: AssemblyProduct("ParkStats.Plugin")] [assembly: AssemblyTitle("ParkStats.Plugin")] [assembly: AssemblyVersion("0.2.1.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace ParkStats.Plugin { internal sealed class ComplaintLog { private const int RecentKept = 40; private const long SameEventMs = 100L; private readonly Func<AttractionInteraction, string> _rideName; private readonly Dictionary<string, Dictionary<string, int>> _places = new Dictionary<string, Dictionary<string, int>>(); private readonly Queue<string> _recent = new Queue<string>(); private HashSet<VisitorThoughtType> _complaints; private IntPtr _lastSystem; private VisitorThoughtType _lastType; private long _lastTick; public IEnumerable<string> Recent => _recent; public ComplaintLog(Func<AttractionInteraction, string> rideName) { _rideName = rideName; } public void Reset() { _places.Clear(); } public unsafe void Record(VisitorThoughtsSystem system, VisitorThoughtType type) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)system == (Object)null || !IsComplaint(type)) { return; } long tickCount = Environment.TickCount64; if (((Il2CppObjectBase)system).Pointer == _lastSystem && type == _lastType && tickCount - _lastTick < 100) { return; } _lastSystem = ((Il2CppObjectBase)system).Pointer; _lastType = type; _lastTick = tickCount; if (!system.CanAddThought(type)) { return; } AIBrain aIBrain = system.AIBrain; if ((Object)(object)aIBrain == (Object)null) { return; } AttractionInteraction attractionInteraction = aIBrain.AttractionInteraction; string text = (((Object)(object)attractionInteraction == (Object)null) ? null : _rideName(attractionInteraction)); string text2 = ((object)(*(VisitorThoughtType*)(&type))/*cast due to .constrained prefix*/).ToString(); if (text != null) { if (!_places.TryGetValue(text2, out var value)) { value = (_places[text2] = new Dictionary<string, int>()); } value[text] = ((!value.TryGetValue(text, out var value2)) ? 1 : (value2 + 1)); } _recent.Enqueue($"{text2} | state {aIBrain.currentStateName} | before {aIBrain.lastState} | at {text ?? "-"}"); while (_recent.Count > 40) { _recent.Dequeue(); } } public IReadOnlyList<CountLine> Places(string thought) { if (!_places.TryGetValue(thought, out var value)) { return Array.Empty<CountLine>(); } return ((IEnumerable<KeyValuePair<string, int>>)value).Select((Func<KeyValuePair<string, int>, CountLine>)((KeyValuePair<string, int> p) => new CountLine(p.Key, p.Value))).ToList(); } private bool IsComplaint(VisitorThoughtType type) { //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) if (_complaints == null) { GameSettingsSO common = GameSettings.Common; object obj; if (common == null) { obj = null; } else { StaticData staticData = common.staticData; obj = ((staticData != null) ? staticData.Thoughts : null); } List<ThoughtSO> val = (List<ThoughtSO>)obj; if (val == null) { return false; } HashSet<VisitorThoughtType> hashSet = new HashSet<VisitorThoughtType>(); for (int i = 0; i < val.Count; i++) { ThoughtSO val2 = val[i]; if ((Object)(object)val2 != (Object)null && val2.MoodEffect < 0f) { hashSet.Add(val2.Type); } } _complaints = hashSet; } return _complaints.Contains(type); } } internal static class Diagnostics { public static void Write(string path, TabletUI tablet, GameReader reader, TabletPanel panel, IReadOnlyList<string> panelLines) { StringBuilder sb = new StringBuilder(); sb.AppendLine("== Panel =="); foreach (string panelLine in panelLines) { sb.AppendLine(panelLine); } sb.AppendLine().AppendLine("== Raw game values =="); List<string> raw = new List<string>(); ParkSnapshot val = Guard(sb, () => reader.Read(raw)); foreach (string item in raw) { sb.AppendLine(item); } sb.AppendLine("park key = " + reader.ParkKey()); sb.AppendLine().AppendLine("== Recent complaints (thought | guest state | state before | attraction) =="); foreach (string item2 in reader.Complaints.Recent) { sb.AppendLine(item2); } if (val != (ParkSnapshot)null) { foreach (string tab in TabletPanel.ModTabs) { StringBuilder stringBuilder = sb.AppendLine(); StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(11, 1, stringBuilder); handler.AppendLiteral("== Tab: "); handler.AppendFormatted(tab); handler.AppendLiteral(" =="); stringBuilder.AppendLine(ref handler); Guard(sb, delegate { //IL_005a: Unknown result type (might be due to invalid IL or missing references) foreach (Row item3 in panel.Rows(panel.TabIndex(tab))) { StringBuilder stringBuilder2 = sb; StringBuilder.AppendInterpolatedStringHandler handler2 = new StringBuilder.AppendInterpolatedStringHandler(3, 2, stringBuilder2); handler2.AppendLiteral("["); handler2.AppendFormatted<RowKind>(item3.Kind); handler2.AppendLiteral("] "); handler2.AppendFormatted(item3.Text); stringBuilder2.AppendLine(ref handler2); } return true; }); } } sb.AppendLine().AppendLine("== Tablet elements =="); Element(sb, "TicketPrice", (Component)(object)tablet.TicketPrice); Element(sb, "TicketPriceInfo", (Component)(object)tablet.TicketPriceInfo); Element(sb, "VisitorsAmount", (Component)(object)tablet.VisitorsAmount); Element(sb, "TeensAmount", (Component)(object)tablet.TeensAmount); Element(sb, "AdultsAmount", (Component)(object)tablet.AdultsAmount); Element(sb, "SeniorsAmount", (Component)(object)tablet.SeniorsAmount); Element(sb, "OverallSlider", (Component)(object)tablet.OverallSlider); Element(sb, "FunSlider", (Component)(object)tablet.FunSlider); Element(sb, "EnergySlider", (Component)(object)tablet.EnergySlider); Element(sb, "HygeineSlider", (Component)(object)tablet.HygeineSlider); Element(sb, "ThirstSlider", (Component)(object)tablet.ThirstSlider); Element(sb, "HungerSlider", (Component)(object)tablet.HungerSlider); Element(sb, "ToiletSlider", (Component)(object)tablet.ToiletSlider); Element(sb, "TrashSlider", (Component)(object)tablet.TrashSlider); StringBuilder sb2 = sb; TabletUIPage managementPage = tablet.ManagementPage; Element(sb2, "ManagementPage.firstButton", (Component)(object)((managementPage != null) ? managementPage.firstButton : null)); TabletUIPage managementPage2 = tablet.ManagementPage; object obj; if (managementPage2 == null) { obj = null; } else { GameObject mainObject = managementPage2.mainObject; obj = ((mainObject != null) ? mainObject.transform : null); } Transform val2 = (Transform)obj; if ((Object)(object)val2 != (Object)null) { sb.AppendLine().AppendLine("== Parents of the management page (nearest first) =="); Transform parent = val2.parent; while ((Object)(object)parent != (Object)null) { Node(sb, parent, 0); parent = parent.parent; } sb.AppendLine().AppendLine("== Management page =="); Tree(sb, val2, 0); } Directory.CreateDirectory(Path.GetDirectoryName(path)); File.WriteAllText(path, sb.ToString()); } private static void Element(StringBuilder sb, string name, Component component) { sb.Append(name).Append(" = "); try { if ((Object)(object)component == (Object)null) { sb.AppendLine("null"); return; } List<string> list = new List<string>(); Transform val = component.transform; while ((Object)(object)val != (Object)null) { list.Add(((Object)val).name); val = val.parent; } list.Reverse(); sb.Append(string.Join("/", list)); Image val2 = ((Il2CppObjectBase)component).TryCast<Image>(); if ((Object)(object)val2 != (Object)null) { sb.Append(" | fill ").Append(N(val2.fillAmount)); } TMP_Text val3 = ((Il2CppObjectBase)component).TryCast<TMP_Text>(); if ((Object)(object)val3 != (Object)null) { sb.Append(" | \"").Append(val3.text).Append('"'); } sb.AppendLine(); } catch (Exception ex) { StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(4, 2, sb); handler.AppendLiteral("<"); handler.AppendFormatted(ex.GetType().Name); handler.AppendLiteral(": "); handler.AppendFormatted(ex.Message); handler.AppendLiteral(">"); sb.AppendLine(ref handler); } } private static void Tree(StringBuilder sb, Transform transform, int depth) { Node(sb, transform, depth); for (int i = 0; i < transform.childCount; i++) { Tree(sb, transform.GetChild(i), depth + 1); } } private static void Node(StringBuilder sb, Transform transform, int depth) { //IL_0048: 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_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) sb.Append(' ', depth * 2); try { GameObject gameObject = ((Component)transform).gameObject; sb.Append(((Object)gameObject).name); if (!gameObject.activeSelf) { sb.Append(" (inactive)"); } RectTransform val = ((Il2CppObjectBase)transform).TryCast<RectTransform>(); if ((Object)(object)val != (Object)null) { Rect rect = val.rect; sb.Append(" | anchors ").Append(V(val.anchorMin)).Append('-') .Append(V(val.anchorMax)) .Append(" pivot ") .Append(V(val.pivot)) .Append(" pos ") .Append(V(val.anchoredPosition)) .Append(" sizeDelta ") .Append(V(val.sizeDelta)) .Append(" size (") .Append(N(((Rect)(ref rect)).width)) .Append(',') .Append(N(((Rect)(ref rect)).height)) .Append(')'); } sb.Append(" |"); Il2CppArrayBase<Component> components = gameObject.GetComponents<Component>(); for (int i = 0; i < components.Length; i++) { if ((Object)(object)components[i] != (Object)null) { sb.Append(' ').Append(Label(components[i])).Append(';'); } } } catch (Exception ex) { StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(5, 2, sb); handler.AppendLiteral(" <"); handler.AppendFormatted(ex.GetType().Name); handler.AppendLiteral(": "); handler.AppendFormatted(ex.Message); handler.AppendLiteral(">"); sb.Append(ref handler); } sb.AppendLine(); } private static string Label(Component component) { //IL_0233: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_02a9: Unknown result type (might be due to invalid IL or missing references) //IL_02c3: Unknown result type (might be due to invalid IL or missing references) //IL_01a2: Unknown result type (might be due to invalid IL or missing references) //IL_01d9: Unknown result type (might be due to invalid IL or missing references) //IL_0117: Unknown result type (might be due to invalid IL or missing references) string fullName = ((Object)component).GetIl2CppType().FullName; try { TMP_Text val = ((Il2CppObjectBase)component).TryCast<TMP_Text>(); if ((Object)(object)val != (Object)null) { string text = val.text ?? ""; if (text.Length > 40) { text = text.Substring(0, 40) + "..."; } DefaultInterpolatedStringHandler defaultInterpolatedStringHandler = new DefaultInterpolatedStringHandler(36, 7); defaultInterpolatedStringHandler.AppendFormatted(fullName); defaultInterpolatedStringHandler.AppendLiteral("[\""); defaultInterpolatedStringHandler.AppendFormatted(text.Replace("\n", "\\n")); defaultInterpolatedStringHandler.AppendLiteral("\" size "); defaultInterpolatedStringHandler.AppendFormatted(N(val.fontSize)); defaultInterpolatedStringHandler.AppendLiteral(" auto "); defaultInterpolatedStringHandler.AppendFormatted(val.enableAutoSizing); defaultInterpolatedStringHandler.AppendLiteral(" "); defaultInterpolatedStringHandler.AppendLiteral("align "); defaultInterpolatedStringHandler.AppendFormatted<TextAlignmentOptions>(val.alignment); defaultInterpolatedStringHandler.AppendLiteral(" font "); TMP_FontAsset font = val.font; defaultInterpolatedStringHandler.AppendFormatted((font != null) ? ((Object)font).name : null); defaultInterpolatedStringHandler.AppendLiteral(" color "); defaultInterpolatedStringHandler.AppendFormatted(C(((Graphic)val).color)); defaultInterpolatedStringHandler.AppendLiteral("]"); return defaultInterpolatedStringHandler.ToStringAndClear(); } Image val2 = ((Il2CppObjectBase)component).TryCast<Image>(); if ((Object)(object)val2 != (Object)null) { DefaultInterpolatedStringHandler defaultInterpolatedStringHandler = new DefaultInterpolatedStringHandler(28, 5); defaultInterpolatedStringHandler.AppendFormatted(fullName); defaultInterpolatedStringHandler.AppendLiteral("[sprite "); Sprite sprite = val2.sprite; defaultInterpolatedStringHandler.AppendFormatted(((sprite != null) ? ((Object)sprite).name : null) ?? "none"); defaultInterpolatedStringHandler.AppendLiteral(" type "); defaultInterpolatedStringHandler.AppendFormatted<Type>(val2.type); defaultInterpolatedStringHandler.AppendLiteral(" fill "); defaultInterpolatedStringHandler.AppendFormatted(N(val2.fillAmount)); defaultInterpolatedStringHandler.AppendLiteral(" color "); defaultInterpolatedStringHandler.AppendFormatted(C(((Graphic)val2).color)); defaultInterpolatedStringHandler.AppendLiteral("]"); return defaultInterpolatedStringHandler.ToStringAndClear(); } Canvas val3 = ((Il2CppObjectBase)component).TryCast<Canvas>(); if ((Object)(object)val3 != (Object)null) { return $"{fullName}[{val3.renderMode} scale {N(val3.scaleFactor)}]"; } CanvasScaler val4 = ((Il2CppObjectBase)component).TryCast<CanvasScaler>(); if ((Object)(object)val4 != (Object)null) { return $"{fullName}[{val4.uiScaleMode} reference {V(val4.referenceResolution)}]"; } return fullName; } catch (Exception ex) { return $"{fullName}[<{ex.GetType().Name}: {ex.Message}>]"; } } private static T Guard<T>(StringBuilder sb, Func<T> action) { try { return action(); } catch (Exception ex) { StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(4, 2, sb); handler.AppendLiteral("<"); handler.AppendFormatted(ex.GetType().Name); handler.AppendLiteral(": "); handler.AppendFormatted(ex.Message); handler.AppendLiteral(">"); sb.AppendLine(ref handler); return default(T); } } private static string V(Vector2 vector) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) return $"({N(vector.x)},{N(vector.y)})"; } private static string C(Color color) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) return $"({N(color.r)},{N(color.g)},{N(color.b)},{N(color.a)})"; } private static string N(float value) { return value.ToString("0.##", CultureInfo.InvariantCulture); } } internal sealed class GameReader { private sealed class GuestStats { public int Counted; public double? AverageCash; public readonly Dictionary<string, int> Low = new Dictionary<string, int>(); public IReadOnlyList<CountLine> LeavingReasons = Array.Empty<CountLine>(); } private sealed class CatalogueEntry { public BuildingSO Building { get; } public BuildOption Option { get; } public CatalogueEntry(BuildingSO building, BuildOption option) { Building = building; Option = option; } } private readonly ManualLogSource _log; private readonly HashSet<string> _reported = new HashSet<string>(); private Dictionary<string, int> _complaintBaseline; private double? _capturedDaySatisfaction; private int? _capturedDay; private List<CatalogueEntry> _catalogue; public ComplaintLog Complaints { get; } public GameReader(ManualLogSource log) { _log = log; Complaints = new ComplaintLog(RideName); } public void ResetComplaintBaseline() { _complaintBaseline = null; Complaints.Reset(); } public void CaptureDaySatisfaction(string moment) { //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Expected O, but got Unknown GameManager game = Ref("GameManager.Instance", () => GameManager.Instance); if ((Object)(object)game == (Object)null) { return; } int valueOrDefault = Get("dailySatisfactionCount", () => game.dailySatisfactionCount).GetValueOrDefault(); float valueOrDefault2 = Get("dailySatisfactionSum", () => game.dailySatisfactionSum).GetValueOrDefault(); if (valueOrDefault > 0) { _capturedDaySatisfaction = Normalize.Fraction((double?)(valueOrDefault2 / (float)valueOrDefault)); _capturedDay = Get("CurrentDay", () => game.CurrentDay.Value); ManualLogSource log = _log; bool flag = default(bool); BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(41, 4, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Day "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<int?>(_capturedDay); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" satisfaction at "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(moment); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(": sum "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(Number(valueOrDefault2)); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" over "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<int>(valueOrDefault); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" guests."); } log.LogInfo(val); } } public ParkSnapshot Read(List<string> raw = null) { //IL_018e: Unknown result type (might be due to invalid IL or missing references) //IL_0193: Unknown result type (might be due to invalid IL or missing references) //IL_019b: Unknown result type (might be due to invalid IL or missing references) //IL_01d2: Unknown result type (might be due to invalid IL or missing references) //IL_0209: Unknown result type (might be due to invalid IL or missing references) //IL_0240: Unknown result type (might be due to invalid IL or missing references) //IL_0277: Unknown result type (might be due to invalid IL or missing references) //IL_0283: Unknown result type (might be due to invalid IL or missing references) //IL_02ae: Unknown result type (might be due to invalid IL or missing references) //IL_02b6: Unknown result type (might be due to invalid IL or missing references) //IL_02d3: Unknown result type (might be due to invalid IL or missing references) //IL_02f0: Unknown result type (might be due to invalid IL or missing references) //IL_030d: Unknown result type (might be due to invalid IL or missing references) //IL_032a: Unknown result type (might be due to invalid IL or missing references) //IL_0347: Unknown result type (might be due to invalid IL or missing references) //IL_0379: Unknown result type (might be due to invalid IL or missing references) //IL_038d: Unknown result type (might be due to invalid IL or missing references) //IL_03be: Unknown result type (might be due to invalid IL or missing references) //IL_03cb: Unknown result type (might be due to invalid IL or missing references) //IL_03e3: Unknown result type (might be due to invalid IL or missing references) //IL_03f6: Unknown result type (might be due to invalid IL or missing references) //IL_0409: Unknown result type (might be due to invalid IL or missing references) //IL_042c: Unknown result type (might be due to invalid IL or missing references) //IL_0453: Unknown result type (might be due to invalid IL or missing references) //IL_0466: Unknown result type (might be due to invalid IL or missing references) //IL_0473: Unknown result type (might be due to invalid IL or missing references) //IL_0495: Unknown result type (might be due to invalid IL or missing references) //IL_04b6: Unknown result type (might be due to invalid IL or missing references) //IL_04eb: Unknown result type (might be due to invalid IL or missing references) //IL_0520: Unknown result type (might be due to invalid IL or missing references) //IL_0557: Unknown result type (might be due to invalid IL or missing references) //IL_058e: Unknown result type (might be due to invalid IL or missing references) //IL_05c8: Unknown result type (might be due to invalid IL or missing references) //IL_0602: Unknown result type (might be due to invalid IL or missing references) //IL_0654: Expected O, but got Unknown GameManager game = Ref("GameManager.Instance", () => GameManager.Instance); if ((Object)(object)game == (Object)null) { return null; } bool valueOrDefault = Get("IsServer", () => ((NetworkBehaviour)game).IsServer) == true; Note(raw, "IsServer", valueOrDefault); FinanceSystem finance = Ref("FinanceSystem", () => game.FinanceSystem); AttractionManager val = Ref("AttractionManager", () => game.AttractionManager); StaffManager staff = Ref("StaffManager", () => game.StaffManager); IReadOnlyList<MoneyLine> readOnlyList2; if (!valueOrDefault || !((Object)(object)finance != (Object)null)) { IReadOnlyList<MoneyLine> readOnlyList = Array.Empty<MoneyLine>(); readOnlyList2 = readOnlyList; } else { readOnlyList2 = ReadTrackers(finance, raw); } IReadOnlyList<MoneyLine> readOnlyList3 = readOnlyList2; GuestStats guestStats = (valueOrDefault ? ReadGuests(game, raw) : new GuestStats()); int? day = Get("CurrentDay", () => game.CurrentDay.Value); int? num = Get("CurrentVisitorCount", () => game.CurrentVisitorCount); bool flag = num > 0; Dictionary<string, IReadOnlyList<string>> sources = (((Object)(object)val == (Object)null) ? new Dictionary<string, IReadOnlyList<string>>() : ReadNeedSources(val, raw)); float? num2 = Get("RecentAverageSatisfaction", () => game.HistorySystem.RecentAverageSatisfaction.Value); ParkSnapshot val2 = new ParkSnapshot(); val2.set_Day(day); val2.set_Money(((Object)(object)finance == (Object)null) ? ((double?)null) : Get("CurrentMoney", (Func<double>)(() => finance.CurrentMoney.Value))); val2.set_TotalEarned(((Object)(object)finance == (Object)null) ? ((double?)null) : Get("TotalMoneyEarned", (Func<double>)(() => finance.TotalMoneyEarned.Value))); val2.set_TotalSpent(((Object)(object)finance == (Object)null) ? ((double?)null) : Get("TotalMoneySpent", (Func<double>)(() => finance.TotalMoneySpent.Value))); val2.set_TicketPrice(((Object)(object)finance == (Object)null) ? ((double?)null) : Get("TicketPrice", (Func<double>)(() => finance.TicketPrice))); val2.set_MoneyToday(Normalize.Signs(readOnlyList3)); IReadOnlyList<LoanLine> loans; if (!((Object)(object)finance == (Object)null)) { loans = ReadLoans(finance); } else { IReadOnlyList<LoanLine> readOnlyList4 = Array.Empty<LoanLine>(); loans = readOnlyList4; } val2.set_Loans(loans); val2.set_Visitors(num); val2.set_ExpectedVisitors(Get("ExpectedMaxVisitors", () => game.ExpectedMaxVisitors.Value)); val2.set_MaxVisitors(Get("MaxNumberOfVisitors", () => game.MaxNumberOfVisitors.Value)); val2.set_VisitorsToday(Get("TotalAmountOfVisitors", () => game.TotalAmountOfVisitors)); val2.set_RefundedVisitors(Get("TotalAmountOfRefundedVisitors", () => game.TotalAmountOfRefundedVisitors)); val2.set_InjuredVisitors(Get("TotalAmountOfInjuredVisitors", () => game.TotalAmountOfInjuredVisitors)); val2.set_Satisfaction(flag ? Normalize.Fraction(Get("CurrentSatisfaction", (Func<double>)(() => game.CurrentSatisfaction.Value))) : ((double?)null)); val2.set_DaySatisfaction(ReadDaySatisfaction(game, day)); val2.set_RecentSatisfaction(Normalize.Fraction((double?)num2)); val2.set_AverageGuestCash(guestStats.AverageCash); val2.set_Needs(ReadNeeds(game, guestStats, flag, sources)); val2.set_Prestige(ReadPrestige(game, val)); val2.set_NextStarTask(ReadNextStarTask(game, raw)); val2.set_AttractionCapacity(((Object)(object)val == (Object)null) ? ((int?)null) : ReadAttractionCapacity(val, raw)); IReadOnlyList<BuildOption> buildOptions; if (!((Object)(object)val == (Object)null)) { buildOptions = ReadBuildOptions(game, val); } else { IReadOnlyList<BuildOption> readOnlyList5 = Array.Empty<BuildOption>(); buildOptions = readOnlyList5; } val2.set_BuildOptions(buildOptions); val2.set_Complaints(ReadComplaints(game, raw)); val2.set_LeavingReasons(guestStats.LeavingReasons); IReadOnlyList<RideRow> rides; if (!((Object)(object)val == (Object)null)) { rides = ReadRides(val, raw); } else { IReadOnlyList<RideRow> readOnlyList6 = Array.Empty<RideRow>(); rides = readOnlyList6; } val2.set_Rides(rides); IReadOnlyList<CapacityLine> capacity; if (!((Object)(object)val == (Object)null)) { capacity = ReadCapacity(val); } else { IReadOnlyList<CapacityLine> readOnlyList7 = Array.Empty<CapacityLine>(); capacity = readOnlyList7; } val2.set_Capacity(capacity); val2.set_CleanlinessThreshold(Normalize.Fraction(Get("CleanlinessThreshold", (Func<double>)(() => GameSettings.Attractions.CleanlinessThreshold)))); val2.set_DurabilityThreshold(Normalize.Fraction(Get("DurabilityThreshold", (Func<double>)(() => GameSettings.Attractions.DurabilityThreshold)))); val2.set_StaffCount(((Object)(object)staff == (Object)null) ? ((int?)null) : Get("CurrentStaffCount", () => staff.CurrentStaffCount.Value)); val2.set_StaffCapacity(((Object)(object)staff == (Object)null) ? ((int?)null) : Get("CurrentMaxStaffCapacity", () => staff.CurrentMaxStaffCapacity.Value)); val2.set_StaffSalary(((Object)(object)staff == (Object)null || !valueOrDefault) ? ((double?)null) : Get("CalculateStaffSalary", (Func<double>)(() => staff.CalculateStaffSalary()))); val2.set_StaffTax(((Object)(object)staff == (Object)null || !valueOrDefault) ? ((double?)null) : Get("CalculateStaffTax", (Func<double>)(() => staff.CalculateStaffTax()))); val2.set_StaffTaxPerExtra(((Object)(object)staff == (Object)null || !valueOrDefault) ? ((double?)null) : Get("CalculateStaffOverCap", (Func<double>)(() => staff.CalculateStaffOverCap()))); if (raw != null) { NoteUnverified(game, finance, val, raw); } return val2; } public IReadOnlyList<(int Day, double Satisfaction)> DaySnapshots() { //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_0038: 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) List<(int, double)> list = new List<(int, double)>(); try { GameManager instance = GameManager.Instance; object obj; if (instance == null) { obj = null; } else { HistorySystem historySystem = instance.HistorySystem; obj = ((historySystem != null) ? historySystem.Snapshots : null); } NetworkList<HistorySnapshot> val = (NetworkList<HistorySnapshot>)obj; if (val == null) { return list; } for (int i = 0; i < val.Count; i++) { HistorySnapshot val2 = val[i]; double? num = Normalize.Fraction((double?)val2.OverallSatisfaction); if (num.HasValue) { double valueOrDefault = num.GetValueOrDefault(); if (valueOrDefault > 0.0) { list.Add((val2.Day, valueOrDefault)); } } } } catch (Exception e) { Report("HistorySystem.Snapshots", e); } return list; } public string ParkKey() { string text = Ref("ParkName", delegate { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) GameManager instance = GameManager.Instance; if (instance == null) { return (string)null; } ParkUpgradeSystem parkUpgradeSystem = instance.ParkUpgradeSystem; if (parkUpgradeSystem == null) { return (string)null; } NetworkVariable<FixedString128Bytes> parkNameNetVar = parkUpgradeSystem.ParkNameNetVar; return (parkNameNetVar == null) ? null : ((object)parkNameNetVar.Value/*cast due to .constrained prefix*/).ToString(); }); if (string.IsNullOrWhiteSpace(text)) { return "default"; } string text2 = new string(text.Where(delegate(char c) { bool flag = char.IsLetterOrDigit(c); if (!flag) { bool flag2 = ((c == ' ' || c == '-' || c == '_') ? true : false); flag = flag2; } return flag; }).ToArray()).Trim(); if (text2.Length != 0) { return text2; } return "default"; } private unsafe IReadOnlyList<MoneyLine> ReadTrackers(FinanceSystem finance, List<string> raw) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_003a: 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_004c: Expected O, but got Unknown //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) List<MoneyLine> list = new List<MoneyLine>(); try { List<MoneyChangeTracker> trackers = finance.Trackers; if (trackers == null) { return list; } for (int i = 0; i < trackers.Count; i++) { MoneyChangeTracker val = trackers[i]; list.Add(new MoneyLine(((object)(*(MoneyChangeReason*)(&val.Reason))/*cast due to .constrained prefix*/).ToString(), (double)val.Amount)); Note(raw, $"Trackers[{i}]", $"{val.Reason} {Number(val.Amount)}"); } } catch (Exception e) { Report("FinanceSystem.Trackers", e); } return list; } private IReadOnlyList<LoanLine> ReadLoans(FinanceSystem finance) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Expected O, but got Unknown List<LoanLine> list = new List<LoanLine>(); try { NetworkList<ActiveLoan> activeLoans = finance.ActiveLoans; if (activeLoans == null) { return list; } for (int i = 0; i < activeLoans.Count; i++) { ActiveLoan val = activeLoans[i]; list.Add(new LoanLine($"Loan {val.LoanID} ({val.DaysLeftToRepay} days left)", (double)val.AmountOwed)); } } catch (Exception e) { Report("FinanceSystem.ActiveLoans", e); } return list; } private GuestStats ReadGuests(GameManager game, List<string> raw) { //IL_014a: 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) GuestStats guestStats = new GuestStats(); try { AINetManager aINetManager = game.AINetManager; List<AIBrain> val = ((aINetManager != null) ? aINetManager.AIBrains : null); if ((Object)(object)aINetManager == (Object)null || val == null) { return guestStats; } double num = 0.0; Dictionary<string, int> dictionary = new Dictionary<string, int>(); for (int i = 0; i < val.Count; i++) { AIBrain val2 = val[i]; if ((Object)(object)val2 == (Object)null) { continue; } AIDataStorage data = val2.Data; if (!((Object)(object)data == (Object)null) && IsParkVisitor(val2, data)) { guestStats.Counted++; num += (double)data.Money; CountIfLow(guestStats, "Fun", data.FunNeed); CountIfLow(guestStats, "Hygiene", data.HygieneNeed); CountIfLow(guestStats, "Thirst", data.ThirstNeed); CountIfLow(guestStats, "Hunger", data.HungerNeed); CountIfLow(guestStats, "Toilet", data.ToiletNeed); CountIfLow(guestStats, "Trash", data.TrashNeed); AISyncedNeed energyNeed = data.EnergyNeed; if (energyNeed != null && UnderHalf(((NetworkVariable<float>)(object)energyNeed).Value, energyNeed.MinValue, energyNeed.MaxValue)) { Add(guestStats.Low, "Energy"); } if (data.IsLeavingPark) { Add(dictionary, Format.Reason(((object)data.LeavingReason/*cast due to .constrained prefix*/).ToString())); } if (raw != null && guestStats.Counted <= 3) { AINeed funNeed = data.FunNeed; Note(raw, $"Guest[{guestStats.Counted}]", $"money {Number(data.Money)} fun {Number(funNeed.Value)} (min {Number(funNeed.MinValue)} max {Number(funNeed.MaxValue)} threshold {Number(funNeed.SatisfactionThreshold)} low {Number(funNeed.LowReactionThreshold)}) toilet {Number(data.ToiletNeed.Value)} (threshold {Number(data.ToiletNeed.SatisfactionThreshold)} low {Number(data.ToiletNeed.LowReactionThreshold)}) happiness {Number(data.Happiness.Value)}"); } } } if (guestStats.Counted > 0) { guestStats.AverageCash = num / (double)guestStats.Counted; } guestStats.LeavingReasons = ((IEnumerable<KeyValuePair<string, int>>)dictionary).Select((Func<KeyValuePair<string, int>, CountLine>)((KeyValuePair<string, int> p) => new CountLine(p.Key, p.Value))).ToList(); Note(raw, "Guests counted", guestStats.Counted); } catch (Exception e) { Report("Guests", e); } return guestStats; } private static bool IsParkVisitor(AIBrain brain, AIDataStorage data) { __c _ = __c.__9; if (_ != null) { return _._get_ParkVisitorList_b__10_0(brain); } if (!data.IsStaff && !data.IsNPC && !data.IsDormant) { return data.DoesHaveTicket; } return false; } private static void CountIfLow(GuestStats stats, string need, AINeed value) { if (value != null && UnderHalf(value.Value, value.MinValue, value.MaxValue)) { Add(stats.Low, need); } } private static bool UnderHalf(float value, float min, float max) { if (max > min) { return (value - min) / (max - min) < 0.5f; } return false; } private IReadOnlyList<NeedStat> ReadNeeds(GameManager game, GuestStats guests, bool hasGuests, Dictionary<string, IReadOnlyList<string>> sources) { return (IReadOnlyList<NeedStat>)(object)new NeedStat[7] { Need("Fun", () => game.FunSatisfaction.Value), Need("Energy", () => game.EnergySatisfaction.Value), Need("Hygiene", () => game.HygeineSatisfaction.Value), Need("Thirst", () => game.ThirstSatisfaction.Value), Need("Hunger", () => game.HungerSatisfaction.Value), Need("Toilet", () => game.ToiletSatisfaction.Value), Need("Trash", () => game.TrashSatisfaction.Value) }; NeedStat Need(string name, Func<float> average) { //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Expected O, but got Unknown int value; NeedStat val = new NeedStat(name, hasGuests ? Normalize.Fraction(Get(name + "Satisfaction", (Func<double>)(() => average()))) : ((double?)null), guests.Low.TryGetValue(name, out value) ? value : 0, guests.Counted); IReadOnlyList<string> sources2; if (!sources.TryGetValue(name, out var value2)) { IReadOnlyList<string> readOnlyList = Array.Empty<string>(); sources2 = readOnlyList; } else { sources2 = value2; } val.set_Sources(sources2); return val; } } private Dictionary<string, IReadOnlyList<string>> ReadNeedSources(AttractionManager manager, List<string> raw) { //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) Dictionary<string, Dictionary<string, int>> dictionary = new Dictionary<string, Dictionary<string, int>>(); try { List<AttractionInteraction> parkAttractions = manager.ParkAttractions; int num = 0; while (parkAttractions != null && num < parkAttractions.Count) { AttractionInteraction val = parkAttractions[num]; if (!((Object)(object)val == (Object)null) && val.IsBuilt) { AttractionData attractionData = val.AttractionData; Il2CppReferenceArray<InteractionStatChange> val2 = ((attractionData != null) ? attractionData.StatChanges : null); if (val2 != null) { string text = RideName(val); for (int i = 0; i < ((Il2CppArrayBase<InteractionStatChange>)(object)val2).Length; i++) { InteractionStatChange val3 = ((Il2CppArrayBase<InteractionStatChange>)(object)val2)[i]; if (val3 == null) { continue; } Note(raw, "StatChange " + text, $"{val3.Target} {Number(val3.Value)}"); string text2 = NeedName(val3.Target); if (text2 != null && !(val3.Value <= 0f)) { if (!dictionary.TryGetValue(text2, out var value)) { value = (dictionary[text2] = new Dictionary<string, int>()); } Add(value, text); } } } } num++; } } catch (Exception e) { Report("StatChanges", e); } Dictionary<string, IReadOnlyList<string>> dictionary3 = new Dictionary<string, IReadOnlyList<string>>(); foreach (KeyValuePair<string, Dictionary<string, int>> item in dictionary) { item.Deconstruct(out var key, out var value2); string key2 = key; List<string> list = (from p in value2.OrderByDescending((KeyValuePair<string, int> p) => p.Value).ThenBy<KeyValuePair<string, int>, string>((KeyValuePair<string, int> p) => p.Key, StringComparer.Ordinal) select (p.Value > 1) ? $"{p.Key} x{p.Value}" : p.Key).ToList(); if (list.Count > 4) { int value3 = list.Count - 4; list = list.Take(4).Append($"{value3} more").ToList(); } dictionary3[key2] = list; } return dictionary3; } private static string NeedName(EStat stat) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Expected I4, but got Unknown return (int)stat switch { 1 => "Fun", 0 => "Energy", 2 => "Hygiene", 8 => "Thirst", 3 => "Hunger", 11 => "Toilet", 12 => "Trash", _ => null, }; } private string ReadNextStarTask(GameManager game, List<string> raw) { try { MissionManager missionManager = game.MissionManager; List<MissionData> val = ((missionManager != null) ? missionManager.ActiveMissions : null); if (val == null) { return null; } float value = game.ParkPrestige.Value; string text = null; for (int i = 0; i < val.Count; i++) { MissionData mission = val[i]; if (!(mission == (MissionData)null)) { bool flag = RaisesPrestige(mission, value); string text2 = Ref("Mission.GetTaskText", () => mission.GetTaskText()); text2 = (string.IsNullOrWhiteSpace(text2) ? null : string.Join("; ", text2.Split('\n', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))); string name = $"Mission[{i}]"; DefaultInterpolatedStringHandler defaultInterpolatedStringHandler = new DefaultInterpolatedStringHandler(32, 4); MissionSO missionSO = mission.MissionSO; defaultInterpolatedStringHandler.AppendFormatted((missionSO != null) ? missionSO.MissionID : null); defaultInterpolatedStringHandler.AppendLiteral(" stage "); MissionStage currentStage = mission.CurrentStage; defaultInterpolatedStringHandler.AppendFormatted((currentStage != null) ? new int?(currentStage.StageIndex) : ((int?)null)); defaultInterpolatedStringHandler.AppendLiteral(" raises prestige "); defaultInterpolatedStringHandler.AppendFormatted(flag); defaultInterpolatedStringHandler.AppendLiteral(" task \""); defaultInterpolatedStringHandler.AppendFormatted(text2); defaultInterpolatedStringHandler.AppendLiteral("\""); Note(raw, name, defaultInterpolatedStringHandler.ToStringAndClear()); if (raw != null) { NoteConditions(mission, raw); } if (flag && text == null) { text = RemainingConditions(mission) ?? text2; } } } return text; } catch (Exception e) { Report("Missions", e); return null; } } private static bool RaisesPrestige(MissionData mission, float prestige) { List<MissionStage> stages = mission.Stages; int num = 0; while (stages != null && num < stages.Count) { MissionStage obj = stages[num]; List<MissionAction> val = ((obj != null) ? obj.Actions : null); int num2 = 0; while (val != null && num2 < val.Count) { MissionAction obj2 = val[num2]; SetParkPrestige_MissionAction val2 = ((obj2 != null) ? ((Il2CppObjectBase)obj2).TryCast<SetParkPrestige_MissionAction>() : null); if (val2 != null && val2.ParkPrestige > prestige) { return true; } num2++; } num++; } return false; } private string RemainingConditions(MissionData mission) { try { List<string> list = new List<string>(); List<string> list2 = new List<string>(); MissionStage currentStage = mission.CurrentStage; List<MissionTransition> val = ((currentStage != null) ? currentStage.Transitions : null); int num = 0; while (val != null && num < val.Count) { MissionTransition obj = val[num]; List<MissionCondition> val2 = ((obj != null) ? obj.Conditions : null); int num2 = 0; while (val2 != null && num2 < val2.Count) { MissionCondition val3 = val2[num2]; string text = ((val3 == null) ? null : val3.GetTaskText()?.Trim()); if (!string.IsNullOrEmpty(text) && !list.Contains(text)) { list.Add(text); if (!val3._hasPassed) { list2.Add(text); } } num2++; } num++; } if (list.Count == 0) { return null; } return (list2.Count == 0) ? "every task is done." : $"{list2.Count} of {list.Count} tasks left: {string.Join("; ", list2)}"; } catch (Exception e) { Report("Mission conditions", e); return null; } } private void NoteConditions(MissionData mission, List<string> raw) { try { MissionStage currentStage = mission.CurrentStage; List<MissionTransition> val = ((currentStage != null) ? currentStage.Transitions : null); int num = 0; while (val != null && num < val.Count) { MissionTransition obj = val[num]; List<MissionCondition> val2 = ((obj != null) ? obj.Conditions : null); int num2 = 0; while (val2 != null && num2 < val2.Count) { MissionCondition val3 = val2[num2]; if (val3 != null) { Note(raw, " condition", $"{((MemberInfo)((Object)val3).GetIl2CppType()).Name} counter {Number(val3.Counter)} passed {val3._hasPassed} text \"{val3.GetTaskText()}\""); } num2++; } num++; } } catch (Exception ex) { Note(raw, " conditions", $"<{ex.GetType().Name}: {ex.Message}>"); } } private double? ReadDaySatisfaction(GameManager game, int? day) { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) try { HistorySystem historySystem = game.HistorySystem; NetworkList<HistorySnapshot> val = ((historySystem != null) ? historySystem.Snapshots : null); if (val != null && day.HasValue) { for (int i = 0; i < val.Count; i++) { HistorySnapshot val2 = val[i]; if (val2.Day == day) { return Normalize.Fraction((double?)val2.OverallSatisfaction); } } } int dailySatisfactionCount = game.dailySatisfactionCount; if (dailySatisfactionCount > 0) { return Normalize.Fraction((double?)(game.dailySatisfactionSum / (float)dailySatisfactionCount)); } return (_capturedDay == day) ? _capturedDaySatisfaction : ((double?)null); } catch (Exception e) { Report("DaySatisfaction", e); return null; } } private PrestigeInfo ReadPrestige(GameManager game, AttractionManager attractions) { //IL_00f5: Unknown result type (might be due to invalid IL or missing references) //IL_00fa: Unknown result type (might be due to invalid IL or missing references) //IL_0101: Unknown result type (might be due to invalid IL or missing references) //IL_0108: Unknown result type (might be due to invalid IL or missing references) //IL_010f: Unknown result type (might be due to invalid IL or missing references) //IL_0119: Expected O, but got Unknown try { int parkPrestigeInt = game.ParkPrestigeInt; int? maxVisitors = null; int? nextLevelMaxVisitors = null; double? num = null; List<ParkPrestigeSettings> prestigeSettings = GameSettings.Park.PrestigeSettings; for (int i = 0; i < prestigeSettings.Count; i++) { ParkPrestigeSettings val = prestigeSettings[i]; int num2 = (int)val.PrestigeLevel; if (num2 == parkPrestigeInt) { maxVisitors = val.MaxVisitors; num = val.DecorationPointsNeeded; } else if (num2 == parkPrestigeInt + 1) { nextLevelMaxVisitors = val.MaxVisitors; } } double? decorationLevel = null; if ((Object)(object)attractions != (Object)null && num > 0.0) { decorationLevel = Math.Clamp((double)attractions.ParkDecoration.Value / num.Value, 0.0, 1.0); } PrestigeInfo val2 = new PrestigeInfo(); val2.set_Level(parkPrestigeInt); val2.set_MaxVisitors(maxVisitors); val2.set_NextLevelMaxVisitors(nextLevelMaxVisitors); val2.set_DecorationLevel(decorationLevel); return val2; } catch (Exception e) { Report("Prestige", e); return null; } } private IReadOnlyList<CountLine> ReadComplaints(GameManager game, List<string> raw) { //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_0196: Unknown result type (might be due to invalid IL or missing references) //IL_00fd: Unknown result type (might be due to invalid IL or missing references) try { GameSettingsSO common = GameSettings.Common; object obj; if (common == null) { obj = null; } else { StaticData staticData = common.staticData; obj = ((staticData != null) ? staticData.Thoughts : null); } List<ThoughtSO> val = (List<ThoughtSO>)obj; TrackerSystem trackerSystem = game.TrackerSystem; object obj2; if (trackerSystem == null) { obj2 = null; } else { TrackerData data = trackerSystem.Data; obj2 = ((data != null) ? data.ThoughtsOccurance : null); } Dictionary<VisitorThoughtType, int> val2 = (Dictionary<VisitorThoughtType, int>)obj2; if (val == null || val2 == null) { return Array.Empty<CountLine>(); } Dictionary<string, int> dictionary = new Dictionary<string, int>(); Dictionary<string, string> thoughtNames = new Dictionary<string, string>(); int num = default(int); for (int i = 0; i < val.Count; i++) { ThoughtSO val3 = val[i]; if (!((Object)(object)val3 == (Object)null) && !(val3.MoodEffect >= 0f)) { string key = Format.Reason(((object)val3.Type/*cast due to .constrained prefix*/).ToString()); thoughtNames[key] = ((object)val3.Type/*cast due to .constrained prefix*/).ToString(); if (raw != null) { Note(raw, $"Thought {val3.Type}", $"mood {Number(val3.MoodEffect)} name \"{Translate(val3.LocalizedName)}\" description \"{Translate(val3.LocalizedDescription)}\""); } if (val2.TryGetValue(val3.Type, ref num) && num > 0) { dictionary[key] = num; } } } Note(raw, "Complaint totals", string.Join(", ", dictionary.Select((KeyValuePair<string, int> p) => $"{p.Key}={p.Value}"))); if (_complaintBaseline == null) { _complaintBaseline = dictionary; } return ((IEnumerable<CountLine>)Normalize.Since((IReadOnlyDictionary<string, int>)_complaintBaseline, (IReadOnlyDictionary<string, int>)dictionary)).Select((Func<CountLine, CountLine>)delegate(CountLine line) { if (thoughtNames.TryGetValue(line.Label, out var value)) { CountLine obj3 = line.<Clone>$(); obj3.set_Note(ComplaintHints.Note(value, Complaints.Places(value))); return obj3; } return line; }).ToList(); } catch (Exception e) { Report("Complaints", e); return Array.Empty<CountLine>(); } } private int? ReadAttractionCapacity(AttractionManager manager, List<string> raw) { try { List<AttractionInteraction> parkAttractions = manager.ParkAttractions; if (parkAttractions == null) { return null; } int num = 0; for (int i = 0; i < parkAttractions.Count; i++) { AttractionInteraction val = parkAttractions[i]; AttractionData val2 = ((val != null) ? val.AttractionData : null); if (val2 == null) { continue; } if (val2.HasCustomMaxVisitorImpact) { num += val2.CustomMaxVisitorImpact; continue; } if (((Component)val).gameObject.activeInHierarchy && ((SimpleInteraction)val).CanBeUsedByVisitor && val.IsBuilt) { num += ((BaseInteraction)val).MaxSimultaneousUsers; } if (val.HasQueue && (Object)(object)val.Queue != (Object)null) { num += (int)MathF.Round((float)val.Queue.QueueSize * 0.5f); } } Note(raw, "Attraction capacity (mod's sum)", num); return num; } catch (Exception e) { Report("Attraction capacity", e); return null; } } private IReadOnlyList<BuildOption> ReadBuildOptions(GameManager game, AttractionManager manager) { try { if (_catalogue == null) { _catalogue = ReadCatalogue(manager); } Dictionary<string, int> dictionary = new Dictionary<string, int>(); List<AttractionInteraction> parkAttractions = manager.ParkAttractions; int num = 0; while (parkAttractions != null && num < parkAttractions.Count) { AttractionInteraction obj = parkAttractions[num]; object obj2; if (obj == null) { obj2 = null; } else { BuildingSO buildingSO = obj.BuildingSO; obj2 = ((buildingSO != null) ? ((BaseInfoSO)buildingSO).UniqueID : null); } string text = (string)obj2; if (text != null) { Add(dictionary, text); } num++; } Dictionary<string, bool?> dictionary2 = new Dictionary<string, bool?>(); int num2 = 0; while (parkAttractions != null && num2 < parkAttractions.Count) { AttractionInteraction val = parkAttractions[num2]; if (!((Object)(object)val == (Object)null)) { bool? value = StaffedOf(val); if (value.HasValue) { dictionary2[RideName(val)] = value; } } num2++; } int parkPrestigeInt = game.ParkPrestigeInt; List<BuildOption> list = new List<BuildOption>(_catalogue.Count); foreach (CatalogueEntry item in _catalogue) { BuildingSO building = item.Building; string lockedBy = null; if (((BaseInfoSO)building).PrestigeLevel > parkPrestigeInt) { lockedBy = $"prestige {((BaseInfoSO)building).PrestigeLevel}"; } else if ((Object)(object)((BaseInfoSO)building).Requirements != (Object)null && !((BaseInfoSO)building).Requirements.AreRequirementsMet) { lockedBy = "research"; } BuildOption obj3 = item.Option.<Clone>$(); obj3.set_Owned(dictionary.TryGetValue(((BaseInfoSO)building).UniqueID ?? "", out var value2) ? value2 : 0); obj3.set_LockedBy(lockedBy); obj3.set_Staffed((item.Option.Category == "Food and drink") ? StaffedOf(building, item.Option.Name, dictionary2) : ((bool?)null)); list.Add(obj3); } return list; } catch (Exception e) { Report("Building catalogue", e); return Array.Empty<BuildOption>(); } } private List<CatalogueEntry> ReadCatalogue(AttractionManager manager) { //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: 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_00f7: Unknown result type (might be due to invalid IL or missing references) //IL_0104: Unknown result type (might be due to invalid IL or missing references) //IL_0131: 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_016b: Expected O, but got Unknown List<CatalogueEntry> list = new List<CatalogueEntry>(); Dictionary<string, BuildingSO> buildingSOs = manager.BuildingSOs; if (buildingSOs == null) { return list; } Enumerator<string, BuildingSO> enumerator = buildingSOs.GetEnumerator(); while (enumerator.MoveNext()) { KeyValuePair<string, BuildingSO> current = enumerator.Current; BuildingSO value = current.Value; if (!((Object)(object)value == (Object)null) && value.IsAttraction && !((BaseInfoSO)value).XBOXOnly && !((BaseInfoSO)value).PS5Only && !(((BaseInfoSO)value).HalloweenCandiesNeededToUnlock > 0f) && !(((BaseInfoSO)value).ChristmasGiftsNeededToUnlock > 0f)) { AttractionData attractionData = value.AttractionData; if (attractionData != null) { List<string> raises = RaisesOf(attractionData); BuildOption val = new BuildOption(); val.set_Name(TranslateTerm(((BaseInfoSO)value).LocalizedNameKey) ?? ((Object)value).name ?? current.Key); val.set_Price((double)((BaseInfoSO)value).Price); val.set_Category(CategoryOf(value.Tags, raises)); val.set_Tier(((BaseInfoSO)value).PrestigeLevel); val.set_Capacity(attractionData.VisitorBonus); val.set_IdealPrice((attractionData.IdealPrice > 0f) ? new double?(attractionData.IdealPrice) : ((double?)null)); val.set_MaintenancePerDay((attractionData.MaintenanceDailyPrice > 0f) ? new double?(attractionData.MaintenanceDailyPrice) : ((double?)null)); val.set_Raises((IReadOnlyList<string>)raises); list.Add(new CatalogueEntry(value, val)); } } } return list; } private bool? StaffedOf(AttractionInteraction attraction) { try { if ((Object)(object)((Il2CppObjectBase)attraction).TryCast<VendingMachineAttraction>() != (Object)null) { return false; } if ((Object)(object)((Il2CppObjectBase)attraction).TryCast<ShopInteraction>() != (Object)null) { return true; } } catch (Exception e) { Report("Shop kind", e); } return null; } private static bool? StaffedOf(BuildingSO building, string name, IReadOnlyDictionary<string, bool?> ownedShops) { if (ownedShops.TryGetValue(name, out var value) && value.HasValue) { return value; } return Demand.StaffedFromNames(((Object)building).name + " " + ((BaseInfoSO)building).UniqueID, name); } private static List<string> RaisesOf(AttractionData data) { //IL_003a: Unknown result type (might be due to invalid IL or missing references) List<string> list = new List<string>(); Il2CppReferenceArray<InteractionStatChange> val = ((data != null) ? data.StatChanges : null); int num = 0; while (val != null && num < ((Il2CppArrayBase<InteractionStatChange>)(object)val).Length) { string text = ((((Il2CppArrayBase<InteractionStatChange>)(object)val)[num] == null || ((Il2CppArrayBase<InteractionStatChange>)(object)val)[num].Value <= 0f) ? null : NeedName(((Il2CppArrayBase<InteractionStatChange>)(object)val)[num].Target)); if (text != null && !list.Contains(text)) { list.Add(text); } num++; } return list; } private static string CategoryOf(BuildingTags tags, IReadOnlyList<string> raises) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0003: Unknown result type (might be due to invalid IL or missing references) if (Has((BuildingTags)2097152)) { return "Hot tubs"; } if (Has((BuildingTags)4194304)) { return "Saunas"; } if (Has((BuildingTags)8)) { return "Slides"; } if (Has((BuildingTags)2)) { return "Diving boards"; } if (Has((BuildingTags)1)) { return "Pools"; } if (Has((BuildingTags)512)) { return "Showers"; } if (Has((BuildingTags)1024)) { return "Toilets"; } if (Has((BuildingTags)2048)) { return "Bins"; } if (Has((BuildingTags)4096)) { return "Loungers"; } if (Has((BuildingTags)33554432)) { return "Benches"; } if (Has((BuildingTags)8192)) { return "Food and drink"; } if (Has((BuildingTags)16777216) || Has((BuildingTags)32)) { return "Inflatables"; } return null; bool Has(BuildingTags tag) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Invalid comparison between Unknown and I4 return (tags & tag) > 0; } } private static string TranslateTerm(string term) { try { if (string.IsNullOrEmpty(term)) { return null; } string translation = LocalizationManager.GetTranslation(term, true, 0, true, false, (GameObject)null, (string)null, true); return string.IsNullOrWhiteSpace(translation) ? null : translation.Trim(); } catch (Exception) { return null; } } private static string Translate(LocalizedString text) { try { string text2 = ((text != null) ? text.mTerm : null); if (string.IsNullOrEmpty(text2)) { return null; } string translation = LocalizationManager.GetTranslation(text2, true, 0, true, false, (GameObject)null, (string)null, true); return string.IsNullOrWhiteSpace(translation) ? null : translation.Trim(); } catch (Exception) { return null; } } public string RideName(AttractionInteraction attraction) { return (Ref("Building.GetName", delegate { Building building = attraction.Building; return (building == null) ? null : building.GetName(); }) ?? Ref("Attraction.name", () => ((Object)attraction).name) ?? "?").Replace("(Clone)", "").Trim(); } private IReadOnlyList<RideRow> ReadRides(AttractionManager manager, List<string> raw) { List<RideRow> list = new List<RideRow>(); try { List<AttractionInteraction> parkAttractions = manager.ParkAttractions; if (parkAttractions == null) { return list; } for (int i = 0; i < parkAttractions.Count; i++) { AttractionInteraction attraction = parkAttractions[i]; if (!((Object)(object)attraction == (Object)null) && Get("IsBuilt", () => attraction.IsBuilt) != false) { list.Add(ReadRide(attraction, raw)); } } } catch (Exception e) { Report("AttractionManager.ParkAttractions", e); } return list; } private RideRow ReadRide(AttractionInteraction attraction, List<string> raw) { //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_034b: Unknown result type (might be due to invalid IL or missing references) //IL_0372: Unknown result type (might be due to invalid IL or missing references) //IL_0398: Unknown result type (might be due to invalid IL or missing references) //IL_03be: Unknown result type (might be due to invalid IL or missing references) //IL_0427: Unknown result type (might be due to invalid IL or missing references) //IL_045d: Unknown result type (might be due to invalid IL or missing references) //IL_046a: Unknown result type (might be due to invalid IL or missing references) //IL_0477: Unknown result type (might be due to invalid IL or missing references) //IL_04c2: Unknown result type (might be due to invalid IL or missing references) //IL_04fa: Unknown result type (might be due to invalid IL or missing references) //IL_0502: Unknown result type (might be due to invalid IL or missing references) //IL_050a: Unknown result type (might be due to invalid IL or missing references) //IL_051c: Unknown result type (might be due to invalid IL or missing references) //IL_054a: Unknown result type (might be due to invalid IL or missing references) //IL_0581: Unknown result type (might be due to invalid IL or missing references) //IL_058d: Unknown result type (might be due to invalid IL or missing references) //IL_059f: Unknown result type (might be due to invalid IL or missing references) //IL_05bc: Unknown result type (might be due to invalid IL or missing references) //IL_05d9: Unknown result type (might be due to invalid IL or missing references) //IL_062b: Expected O, but got Unknown string text = RideName(attraction); List<string> raises = Ref("StatChanges", () => RaisesOf(attraction.AttractionData)) ?? new List<string>(); BuildingSO building = Ref("BuildingSO", () => attraction.BuildingSO); double? num = Get("CurrentPrice", (Func<double>)(() => attraction.CurrentPrice)); double? num2 = Get("IdealPrice", (Func<double>)(() => attraction.AttractionData.IdealPrice)); double? num3 = Get("MaintenanceDailyPrice", (Func<double>)(() => attraction.MaintenanceDailyPrice)); double? num4 = ((Get("HasCleanness", () => attraction.HasCleanness) == true) ? Get("CurrentCleannessLevel", (Func<double>)(() => attraction.CurrentCleannessLevel.Value)) : ((double?)null)); double? num5 = ((Get("HasDurability", () => attraction.HasDurability) == true) ? Get("CurrentDurabilityLevel", (Func<double>)(() => attraction.CurrentDurabilityLevel.Value)) : ((double?)null)); ShopInteraction shop = Ref("ShopInteraction", () => ((Il2CppObjectBase)attraction).TryCast<ShopInteraction>()); int? num6 = (((Object)(object)shop == (Object)null) ? ((int?)null) : Get("CurrentStockCount", () => shop.CurrentStockCount)); int? num7 = (((Object)(object)shop == (Object)null) ? ((int?)null) : Get("StockCapacity", () => shop.StockCapacity)); if (raw != null) { Note(raw, "Ride " + text, $"price {Number(num)} ideal {Number(num2)} clean {Number(num4)} durability {Number(num5)} maintenance {Number(num3)} request {HasOpenRequest(attraction)} stock {num6}/{num7} visitorBonus {Get("VisitorBonus", () => attraction.AttractionData.VisitorBonus)} maxUsers {Get("MaxSimultaneousUsers", () => attraction.AttractionData.MaxSimultaneousUsers)} queueFull {Get("IsQueueFull", () => attraction.IsQueueFull)}"); } RideRow val = new RideRow(); val.set_Name(text); val.set_IsOpen(Get("IsOpen", () => attraction.IsOpen) ?? true); val.set_IsBroken(Get("IsMalfunctioning", () => attraction.IsMalfunctioning.Value) == true); val.set_UsesToday(Get("AmountOfUsesToday", () => attraction.AmountOfUsesToday).GetValueOrDefault()); val.set_Price((num > 0.0) ? num : ((num2 > 0.0) ? new double?(0.0) : ((double?)null))); val.set_IdealPrice((num2 > 0.0) ? num2 : ((double?)null)); val.set_Cleanliness(Normalize.Fraction(num4)); val.set_Durability(Normalize.Fraction(num5)); val.set_QueueLength((Get("HasQueue", () => attraction.HasQueue) == true) ? Get("Queue.AIVisitorCount", () => attraction.Queue.AIVisitorCount) : ((int?)null)); val.set_MaintenancePerDay((num3 > 0.0) ? num3 : ((double?)null)); val.set_StockLeft(num6); val.set_StockCapacity(num7); val.set_HasOpenRequest(HasOpenRequest(attraction)); val.set_Category(((Object)(object)building == (Object)null) ? null : Ref("Category", () => CategoryOf(building.Tags, raises))); val.set_Tier(((Object)(object)building == (Object)null) ? ((int?)null) : Get("PrestigeLevel", () => ((BaseInfoSO)building).PrestigeLevel)); val.set_Raises((IReadOnlyList<string>)raises); val.set_Staffed(StaffedOf(attraction)); val.set_UsersNow(Get("NumCurrentUsers", () => ((SimpleInteraction)attraction).NumCurrentUsers)); val.set_Capacity(Get("MaxSimultaneousUsers", () => ((BaseInteraction)attraction).MaxSimultaneousUsers)); double? num8 = Get("BuildingSO.Price", (Func<double>)(() => ((BaseInfoSO)attraction.BuildingSO).Price)); double? buildPrice; if (num8.HasValue) { double valueOrDefault = num8.GetValueOrDefault(); if (valueOrDefault > 0.0) { buildPrice = valueOrDefault; goto IL_0625; } } buildPrice = null; goto IL_0625; IL_0625: val.set_BuildPrice(buildPrice); return val; } private bool HasOpenRequest(AttractionInteraction attraction) { try { SaunaInteraction val = ((Il2CppObjectBase)attraction).TryCast<SaunaInteraction>(); if ((Object)(object)val != (Object)null) { return val.HasActiveRequest.Value && !val.ActiveRequestFulfilled.Value; } HotTubInteraction val2 = ((Il2CppObjectBase)attraction).TryCast<HotTubInteraction>(); if ((Object)(object)val2 != (Object)null) { return val2.haveRequest.Value && !val2.AreSettingsCorrect(); } } catch (Exception e) { Report("Guest requests", e); } return false; } private IReadOnlyList<CapacityLine> ReadCapacity(AttractionManager manager) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Expected O, but got Unknown //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0047: 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_0057: Expected O, but got Unknown //IL_0066: 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_0072: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Expected O, but got Unknown List<CapacityLine> list = new List<CapacityLine>(); try { AttractionsInfo attractionsInfo = manager.AttractionsInfo; if (attractionsInfo.MaxPoolsUsers > 0) { list.Add(new CapacityLine("Pools", attractionsInfo.CurrentPoolsUsers, attractionsInfo.MaxPoolsUsers)); } if (attractionsInfo.MaxSlidersUsers > 0) { list.Add(new CapacityLine("Slides", attractionsInfo.CurrentSlidersUsers, attractionsInfo.MaxSlidersUsers)); } if (attractionsInfo.MaxToiletsUsers > 0) { list.Add(new CapacityLine("Toilets", attractionsInfo.CurrentToiletsUsers, attractionsInfo.MaxToiletsUsers)); } } catch (Exception e) { Report("AttractionsInfo", e); } return list; } private void NoteUnverified(GameManager game, FinanceSystem finance, AttractionManager attractions, List<string> raw) { //IL_0617: Unknown result type (might be due to invalid IL or missing references) //IL_061c: Unknown result type (might be due to invalid IL or missing references) //IL_066a: Unknown result type (might be due to invalid IL or missing references) //IL_0684: Unknown result type (might be due to invalid IL or missing references) //IL_06a9: Unknown result type (might be due to invalid IL or missing references) //IL_06e5: Unknown result type (might be due to invalid IL or missing references) //IL_06ea: Unknown result type (might be due to invalid IL or missing references) //IL_06f3: Unknown result type (might be due to invalid IL or missing references) //IL_070a: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)finance != (Object)null) { Note(raw, "BaseTicketPrice", Get("BaseTicketPrice", () => finance.BaseTicketPrice)); Note(raw, "TicketPriceModifier", Get("TicketPriceModifier", () => finance.TicketPriceModifier)); Note(raw, "GetTargetTicketPrice()", Get("GetTargetTicketPrice", () => finance.GetTargetTicketPrice())); Note(raw, "DailyDecorTips", Get("DailyDecorTips", () => finance.DailyDecorTips)); Note(raw, "DailyTipsTicketBase", Get("DailyTipsTicketBase", () => finance.DailyTipsTicketBase)); } Note(raw, "CurrentSatisfaction", Get("CurrentSatisfaction", () => game.CurrentSatisfaction.Value)); Note(raw, "dailySatisfactionSum", Get("dailySatisfactionSum", () => game.dailySatisfactionSum)); Note(raw, "dailySatisfactionCount", Get("dailySatisfactionCount", () => game.dailySatisfactionCount)); Note(raw, "TotalAmountOfVisitors", Get("TotalAmountOfVisitors", () => game.TotalAmountOfVisitors)); Note(raw, "CleanlinessThreshold", Get("CleanlinessThreshold", () => GameSettings.Attractions.CleanlinessThreshold)); Note(raw, "CleanlinessStaffPreventionThreshold", Get("CleanlinessStaffPreventionThreshold", () => GameSettings.Attractions.CleanlinessStaffPreventionThreshold)); Note(raw, "DurabilityThreshold", Get("DurabilityThreshold", () => GameSettings.Attractions.DurabilityThreshold)); Note(raw, "DurabilityStaffPreventionThreshold", Get("DurabilityStaffPreventionThreshold", () => GameSettings.Attractions.DurabilityStaffPreventionThreshold)); NoteIncomeLevers(game, finance, attractions, raw); Note(raw, "ParkPrestige", Get("ParkPrestige", () => game.ParkPrestige.Value)); StaffManager staff = game.StaffManager; if ((Object)(object)staff != (Object)null) { Note(raw, "CalculateStaffSalary()", Get("CalculateStaffSalary", () => staff.CalculateStaffSalary())); Note(raw, "CalculateStaffTax()", Get("CalculateStaffTax", () => staff.CalculateStaffTax())); Note(raw, "CalculateStaffOverCap()", Get("CalculateStaffOverCap", () => staff.CalculateStaffOverCap())); Note(raw, "CalculateStaffOvertimePay()", Get("CalculateStaffOvertimePay", () => staff.CalculateStaffOvertimePay())); Note(raw, "StaffTaxPercentPerOverhire", Get("StaffTaxPercentPerOverhire", () => GameSettings.Staff.StaffTaxPercentPerOverhire)); } for (float num = 0.2f; num <= 1.001f; num += 0.1f) { float level = num; Note(raw, "GetSatisfactionPenaltyMultiplier(" + Number(level) + ")", Get("GetSatisfactionPenaltyMultiplier sample", () => GameSettings.Park.GetSatisfactionPenaltyMultiplier(level))); } Note(raw, "ParkPrestigeInt", Get("ParkPrestigeInt", () => game.ParkPrestigeInt)); Note(raw, "CurrentBonusVisitorCount", Get("CurrentBonusVisitorCount", () => game.CurrentBonusVisitorCount)); Note(raw, "CurrentBaseVisitorCount", Get("CurrentBaseVisitorCount", () => game.CurrentBaseVisitorCount)); Note(raw, "RecentAverageSatisfaction", Get("RecentAverageSatisfaction", () => game.HistorySystem.RecentAverageSatisfaction.Value)); if ((Object)(object)attractions != (Object)null) { Note(raw, "ParkDecoration", Get("ParkDecoration", () => attractions.ParkDecoration.Value)); Note(raw, "ParkDecorationLevel", Get("ParkDecorationLevel", () => attractions.ParkDecorationLevel.Value)); } try { List<ParkPrestigeSettings> prestigeSettings = GameSettings.Park.PrestigeSettings; for (int num2 = 0; num2 < prestigeSettings.Count; num2++) { ParkPrestigeSettings val = prestigeSettings[num2]; Note(raw, $"PrestigeSettings[{num2}]", $"level {Number(val.PrestigeLevel)} maxVisitors {val.MaxVisitors} decorationPointsNeeded {val.DecorationPointsNeeded}"); } NetworkList<HistorySnapshot> snapshots = game.HistorySystem.Snapshots; for (int num3 = 0; num3 < snapshots.Count; num3++) { HistorySnapshot val2 = snapshots[num3]; Note(raw, $"HistorySnapshot[{num3}]", $"day {val2.Day} overall {Number(val2.OverallSatisfaction)} fun {Number(val2.FunSatisfaction)}"); } Vector2 satisfactionRange = GameSettings.Park.SatisfactionRange; Note(raw, "SatisfactionRange", Number(satisfactionRange.x) + ".." + Number(satisfactionRange.y)); } catch (Exception e) { Report("PrestigeSettings", e); } } private void NoteIncomeLevers(GameManager game, FinanceSystem finance, AttractionManager attractions, List<string> raw) { //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_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_0112: Unknown result type (might be due to invalid IL or missing references) try { TicketSettings ticket = GameSettings.Ticket; Note(raw, "Ticket.MaxTipsPercentage", ticket.MaxTipsPercentage); Note(raw, "Ticket.MaxDecorTipsPercentage", ticket.MaxDecorTipsPercentage); Note(raw, "Ticket.PoolTicketPriceBonus", ticket.PoolTicketPriceBonus); Note(raw, "Ticket.SlideTicketPriceBonus", ticket.SlideTicketPriceBonus); Note(raw, "Ticket.SlidePassBonus", ticket.SlidePassBonus); Note(raw, "Ticket.SeniorDiscount", ticket.SeniorDiscount); Note(raw, "Ticket.TeenDiscount", ticket.TeenDiscount); Vector2 visitorMoneyBonus = ticket.VisitorMoneyBonus; Note(raw, "Ticket.VisitorMoneyBonus", Number(visitorMoneyBonus.x) + ".." + Number(visitorMoneyBonus.y)); for (float num = 0f; num <= 1.001f; num += 0.25f) { float level = num; Note(raw, "Ticket.GetTipsMultiplier(" + Number(level) + ")", Get("GetTipsMultiplier", () => ticket.GetTipsMultiplier(level))); } TicketPrestigeSettings prestigeSettings = ticket.GetPrestigeSettings(game.ParkPrestigeInt); Note(raw, "TicketPrestigeSettings.BaseTicketPrice", (prestigeSettings != null) ? new float?(prestigeSettings.BaseTicketPrice) : ((float?)null)); Note(raw, "Finance.PoolPricePerTier", GameSettings.Finance.PoolPricePerTier); Note(raw, "Finance.SlidePricePerTier", GameSettings.Finance.SlidePricePerTier); Note(raw, "Finance.UpgradeATMPrice", GameSettings.Finance.UpgradeATMPrice); Note(raw, "TicketMachinesBought", Get("TicketMachinesBought", () => game.ParkUpgradeSystem.TicketMachinesBought)); if ((Object)(object)finance != (Object)null) { Note(raw, "GetTipsTechMultiplier()", Get("GetTipsTechMultiplier", () => finance.GetTipsTechMultiplier())); } int bonus = 0; List<AttractionInteraction> val = ((attractions != null) ? attractions.ParkAttractions : null); int num2 = 0; while (val != null && num2 < val.Count) { AttractionInteraction val2 = val[num2]; if ((Object)(object)val2 != (Object)null && val2.IsBuilt) { bonus += val2.AttractionData.VisitorBonus; } num2++; } Note(raw, "Sum of attraction VisitorBonus", bonus); Note(raw, "Park.minVisitors", GameSettings.Park.minVisitors); Note(raw, "Park.GetVisitorLimit(sum)", Get("GetVisitorLimit", () => GameSettings.Park.GetVisitorLimit(bonus))); } catch (Exception e) { Report("Income levers", e); } } private static void Add(Dictionary<string, int> counts, string key) { counts[key] = ((!counts.TryGetValue(key, out var value)) ? 1 : (value + 1)); } private static void Note(List<string> raw, string name, object value) { raw?.Add(name + " = " + ((value is IFormattable formattable) ? formattable.ToString(null, CultureInfo.InvariantCulture) : (value?.ToString() ?? "n/a"))); } private static string Number(double? value) { return value?.ToString("0.###", CultureInfo.InvariantCulture) ?? "n/a"; } private T? Get<T>(string what, Func<T> read) where T : struct { try { return read(); } catch (Exception e) { Report(what, e); return null; } } private T Ref<T>(string what, Func<T> read) where T : class { try { return read(); } catch (Exception e) { Report(what, e); return null; } } private void Report(string what, Exception e) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Expected O, but got Unknown if (_reported.Add(what)) { ManualLogSource log = _log; bool flag = default(bool); BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(19, 3, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Could not read "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(what); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(": "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(e.GetType().Name); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(": "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(e.Message); } log.LogWarning(val); } } } [HarmonyPatch] internal static class Hooks { private static PropertyInfo _coroutineOwner; [HarmonyPostfix] [HarmonyPatch(typeof(TabletUI), "UpdateManagementPage")] private static void AfterUpdateManagementPage(TabletUI __instance) { Tablet(__instance, "UpdateManagementPage"); } [HarmonyPostfix] [HarmonyPatch(typeof(TabletUI), "OnManagementButtonPressed")] private static void AfterManagementButton(TabletUI __instance) { Tablet(__instance, "OnManagementButtonPressed"); } [HarmonyPostfix] [HarmonyPatch(typeof(TabletUI), "ToggleSubMenu")] private static void AfterToggleSubMenu(TabletUI __instance) { Tablet(__instance, "ToggleSubMenu"); } [HarmonyPostfix] [HarmonyPatch(typeof(TabletUI), "UpdateTime")] private static void AfterUpdateTime(TabletUI __instance) { Tablet(__instance, "UpdateTime"); } [HarmonyPostfix] [HarmonyPatch(typeof(TabletUI), "UpdateVisitorsInfo")] private static void AfterUpdateVisitorsInfo(TabletUI __instance) { Tablet(__instance, "UpdateVisitorsInfo"); } [HarmonyPrefix] [HarmonyPatch(typeof(FinanceSystem), "ResetTrackers")] private static void BeforeResetTrackers() { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Expected O, but got Unknown try { Plugin.Instance.RecordDay(); } catch (Exception ex) { ManualLogSource log = ((BasePlugin)Plugin.Instance).Log; bool flag = default(bool); BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(26, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Could not record the day: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<Exception>(ex); } log.LogError(val); } } [HarmonyPrefix] [HarmonyPatch(typeof(VisitorThoughtsSystem), "AddThought", new Type[] { typeof(VisitorThoughtType), typeof(bool) })] private static void BeforeThought(VisitorThoughtsSystem __instance, VisitorThoughtType type) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) Thought(__instance, type); } [HarmonyPrefix] [HarmonyPatch(typeof(VisitorThoughtsSystem), "AddThought", new Type[] { typeof(VisitorThoughtType), typeof(float), typeof(bool) })] private static void BeforeThoughtWithValue(VisitorThoughtsSystem __instance, VisitorThoughtType type) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) Thought(__instance, type); } private static void Thought(VisitorThoughtsSystem system, VisitorThoughtType type) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) try { Plugin.Instance.RecordThought(system, type); } catch (Exception e) { Plugin.Instance.ReportOnce("thought hook", e); } } [HarmonyPrefix] [HarmonyPatch(typeof(GameManager), "FinalizeDailySatisfaction")] private static void BeforeFinalizeSatisfaction() { CaptureSatisfaction("finalise"); } [HarmonyPrefix] [HarmonyPatch(typeof(GameManager), "ResetDailySatisfaction")] private static void BeforeResetSatisfaction() { CaptureSatisfaction("reset"); } public static void PatchPageToggle(Harmony harmony, ManualLogSource log) { //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Expected O, but got Unknown //IL_00be: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Expected O, but got Unknown Type? type = typeof(TabletUI).GetNestedTypes().FirstOrDefault((Type t) => t.Name.Contains("WaitOneFrameThenToggleSubMenu")); MethodInfo methodInfo = type?.GetMethod("MoveNext", BindingFlags.Instance | BindingFlags.Public); _coroutineOwner = type?.GetProperty("__4__this"); if (methodInfo == null || _coroutineOwner == null) { bool flag = default(bool); BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(61, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("TabletUI."); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>("WaitOneFrameThenToggleSubMenu"); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" not found; the stats tabs may appear a moment late."); } log.LogWarning(val); } else { harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(Hooks), "AfterPageToggle", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } private static void AfterPageToggle(object __instance) { try { object? value = _coroutineOwner.GetValue(__instance); TabletUI val = (TabletUI)((value is TabletUI) ? value : null); if (val != null) { Tablet(val, "PageToggle"); } } catch (Exception e) { Plugin.Instance.ReportOnce("tablet hook PageToggle", e); } } private static void CaptureSatisfaction(string moment) { try { Plugin.Instance.CaptureDaySatisfaction(moment); } catch (Exception e) { Plugin.Instance.ReportOnce("satisfaction hook " + moment, e); } } private static void Tablet(TabletUI tablet, string source) { try { Plugin.Instance.Panel.OnTabletEvent(tablet, source); } catch (Exception e) { Plugin.Instance.ReportOnce("tablet hook " + source, e); } } } internal static class MethodAddresses { private const string MethodPrefix = "NativeMethodInfoPtr_"; private const string FieldPrefix = "NativeFieldInfoPtr_"; private static readonly Type[] Types = new Type[22] { typeof(GameManager), typeof(ParkSettings), typeof(HistorySystem), typeof(FinanceSystem), typeof(TicketSettings), typeof(TicketPrestigeSettings), typeof(StaffManager), typeof(AINetManager), typeof(AttractionManager), typeof(AttractionInteraction), typeof(AttractionData), typeof(SaunaInteraction), typeof(HotTubInteraction), typeof(ShopInteraction), typeof(VisitorThoughtsSystem), typeof(TrackerSystem), typeof(AIBrain), typeof(AIDataStorage), typeof(VisitorSettings), typeof(SpawnSettings), typeof(AnimationCurve), typeof(Mathf) }; public static void Write(string path) { ProcessModule processModule = ((IEnumerable)Process.GetCurrentProcess().Modules).Cast<ProcessModule>().FirstOrDefault((ProcessModule m) => string.Equals(m.ModuleName, "GameAssembly.dll", StringComparison.OrdinalIgnoreCase)); if (processModule == null) { return; } long num = processModule.BaseAddress.ToInt64(); StringBuilder stringBuilder = new StringBuilder(); StringBuilder stringBuilder2 = stringBuilder; StringBuilder stringBuilder3 = stringBuilder2; StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(68, 2, stringBuilder2); handler.AppendLiteral("# GameAssembly.dll base "); handler.AppendFormatted(num, "x"); handler.AppendLiteral(", size "); handler.AppendFormatted(processModule.ModuleMemorySize, "x"); handler.AppendLiteral(". Addresses are relative to the base."); stringBuilder3.AppendLine(ref handler); Type[] types = Types; foreach (Type type in types) { FieldInfo[] fields = type.GetFields(BindingFlags.DeclaredOnly | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); foreach (FieldInfo fieldInfo in fields) { if (fieldInfo.FieldType != typeof(IntPtr)) { continue; } try { IntPtr intPtr = (IntPtr)fieldInfo.GetValue(null); if (intPtr == IntPtr.Zero) { continue; } if (fieldInfo.Name.StartsWith("NativeMethodInfoPtr_")) { long num4 = Marshal.ReadIntPtr(intPtr).ToInt64(); if (num4 != 0L) { stringBuilder2 = stringBuilder; StringBuilder stringBuilder4 = stringBuilder2; handler = new StringBuilder.AppendInterpolatedStringHandler(5, 3, stringBuilder2); handler.AppendLiteral("M "); handler.AppendFormatted(type.FullName); handler.AppendLiteral("::"); string name = fieldInfo.Name; int length = "NativeMethodInfoPtr_".Length; handler.AppendFormatted(name.Substring(length, name.Length - length)); handler.AppendLiteral(" "); handler.AppendFormatted(num4 - num, "x"); stringBuilder4.AppendLine(ref handler); } } else if (fieldInfo.Name.StartsWith("NativeFieldInfoPtr_")) { stringBuilder2 = stringBuilder; StringBuilder stringBuilder5 = stringBuilder2; handler = new StringBuilder.AppendInterpolatedStringHandler(5, 3, stringBuilder2); handler.AppendLiteral("F "); handler.AppendFormatted(type.FullName); handler.AppendLiteral("::"); string name = fieldInfo.Name; int length = "NativeFieldInfoPtr_".Length; handler.AppendFormatted(name.Substring(length, name.Length - length)); handler.AppendLiteral(" "); handler.AppendFormatted(IL2CPP.il2cpp_field_get_offset(intPtr), "x"); stringBuilder5.AppendLine(ref handler); } } catch (Exception ex) { stringBuilder2 = stringBuilder; StringBuilder stringBuilder6 = stringBuilder2; handler = new StringBuilder.AppendInterpolatedStringHandler(7, 3, stringBuilder2); handler.AppendLiteral("# "); handler.AppendFormatted(type.FullName); handler.AppendLiteral("::"); handler.AppendFormatted(fieldInfo.Name); handler.AppendLiteral(" <"); handler.AppendFormatted(ex.GetType().Name); handler.AppendLiteral(">"); stringBuilder6.AppendLine(ref handler); } } } Directory.CreateDirectory(Path.GetDirectoryName(path)); File.WriteAllText(path, stringBuilder.ToString()); } } [BepInPlugin("com.github.kirby1997.parkstats", "ParkStats", "0.2.1")] public sealed class Plugin : BasePlugin { public const string Id = "com.github.kirby1997.parkstats"; public const string Name = "ParkStats"; public const string Version = "0.2.1"; private readonly HashSet<string> _reported = new HashSet<string>(); private GameReader _reader; private HistoryStore _history; private string _historyKey; private int _historyDays; private bool _diagnosticsLogged; internal static Plugin Instance { get; private set; } internal TabletPanel Panel { get; private set; } private static string DataDirectory => Path.Combine(Paths.ConfigPath, "ParkStats"); public override void Load() { //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Expected O, but got Unknown //IL_0115: Unknown result type (might be due to invalid IL or missing references) //IL_011a: Unknown result type (might be due to invalid IL or missing references) //IL_0135: Expected O, but got Unknown //IL_013f: Unknown result type (might be due to invalid IL or missing references) //IL_0145: Expected O, but got Unknown Instance = this; ConfigEntry<bool> obj = ((BasePlugin)this).Config.Bind<bool>("General", "Enabled", true, "Add the stats tabs to the tablet's Park Management page."); ConfigEntry<float> val = ((BasePlugin)this).Config.Bind<float>("General", "FontScale", 1f, "Multiplier for the text size in the stats tabs."); ConfigEntry<int> val2 = ((BasePlugin)this).Config.Bind<int>("General", "HistoryDays", 60, "How many days the History tab keeps."); ConfigEntry<bool> val3 = ((BasePlugin)this).Config.Bind<bool>("Debug", "WriteDiagnostics", false, "Write diagnostics.txt and method-addresses.txt to BepInEx/config/ParkStats when the Park Management page is opened. They list what the mod reads from the game and the page layout, for fixing the mod after game updates. Turn this on before reporting a problem."); bool flag = default(bool); BepInExInfoLogInterpolatedStringHandler val4; if (!obj.Value) { ManualLogSource log = ((BasePlugin)this).Log; val4 = new BepInExInfoLogInterpolatedStringHandler(27, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val4).AppendFormatted<string>("ParkStats"); ((BepInExLogInterpolatedStringHandler)val4).AppendLiteral(" is disabled in its config."); } log.LogInfo(val4); return; } _historyDays = val2.Value; _reader = new GameReader(((BasePlugin)this).Log); Panel = new TabletPanel(((BasePlugin)this).Log, _reader, History, val.Value, val3.Value ? new Action<TabletUI, IReadOnlyList<string>>(WriteDiagnostics) : null); Harmony val5 = new Harmony("com.github.kirby1997.parkstats"); val5.PatchAll(typeof(Hooks)); Hooks.PatchPageToggle(val5, ((BasePlugin)this).Log); ManualLogSource log2 = ((BasePlugin)this).Log; val4 = new BepInExInfoLogInterpolatedStringHandler(8, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val4).AppendFormatted<string>("ParkStats"); ((BepInExLogInterpolatedStringHandler)val4).AppendLiteral(" "); ((BepInExLogInterpolatedStringHandler)val4).AppendFormatted<string>("0.2.1"); ((BepInExLogInterpolatedStringHandler)val4).AppendLiteral(" loaded"); } log2.LogInfo(val4); } internal void RecordDay() { //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Expected O, but got Unknown ParkSnapshot val = _reader.Read(); if (!(val == (ParkSnapshot)null) && val.MoneyToday.Count != 0) { DayRecord val2 = DayRecord.From(val); HistoryStore obj = History(); if (obj != null) { obj.Add(val2); } _reader.ResetComplaintBaseline(); ManualLogSource log = ((BasePlugin)this).Log; bool flag = default(bool); BepInExInfoLogInterpolatedStringHandler val3 = new BepInExInfoLogInterpolatedStringHandler(49, 4, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val3).AppendLiteral("Recorded day "); ((BepInExLogInterpolatedStringHandler)val3).AppendFormatted<int>(val2.Day); ((BepInExLogInterpolatedStringHandler)val3).AppendLiteral(": income "); ((BepInExLogInterpolatedStringHandler)val3).AppendFormatted<string>(Format.Money((double?)val2.Income)); ((BepInExLogInterpolatedStringHandler)val3).AppendLiteral(", expenses "); ((BepInExLogInterpolatedStringHandler)val3).AppendFormatted<string>(Format.Money((double?)val2.Expenses)); ((BepInExLogInterpolatedStringHandler)val3).AppendLiteral(", "); ((BepInExLogInterpolatedStringHandler)val3).AppendLiteral("satisfaction "); ((BepInExLogInterpolatedStringHandler)val3).AppendFormatted<string>(Format.Percent(val2.Satisfaction)); ((BepInExLogInterpolatedStringHandler)val3).AppendLiteral("."); } log.LogInfo(val3); } } internal void RecordThought(VisitorThoughtsSystem system, VisitorThoughtType type) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) _reader.Complaints.Record(system, type); } internal void CaptureDaySatisfaction(string moment) { _reader.CaptureDaySatisfaction(moment); } internal void ReportOnce(string what, Exception e) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Expected O, but got Unknown if (_reported.Add(what)) { ManualLogSource log = ((BasePlugin)this).Log; bool flag = default(bool); BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(9, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(what); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" failed: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<Exception>(e); } log.LogError(val); } } private HistoryStore History() { //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Expected O, but got Unknown try { string text = _reader.ParkKey(); if (_history == null || text != _historyKey) { _history = new HistoryStore(Path.Combine(DataDirectory, "history-" + text + ".json"), _historyDays); _historyKey = text; } return _history; } catch (Exception e) { ReportOnce("history file", e); return null; } } private void WriteDiagnostics(TabletUI tablet, IReadOnlyList<string> panelLines) { //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Expected O, but got Unknown try { string text = Path.Combine(DataDirectory, "diagnostics.txt"); Diagnostics.Write(text, tablet, _reader, Panel, panelLines); if (!_diagnosticsLogged) { ManualLogSource log = ((BasePlugin)this).Log; bool flag = default(bool); BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(23, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Diagnostics written to "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(text); } log.LogInfo(val); MethodAddresses.Write(Path.Combine(DataDirectory, "method-addresses.txt")); } _diagnosticsLogged = true; } catch (Exception e) { ReportOnce("diagnostics", e); } } } internal sealed class TabletPanel { private readonly record struct Box(float XMin, float YMin, float XMax, float YMax) { public float Width => XMax - XMin; public float Height => YMax - YMin; public float CenterX => (XMin + XMax) / 2f; public float CenterY => (YMin + YMax) / 2f; public override string ToString() { return $"x {N(XMin)}..{N(XMax)} y {N(YMin)}..{N(YMax)} ({N(Width)} x {N(Height)})"; } } private static readonly string[] TabNames = new string[8] { "Park", "Today", "Money", "Rides", "Guests", "Build", "History", "Advice" }; private const int ParkTab = 0; private const long RefreshIntervalMs = 1000L; private const float Padding = 12f; private const float ContentPadding = 44f; private const float MinTabHeight = 36f; private const float MaxTabHeight = 80f; private const float CharactersPerLine = 32f; private static readonly Color PanelColor = new Color(0.04f, 0.17f, 0.32f, 1f); private static readonly Color TabColor = new Color(0.02f, 0.55f, 0.72f, 1f); private static readonly Color SelectedTabColor = new Color(0.96f, 0.78f, 0.08f, 1f); private readonly ManualLogSource _log; private readonly GameReader _reader; private readonly Func<HistoryStore> _history; private readonly float _fontScale; private readonly Action<TabletUI, IReadOnlyList<string>> _diagnose; private readonly Dictionary<string, int> _events = new Dictionary<string, int>(); private readonly List<string> _geometry = new List<string>(); private IntPtr _tabletPointer; private TabletUI _tablet; private bool _broken; private bool _laidOut; private bool _diagnosed; private long _lastLayout; private RectTransform _root; private RectTransform _card; private readonly List<GameObject> _gameStats = new List<GameObject>(); private readonly List<bool> _gameStatsWereActive = new List<bool>(); private GameObject _bar; private GameObject _content; private TextMeshProUGUI _text; private ScrollRect _scroll; private Image[] _tabImages = Array.Empty<Image>(); private int _selected; private long _lastRefresh; public static IReadOnlyList<string> ModTabs => TabNames.Skip(1).ToArray(); public TabletPane