Decompiled source of RogueGolf v0.14.0
plugins/RogueGolf/RogueGolf.Core.dll
Decompiled 13 hours 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.Security.Cryptography; using System.Text; using Microsoft.CodeAnalysis; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyVersion("0.0.0.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 RogueGolf.Core { public static class BalanceMigration { public static int Apply0101(IDictionary<string, float> values, bool alreadyApplied) { if (alreadyApplied) { return 0; } int changed = 0; Replace("TimedLow", 0.6f, 0.35f); Replace("TimedHigh", 1.5f, 2f); Replace("glass_cannon.Outgoing", 1.5f, 2f); Replace("glass_cannon.Incoming", 1.5f, 2f); Replace("golf_kart_warfare.TrafficRate", 2f, 3f); Replace("friendly_fire_sale.Outgoing", 2f, 2.5f); Replace("total_war.Ammo", 8f, 7f); return changed; void Replace(string key, float before, float after) { if (values.TryGetValue(key, out var value) && value == before) { values[key] = after; changed++; } } } } public enum Parameter { ShotPower, Gravity, BallMass, BallScale, Bounce, Friction, Outgoing, Incoming, ItemRate, Ammo, ItemQuality, Protection, TrafficRate, Count } [Flags] public enum Mechanic { None = 0, HotStart = 1, RandomLoadout = 2, EqualLoadout = 4, Pacifist = 8, SuddenInventory = 0x10, ChaosLoadout = 0x20, DeathExplosion = 0x40, ExplosiveCollisions = 0x80, MusicalChairs = 0x100, LeaderTax = 0x200, LastPlaceInsurance = 0x400, ComebackSeason = 0x800, UnstableReality = 0x1000, RandomRules = 0x2000, GravityRoulette = 0x4000, RealityBreakdown = 0x8000, TotalWar = 0x10000, NuclearGolf = 0x20000, LawOfZeus = 0x40000 } public readonly struct Contribution { public readonly Parameter Parameter; public readonly float Factor; public Contribution(Parameter parameter, float factor) { if (parameter < Parameter.ShotPower || parameter >= Parameter.Count || !VoteSettings.Finite(factor) || factor < 0.01f || factor > 120f) { throw new ArgumentException("Invalid contribution"); } Parameter = parameter; Factor = factor; } } public static class CanonicalCatalog { public static float Minimum(Parameter p) { return p switch { Parameter.Friction => 0.01f, Parameter.BallScale => 0.2f, Parameter.Gravity => 0.1f, _ => 0.25f, }; } public static float Maximum(Parameter p) { return p switch { Parameter.ShotPower => 3, Parameter.TrafficRate => 3, Parameter.Bounce => 2, Parameter.ItemQuality => 10, Parameter.ItemRate => 12, Parameter.Ammo => 20, Parameter.BallMass => 10, Parameter.BallScale => 4, _ => 4, }; } public static string TemporaryRuleId(int variant) { return (new string[8] { "power_surge", "dead_arms", "bouncy_ball", "fast_greens", "low_gravity", "small_ball", "thick_skin", "big_ball" })[variant % 8]; } public static bool IsTimed(Mechanic m) { return (m & (Mechanic.UnstableReality | Mechanic.RandomRules | Mechanic.GravityRoulette | Mechanic.RealityBreakdown)) != 0; } public static EffectChannel Required(Parameter p) { switch (p) { case Parameter.ShotPower: return EffectChannel.ShotPower; case Parameter.Gravity: return EffectChannel.Gravity; case Parameter.BallMass: return EffectChannel.BallMass; case Parameter.BallScale: return EffectChannel.Scale; case Parameter.Bounce: return EffectChannel.Bounce; case Parameter.Friction: return EffectChannel.Friction; case Parameter.Outgoing: case Parameter.Incoming: return EffectChannel.Knockback; case Parameter.ItemRate: case Parameter.ItemQuality: return EffectChannel.ItemSpawn; case Parameter.Ammo: return EffectChannel.Inventory; case Parameter.Protection: return EffectChannel.Protection; case Parameter.TrafficRate: return EffectChannel.Traffic; default: throw new ArgumentException("Unknown parameter"); } } public static Contribution[] Variation(Mechanic rule, int variant, ModifierSettings settings = null) { if (!IsTimed(rule)) { return Array.Empty<Contribution>(); } settings = settings ?? new ModifierSettings(); if (rule == Mechanic.GravityRoulette) { return new Contribution[1] { new Contribution(Parameter.Gravity, (new float[4] { settings.Get("RouletteMoon", 0.2f), settings.Get("RouletteLow", 0.6f), 1f, settings.Get("RouletteHigh", 1.5f) })[variant % 4]) }; } RunRandom random = new RunRandom((uint)((variant + 1) * 7919)); Parameter[] array = new Parameter[6] { Parameter.Gravity, Parameter.Bounce, Parameter.Friction, Parameter.ShotPower, Parameter.Incoming, Parameter.BallScale }; if (rule == Mechanic.RealityBreakdown) { return array.Select((Parameter p) => new Contribution(p, (random.Next(2) == 0) ? settings.Get("TimedLow", 0.35f) : settings.Get("TimedHigh", 2f))).ToArray(); } Parameter parameter = array[variant % array.Length]; return new Contribution[1] { new Contribution(parameter, (variant % 2 == 0) ? settings.Get("TimedLow", 0.35f) : settings.Get("TimedHigh", 2f)) }; } public static ModifierDefinition[] Create(ModifierSettings s) { List<ModifierDefinition> result = new List<ModifierDefinition>(); ModifierRarity rarity = ModifierRarity.Common; ModifierRarity rarity2 = ModifierRarity.Rare; ModifierRarity rarity3 = ModifierRarity.Legendary; ModifierRarity rarity4 = ModifierRarity.Boss; EffectDuration duration = EffectDuration.OneHole; Contribution[] terms = Array.Empty<Contribution>(); EffectChannel effectChannel = EffectChannel.Inventory; EffectChannel effectChannel2 = EffectChannel.Knockback; EffectChannel needs = EffectChannel.ShotPower | EffectChannel.Gravity | EffectChannel.Scale | EffectChannel.Bounce | EffectChannel.Friction | effectChannel2; Add("power_surge", "Power Surge", rarity, new Contribution[1] { C(Parameter.ShotPower, s.PowerSurge) }); Add("dead_arms", "Dead Arms", rarity, new Contribution[1] { C(Parameter.ShotPower, 0.8f) }); Add("heavy_ball", "Heavy Ball", rarity, new Contribution[1] { C(Parameter.BallMass, s.HeavyBall) }, Mechanic.None, EffectDuration.Run, "mass"); Add("featherweight", "Featherweight", rarity, new Contribution[1] { C(Parameter.BallMass, s.Featherweight) }, Mechanic.None, EffectDuration.Run, "mass"); Add("bouncy_ball", "Bouncy Ball", rarity, new Contribution[1] { C(Parameter.Bounce, 1.4f) }); Add("big_ball", "Big Ball", rarity, new Contribution[1] { C(Parameter.BallScale, 1.25f) }, Mechanic.None, EffectDuration.Run, "scale"); Add("small_ball", "Small Ball", rarity, new Contribution[1] { C(Parameter.BallScale, 0.75f) }, Mechanic.None, EffectDuration.Run, "scale"); Add("fast_greens", "Fast Greens", rarity, new Contribution[1] { C(Parameter.Friction, 0.6f) }, Mechanic.None, EffectDuration.Run, "friction"); Add("sticky_greens", "Sticky Greens", rarity, new Contribution[1] { C(Parameter.Friction, 1.6f) }, Mechanic.None, EffectDuration.Run, "friction"); Add("item_rush", "Item Rush", rarity, new Contribution[1] { C(Parameter.ItemRate, 2f) }); Add("strong_arms", "Strong Arms", rarity, new Contribution[1] { C(Parameter.Outgoing, 1.25f) }); Add("thick_skin", "Thick Skin", rarity, new Contribution[1] { C(Parameter.Incoming, 0.8f) }); Add("glass_jaw", "Glass Jaw", rarity, new Contribution[1] { C(Parameter.Incoming, 1.25f) }); Add("extra_ammo", "Extra Ammo", rarity, new Contribution[1] { C(Parameter.Ammo, 2f) }); Add("hot_start", "Hot Start", rarity, terms, Mechanic.HotStart, EffectDuration.Run, "starting_loadout", effectChannel, "Basic item each hole"); Add("low_gravity", "Low Gravity", rarity2, new Contribution[1] { C(Parameter.Gravity, s.LowGravity) }, Mechanic.None, EffectDuration.Run, "gravity"); Add("high_gravity", "High Gravity", rarity2, new Contribution[1] { C(Parameter.Gravity, s.HighGravity) }, Mechanic.None, EffectDuration.Run, "gravity"); Add("tiny_ball", "Tiny Ball", rarity2, new Contribution[1] { C(Parameter.BallScale, 0.5f) }, Mechanic.None, EffectDuration.Run, "scale"); Add("giant_ball", "Giant Ball", rarity2, new Contribution[1] { C(Parameter.BallScale, 2f) }, Mechanic.None, EffectDuration.Run, "scale"); Add("high_knockback", "High Knockback", rarity2, new Contribution[1] { C(Parameter.Incoming, 1.75f) }); Add("sumo_golf", "Sumo Golf", rarity2, new Contribution[3] { C(Parameter.BallMass, 3f), C(Parameter.Outgoing, 1.5f), C(Parameter.Incoming, 0.6f) }); Add("pinball", "Pinball", rarity2, new Contribution[2] { C(Parameter.Bounce, 1.7f), C(Parameter.Friction, 0.35f) }); Add("ice_rink", "Ice Rink", rarity2, new Contribution[1] { C(Parameter.Friction, 0.1f) }, Mechanic.None, EffectDuration.Run, "friction"); Add("supercharged", "Supercharged", rarity2, new Contribution[1] { C(Parameter.ShotPower, 1.5f) }); Add("weaklings", "Weaklings", rarity2, new Contribution[1] { C(Parameter.ShotPower, 0.5f) }); Add("arms_race", "Arms Race", rarity2, new Contribution[1] { C(Parameter.ItemQuality, 3f) }); Add("pacifist_golf", "Pacifist Golf", rarity2, terms, Mechanic.Pacifist, duration, "", effectChannel | effectChannel2, "Offensive item hits blocked"); Add("random_loadout", "Random Loadout", rarity2, terms, Mechanic.RandomLoadout, EffectDuration.Run, "starting_loadout", effectChannel, "Independent random item each hole"); Add("equal_opportunity_violence", "Equal Opportunity Violence", rarity2, terms, Mechanic.EqualLoadout, EffectDuration.Run, "starting_loadout", effectChannel, "Same random item for everyone each hole"); Add("glass_cannon", "Glass Cannon", rarity2, new Contribution[2] { C(Parameter.Outgoing, 2f), C(Parameter.Incoming, 2f) }); Add("juggernauts", "Juggernauts", rarity2, new Contribution[2] { C(Parameter.Incoming, 0.4f), C(Parameter.BallMass, 3f) }); Add("leader_tax", "Leader Tax", rarity2, terms, Mechanic.LeaderTax, EffectDuration.Run, "", EffectChannel.ShotPower | EffectChannel.Ranking, "Leader shot power: " + (s.Get("LeaderTaxFactor", 0.8f) * 100f).ToString("0") + "%"); Add("last_place_insurance", "Last Place Insurance", rarity2, terms, Mechanic.LastPlaceInsurance, EffectDuration.Run, "", EffectChannel.ShotPower | EffectChannel.Ranking, "Last place shot power: " + (s.Get("LastPlaceFactor", 1.2f) * 100f).ToString("0") + "%"); Add("no_mercy", "No Mercy", rarity2, new Contribution[1] { C(Parameter.Protection, 0.5f) }); Add("sudden_inventory", "Sudden Inventory", rarity2, terms, Mechanic.SuddenInventory, EffectDuration.Run, "", effectChannel, "Periodic random item grants"); Add("moon_golf", "Moon Golf", rarity3, new Contribution[1] { C(Parameter.Gravity, 0.2f) }, Mechanic.None, EffectDuration.Run, "gravity"); Add("jupiter_golf", "Jupiter Golf", rarity3, new Contribution[1] { C(Parameter.Gravity, 2.5f) }, Mechanic.None, EffectDuration.Run, "gravity"); Add("knockback_apocalypse", "Knockback Apocalypse", rarity3, new Contribution[1] { C(Parameter.Incoming, 3f) }); Add("arsenal", "Arsenal", rarity3, new Contribution[2] { C(Parameter.ItemRate, 4f), C(Parameter.ItemQuality, 5f) }); Add("infinite_warfare", "Infinite Warfare", rarity3, new Contribution[1] { C(Parameter.Ammo, 5f) }); Add("death_explosion", "Death Explosion", rarity3, terms, Mechanic.DeathExplosion, EffectDuration.Run, "", EffectChannel.Explosions, "Explode on elimination"); Add("explosive_collisions", "Explosive Collisions", rarity3, terms, Mechanic.ExplosiveCollisions, EffectDuration.Run, "", EffectChannel.Explosions, "Fast ball landings and player impacts explode; the landing ball ignores its own blast"); Add("balloon_animals", "Balloon Animals", rarity3, new Contribution[4] { C(Parameter.BallScale, 3f), C(Parameter.BallMass, 0.3f), C(Parameter.Incoming, 1.6f), C(Parameter.Bounce, 1.4f) }); Add("cannonballs", "Cannonballs", rarity3, new Contribution[3] { C(Parameter.BallScale, 0.5f), C(Parameter.BallMass, 8f), C(Parameter.Incoming, 0.5f) }); Add("chaos_loadout", "Chaos Loadout", rarity3, terms, Mechanic.ChaosLoadout, EffectDuration.Run, "", effectChannel, "Periodic inventory rerolls"); Add("musical_chairs", "Musical Chairs", rarity3, terms, Mechanic.MusicalChairs, EffectDuration.Run, "", EffectChannel.Teleport, "Periodically swap ball positions"); Add("rubber_world", "Rubber World", rarity3, new Contribution[1] { C(Parameter.Bounce, 2f) }); Add("zero_traction", "Zero Traction", rarity3, new Contribution[1] { C(Parameter.Friction, 0.01f) }, Mechanic.None, EffectDuration.Run, "friction"); Add("maximum_overdrive", "Maximum Overdrive", rarity3, new Contribution[1] { C(Parameter.ShotPower, 2f) }); Add("golf_kart_warfare", "Golf Kart Warfare", rarity3, new Contribution[1] { C(Parameter.TrafficRate, 3f) }, Mechanic.None, EffectDuration.Run, "", EffectChannel.None, "Compatible traffic courses only"); Add("friendly_fire_sale", "Friendly Fire Sale", rarity3, new Contribution[1] { C(Parameter.Outgoing, 2.5f) }, Mechanic.None, EffectDuration.Run, "", EffectChannel.None, "Player versus player knockback boosted"); Add("comeback_season", "Comeback Season", rarity3, terms, Mechanic.ComebackSeason, EffectDuration.Run, "", EffectChannel.ShotPower | EffectChannel.Ranking, "Rank shot power up to " + (s.Get("ComebackMaximum", 1.3f) * 100f).ToString("0") + "%"); Add("unstable_reality", "Unstable Reality", rarity3, terms, Mechanic.UnstableReality, EffectDuration.Run, "", needs, "One physics property rerolls periodically"); Add("random_rules", "Random Rules", rarity3, terms, Mechanic.RandomRules, EffectDuration.Run, "", needs, "One removable physics rule rotates periodically"); Add("law_of_zeus", "LAW OF ZEUS", rarity4, terms, Mechanic.LawOfZeus, duration, "", EffectChannel.WorldEvents, "The gods are displeased"); Add("gravity_roulette", "Gravity Roulette", rarity4, terms, Mechanic.GravityRoulette, duration, "", EffectChannel.Gravity, "Gravity presets cycle periodically"); Add("reality_breakdown", "Reality Breakdown", rarity4, terms, Mechanic.RealityBreakdown, duration, "", needs, "Multiple physics properties reroll"); Add("item_storm", "Item Storm", rarity4, new Contribution[1] { C(Parameter.ItemRate, 10f) }, Mechanic.None, duration); Add("total_war", "Total War", rarity4, new Contribution[1] { C(Parameter.Ammo, 7f) }, Mechanic.TotalWar, duration, "", effectChannel, "Powerful loadouts replenished periodically"); Add("nuclear_golf", "Nuclear Golf", rarity4, terms, Mechanic.NuclearGolf, duration, "", EffectChannel.Explosions, "Large bounded death and impact explosions"); Add("ballpocalypse", "Ballpocalypse", rarity4, new Contribution[2] { C(Parameter.BallScale, 4f), C(Parameter.BallMass, 3f) }, Mechanic.None, duration); Add("micro_golf", "Micro Golf", rarity4, new Contribution[1] { C(Parameter.BallScale, 0.2f) }, Mechanic.None, duration); Add("orbital_golf", "Orbital Golf", rarity4, new Contribution[2] { C(Parameter.Gravity, 0.15f), C(Parameter.ShotPower, 2.5f) }, Mechanic.None, duration); Add("pinball_hell", "Pinball Hell", rarity4, new Contribution[3] { C(Parameter.Bounce, 2f), C(Parameter.Friction, 0.05f), C(Parameter.Incoming, 3f) }, Mechanic.None, duration); Add("arsenal_apocalypse", "Arsenal Apocalypse", rarity4, new Contribution[3] { C(Parameter.ItemRate, 10f), C(Parameter.ItemQuality, 8f), C(Parameter.Ammo, 8f) }, Mechanic.None, duration); return result.ToArray(); void Add(string id, string name, ModifierRarity modifierRarity, Contribution[] source, Mechanic rule = Mechanic.None, EffectDuration effectDuration = EffectDuration.Run, string group = "", EffectChannel effectChannel4 = EffectChannel.None, string detail = "") { Contribution[] array = source.Select((Contribution c) => C(c.Parameter, s.Get(id + "." + c.Parameter, c.Factor))).ToArray(); Contribution[] array2 = array; for (int num = 0; num < array2.Length; num++) { Contribution contribution = array2[num]; if (contribution.Factor < Minimum(contribution.Parameter) || contribution.Factor > Maximum(contribution.Parameter)) { throw new ArgumentException("Tuning outside channel bounds: " + id); } } EffectChannel effectChannel3 = effectChannel4; array2 = array; for (int num = 0; num < array2.Length; num++) { Contribution contribution2 = array2[num]; effectChannel3 |= Required(contribution2.Parameter); } string text = string.Join(", ", array.Select((Contribution c) => (c.Factor * 100f).ToString("0", CultureInfo.InvariantCulture) + "% " + c.Parameter)); if (detail != "") { text = text + ((text == "") ? "" : ". ") + detail; } result.Add(new ModifierDefinition(id, name, modifierRarity, effectChannel3, (array.Length == 1) ? array[0].Factor : 1f, group, effectDuration, modifierRarity switch { ModifierRarity.Legendary => 2, ModifierRarity.Rare => 3, ModifierRarity.Common => 4, _ => 1, }, array, rule, modifierRarity.ToString() + " | " + text + ". " + ((effectDuration == EffectDuration.OneHole) ? "Next hole only." : "For this run."))); } static Contribution C(Parameter p, float f) { return new Contribution(p, f); } } } public static class Conditions { public static string Direction(ModifierDefinition d) { if (CanonicalCatalog.IsTimed(d.Rule)) { return "~"; } bool flag = d.Contributions.Any((Contribution c) => c.Factor > 1f); bool flag2 = d.Contributions.Any((Contribution c) => c.Factor < 1f); if (!(flag && flag2)) { if (!flag) { if (!flag2) { return ""; } return "−"; } return "+"; } return "±"; } public static KeyValuePair<string, string>[] Rows(ModifierRegistry registry, ModifierSettings settings, IEnumerable<ActiveModifier> effects, int hole, float personalShot = 1f) { ActiveModifier[] array = effects.Where((ActiveModifier e) => !e.Suspended && e.FirstHole <= hole).ToArray(); EffectValues effectValues = registry.Compose(array); HashSet<Parameter> hashSet = new HashSet<Parameter>(); Mechanic mechanic = Mechanic.None; ActiveModifier[] array2 = array; foreach (ActiveModifier activeModifier in array2) { ModifierDefinition modifierDefinition = registry.Get(activeModifier.Id); mechanic |= modifierDefinition.Rule; foreach (Contribution item in modifierDefinition.Contributions.Concat((modifierDefinition.Rule == Mechanic.RandomRules) ? registry.Get(CanonicalCatalog.TemporaryRuleId(activeModifier.Variant)).Contributions : CanonicalCatalog.Variation(modifierDefinition.Rule, activeModifier.Variant, settings))) { hashSet.Add(item.Parameter); } } if (Math.Abs(personalShot - 1f) > 0.0001f) { hashSet.Add(Parameter.ShotPower); } List<KeyValuePair<string, string>> rows = new List<KeyValuePair<string, string>>(); if ((mechanic & Mechanic.Pacifist) != Mechanic.None) { Add("pacifist", "Offensive items disabled"); } Parameter[] array3 = new Parameter[13] { Parameter.Gravity, Parameter.ShotPower, Parameter.BallScale, Parameter.BallMass, Parameter.Bounce, Parameter.Friction, Parameter.Outgoing, Parameter.Incoming, Parameter.Ammo, Parameter.ItemRate, Parameter.ItemQuality, Parameter.Protection, Parameter.TrafficRate }; for (int num = 0; num < array3.Length; num++) { Parameter parameter = array3[num]; if (hashSet.Contains(parameter)) { float value = ((parameter == Parameter.ShotPower) ? Math.Max(0.25f, Math.Min(3f, effectValues[parameter] * personalShot)) : effectValues[parameter]); string text = ((parameter == Parameter.ShotPower) ? "Shot power" : EffectPresentation.ParameterName(parameter)); if (parameter == Parameter.Outgoing && array.Any((ActiveModifier e) => e.Id == "friendly_fire_sale")) { text += " (PvP)"; } Add(parameter.ToString(), text + " ×" + EffectPresentation.Number(value)); } } if ((mechanic & Mechanic.LawOfZeus) != Mechanic.None) { Add("zeus", "Thunderstorms across the course"); } if ((mechanic & Mechanic.TotalWar) != Mechanic.None) { Add("inventory", "Powerful loadouts refill"); } else if ((mechanic & Mechanic.ChaosLoadout) != Mechanic.None) { Add("inventory", "Inventories reroll"); } else if ((mechanic & Mechanic.SuddenInventory) != Mechanic.None) { Add("inventory", "Random items granted"); } if ((mechanic & Mechanic.MusicalChairs) != Mechanic.None) { Add("swap", "Ball positions swap"); } if ((mechanic & Mechanic.Pacifist) == 0) { if ((mechanic & (Mechanic.DeathExplosion | Mechanic.NuclearGolf)) != Mechanic.None) { Add("death", "Explode on elimination"); } if ((mechanic & (Mechanic.ExplosiveCollisions | Mechanic.NuclearGolf)) != Mechanic.None) { Add("impact", "Explosive impacts"); } } return rows.ToArray(); void Add(string id, string value2) { rows.Add(new KeyValuePair<string, string>(id, value2)); } } } public enum EffectGlyph { Bolt, Weight, Feather, Ball, Bounce, Ice, Glue, Gift, Fist, Shield, Ammo, Orbit, Dice, Peace, Crown, Flag, Clock, Explosion, Swap, Car, World, Skull, Infinity, Storm } public readonly struct EffectSymbol { public readonly EffectGlyph Glyph; public readonly int Variant; public EffectSymbol(EffectGlyph glyph, int variant = 0) { Glyph = glyph; Variant = variant; } } public static class EffectPresentation { private static readonly Dictionary<string, EffectSymbol> symbols = BuildSymbols(); private static Dictionary<string, EffectSymbol> BuildSymbols() { Dictionary<string, EffectSymbol> result = new Dictionary<string, EffectSymbol>(); Add(EffectGlyph.Bolt, new string[5] { "power_surge", "supercharged", "maximum_overdrive", "dead_arms", "weaklings" }); Add(EffectGlyph.Weight, new string[3] { "heavy_ball", "sumo_golf", "cannonballs" }); Add(EffectGlyph.Feather, new string[2] { "featherweight", "balloon_animals" }); Add(EffectGlyph.Ball, new string[6] { "small_ball", "big_ball", "tiny_ball", "giant_ball", "micro_golf", "ballpocalypse" }); Add(EffectGlyph.Bounce, new string[4] { "bouncy_ball", "pinball", "rubber_world", "pinball_hell" }); Add(EffectGlyph.Ice, new string[3] { "fast_greens", "ice_rink", "zero_traction" }); Add(EffectGlyph.Glue, new string[1] { "sticky_greens" }); Add(EffectGlyph.Gift, new string[4] { "item_rush", "hot_start", "arsenal", "arsenal_apocalypse" }); Add(EffectGlyph.Fist, new string[5] { "strong_arms", "high_knockback", "glass_cannon", "knockback_apocalypse", "friendly_fire_sale" }); Add(EffectGlyph.Shield, new string[5] { "thick_skin", "glass_jaw", "juggernauts", "no_mercy", "last_place_insurance" }); Add(EffectGlyph.Ammo, new string[3] { "extra_ammo", "arms_race", "total_war" }); Add(EffectGlyph.Orbit, new string[6] { "low_gravity", "high_gravity", "moon_golf", "jupiter_golf", "orbital_golf", "gravity_roulette" }); Add(EffectGlyph.Dice, new string[4] { "random_loadout", "equal_opportunity_violence", "chaos_loadout", "random_rules" }); Add(EffectGlyph.Peace, new string[1] { "pacifist_golf" }); Add(EffectGlyph.Crown, new string[1] { "leader_tax" }); Add(EffectGlyph.Flag, new string[1] { "comeback_season" }); Add(EffectGlyph.Clock, new string[1] { "sudden_inventory" }); Add(EffectGlyph.Explosion, new string[3] { "death_explosion", "explosive_collisions", "nuclear_golf" }); Add(EffectGlyph.Swap, new string[1] { "musical_chairs" }); Add(EffectGlyph.Car, new string[1] { "golf_kart_warfare" }); Add(EffectGlyph.World, new string[2] { "unstable_reality", "reality_breakdown" }); Add(EffectGlyph.Infinity, new string[1] { "infinite_warfare" }); Add(EffectGlyph.Storm, new string[2] { "item_storm", "law_of_zeus" }); return result; void Add(EffectGlyph glyph, string[] ids) { for (int i = 0; i < ids.Length; i++) { result.Add(ids[i], new EffectSymbol(glyph, i + 1)); } } } public static EffectSymbol Symbol(string id) { if (!symbols.TryGetValue(id, out var value)) { throw new ArgumentException("Missing effect icon: " + id); } return value; } public static string Number(float value) { return value.ToString("0.##", CultureInfo.InvariantCulture); } public static string ParameterName(Parameter p) { return (new string[13] { "Shot speed", "Gravity", "Ball mass", "Ball size", "Bounce", "Ground friction", "Knockback dealt", "Knockback received", "Item spawn rate", "Item uses", "Powerful-item odds", "Recovery protection", "Traffic rate" })[(int)p]; } public static string Factor(Contribution c) { return ParameterName(c.Parameter) + " ×" + Number(c.Factor); } public static string Status(ActiveModifier effect, int currentHole) { string text = ((effect.RemainingHoles < 0) ? "For this run" : ((effect.FirstHole > currentHole) ? "Next hole only" : "This hole only")); return (effect.Suspended ? "Suspended · " : ((effect.FirstHole > currentHole) ? "Upcoming · " : "")) + text; } public static string Details(ModifierDefinition d, ModifierSettings settings, ActiveModifier active = null) { List<string> list = d.Contributions.Select(Factor).ToList(); string text = "Every " + Number(settings.Interval) + "s"; switch (d.Rule) { case Mechanic.LawOfZeus: list.Add("The gods are displeased. Continuous vanilla storms target greens, players, balls, item points and fairway routes. Host-limited to at most seven storms; one hole only."); break; case Mechanic.HotStart: list.Add("Start each hole with a basic item."); break; case Mechanic.RandomLoadout: list.Add("Each player starts with an independently drawn item."); break; case Mechanic.EqualLoadout: list.Add("Everyone starts with the same randomly drawn item."); break; case Mechanic.Pacifist: list.Add("Offensive item use and offensive item hits are blocked."); break; case Mechanic.SuddenInventory: list.Add(text + ", receive a random item if a slot is free."); break; case Mechanic.ChaosLoadout: list.Add(text + ", inventories reroll. Active item use delays the reroll."); break; case Mechanic.MusicalChairs: list.Add(text + ", balls swap positions. Requires at least two balls."); break; case Mechanic.TotalWar: list.Add(text + ", powerful loadouts are replenished."); break; case Mechanic.LeaderTax: list.Add("Leader shot speed ×" + Number(settings.Get("LeaderTaxFactor", 0.8f)) + ". No solo penalty."); break; case Mechanic.LastPlaceInsurance: list.Add("Last-place shot speed ×" + Number(settings.Get("LastPlaceFactor", 1.2f)) + ". Requires multiple players."); break; case Mechanic.ComebackSeason: list.Add("Rank-based shot speed assistance, up to ×" + Number(settings.Get("ComebackMaximum", 1.3f)) + ". Requires multiple players."); break; case Mechanic.DeathExplosion: case Mechanic.ExplosiveCollisions: case Mechanic.NuclearGolf: list.Add((d.Rule == Mechanic.DeathExplosion) ? "Explode on elimination." : ((d.Rule == Mechanic.ExplosiveCollisions) ? "Fast ball landings and player impacts cause explosions." : "Eliminations, fast ball landings and player impacts cause large explosions.")); if (d.Rule != Mechanic.DeathExplosion) { list.Add("The landing ball is immune to its own blast; other targets are affected normally."); } list.Add("Blast radius " + Number(Math.Min(12f, settings.ExplosionRadius * (float)((d.Rule != Mechanic.NuclearGolf) ? 1 : 2))) + "m · cooldown " + Number(settings.ExplosionCooldown) + "s" + ((d.Rule == Mechanic.DeathExplosion) ? "" : (" · impact threshold " + Number(settings.ImpactThreshold)))); break; case Mechanic.UnstableReality: case Mechanic.RandomRules: case Mechanic.GravityRoulette: case Mechanic.RealityBreakdown: list.Add(text + ((d.Rule == Mechanic.GravityRoulette) ? ", gravity cycles." : ((d.Rule == Mechanic.RandomRules) ? ", a temporary rule rotates." : ", physics rerolls."))); if (d.Rule == Mechanic.UnstableReality || d.Rule == Mechanic.RealityBreakdown) { list.Add("Reroll factors: ×" + Number(settings.Get("TimedLow", 0.35f)) + " / ×" + Number(settings.Get("TimedHigh", 2f)) + " (combined limits apply)."); } if (active != null) { Contribution[] source = ((d.Rule == Mechanic.RandomRules) ? ModifierRegistry.Create(settings).Get(CanonicalCatalog.TemporaryRuleId(active.Variant)).Contributions : CanonicalCatalog.Variation(d.Rule, active.Variant, settings)); list.Add("Current: " + string.Join(" · ", source.Select(Factor))); } break; } if (d.Id == "golf_kart_warfare") { list.Add("Only active on courses with supported traffic."); } if (d.Id == "friendly_fire_sale") { list.Add("Extra outgoing knockback applies to player-versus-player hits only; native team protections remain."); } return string.Join("\n", list); } public static bool HasCycle(Mechanic rule) { if (!CanonicalCatalog.IsTimed(rule) && rule != Mechanic.SuddenInventory && rule != Mechanic.ChaosLoadout && rule != Mechanic.MusicalChairs) { return rule == Mechanic.TotalWar; } return true; } public static string CycleValues(ModifierDefinition definition, ModifierSettings settings, ActiveModifier active) { if (CanonicalCatalog.IsTimed(definition.Rule)) { Contribution[] source = ((definition.Rule == Mechanic.RandomRules) ? ModifierRegistry.Create(settings).Get(CanonicalCatalog.TemporaryRuleId(active.Variant)).Contributions : CanonicalCatalog.Variation(definition.Rule, active.Variant, settings)); return string.Join(" · ", source.Select(Factor)); } if (definition.Rule != Mechanic.SuddenInventory) { if (definition.Rule != Mechanic.ChaosLoadout) { if (definition.Rule != Mechanic.MusicalChairs) { return "Powerful loadouts replenish"; } return "Ball positions swap"; } return "Inventory reroll"; } return "Random item when a slot is free"; } public static string Combined(ModifierRegistry registry, IEnumerable<ActiveModifier> active) { EffectValues values = registry.Compose(active); IEnumerable<string> values2 = from Parameter p in Enum.GetValues(typeof(Parameter)) where p != Parameter.Count && Math.Abs(values[p] - 1f) > 0.0001f select Factor(new Contribution(p, values[p])); string text = string.Join(" · ", values2); if (!(text == "")) { return text; } return "Standard physics. Item and special rules are listed below."; } } public readonly struct GravityVector { public readonly float X; public readonly float Y; public readonly float Z; public GravityVector(float x, float y, float z) { if (!Valid(x) || !Valid(y) || !Valid(z)) { throw new ArgumentOutOfRangeException("x"); } X = x; Y = y; Z = z; } private static bool Valid(float n) { if (VoteSettings.Finite(n)) { return Math.Abs(n) <= 1000f; } return false; } public GravityVector Scale(float factor) { return new GravityVector(X * factor, Y * factor, Z * factor); } } public sealed class GravityLease { private readonly Func<GravityVector> read; private readonly Action<GravityVector> write; private GravityVector original; private string run = ""; public bool Active => run != ""; public GravityLease(Func<GravityVector> read, Action<GravityVector> write) { this.read = read; this.write = write; } public void Apply(string runId, GravityVector hostBaseline, float factor = 0.6f) { if (string.IsNullOrEmpty(runId)) { throw new ArgumentException("Effect requires a run identity"); } if (!VoteSettings.Finite(factor) || factor < 0.1f || factor > 4f) { throw new ArgumentOutOfRangeException("factor"); } if (run != runId) { Restore(); original = read(); run = runId; } write(hostBaseline.Scale(factor)); } public void Restore() { if (Active) { write(original); run = ""; } } } public static class LowGravity { public const string Id = "low_gravity"; public const float Factor = 0.6f; public static VoteOption[] Candidates(bool skip) { VoteOption voteOption = new VoteOption("low_gravity", "Low Gravity", "60% gravity for balls and players. Lasts this run; does not stack."); if (skip) { return new VoteOption[2] { voteOption, new VoteOption("skip", "Skip", "No new modifier. Uses one shared skip.") }; } return new VoteOption[1] { voteOption }; } } public sealed class IntermissionGate { private Action continuation; private Func<bool> valid; private string run = ""; private int hole = -1; public bool Held => continuation != null; public bool WasIntercepted(string runId, int holeIndex) { if (run == runId) { return hole == holeIndex; } return false; } public bool Hold(string runId, int holeIndex, Action resume, Func<bool> isValid) { if (Held || WasIntercepted(runId, holeIndex)) { return false; } if (resume == null || isValid == null) { throw new ArgumentNullException(); } run = runId; hole = holeIndex; continuation = resume; valid = isValid; return true; } public bool Release() { if (!Held) { return false; } Action action = continuation; Func<bool> func = valid; continuation = null; valid = null; if (!func()) { return false; } action(); return true; } public void Cancel() { continuation = null; valid = null; run = ""; hole = -1; } } [Flags] public enum EffectChannel { None = 0, ShotPower = 1, Gravity = 2, BallMass = 4, Scale = 8, Bounce = 0x10, Friction = 0x20, Knockback = 0x40, ItemSpawn = 0x80, Inventory = 0x100, Explosions = 0x200, Teleport = 0x400, Ranking = 0x800, Protection = 0x1000, Traffic = 0x2000, WorldEvents = 0x4000, All = 0x7FFF } public enum ModifierRarity { Common, Rare, Legendary, Boss } public enum EffectDuration { Run, OneHole } public enum StackPolicy { Unique } public sealed class ModifierDefinition { private readonly Contribution[] contributions; public string Id { get; } public string Name { get; } public string Description { get; } public ModifierRarity Rarity { get; } public EffectDuration Duration { get; } public StackPolicy Stacking => StackPolicy.Unique; public string ExclusiveGroup { get; } public EffectChannel Channel { get; } public float Factor { get; } public int Weight { get; } public Contribution[] Contributions => (Contribution[])contributions.Clone(); public Mechanic Rule { get; } public bool TemporarySafe => Rule == Mechanic.None; public ModifierDefinition(string id, string name, ModifierRarity rarity, EffectChannel channel, float factor, string group = "", EffectDuration duration = EffectDuration.Run, int weight = 1, Contribution[] contributions = null, Mechanic rule = Mechanic.None, string description = null) { if (string.IsNullOrEmpty(id) || (!ValidFactor(factor) && contributions == null) || weight < 1 || weight > 100 || channel == EffectChannel.None || (channel & ~EffectChannel.All) != EffectChannel.None) { throw new ArgumentException("Invalid modifier definition"); } this.contributions = ((contributions != null) ? ((Contribution[])contributions.Clone()) : new Contribution[1] { new Contribution(channel switch { EffectChannel.Gravity => Parameter.Gravity, EffectChannel.ShotPower => Parameter.ShotPower, _ => Parameter.BallMass, }, factor) }); Rule = rule; Id = id; Name = name; Rarity = rarity; Channel = channel; Factor = factor; ExclusiveGroup = group; Duration = duration; Weight = weight; string text = (factor * 100f).ToString("0", CultureInfo.InvariantCulture) + "%"; Description = description ?? (rarity.ToString() + " | " + text + " " + channel switch { EffectChannel.Gravity => "global gravity", EffectChannel.ShotPower => "ball shot speed", _ => "ball mass", } + ". " + ((duration == EffectDuration.Run) ? "For this run." : "Next hole only.")); } public static bool ValidFactor(float f) { if (VoteSettings.Finite(f) && f >= 0.1f) { return f <= 4f; } return false; } } public sealed class ModifierSettings { private readonly Dictionary<string, float> extra; public KeyValuePair<string, float>[] Extra => extra.OrderBy<KeyValuePair<string, float>, string>((KeyValuePair<string, float> p) => p.Key, StringComparer.Ordinal).ToArray(); public float Interval => Get("IntervalSeconds", 20f); public float ImpactThreshold => Get("ImpactThreshold", 12f); public float ExplosionCooldown => Get("ExplosionCooldown", 2f); public float ExplosionRadius => Get("ExplosionRadius", 5f); public float PowerSurge { get; } public float HeavyBall { get; } public float Featherweight { get; } public float LowGravity { get; } public float HighGravity { get; } public float Get(string key, float fallback) { if (!extra.TryGetValue(key, out var value)) { return fallback; } return value; } public ModifierSettings(float power = 1.2f, float heavy = 2f, float feather = 0.5f, float low = 0.6f, float high = 1.5f, IEnumerable<KeyValuePair<string, float>> tuning = null) { if (!ModifierDefinition.ValidFactor(power) || !ModifierDefinition.ValidFactor(heavy) || !ModifierDefinition.ValidFactor(feather) || !ModifierDefinition.ValidFactor(low) || !ModifierDefinition.ValidFactor(high) || power < 1f || power > 3f || heavy < 1f || feather > 1f || low > 1f || high < 1f) { throw new ArgumentOutOfRangeException("power"); } extra = ((tuning == null) ? new Dictionary<string, float>() : tuning.ToDictionary<KeyValuePair<string, float>, string, float>((KeyValuePair<string, float> p) => p.Key, (KeyValuePair<string, float> p) => p.Value, StringComparer.Ordinal)); if (extra.Count > 256 || extra.Any((KeyValuePair<string, float> p) => p.Key.Length > 64 || !VoteSettings.Finite(p.Value) || p.Value < 0.01f || p.Value > 120f)) { throw new ArgumentException("Invalid tuning"); } if (Interval < 5f || Interval > 120f || ImpactThreshold < 5f || ImpactThreshold > 80f || ExplosionCooldown < 0.5f || ExplosionCooldown > 30f || ExplosionRadius < 1f || ExplosionRadius > 12f) { throw new ArgumentException("Unsafe runtime settings"); } PowerSurge = power; HeavyBall = heavy; Featherweight = feather; LowGravity = low; HighGravity = high; } } public sealed class ActiveModifier { private int lastCompleted = -1; public string Id { get; } public int FirstHole { get; } public int RemainingHoles { get; private set; } public bool Suspended { get; set; } public int Variant { get; set; } public ActiveModifier(string id, int firstHole, int remaining = -1, bool suspended = false) { Id = id; FirstHole = firstHole; RemainingHoles = remaining; Suspended = suspended; } public bool CompleteHole(int hole) { if (hole < FirstHole || hole <= lastCompleted || RemainingHoles < 1) { return false; } lastCompleted = hole; RemainingHoles--; return RemainingHoles == 0; } public ActiveModifier Copy() { return new ActiveModifier(Id, FirstHole, RemainingHoles, Suspended) { lastCompleted = lastCompleted, Variant = Variant }; } } public readonly struct EffectValues { private readonly float[] values; public float this[Parameter channel] { get { if (values != null) { return values[(int)channel]; } return 1f; } } public float ShotPower => this[Parameter.ShotPower]; public float Gravity => this[Parameter.Gravity]; public float BallMass => this[Parameter.BallMass]; public static EffectValues Normal => new EffectValues(1f, 1f, 1f); public EffectValues(float shot, float gravity, float mass) { values = Enumerable.Repeat(1f, 13).ToArray(); values[0] = shot; values[1] = gravity; values[2] = mass; } public EffectValues(float[] source) { values = (float[])source.Clone(); } } public sealed class ModifierRegistry { private readonly Dictionary<string, ModifierDefinition> definitions; private readonly ModifierSettings settings; public ModifierDefinition[] Definitions => definitions.Values.OrderBy<ModifierDefinition, string>((ModifierDefinition d) => d.Id, StringComparer.Ordinal).ToArray(); public ModifierRegistry(IEnumerable<ModifierDefinition> source, ModifierSettings settings = null) { this.settings = settings ?? new ModifierSettings(); definitions = source.ToDictionary<ModifierDefinition, string>((ModifierDefinition d) => d.Id, StringComparer.Ordinal); } public ModifierDefinition Get(string id) { if (!definitions.TryGetValue(id, out var value)) { return null; } return value; } public static ModifierRegistry Create(ModifierSettings s) { return new ModifierRegistry(CanonicalCatalog.Create(s), s); } public EffectValues Compose(IEnumerable<ActiveModifier> effects) { double[] array = Enumerable.Repeat(1.0, 13).ToArray(); foreach (ActiveModifier item in effects.Where((ActiveModifier e) => !e.Suspended).OrderBy<ActiveModifier, string>((ActiveModifier e) => e.Id, StringComparer.Ordinal)) { ModifierDefinition modifierDefinition = Get(item.Id) ?? throw new ArgumentException("Unknown effect"); foreach (Contribution item2 in modifierDefinition.Contributions.Concat((modifierDefinition.Rule == Mechanic.RandomRules) ? Get(CanonicalCatalog.TemporaryRuleId(item.Variant)).Contributions : CanonicalCatalog.Variation(modifierDefinition.Rule, item.Variant, settings))) { array[(int)item2.Parameter] *= item2.Factor; } } return new EffectValues(array.Select((double v, int i) => Clamp(v, CanonicalCatalog.Minimum((Parameter)i), CanonicalCatalog.Maximum((Parameter)i))).ToArray()); } private static float Clamp(double x, double min, double max) { return (float)Math.Max(min, Math.Min(max, x)); } public void Validate(ActiveModifier[] effects, string[] available, PersistenceMode persistence = PersistenceMode.StackForRun) { if (available.Distinct().Count() != available.Length || available.Any((string id) => Get(id) == null) || effects.Select((ActiveModifier e) => e.Id).Distinct().Count() != effects.Length) { throw new ArgumentException("Unknown or duplicate modifiers"); } HashSet<string> hashSet = new HashSet<string>(); foreach (ActiveModifier activeModifier in effects) { ModifierDefinition modifierDefinition = Get(activeModifier.Id); if (modifierDefinition == null || !available.Contains(activeModifier.Id) || activeModifier.FirstHole < 0 || activeModifier.FirstHole > 999 || activeModifier.Variant < 0 || activeModifier.Variant > 31 || (!CanonicalCatalog.IsTimed(modifierDefinition.Rule) && activeModifier.Variant != 0) || activeModifier.RemainingHoles != ((modifierDefinition.Duration != EffectDuration.Run || persistence == PersistenceMode.PerHole) ? 1 : (-1)) || (modifierDefinition.ExclusiveGroup != "" && !hashSet.Add(modifierDefinition.ExclusiveGroup))) { throw new ArgumentException("Invalid active modifier"); } } } } public sealed class ModifierManager { private readonly HashSet<string> available; private readonly List<ActiveModifier> active = new List<ActiveModifier>(); private int breatherHole = -1; public ModifierRegistry Registry { get; } public ActiveModifier[] Active => active.Select(delegate(ActiveModifier e) { ActiveModifier activeModifier = e.Copy(); if (breatherHole >= 0) { activeModifier.Suspended = true; } return activeModifier; }).ToArray(); public string[] Available => available.OrderBy<string, string>((string id) => id, StringComparer.Ordinal).ToArray(); public EffectValues Values => Registry.Compose(Active); public bool HasEligible => Available.Any(CanActivate); public Func<ModifierDefinition, bool> ContextAllows { get; set; } = (ModifierDefinition d) => true; public ModifierManager(ModifierRegistry registry, IEnumerable<string> enabled, EffectChannel capabilities) { Registry = registry; available = new HashSet<string>(enabled.Where((string id) => registry.Get(id) != null && (registry.Get(id).Channel & capabilities) == registry.Get(id).Channel)); } public bool CanActivate(string id) { ModifierDefinition d = Registry.Get(id); if (d != null && ContextAllows(d) && available.Contains(id)) { return !active.Any((ActiveModifier a) => a.Id == id || (d.ExclusiveGroup != "" && Registry.Get(a.Id).ExclusiveGroup == d.ExclusiveGroup)); } return false; } public bool Activate(string id, int firstHole, PersistenceMode persistence = PersistenceMode.StackForRun) { if (firstHole < 0 || firstHole > 999 || !CanActivate(id)) { return false; } ModifierDefinition modifierDefinition = Registry.Get(id); active.Add(new ActiveModifier(id, firstHole, (modifierDefinition.Duration != EffectDuration.Run || persistence == PersistenceMode.PerHole) ? 1 : (-1))); return true; } public bool CompleteHole(int hole) { bool flag = breatherHole >= 0 && hole >= breatherHole; if (flag) { breatherHole = -1; } return active.RemoveAll((ActiveModifier e) => e.CompleteHole(hole)) > 0 || flag; } public void SuspendForHole(int hole) { if (hole < 0 || hole > 999) { throw new ArgumentOutOfRangeException("hole"); } breatherHole = hole; } public bool Remove(string id) { return active.RemoveAll((ActiveModifier e) => e.Id == id) > 0; } public bool Suspend(string id, bool value) { ActiveModifier activeModifier = active.FirstOrDefault((ActiveModifier a) => a.Id == id); if (activeModifier == null || activeModifier.Suspended == value) { return false; } activeModifier.Suspended = value; return true; } public bool SetVariant(string id, int variant) { ActiveModifier activeModifier = active.FirstOrDefault((ActiveModifier a) => a.Id == id); if (activeModifier == null || variant < 0 || variant > 31 || !CanonicalCatalog.IsTimed(Registry.Get(id).Rule)) { return false; } activeModifier.Variant = variant; return true; } public VoteOption[] Candidates(int count, bool skip, RunRandom random) { if (count < 1 || count > 5) { throw new ArgumentOutOfRangeException("count"); } List<ModifierDefinition> list = Available.Where(CanActivate).Select(Registry.Get).ToList(); List<VoteOption> list2 = new List<VoteOption>(); while (list.Count > 0 && list2.Count < count) { int num = random.Next(list.Sum((ModifierDefinition d) => d.Weight)); int index = 0; while (num >= list[index].Weight) { num -= list[index++].Weight; } ModifierDefinition modifierDefinition = list[index]; list.RemoveAt(index); list2.Add(new VoteOption(modifierDefinition.Id, modifierDefinition.Name, modifierDefinition.Description)); } if (list2.Count > 0 && skip) { list2.Add(new VoteOption("skip", "Skip", "No new modifier. Uses one shared skip.")); } return list2.ToArray(); } public void Clear() { active.Clear(); breatherHole = -1; } } public sealed class ScalarLease { private readonly Func<float> read; private readonly Action<float> write; private bool owned; private float baseline; public ScalarLease(Func<float> read, Action<float> write) { this.read = read; this.write = write; } public void Apply(float factor) { if (!VoteSettings.Finite(factor) || factor <= 0f || factor > 10f) { throw new ArgumentOutOfRangeException("factor"); } if (factor == 1f) { Restore(); return; } if (!owned) { baseline = read(); owned = true; } write(baseline * factor); } public void Restore() { if (owned) { write(baseline); owned = false; } } } public enum SelectionMode { Vote, Random, Hybrid } public enum PersistenceMode { StackForRun, PerHole, Mixed } public enum SkipMode { SharedRun, PerPlayer, Unlimited, Disabled } public enum SkipBehavior { NoNewModifier, VanillaHole } public sealed class ProgressionSettings { private readonly int[] tiers; private readonly int[] starts; private readonly Dictionary<string, int> weights; public SelectionMode Selection { get; } public PersistenceMode Persistence { get; } public SkipMode Skips { get; } public SkipBehavior SkipEffect { get; } public bool BossEnabled { get; } public int BossInterval { get; } public bool RandomSkips { get; } public int RandomSkipWeight { get; } public int RandomModifierWeight { get; } public int[] TierWeights => (int[])tiers.Clone(); public int[] BandStarts => (int[])starts.Clone(); public KeyValuePair<string, int>[] ModifierWeights => weights.OrderBy<KeyValuePair<string, int>, string>((KeyValuePair<string, int> p) => p.Key, StringComparer.Ordinal).ToArray(); public int Weight(string id) { if (!weights.TryGetValue(id, out var value)) { return 100; } return value; } public ProgressionSettings(SelectionMode selection = SelectionMode.Vote, PersistenceMode persistence = PersistenceMode.StackForRun, SkipMode skips = SkipMode.SharedRun, SkipBehavior skipEffect = SkipBehavior.NoNewModifier, bool bossEnabled = true, int bossInterval = 4, bool randomSkips = false, int randomSkipWeight = 10, int randomModifierWeight = 100, int[] tierWeights = null, int[] bandStarts = null, IEnumerable<KeyValuePair<string, int>> modifierWeights = null) { if (!Enum.IsDefined(typeof(SelectionMode), selection) || !Enum.IsDefined(typeof(PersistenceMode), persistence) || !Enum.IsDefined(typeof(SkipMode), skips) || !Enum.IsDefined(typeof(SkipBehavior), skipEffect) || bossInterval < 1 || bossInterval > 99 || randomSkipWeight < 0 || randomSkipWeight > 10000 || randomModifierWeight < 1 || randomModifierWeight > 10000) { throw new ArgumentException("Invalid progression settings"); } tiers = (int[])(tierWeights ?? new int[14] { 75, 25, 0, 55, 35, 10, 35, 45, 20, 20, 45, 35, 25, 75 }).Clone(); starts = (int[])(bandStarts ?? new int[4] { 2, 4, 6, 8 }).Clone(); weights = ((modifierWeights == null) ? new Dictionary<string, int>() : modifierWeights.ToDictionary<KeyValuePair<string, int>, string, int>((KeyValuePair<string, int> p) => p.Key, (KeyValuePair<string, int> p) => p.Value, StringComparer.Ordinal)); if (tiers.Length != 14 || tiers.Any((int w) => w < 0 || w > 10000) || starts.Length != 4 || starts[0] != 2 || starts.Any((int h) => h < 2 || h > 999) || !starts.SequenceEqual(from h in starts.Distinct() orderby h select h) || weights.Count > 128 || weights.Any((KeyValuePair<string, int> p) => string.IsNullOrEmpty(p.Key) || p.Key.Length > 32 || p.Value < 0 || p.Value > 10000)) { throw new ArgumentException("Invalid progression weights or bands"); } Selection = selection; Persistence = persistence; Skips = skips; SkipEffect = skipEffect; BossEnabled = bossEnabled; BossInterval = bossInterval; RandomSkips = randomSkips; RandomSkipWeight = randomSkipWeight; RandomModifierWeight = randomModifierWeight; } public bool IsBoss(int selectionNumber) { if (BossEnabled && selectionNumber > 0) { return selectionNumber % BossInterval == 0; } return false; } public int TierWeight(int upcomingHole, ModifierRarity rarity, bool boss) { if (boss) { return rarity switch { ModifierRarity.Boss => tiers[13], ModifierRarity.Legendary => tiers[12], _ => 0, }; } if (rarity == ModifierRarity.Boss) { return 0; } int num = 0; for (int i = 1; i < starts.Length; i++) { if (upcomingHole >= starts[i]) { num = i; } } return tiers[(int)(num * 3 + rarity)]; } } public static class ProgressionCandidates { public static VoteOption[] Generate(ModifierManager manager, ProgressionSettings settings, int upcomingHole, int selectionNumber, int count, RunRandom random) { if (count < 1 || count > 5 || upcomingHole < 2 || upcomingHole > 1000 || selectionNumber < 1) { throw new ArgumentOutOfRangeException("count"); } bool boss = settings.IsBoss(selectionNumber); List<ModifierDefinition> list = (from d in manager.Available.Where(manager.CanActivate).Select(manager.Registry.Get) where settings.Weight(d.Id) > 0 && settings.TierWeight(upcomingHole, d.Rarity, boss) > 0 select d).OrderBy<ModifierDefinition, string>((ModifierDefinition d) => d.Id, StringComparer.Ordinal).ToList(); List<VoteOption> list2 = new List<VoteOption>(); while (list.Count > 0 && list2.Count < count) { ModifierRarity[] array = (from t in list.Select((ModifierDefinition d) => d.Rarity).Distinct() orderby t select t).ToArray(); int num = random.Next(array.Sum((ModifierRarity t) => settings.TierWeight(upcomingHole, t, boss))); ModifierRarity tier = array[0]; ModifierRarity[] array2 = array; for (int num2 = 0; num2 < array2.Length; num2++) { ModifierRarity rarity = (tier = array2[num2]); num -= settings.TierWeight(upcomingHole, rarity, boss); if (num < 0) { break; } } ModifierDefinition[] array3 = list.Where((ModifierDefinition d) => d.Rarity == tier).ToArray(); num = random.Next(array3.Sum((ModifierDefinition d) => settings.Weight(d.Id))); ModifierDefinition modifierDefinition = array3[0]; ModifierDefinition[] array4 = array3; foreach (ModifierDefinition modifierDefinition2 in array4) { modifierDefinition = modifierDefinition2; num -= settings.Weight(modifierDefinition2.Id); if (num < 0) { break; } } string text = modifierDefinition.Description; if (settings.Persistence == PersistenceMode.PerHole) { text = text.Replace("For this run.", "Next hole only."); } list2.Add(new VoteOption(modifierDefinition.Id, modifierDefinition.Name, text)); list.Remove(modifierDefinition); } return list2.ToArray(); } public static string Automatic(VoteOption[] candidates, ProgressionSettings settings, bool skipAvailable, RunRandom random) { if (candidates.Length == 0) { return "none"; } if (skipAvailable && settings.RandomSkips && settings.RandomSkipWeight > 0 && random.Next(settings.RandomModifierWeight + settings.RandomSkipWeight) < settings.RandomSkipWeight) { return "skip"; } return candidates[random.Next(candidates.Length)].Id; } } public sealed class SkipLedger { private readonly SkipMode mode; private readonly int initial; private readonly Dictionary<ulong, int> personal = new Dictionary<ulong, int>(); public int Shared { get; private set; } public SkipLedger(SkipMode mode, int budget) { if (budget < 0 || budget > 99) { throw new ArgumentOutOfRangeException("budget"); } this.mode = mode; initial = budget; Shared = budget; } public int Remaining(ulong player) { if (mode == SkipMode.Disabled) { return 0; } if (mode == SkipMode.Unlimited) { return 99; } if (mode == SkipMode.SharedRun) { return Shared; } if (player == 0L) { return 0; } if (!personal.TryGetValue(player, out var value)) { return initial; } return value; } public bool Charge(IEnumerable<ulong> payers) { ulong[] array = payers.Distinct().ToArray(); if (array.Length == 0 || array.Any((ulong id) => Remaining(id) <= 0)) { return false; } if (mode == SkipMode.SharedRun) { Shared--; } if (mode == SkipMode.PerPlayer) { ulong[] array2 = array; foreach (ulong num2 in array2) { personal[num2] = Remaining(num2) - 1; } } return true; } } public sealed class RunChoice { public int Hole { get; } public string Id { get; } public bool Boss { get; } public RunChoice(int hole, string id, bool boss) { if (hole < 2 || hole > 1000 || string.IsNullOrEmpty(id) || id.Length > 32) { throw new ArgumentException("Invalid run choice"); } Hole = hole; Id = id; Boss = boss; } } public enum PacketKind : byte { Hello = 1, Snapshot, Vote, BreakTie } public sealed class RunSnapshot { public string Session = ""; public string RunId = ""; public string Winner = ""; public string Notice = ""; public long Revision; public RunState State; public int Hole = -1; public int Skips; public int VoteId; public int Recipient; public int Host; public uint Seed; public bool ModifierMode; public ProgressionSettings Progression = new ProgressionSettings(); public int SelectionNumber; public bool BossSelection; public bool SummaryComplete; public RunChoice[] History = Array.Empty<RunChoice>(); public Dictionary<int, int> PersonalSkips = new Dictionary<int, int>(); public Dictionary<ulong, float> PlayerShotFactors = new Dictionary<ulong, float>(); public GravityVector GravityBaseline; public ModifierSettings ModifierSettings = new ModifierSettings(); public string[] AvailableModifiers = Array.Empty<string>(); public ActiveModifier[] ActiveModifiers = Array.Empty<ActiveModifier>(); public double Deadline; public double TieDeadline; public double RevealUntil; public double NextCycleAt; public VoteSettings Settings = new VoteSettings(); public VoteOption[] Options = Array.Empty<VoteOption>(); public int[] Eligible = Array.Empty<int>(); public Dictionary<int, string> Ballots = new Dictionary<int, string>(); public string[] Tied = Array.Empty<string>(); } public sealed class Packet { public PacketKind Kind; public string Session = ""; public string Nonce = ""; public string RunId = ""; public string Option = ""; public int VoteId; public long Sequence; public EffectChannel Capabilities; public RunSnapshot Snapshot; } public static class Wire { public const int Version = 7; public const int MaxBytes = 16384; public const int MaxPlayers = 64; private static readonly Encoding Utf8 = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true); public static byte[] Encode(Packet packet) { using MemoryStream memoryStream = new MemoryStream(); using BinaryWriter binaryWriter = new BinaryWriter(memoryStream, Utf8, leaveOpen: true); binaryWriter.Write(7); binaryWriter.Write((byte)packet.Kind); Text(binaryWriter, packet.Session, 64); if (packet.Kind == PacketKind.Hello) { Text(binaryWriter, packet.Nonce, 64); binaryWriter.Write((ushort)packet.Capabilities); } else if (packet.Kind == PacketKind.Snapshot) { WriteSnapshot(binaryWriter, packet.Snapshot); } else { if (packet.Kind != PacketKind.Vote && packet.Kind != PacketKind.BreakTie) { throw new InvalidDataException("Unknown packet kind"); } Text(binaryWriter, packet.RunId, 64); binaryWriter.Write(packet.VoteId); binaryWriter.Write(packet.Sequence); Text(binaryWriter, packet.Option, 32); } binaryWriter.Flush(); if (memoryStream.Length > 16384) { throw new InvalidDataException("Oversized packet"); } return memoryStream.ToArray(); } public static Packet Decode(byte[] bytes) { if (bytes == null || bytes.Length > 16384) { throw new InvalidDataException("Oversized packet"); } using MemoryStream memoryStream = new MemoryStream(bytes, writable: false); using BinaryReader binaryReader = new BinaryReader(memoryStream, Utf8, leaveOpen: true); if (binaryReader.ReadInt32() != 7) { throw new InvalidDataException("Protocol mismatch"); } Packet packet = new Packet { Kind = (PacketKind)binaryReader.ReadByte(), Session = Text(binaryReader, 64) }; if (packet.Kind == PacketKind.Hello) { packet.Nonce = Text(binaryReader, 64); packet.Capabilities = (EffectChannel)binaryReader.ReadUInt16(); if ((packet.Capabilities & ~EffectChannel.All) != EffectChannel.None) { throw new InvalidDataException("Invalid capabilities"); } } else if (packet.Kind == PacketKind.Snapshot) { packet.Snapshot = ReadSnapshot(binaryReader); packet.Snapshot.Session = packet.Session; } else { if (packet.Kind != PacketKind.Vote && packet.Kind != PacketKind.BreakTie) { throw new InvalidDataException("Unknown packet kind"); } packet.RunId = Text(binaryReader, 64); packet.VoteId = binaryReader.ReadInt32(); packet.Sequence = binaryReader.ReadInt64(); packet.Option = Text(binaryReader, 32); } if (memoryStream.Position != memoryStream.Length) { throw new InvalidDataException("Trailing bytes"); } return packet; } private static void WriteSnapshot(BinaryWriter w, RunSnapshot s) { Text(w, s.RunId, 64); w.Write(s.Revision); w.Write((byte)s.State); w.Write(s.Hole); w.Write(s.Skips); w.Write(s.VoteId); w.Write(s.Recipient); w.Write(s.Host); w.Write(s.Seed); w.Write(s.ModifierMode); WriteProgression(w, s); w.Write(s.GravityBaseline.X); w.Write(s.GravityBaseline.Y); w.Write(s.GravityBaseline.Z); w.Write(s.ModifierSettings.PowerSurge); w.Write(s.ModifierSettings.HeavyBall); w.Write(s.ModifierSettings.Featherweight); w.Write(s.ModifierSettings.LowGravity); w.Write(s.ModifierSettings.HighGravity); Count(w, s.ModifierSettings.Extra.Length, 200); KeyValuePair<string, float>[] extra = s.ModifierSettings.Extra; for (int i = 0; i < extra.Length; i++) { KeyValuePair<string, float> keyValuePair = extra[i]; Text(w, keyValuePair.Key, 64); w.Write(keyValuePair.Value); } Count(w, s.PlayerShotFactors.Count, 64); foreach (KeyValuePair<ulong, float> item in s.PlayerShotFactors.OrderBy((KeyValuePair<ulong, float> p) => p.Key)) { w.Write(item.Key); w.Write(item.Value); } Count(w, s.AvailableModifiers.Length, 128); string[] availableModifiers = s.AvailableModifiers; foreach (string value in availableModifiers) { Text(w, value, 32); } Count(w, s.ActiveModifiers.Length, 128); ActiveModifier[] activeModifiers = s.ActiveModifiers; foreach (ActiveModifier activeModifier in activeModifiers) { Text(w, activeModifier.Id, 32); w.Write(activeModifier.FirstHole); w.Write(activeModifier.RemainingHoles); w.Write(activeModifier.Suspended); w.Write(activeModifier.Variant); } w.Write(s.Deadline); w.Write(s.TieDeadline); w.Write(s.RevealUntil); w.Write(s.NextCycleAt); Text(w, s.Winner, 32); Text(w, s.Notice, 256); w.Write(s.Settings.CandidateCount); w.Write(s.Settings.Duration); w.Write(s.Settings.AllowChanges); w.Write(s.Settings.EarlyClose); w.Write((byte)s.Settings.Tie); w.Write(s.Settings.SkipBudget); w.Write(s.Settings.RevealSeconds); Count(w, s.Options.Length, 6); VoteOption[] options = s.Options; foreach (VoteOption voteOption in options) { Text(w, voteOption.Id, 32); Text(w, voteOption.Name, 64); Text(w, voteOption.Description, 256); } Count(w, s.Eligible.Length, 64); int[] eligible = s.Eligible; foreach (int value2 in eligible) { w.Write(value2); } Count(w, s.Ballots.Count, 64); foreach (KeyValuePair<int, string> item2 in s.Ballots.OrderBy((KeyValuePair<int, string> x) => x.Key)) { w.Write(item2.Key); Text(w, item2.Value, 32); } Count(w, s.Tied.Length, 6); availableModifiers = s.Tied; foreach (string value3 in availableModifiers) { Text(w, value3, 32); } } private static RunSnapshot ReadSnapshot(BinaryReader r) { RunSnapshot s = new RunSnapshot { RunId = Text(r, 64), Revision = r.ReadInt64(), State = (RunState)r.ReadByte(), Hole = r.ReadInt32(), Skips = r.ReadInt32(), VoteId = r.ReadInt32(), Recipient = r.ReadInt32(), Host = r.ReadInt32(), Seed = r.ReadUInt32(), ModifierMode = r.ReadBoolean() }; ReadProgression(r, s); s.GravityBaseline = new GravityVector(r.ReadSingle(), r.ReadSingle(), r.ReadSingle()); float power = r.ReadSingle(); float heavy = r.ReadSingle(); float feather = r.ReadSingle(); float low = r.ReadSingle(); float high = r.ReadSingle(); Dictionary<string, float> dictionary = new Dictionary<string, float>(); int num = Count(r, 200); for (int i = 0; i < num; i++) { dictionary.Add(Text(r, 64), r.ReadSingle()); } s.ModifierSettings = new ModifierSettings(power, heavy, feather, low, high, dictionary); int num2 = Count(r, 64); for (int j = 0; j < num2; j++) { ulong key = r.ReadUInt64(); float num3 = r.ReadSingle(); if (!VoteSettings.Finite(num3) || num3 < 0.5f || num3 > 1.5f) { throw new InvalidDataException("Invalid rank factor"); } s.PlayerShotFactors.Add(key, num3); } s.AvailableModifiers = new string[Count(r, 128)]; for (int k = 0; k < s.AvailableModifiers.Length; k++) { s.AvailableModifiers[k] = Text(r, 32); } s.ActiveModifiers = new ActiveModifier[Count(r, 128)]; for (int l = 0; l < s.ActiveModifiers.Length; l++) { s.ActiveModifiers[l] = new ActiveModifier(Text(r, 32), r.ReadInt32(), r.ReadInt32(), r.ReadBoolean()) { Variant = r.ReadInt32() }; } ModifierRegistry.Create(s.ModifierSettings).Validate(s.ActiveModifiers, s.AvailableModifiers, s.Progression.Persistence); s.Deadline = r.ReadDouble(); s.TieDeadline = r.ReadDouble(); s.RevealUntil = r.ReadDouble(); s.NextCycleAt = r.ReadDouble(); s.Winner = Text(r, 32); s.Notice = Text(r, 256); if (s.Revision < 0 || !Enum.IsDefined(typeof(RunState), s.State) || s.Hole < -1 || s.Hole > 999 || s.Skips < 0 || s.Skips > 99 || !VoteSettings.Finite(s.Deadline) || !VoteSettings.Finite(s.TieDeadline) || !VoteSettings.Finite(s.RevealUntil) || !VoteSettings.Finite(s.NextCycleAt) || s.NextCycleAt < 0.0) { throw new InvalidDataException("Invalid snapshot fields"); } if (s.ActiveModifiers.Length != 0 && (!s.ModifierMode || s.RunId == "" || s.State == RunState.Inactive || s.State == RunState.CleaningUp || s.State == RunState.RunComplete)) { throw new InvalidDataException("Effect outside an active modifier run"); } s.Settings = new VoteSettings(r.ReadInt32(), r.ReadDouble(), r.ReadBoolean(), r.ReadBoolean(), (TieRule)r.ReadByte(), r.ReadInt32(), r.ReadDouble()); s.Options = new VoteOption[Count(r, 6)]; for (int m = 0; m < s.Options.Length; m++) { s.Options[m] = new VoteOption(Text(r, 32), Text(r, 64), Text(r, 256)); } s.Eligible = new int[Count(r, 64)]; for (int n = 0; n < s.Eligible.Length; n++) { s.Eligible[n] = r.ReadInt32(); } int num4 = Count(r, 64); for (int num5 = 0; num5 < num4; num5++) { int key2 = r.ReadInt32(); if (s.Ballots.ContainsKey(key2)) { throw new InvalidDataException("Duplicate ballot"); } s.Ballots.Add(key2, Text(r, 32)); } s.Tied = new string[Count(r, 6)]; for (int num6 = 0; num6 < s.Tied.Length; num6++) { s.Tied[num6] = Text(r, 32); } HashSet<string> ids = new HashSet<string>(s.Options.Select((VoteOption o) => o.Id)); if (ids.Count != s.Options.Length || s.Eligible.Distinct().Count() != s.Eligible.Length || s.Ballots.Any((KeyValuePair<int, string> b) => !s.Eligible.Contains(b.Key) || !ids.Contains(b.Value)) || s.Tied.Any((string t) => !ids.Contains(t)) || (s.Winner != "" && !ids.Contains(s.Winner))) { throw new InvalidDataException("Inconsistent snapshot"); } return s; } private static void WriteProgression(BinaryWriter w, RunSnapshot s) { ProgressionSettings progression = s.Progression; w.Write((byte)progression.Selection); w.Write((byte)progression.Persistence); w.Write((byte)progression.Skips); w.Write((byte)progression.SkipEffect); w.Write(progression.BossEnabled); w.Write(progression.BossInterval); w.Write(progression.RandomSkips); w.Write(progression.RandomSkipWeight); w.Write(progression.RandomModifierWeight); int[] tierWeights = progression.TierWeights; foreach (int value in tierWeights) { w.Write(value); } tierWeights = progression.BandStarts; foreach (int value2 in tierWeights) { w.Write(value2); } Count(w, progression.ModifierWeights.Length, 128); KeyValuePair<string, int>[] modifierWeights = progression.ModifierWeights; for (int i = 0; i < modifierWeights.Length; i++) { KeyValuePair<string, int> keyValuePair = modifierWeights[i]; Text(w, keyValuePair.Key, 32); w.Write(keyValuePair.Value); } w.Write(s.SelectionNumber); w.Write(s.BossSelection); w.Write(s.SummaryComplete); Count(w, s.History.Length, 64); RunChoice[] history = s.History; foreach (RunChoice runChoice in history) { w.Write(runChoice.Hole); Text(w, runChoice.Id, 32); w.Write(runChoice.Boss); } Count(w, s.PersonalSkips.Count, 64); foreach (KeyValuePair<int, int> item in s.PersonalSkips.OrderBy((KeyValuePair<int, int> pv) => pv.Key)) { w.Write(item.Key); w.Write(item.Value); } } private static void ReadProgression(BinaryReader r, RunSnapshot s) { SelectionMode selection = (SelectionMode)r.ReadByte(); PersistenceMode persistence = (PersistenceMode)r.ReadByte(); SkipMode skips = (SkipMode)r.ReadByte(); SkipBehavior skipEffect = (SkipBehavior)r.ReadByte(); bool bossEnabled = r.ReadBoolean(); int bossInterval = r.ReadInt32(); bool randomSkips = r.ReadBoolean(); int randomSkipWeight = r.ReadInt32(); int randomModifierWeight = r.ReadInt32(); int[] array = new int[14]; for (int i = 0; i < 14; i++) { array[i] = r.ReadInt32(); } int[] array2 = new int[4]; for (int j = 0; j < 4; j++) { array2[j] = r.ReadInt32(); } Dictionary<string, int> dictionary = new Dictionary<string, int>(); int num = Count(r, 128); for (int k = 0; k < num; k++) { dictionary.Add(Text(r, 32), r.ReadInt32()); } s.Progression = new ProgressionSettings(selection, persistence, skips, skipEffect, bossEnabled, bossInterval, randomSkips, randomSkipWeight, randomModifierWeight, array, array2, dictionary); s.SelectionNumber = r.ReadInt32(); s.BossSelection = r.ReadBoolean(); s.SummaryComplete = r.ReadBoolean(); ModifierRegistry registry = ModifierRegistry.Create(new ModifierSettings()); if (s.SelectionNumber < 0 || s.SelectionNumber > 999 || dictionary.Keys.Any((string id) => registry.Get(id) == null)) { throw new InvalidDataException("Invalid progression state"); } s.History = new RunChoice[Count(r, 64)]; for (int num2 = 0; num2 < s.History.Length; num2++) { RunChoice runChoice = new RunChoice(r.ReadInt32(), Text(r, 32), r.ReadBoolean()); if ((runChoice.Id != "skip" && runChoice.Id != "none" && registry.Get(runChoice.Id) == null) || (num2 > 0 && s.History[num2 - 1].Hole >= runChoice.Hole)) { throw new InvalidDataException("Invalid run history"); } s.History[num2] = runChoice; } if (s.History.Length > s.SelectionNumber) { throw new InvalidDataException("History exceeds selections"); } num = Count(r, 64); for (int num3 = 0; num3 < num; num3++) { int key = r.ReadInt32(); int num4 = r.ReadInt32(); if (num4 < 0 || num4 > 99) { throw new InvalidDataException("Invalid personal skip budget"); } s.PersonalSkips.Add(key, num4); } } private static void Text(BinaryWriter w, string value, int max) { byte[] bytes = Utf8.GetBytes(value ?? ""); if (bytes.Length > max) { throw new InvalidDataException("String too long"); } w.Write((ushort)bytes.Length); w.Write(bytes); } private static string Text(BinaryReader r, int max) { int num = r.ReadUInt16(); if (num > max) { throw new InvalidDataException("String too long"); } byte[] array = r.ReadBytes(num); if (array.Length != num) { throw new EndOfStreamException(); } return Utf8.GetString(array); } private static void Count(BinaryWriter w, int count, int max) { if (count < 0 || count > max) { throw new InvalidDataException("Collection too large"); } w.Write((byte)count); } private static int Count(BinaryReader r, int max) { byte num = r.ReadByte(); if (num > max) { throw new InvalidDataException("Collection too large"); } return num; } } public sealed class Replica { private string session = ""; public RunSnapshot Current { get; private set; } public void Reset(string expectedSession) { session = expectedSession; Current = null; } public bool Accept(RunSnapshot snapshot) { if (snapshot.Session != session || (Current != null && snapshot.Revision <= Current.Revision)) { return false; } Current = snapshot; return true; } } public static class RankEffects { public static Dictionary<ulong, float> Calculate(IEnumerable<ulong> nativeOrder, Mechanic rules, ModifierSettings settings) { ulong[] array = nativeOrder.ToArray(); if (array.Length > 64 || array.Distinct().Count() != array.Length) { throw new ArgumentException("Invalid leaderboard"); } Dictionary<ulong, float> dictionary = new Dictionary<ulong, float>(); if (array.Length < 2) { return dictionary; } for (int i = 0; i < array.Length; i++) { float num = 1f; if (i == 0 && (rules & Mechanic.LeaderTax) != Mechanic.None) { num *= settings.Get("LeaderTaxFactor", 0.8f); } if (i == array.Length - 1 && (rules & Mechanic.LastPlaceInsurance) != Mechanic.None) { num *= settings.Get("LastPlaceFactor", 1.2f); } if ((rules & Mechanic.ComebackSeason) != Mechanic.None) { num *= 1f + (settings.Get("ComebackMaximum", 1.3f) - 1f) * (float)i / (float)(array.Length - 1); } dictionary[array[i]] = Math.Max(0.5f, Math.Min(1.5f, num)); } return dictionary; } } public enum RunState { Inactive, InitializingRun, StartingHole, PlayingHole, HoleEnding, PreparingVote, Voting, ResolvingVote, ApplyingModifier, RunComplete, CleaningUp } public sealed class RunController { public RunState State { get; private set; } public string RunId { get; private set; } = ""; public long Revision { get; private set; } public int HoleIndex { get; private set; } = -1; public bool Active => State != RunState.Inactive; public event Action<string> Changed; public event Action<string> Warning; public bool Begin(string id) { if (Active) { return false; } if (string.IsNullOrEmpty(id)) { throw new ArgumentException("Run identity is required.", "id"); } RunId = id; HoleIndex = -1; Move(RunState.InitializingRun, "RunStarted"); return true; } public bool HoleLoaded(int index) { if (!Active || index < 0 || index <= HoleIndex) { return false; } if (State != RunState.InitializingRun && State != RunState.StartingHole && State != RunState.HoleEnding) { return Invalid("HoleLoaded"); } HoleIndex = index; Move(RunState.StartingHole, "HoleLoaded"); return true; } public bool HoleStarted(int index) { if (!Active || index != HoleIndex || State == RunState.PlayingHole) { return false; } if (State != RunState.StartingHole) { return Invalid("HoleStarted"); } Move(RunState.PlayingHole, "HoleStarted"); return true; } public bool HoleEnded(int index) { if (!Active || index != HoleIndex || State == RunState.HoleEnding) { return false; } if (State != RunState.PlayingHole && State != RunState.StartingHole) { return false; } Move(RunState.HoleEnding, "HoleEnded"); return true; } public bool Transition(RunState expected, RunState next, string reason) { if (State != expected || !Allowed(expected, next)) { return Invalid(reason); } Move(next, reason); return true; } public void Complete() { if (Active) { Move(RunState.RunComplete, "RunCompleted"); Stop("CourseComplete"); } } public void Stop(string reason) { if (Active) { Move(RunState.CleaningUp, "Cleanup:" + reason); HoleIndex = -1; Move(RunState.Inactive, "RunStopped:" + reason); } } private bool Invalid(string signal) { this.Warning?.Invoke(signal + " rejected in " + State); return false; } private static bool Allowed(RunState from, RunState to) { switch (from) { case RunState.InitializingRun: return to == RunState.StartingHole; case RunState.StartingHole: return to == RunState.PlayingHole; case RunState.PlayingHole: return to == RunState.HoleEnding; case RunState.HoleEnding: if (to != RunState.PreparingVote) { return to == RunState.StartingHole; } return true; case RunState.PreparingVote: if (to != RunState.Voting) { return to == RunState.StartingHole; } return true; case RunState.Voting: if (to != RunState.ResolvingVote) { return to == RunState.StartingHole; } return true; case RunState.ResolvingVote: if (to != RunState.ApplyingModifier) { return to == RunState.StartingHole; } return true; case RunState.ApplyingModifier: return to == RunState.StartingHole; default: return false; } } private void Move(RunState state, string reason) { State = state; Revision++; this.Changed?.Invoke(reason); } } public static class SecureVoteRandom { public static int Next(int count) { if (count <= 0) { throw new ArgumentOutOfRangeException("count"); } ulong num = 4294967296L - 4294967296uL % (ulong)(uint)count; byte[] array = new byte[4]; using RandomNumberGenerator randomNumberGenerator = RandomNumberGenerator.Create(); uint num2; do { randomNumberGenerator.GetBytes(array); num2 = BitConverter.ToUInt32(array, 0); } while (num2 >= num); return (int)(num2 % (uint)count); } } public static class TestStack { public static ModifierManager Build(ModifierRegistry registry, IEnumerable<string> ids, int hole, PersistenceMode persistence, EffectChannel capabilities, Func<ModifierDefinition, bool> context) { string[] array = ids.ToArray(); if (array.Length > 64 || array.Distinct().Count() != array.Length) { throw new ArgumentException("Duplicate or oversized test stack"); } ModifierManager modifierManager = new ModifierManager(registry, registry.Definitions.Select((ModifierDefinition d) => d.Id), capabilities) { ContextAllows = context }; string[] array2 = array; foreach (string text in array2) { if (!modifierManager.Activate(text, hole, persistence)) { throw new ArgumentException("Unsupported or incompatible modifier: " + text); } } return modifierManager; } } public sealed class VoteCoordinator { private readonly RunRandom random; private readonly bool manageSkips; private int nextId; private int appliedId = -1; public VoteSettings Settings { get; } public uint Seed { get; } public int Skips { get; private set; } public VoteSession Vote { get; private set; } public double RevealUntil { get; private set; } public Func<int, int> RandomChoice { get; set; } public VoteCoordinator(VoteSettings settings, uint seed, bool manageSkips = true) { this.manageSkips = manageSkips; Settings = settings; Seed = seed; random = new RunRandom(seed); Skips = settings.SkipBudget; } public void Open(IEnumerable<int> players, double now, VoteOption[] options = null) { if (Vote != null && Vote.Winner == "") { throw new InvalidOperationException("A vote is already open"); } Vote = new VoteSession(++nextId, options ?? DummyCandidates.Generate(Settings.CandidateCount, Skips > 0, random), players, now, Settings, random); if (RandomChoice != null) { Vote.RandomChoice = RandomChoice; } RevealUntil = 0.0; } public bool Tick(double now) { if (Vote == null) { return false; } bool result = Vote.Tick(now); if (Vote.Winner != "" && appliedId != Vote.Id) { appliedId = Vote.Id; if (manageSkips && Vote.Winner == "skip") { if (Skips <= 0) { throw new InvalidOperationException("Skip budget exhausted"); } Skips--; } RevealUntil = now + Settings.RevealSeconds; result = true; } return result; } public void ClearVote() { Vote = null; RevealUntil = 0.0; } } public sealed class IntentGuard { private readonly Dictionary<int, long> sequence = new Dictionary<int, long>(); private readonly Dictionary<int, double> lastTime = new Dictionary<int, double>(); public bool Accept(int player, long next, double now) { if (next <= 0 || !VoteSettings.Finite(now) || (sequence.TryGetValue(player, out var value) && next <= value) || (lastTime.TryGetValue(player, out var value2) && now - value2 < 0.1)) { return false; } sequence[player] = next; lastTime[player] = now; return true; } public void Remove(int player) { sequence.Remove(player); lastTime.Remove(player); } public void Clear() { sequence.Clear(); lastTime.Clear(); } } public enum TieRule { Random, HostBreaksTie, RandomExceptSkip, FirstToReachCount } public sealed class VoteSettings { public int CandidateCount { get; } public double Duration { get; } public bool AllowChanges { get; } public bool EarlyClose { get; } public TieRule Tie { get; } public int SkipBudget { get; } public double RevealSeconds { get; } public VoteSettings(int candidates = 3, double duration = 15.0, bool allowChanges = true, bool earlyClose = true, TieRule tie = TieRule.Random, int skips = 2, double reveal = 3.0) { if (candidates < 2 || candidates > 5 || !Finite(duration) || duration < 1.0 || duration > 120.0 || !Enum.IsDefined(typeof(TieRule), tie) || skips < 0 || skips > 99 || !Finite(reveal) || reveal < 1.0 || reveal > 15.0) { throw new ArgumentOutOfRangeException("candidates", "Invalid voting settings"); } CandidateCount = candidates; Duration = duration; AllowChanges = allowChanges; EarlyClose = earlyClose; Tie = tie; SkipBudget = skips; RevealSeconds = reveal; } public static bool Finite(double value) { if (!double.IsNaN(value)) { return !double.IsInfinity(value); } return false; } } public sealed class RunRandom { public uint State { get; private set; } public RunRandom(uint seed) { State = ((seed == 0) ? 2654435769u : seed); } public int Next(int count) { if (count <= 0) { throw new ArgumentOutOfRangeException("count"); } uint state = State; state ^= state << 13; state ^= state >> 17; state = (State = state ^ (state << 5)); return (int)((long)state * (long)(uint)count >>> 32); } } public sealed class VoteOption { public string Id { get; } public string Name { get; } public string Description { get; } public bool IsSkip => Id == "skip"; public VoteOption(string id, string name, string description) { Id = id; Name = name; Description = description; } } public static class DummyCandidates { public static VoteOption[] Generate(int count, bool skip, RunRandom random) { List<string> list = new List<string> { "Ruby", "Emerald", "Sapphire", "Amber", "Amethyst" }; List<VoteOption> list2 = new List<VoteOption>(); for (int i = 0; i < count; i++) { int index = random.Next(list.Count); string text = list[index]; list.RemoveAt(index); list2.Add(new VoteOption(text.ToLowerInvariant(), text, "Prototype choice — no gameplay effect.")); } if (skip) { list2.Add(new VoteOption("skip", "Skip", "Add no new choice. Uses one shared skip.")); } return list2.ToArray(); } } public sealed class VoteSession { private readonly VoteSettings settings; private readonly RunRandom random; private readonly HashSet<int> eligible; private readonly Dictionary<int, string> ballots = new Dictionary<int, string>(); private readonly Dictionary<string, long> firstCount = new Dictionary<string, long>(); private long order; public Func<int, int> RandomChoice { get; set; } public int Id { get; } public VoteOption[] Options { get; } public double Deadline { get; } public double TieDeadline { get; private set; } public string Winner { get; private set; } = ""; public string[] Tied { get; private set; } = Array.Empty<string>(); public bool Closed { get; private set; } public bool AwaitingHost { get { if (Closed && Winner == "") { return Tied.Length != 0; } return false; } } public IReadOnlyDictionary<int, string> Ballots => ballots; public int[] Eligible => eligible.OrderBy((int x) => x).ToArray(); public Func<int, bool> MaySkip { get; set; } = (int player) => true; public VoteSession(int id, VoteOption[] options, IEnumerable<int> voters, double now, VoteSettings rules, RunRandom rng) { if (!VoteSettings.Finite(now) || options.Length < 1 || options.Length > 6 || options.Select((VoteOption o) => o.Id).Distinct().Count() != options.Length) { throw new ArgumentException("Invalid vote"); } Id = id; Options = options.ToArray(); eligible = new HashSet<int>(voters); settings = rules; random = rng; RandomChoice = random.Next; Deadline = now + rules.Duration; } public bool ResolveAutomatic(string option) { if (Closed || !Options.Any((VoteOption o) => o.Id == option)) { return false; } Closed = true; Winner = option; return true; } public bool Cast(int player, string option, double now) { if ((option == "skip" && !MaySkip(player)) || !VoteSettings.Finite(now) || Closed || now >= Deadline || !eligible.Contains(player) || !Options.Any((VoteOption o) => o.Id == option)) { return false; } if (ballots.TryGetValue(player, out var value) && (!settings.AllowChanges || value == option)) { return false; } ballots[player] = option; order++; int num = ballots.Values.Count((string x) => x == option); string key = option + ":" + num; if (!firstCount.ContainsKey(key)) { firstCount.Add(key, order); } return true; } public bool Disconnect(int player) { bool num = eligible.Remove(player); ballots.Remove(player); if (num && AwaitingHost) { Closed = false; Tied = Array.Empty<string>(); TieDeadline = 0.0; } return num; } public bool Tick(double now) { if (!VoteSettings.Finite(now)) { return false; } if (AwaitingHost && now >= TieDeadline) { Winner = Tied[RandomChoice(Tied.Length)]; return true; } if (Closed || (now < Deadline && (!settings.EarlyClose || ballots.Count != eligible.Count))) { return false; } Closed = true; int max = Options.Max((VoteOption o) => ballots.Values.Count((string v) => v == o.Id)); List<string> list = (from o in Options where ballots.Values.Count((string v) => v == o.Id) == max && (max > 0 || !o.IsSkip) select o.Id).ToList(); if (settings.Tie == TieRule.RandomExceptSkip && list.Count > 1) { list.Remove("skip"); } if (list.Count == 1) { Winner = list[0]; } else if (settings.Tie == TieRule.HostBreaksTie && max > 0) { Tied = list.ToArray(); TieDeadline = now + 5.0; } else if (settings.Tie == TieRule.FirstToReachCount && max > 0) { Winner = list.OrderBy((string id) => firstCount[id + ":" + max]).First(); } else { Winner = list[RandomChoice(list.Count)]; } return true; } public bool HostChoose(string option, double now) { if (!AwaitingHost || now >= TieDeadline || !Tied.Contains(option)) { return false; } Winner = option; return true; } } public interface IWorldHazard { bool Alive { get; } void Observe(); void Stop(); } public interface IActiveWorldEvent { string Id { get; } void Start(WorldEventController controller, double now); } public sealed class WorldEventController { private sealed class Job { public string Id; public double Next; public double Min; public double Max; public Action Action; } private sealed class Owned { public string Source; public string Type; public IWorldHazard Hazard; } private readonly List<Job> jobs = new List<Job>(); private readonly List<Owned> owned = new List<Owned>(); private readonly Queue<double> requests = new Queue<double>(); private readonly int perType; private readonly int perSource; private readonly int perSecond; private double now; private bool stopped; public RunRandom Random { get; } public int ActiveCount => owned.Count; public int ScheduledCount => jobs.Count; public int Spawned { get; private set; } public int Rejected { get; private set; } public int MaxActive { get; } public WorldEventController(uint seed, int maxActive = 7, int perType = 7, int perSource = 7, int perSecond = 4) { if (maxActive < 1 || maxActive > 32 || perType < 1 || perSource < 1 || perSecond < 1 || perSecond > 32) { throw new ArgumentException("Invalid world spawn budget"); } Random = new RunRandom(seed); MaxActive = maxActive; this.perType = perType; this.perSource = perSource; this.perSecond = perSecond; } public double Between(double min, double max) { return min + (max - min) * (double)Random.Next(1000000) / 1000000.0; } public void ScheduleRepeating(string source, double min, double max, Action action, double time) { if (stopped || jobs.Count >= 8 || jobs.Any((Job j) => j.Id == source) || string.IsNullOrEmpty(source) || action == null || double.IsNaN(min) || double.IsNaN(max) || double.IsInfinity(min) || double.IsInfinity(max) || min < 0.1 || max < min || max > 120.0) { throw new ArgumentException("Invalid event schedule"); } jobs.Add(new Job { Id = source, Min = min, Max = max, Next = time + Between(min, max), Action = action }); } public void Tick(double time, bool playable) { if (stopped) { return; } if (!playable) { Stop(); return; } now = time; foreach (Owned item in owned) { item.Hazard.Observe(); } owned.RemoveAll((Owned o) => !o.Hazard.Alive); while (requests.Count > 0 && now - requests.Peek() >= 1.0) { requests.Dequeue(); } Job[] array = jobs.ToArray(); foreach (Job job in array) { if (stopped) { break; } if (!(now < job.Next)) { job.Next = now + Between(job.Min, job.Max); try { job.Action(); } catch { Stop(); throw; } } } } public bool TrySpawn(string source, string type, Func<IWorldHazard> spawn, int externalActive = 0) { if (stopped || owned.Count + Math.Max(0, externalActive) >= MaxActive || owned.Count((Owned o) => o.Source == source) >= perSource || owned.Count((Owned o) => o.Type == type) >= perType || requests.Count >= perSecond) { Rejected++; return false; } requests.Enqueue(now); IWorldHazard worldHazard = spawn(); if (worldHazard == null) { return false; } owned.Add(new Owned { Source = source, Type = type, Hazard = worldHazard }); Spawned++; return true; } public void Stop() { if (stopped) { return; } stopped = true; jobs.Clear(); requests.Clear(); Exception ex = null; Owned[] array = this.owned.ToArray(); foreach (Owned owned in array) { try { owned.Hazard.Stop(); } catch (Exception ex2) { ex = ex2; } } this.owned.Clear(); if (ex == null) { return; } throw new InvalidOperationException("World hazard cleanup failed", ex); } } public static class WorldSpawnPatterns { public static int ChooseCategory(RunRandom random, int[] weights, bool[] available, int previous) { if (weights.Length != available.Length || weights.Length > 32 || weights.Any((int w) => w < 0 || w > 1000)) { throw new ArgumentException("Invalid targeting weights"); } int[] array = (from i in Enumerable.Range(0, weights.Length) where available[i] && weights[i] > 0 select i).ToArray(); if (array.Length == 0) { return -1; } int[] array2 = array.Where((int i) => i != previous).ToArray(); if (array2.Length == 0) { array2 = array; } int num = random.Next(array2.Sum((int i) => weights[i])); int[] array3 = array2; foreach (int num3 in array3) { num -= weights[num3]; if (num < 0) { return num3; } } return array2[^1]; } public static int Burst(WorldEventController controller, int min, int max, Func<bool> spawn) { if (min < 1 || max < min || max > 8) { throw new ArgumentException("Invalid burst"); } int num = min + controller.Random.Next(max - min + 1); int num2 = 0; for (int i = 0; i < num; i++) { if (spawn()) { num2++; } } return num2; } } public sealed class LawOfZeusEvent : IActiveWorldEvent { private readonly double minInterval; private readonly double maxInterval; private readonly int minBurst; private readonly int maxBurst; private readonly Func<bool> spawn; private readonly Action<int, int> report; public string Id => "law_of_zeus"; public LawOfZeusEvent(Func<bool> spawn, Action<int, int> report, double minInterval = 0.8, double maxInterval = 1.5, int minBurst = 1, int maxBurst = 2) { this.spawn = spawn; this.report = report; this.minInterval = minInterval; this.maxInterval = maxInterval; this.minBurst = minBurst; this.maxBurst = maxBurst; } public void Start(WorldEventController controller, double now) { int burst = 0; controller.ScheduleRepeating(Id, minInterval, maxInterval, delegate { int arg = WorldSpawnPatterns.Burst(controller, minBurst, maxBurst, spawn); report?.Invoke(++burst, arg); }, now); } } }
plugins/RogueGolf/RogueGolf.Plugin.dll
Decompiled 13 hours ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Text.RegularExpressions; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using Mirror; using RogueGolf.Core; using Steamworks; using Steamworks.Data; using TMPro; using Unity.Collections; using UnityEngine; using UnityEngine.InputSystem; using UnityEngine.InputSystem.Controls; using UnityEngine.SceneManagement; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyVersion("0.0.0.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 RogueGolf.Plugin { [BepInPlugin("technomancer.roguegolf", "RogueGolf", "0.14.0")] public sealed class RogueGolfPlugin : BaseUnityPlugin { private sealed class SettingRow { internal string Name; internal string Value; internal Action Previous; internal Action Next; } private sealed class ContactState { internal readonly HashSet<int> Balls; internal readonly float Bounce; internal readonly float Friction; internal volatile string Sample; internal ContactState(IEnumerable<int> balls, float bounce, float friction) { Balls = new HashSet<int>(balls); Bounce = bounce; Friction = friction; } } private sealed class BallShapeLease { private readonly GolfBall ball; private readonly float radius; private readonly Dictionary<Transform, Vector3> visuals = new Dictionary<Transform, Vector3>(); internal float NativeRadius => radius; internal BallShapeLease(GolfBall value) { //IL_00a0: Unknown result type (might be due to invalid IL or missing references) ball = value; radius = ball.Collider.radius; MeshRenderer[] componentsInChildren = ((Component)ball).GetComponentsInChildren<MeshRenderer>(true); for (int i = 0; i < componentsInChildren.Length; i++) { Transform val = ((Component)componentsInChildren[i]).transform; while ((Object)(object)val.parent != (Object)(object)((Component)ball).transform && (Object)(object)val.parent != (Object)null) { val = val.parent; } if ((Object)(object)val != (Object)(object)((Component)ball).transform && !visuals.ContainsKey(val)) { visuals[val] = val.localScale; } } } internal void Apply(float factor) { //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)ball == (Object)null) { return; } ball.Collider.radius = radius * factor; foreach (KeyValuePair<Transform, Vector3> visual in visuals) { if ((Object)(object)visual.Key != (Object)null) { visual.Key.localScale = visual.Value * factor; } } } } private struct HitContext { internal PlayerInfo Attacker; internal bool GolfSwing; } private sealed class UiAction { internal RectTransform Rect; internal Action Run; internal ScorecardShape Shape; internal Color Normal; } private WorldEventController worldEvents; private WorldSpawnLocations eventLocations; private uint eventSeed; private string eventRun = ""; private int eventHole = -1; private bool eventFaulted; private ConfigEntry<int> stormCap; private ConfigEntry<int> burstMin; private ConfigEntry<int> burstMax; private ConfigEntry<float> burstIntervalMin; private ConfigEntry<float> burstIntervalMax; private ConfigEntry<bool> eventVerbose; private ConfigEntry<int>[] eventWeights; private TextMeshProUGUI cycleLabel; private CycleRing cycleRing; private readonly Dictionary<string, string> conditionValues = new Dictionary<string, string>(); private readonly Dictionary<string, float> conditionChanged = new Dictionary<string, float>(); private readonly List<KeyValuePair<string, TextMeshProUGUI>> conditionLabels = new List<KeyValuePair<string, TextMeshProUGUI>>(); private string conditionRun = ""; private GravityLease gravity; private GravityVector gravityBaseline; private ModifierSettings modifierSettings = new ModifierSettings(1.2f, 2f, 0.5f, 0.6f, 1.5f, (IEnumerable<KeyValuePair<string, float>>)null); private ModifierManager modifiers; private RunRandom modifierRandom; private EffectValues effects = EffectValues.Normal; private readonly Dictionary<Rigidbody, ScalarLease> massLeases = new Dictionary<Rigidbody, ScalarLease>(); private readonly Dictionary<string, ConfigEntry<bool>> modifierEnabled = new Dictionary<string, ConfigEntry<bool>>(); private readonly Dictionary<string, ConfigEntry<float>> tuning = new Dictionary<string, ConfigEntry<float>>(); private ConfigEntry<float> powerFactor; private ConfigEntry<float> heavyFactor; private ConfigEntry<float> featherFactor; private ConfigEntry<float> lowFactor; private ConfigEntry<float> highFactor; private ConfigEntry<bool> traceGravity; private MethodInfo swingSpeedMethod; private string ballSample = ""; private string playerSample = ""; private int peerSceneRefreshFrame = -1; private ConfigEntry<Key> testingKey; private bool testingOpen; private bool testingUiReady; private int testingPage; private int consoleSuppressFrame = -1; private string testingDetail = ""; private string testingMessage = ""; private string testingRangeId = ""; private readonly HashSet<string> testingDraft = new HashSet<string>(); private readonly IntermissionGate gate = new IntermissionGate(); private ConfigEntry<PrototypeStage> stage; private ConfigEntry<float> holdSeconds; private PrototypeStage runStage; private MethodInfo nextMethod; private bool releasing; private double releaseAt; private ConfigEntry<bool> labCommands; private double nextLabRead; private GolfBall labBlastNeighbor; private bool dampingTraced; private Collider[] scoringTriggers = Array.Empty<Collider>(); private bool cupTraced; private readonly Dictionary<Collider, float> cupSurfaceHeights = new Dictionary<Collider, float>(); private readonly Dictionary<GolfBall, BallShapeLease> shapes = new Dictionary<GolfBall, BallShapeLease>(); private static volatile ContactState contacts = new ContactState(Array.Empty<int>(), 1f, 1f); [ThreadStatic] private static HitContext hitContext; private ConfigEntry<SelectionMode> selectionMode; private ConfigEntry<PersistenceMode> persistenceMode; private ConfigEntry<SkipMode> skipMode; private ConfigEntry<SkipBehavior> skipBehavior; private ConfigEntry<bool> bossEnabled; private ConfigEntry<bool> randomSkips; private ConfigEntry<int> bossInterval; private ConfigEntry<int> randomSkipWeight; private ConfigEntry<int> randomModifierWeight; private readonly List<ConfigEntry<int>> tierWeights = new List<ConfigEntry<int>>(); private readonly List<ConfigEntry<int>> bandStarts = new List<ConfigEntry<int>>(); private readonly Dictionary<string, ConfigEntry<int>> modifierWeights = new Dictionary<string, ConfigEntry<int>>(); private ProgressionSettings progression = new ProgressionSettings((SelectionMode)0, (PersistenceMode)0, (SkipMode)0, (SkipBehavior)0, true, 4, false, 10, 100, (int[])null, (int[])null, (IEnumerable<KeyValuePair<string, int>>)null); private SkipLedger skipLedger; private readonly Dictionary<int, ulong> votePlayers = new Dictionary<int, ulong>(); private readonly List<RunChoice> runHistory = new List<RunChoice>(); private RunRandom selectionRandom; private int selectionNumber; private bool bossSelection; private bool summaryComplete; private uint progressionSeed; private string dismissedSummary = ""; private int summaryPage; public const string Id = "technomancer.roguegolf"; public const string Version = "0.14.0"; internal static RogueGolfPlugin Instance; internal readonly RunController Run = new RunController(); private Harmony harmony; private ConfigEntry<bool> enabledMode; private bool subscribed; private bool quitting; private string detailId = ""; private readonly ThunderstormAdapter thunderAdapter = new ThunderstormAdapter(); private ThunderstormAdapter.Lease thunderProof; private string thunderProofRun = ""; private int thunderProofHole = -1; private int thunderProofStrikes; private double thunderProofNext; private NetworkBridge bridge; private VoteCoordinator voting; private ConfigEntry<int> candidateCount; private ConfigEntry<int> skipBudget; private ConfigEntry<int> seed; private ConfigEntry<float> voteSeconds; private ConfigEntry<float> revealSeconds; private ConfigEntry<bool> voteChanges; private ConfigEntry<bool> earlyClose; private ConfigEntry<TieRule> tieRule; private long snapshotRevision; private string notice = ""; private double noticeUntil; private int lastWinnerVote; private bool networkFault; private static readonly Color Paper = Color32.op_Implicit(new Color32(byte.MaxValue, (byte)251, (byte)224, byte.MaxValue)); private static readonly Color Ink = Color32.op_Implicit(new Color32((byte)48, (byte)58, (byte)37, byte.MaxValue)); private static readonly Color Green = Color32.op_Implicit(new Color32((byte)112, (byte)181, (byte)65, byte.MaxValue)); private static readonly Color Gold = Color32.op_Implicit(new Color32((byte)249, (byte)198, (byte)67, byte.MaxValue)); private static readonly Color Muted = Color32.op_Implicit(new Color32((byte)107, (byte)116, (byte)88, byte.MaxValue)); private GameObject uiObject; private RectTransform uiRoot; private RectTransform uiContent; private TMP_FontAsset uiFont; private TMP_FontAsset uiTitleFont; private string uiKey = ""; private int selected; private int effectPage; private int settingsPage; private int pauseTab; private int weightTab; private bool cursorOwned; private bool previousVisible; private bool uiFocus; private bool pauseMinimized; private bool setupOpen; private CursorLockMode previousCursor; private ModifierSettings hudSettings; private ModifierRegistry hudRegistry; private readonly List<UiAction> uiActions = new List<UiAction>(); private ActiveModifier[] remoteActive = Array.Empty<ActiveModifier>(); private Dictionary<ulong, float> playerShotFactors = new Dictionary<ulong, float>(); private RunRandom worldRandom; private double nextWorldTick; private double nextPlayerScan; private double nextExplosion; private int worldHole = -1; private int explosionsThisHole; private readonly HashSet<uint> grantedPlayers = new HashSet<uint>(); private readonly Dictionary<PlayerGolfer, Vector3> deathPositions = new Dictionary<PlayerGolfer, Vector3>(); private readonly HashSet<string> verifiedTrafficScenes = new HashSet<string>(); private ItemType sharedLoadout; private MethodInfo removeItem; private MethodInfo explodeRocket; private bool creatingExplosion; private bool pendingAmmoTrim; private Hittable explosionSourceBall; private Mechanic mechanics; private readonly HashSet<string> currentIds = new HashSet<string>(); internal EffectChannel Capabilities { get; private set; } private string GravitySampleKey { get { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) string text = CourseManager.CurrentHoleCourseIndex.ToString(); Vector3 val = Physics.gravity; return text + ":" + ((Vector3)(ref val)).ToString("F4"); } } private bool InDrivingRange => SingletonBehaviour<DrivingRangeManager>.HasInstance; private bool RangeTesting => testingRangeId != ""; private bool EffectsPlaying { get { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Invalid comparison between Unknown and I4 if ((int)Run.State != 3) { return RangeTesting; } return true; } } private string EffectRunId { get { if (!RangeTesting) { return Run.RunId; } return testingRangeId; } } private int TestHole { get { if (!RangeTesting) { return Run.HoleIndex; } return 0; } } private bool ViewingRangeTest { get { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Invalid comparison between Unknown and I4 if (View != null && (int)View.State == 3) { return View.RunId.StartsWith("range-test-", StringComparison.Ordinal); } return false; } } private bool MatchSetupVisible { get { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Invalid comparison between Unknown and I4 if ((InputManager.CurrentModeMask & 0x100) != 0) { if ((int)InputManager.CurrentMode != 256) { if (setupOpen) { return uiFocus; } return false; } return true; } return false; } } private RunSnapshot View { get { NetworkBridge networkBridge = bridge; if (networkBridge == null) { return null; } return networkBridge.Replica.Current; } } private bool OverlayVisible { get { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Invalid comparison between Unknown and I4 //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Invalid comparison between Unknown and I4 //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Invalid comparison between Unknown and I4 //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Invalid comparison between Unknown and I4 if (View != null) { if ((int)View.State != 6 && (int)View.State != 7 && (int)View.State != 8) { return (int)View.State == 5; } return true; } return false; } } private bool Paused { get { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Invalid comparison between Unknown and I4 if (!MatchSetupVisible && (InputManager.CurrentModeMask & 0x20) != 0) { if ((int)InputManager.CurrentMode != 32) { return uiFocus; } return true; } return false; } } private bool NativeMenuFocused { get { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Invalid comparison between Unknown and I4 //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Invalid comparison between Unknown and I4 //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Invalid comparison between Unknown and I4 //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Invalid comparison between Unknown and I4 if ((int)InputManager.CurrentMode != 32 && (int)InputManager.CurrentMode != 256 && (int)InputManager.CurrentMode != 1024) { return (int)InputManager.CurrentMode == 512; } return true; } } private bool InputAvailable { get { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Invalid comparison between Unknown and I4 //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Invalid comparison between Unknown and I4 //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Invalid comparison between Unknown and I4 if (Application.isFocused && !NativeMenuFocused && (int)InputManager.CurrentMode != 64 && (int)InputManager.CurrentMode != 2048) { return (int)InputManager.CurrentMode != 128; } return false; } } private bool SummaryVisible { get { if (View != null && View.SummaryComplete && dismissedSummary != View.RunId) { return !NativeMenuFocused; } return false; } } private ModifierRegistry UiRegistry { get { //IL_0053: Unknown result type (might be due to invalid IL or missing references) if (hudSettings != View?.ModifierSettings || hudRegistry == null) { hudSettings = (ModifierSettings)(((object)View?.ModifierSettings) ?? ((object)new ModifierSettings(1.2f, 2f, 0.5f, 0.6f, 1.5f, (IEnumerable<KeyValuePair<string, float>>)null))); hudRegistry = ModifierRegistry.Create(hudSettings); } return hudRegistry; } } private IEnumerable<ActiveModifier> CurrentEffects { get { ModifierManager obj = modifiers; return ((obj != null) ? obj.Active : null) ?? remoteActive; } } private void ConfigureActiveEvents() { //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Expected O, but got Unknown //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Expected O, but got Unknown //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Expected O, but got Unknown //IL_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Expected O, but got Unknown //IL_0110: Unknown result type (might be due to invalid IL or missing references) //IL_011a: Expected O, but got Unknown stormCap = ((BaseUnityPlugin)this).Config.Bind<int>("LawOfZeus", "MaxActiveStorms", 7, new ConfigDescription("Maximum RogueGolf storms, including fading clouds. Existing vanilla item storms consume free capacity. Captured when the event starts.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 7), Array.Empty<object>())); burstMin = ((BaseUnityPlugin)this).Config.Bind<int>("LawOfZeus", "MinBurstSize", 1, new ConfigDescription("Whole storms per burst, not individual bolts.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 3), Array.Empty<object>())); burstMax = ((BaseUnityPlugin)this).Config.Bind<int>("LawOfZeus", "MaxBurstSize", 2, new ConfigDescription("Maximum whole storms per burst.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 3), Array.Empty<object>())); burstIntervalMin = ((BaseUnityPlugin)this).Config.Bind<float>("LawOfZeus", "MinBurstInterval", 0.8f, new ConfigDescription("Scaled seconds between bursts.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.4f, 10f), Array.Empty<object>())); burstIntervalMax = ((BaseUnityPlugin)this).Config.Bind<float>("LawOfZeus", "MaxBurstInterval", 1.5f, new ConfigDescription("Maximum scaled seconds between bursts.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.4f, 10f), Array.Empty<object>())); eventVerbose = ((BaseUnityPlugin)this).Config.Bind<bool>("LawOfZeus", "VerboseSpawns", false, "Log each chosen storm center; normal logs summarize bursts only."); string[] source = new string[5] { "GreenWeight", "PlayerWeight", "BallWeight", "ItemPointWeight", "FairwayWeight" }; int[] defaults = new int[5] { 25, 25, 15, 20, 15 }; eventWeights = source.Select((string n, int i) => ((BaseUnityPlugin)this).Config.Bind<int>("LawOfZeus", n, defaults[i], new ConfigDescription("Relative area targeting weight. Zero excludes this category. Unavailable landmarks are omitted.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 1000), Array.Empty<object>()))).ToArray(); Channel((EffectChannel)16384, delegate { if (AccessTools.Method(typeof(ThunderstormManager), "ServerCreateThunderstorm", new Type[3] { typeof(Vector3), typeof(PlayerInventory), typeof(ItemUseId) }, (Type[])null) == null) { throw new MissingMethodException("Vanilla thunderstorm factory"); } }); } private void TickActiveEvents() { //IL_0164: Unknown result type (might be due to invalid IL or missing references) //IL_016e: Expected O, but got Unknown //IL_01d4: Unknown result type (might be due to invalid IL or missing references) if (!NetworkServer.active || !enabledMode.Value || !EffectsPlaying || modifiers == null || !modifiers.Active.Any((ActiveModifier e) => e.Id == "law_of_zeus" && !e.Suspended && e.FirstHole <= TestHole) || bridge == null || !bridge.CanStart(out var _)) { StopActiveEvents(); } else { if (eventFaulted) { return; } try { if (worldEvents != null && (eventRun != EffectRunId || eventHole != TestHole)) { StopActiveEvents(); } if (worldEvents == null) { StopThunderProof(); eventRun = EffectRunId; eventHole = TestHole; eventLocations = new WorldSpawnLocations(); int cap = stormCap.Value; int[] weights = eventWeights.Select((ConfigEntry<int> w) => w.Value).ToArray(); if (weights.Sum() == 0) { throw new InvalidOperationException("At least one Zeus targeting weight must be positive."); } worldEvents = new WorldEventController(eventSeed ^ (uint)((TestHole + 1) * 7919), cap, cap, cap, 4); new LawOfZeusEvent((Func<bool>)(() => SpawnZeusStorm(weights)), (Action<int, int>)delegate(int burst, int count) { if (count > 0) { ((BaseUnityPlugin)this).Logger.LogInfo((object)("[EVENT] LawOfZeus burst=" + burst + " count=" + count + " active=" + worldEvents.ActiveCount + "/" + cap)); } }, (double)burstIntervalMin.Value, (double)Math.Max(burstIntervalMin.Value, burstIntervalMax.Value), burstMin.Value, Math.Max(burstMin.Value, burstMax.Value)).Start(worldEvents, Time.timeAsDouble); ((BaseUnityPlugin)this).Logger.LogInfo((object)("[EVENT] LawOfZeus started cap=" + cap + " " + eventLocations.Summary)); } worldEvents.Tick(Time.timeAsDouble, true); } catch (Exception ex) { try { StopActiveEvents(); } finally { eventFaulted = true; } ((BaseUnityPlugin)this).Logger.LogError((object)("[EVENT] Disabled for this hole after failure: " + ex)); } } } private bool SpawnZeusStorm(int[] weights) { Vector3[] activeCenters = ThunderstormAdapter.ActiveCenters; if (activeCenters.Length >= worldEvents.MaxActive) { return false; } if (!eventLocations.TryScatter(worldEvents.Random, weights, Players(), activeCenters, out var point, out var category)) { return false; } bool num = worldEvents.TrySpawn("law_of_zeus", "thunderstorm", (Func<IWorldHazard>)(() => (IWorldHazard)(object)thunderAdapter.Spawn(point)), Math.Max(0, activeCenters.Length - worldEvents.ActiveCount)); if (num && eventVerbose.Value) { ((BaseUnityPlugin)this).Logger.LogInfo((object)("[EVENT] Thunderstorm target=" + category + " position=" + ((object)Unsafe.As<Vector3, Vector3>(ref point)/*cast due to .constrained prefix*/).ToString())); } return num; } private void StopActiveEvents() { WorldEventController val = worldEvents; worldEvents = null; eventLocations = null; eventRun = ""; eventHole = -1; if (val != null) { val.Stop(); ((BaseUnityPlugin)this).Logger.LogInfo((object)("[EVENT] Stopped; schedules=0 owned=0 spawned=" + val.Spawned + " budgetRejected=" + val.Rejected)); } } private void DrawCycles() { //IL_0207: Unknown result type (might be due to invalid IL or missing references) //IL_020c: Unknown result type (might be due to invalid IL or missing references) //IL_0258: Unknown result type (might be due to invalid IL or missing references) //IL_025d: Unknown result type (might be due to invalid IL or missing references) //IL_02d7: Unknown result type (might be due to invalid IL or missing references) //IL_0341: Unknown result type (might be due to invalid IL or missing references) //IL_03c0: Unknown result type (might be due to invalid IL or missing references) //IL_03c5: Unknown result type (might be due to invalid IL or missing references) RunSnapshot s = View; if (conditionRun != s.RunId) { conditionValues.Clear(); conditionChanged.Clear(); conditionRun = s.RunId; } float value = 1f; if ((Object)(object)GameManager.LocalPlayerId != (Object)null) { s.PlayerShotFactors.TryGetValue(GameManager.LocalPlayerId.Guid, out value); } if (value <= 0f) { value = 1f; } KeyValuePair<string, string>[] array = Conditions.Rows(UiRegistry, s.ModifierSettings, (IEnumerable<ActiveModifier>)s.ActiveModifiers, s.Hole, value); bool flag = s.ActiveModifiers.Any((ActiveModifier e) => !e.Suspended && e.FirstHole <= s.Hole && EffectPresentation.HasCycle(UiRegistry.Get(e.Id).Rule)); if (array.Length == 0 && !flag) { return; } KeyValuePair<string, string>[] array2 = array; for (int num = 0; num < array2.Length; num++) { KeyValuePair<string, string> keyValuePair = array2[num]; if (!conditionValues.TryGetValue(keyValuePair.Key, out var value2) || value2 != keyValuePair.Value) { conditionChanged[keyValuePair.Key] = Time.unscaledTime; } conditionValues[keyValuePair.Key] = keyValuePair.Value; } string[] array3 = conditionValues.Keys.Except(array.Select((KeyValuePair<string, string> r) => r.Key)).ToArray(); foreach (string key in array3) { conditionValues.Remove(key); } int num2 = Math.Min(10, array.Length); Transform transform = ((Component)Shape((Transform)(object)uiContent, 20f, 145f, 282f, 45 + num2 * 22 + ((array.Length > num2) ? 22 : 0), Color32.op_Implicit(new Color32((byte)24, (byte)39, (byte)30, (byte)185)), 10f)).transform; Label(transform, 12f, 9f, 217f, 24f, "CONDITIONS", 13f, Color32.op_Implicit(new Color32((byte)215, (byte)225, (byte)199, byte.MaxValue)), bold: true); if (flag) { RectTransform val = Rect(transform, 239f, 7f, 32f, 32f); cycleRing = ((Component)val).gameObject.AddComponent<CycleRing>(); ((Graphic)cycleRing).raycastTarget = false; cycleLabel = Label(transform, 240f, 12f, 30f, 21f, "", 12f, Color.white, bold: true); ((TMP_Text)cycleLabel).alignment = (TextAlignmentOptions)514; } for (int num3 = 0; num3 < num2; num3++) { conditionLabels.Add(new KeyValuePair<string, TextMeshProUGUI>(array[num3].Key, Label(transform, 12f, 40 + num3 * 22, 258f, 22f, array[num3].Value, 16f, Color.white))); } if (array.Length > num2) { Label(transform, 12f, 40 + num2 * 22, 258f, 22f, "+" + (array.Length - num2) + " conditions · Esc for details", 13f, Color32.op_Implicit(new Color32((byte)215, (byte)225, (byte)199, byte.MaxValue))); } } private void UpdateCycleDisplay() { //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_006d: 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) foreach (KeyValuePair<string, TextMeshProUGUI> conditionLabel in conditionLabels) { if ((Object)(object)conditionLabel.Value != (Object)null) { float value; float num = (conditionChanged.TryGetValue(conditionLabel.Key, out value) ? (Time.unscaledTime - value) : 10f); ((Graphic)conditionLabel.Value).color = Color.Lerp(Color32.op_Implicit(new Color32(byte.MaxValue, (byte)220, (byte)105, byte.MaxValue)), Color.white, Mathf.Clamp01(num / 1.1f)); } } if (!((Object)(object)cycleLabel == (Object)null) && !((Object)(object)cycleRing == (Object)null) && View != null) { double num2 = Math.Max(0.0, View.NextCycleAt - NetworkTime.time); ((TMP_Text)cycleLabel).text = ((View.NextCycleAt <= 0.0) ? "–" : Math.Ceiling(num2).ToString()); cycleRing.Progress = Mathf.Clamp01((float)(num2 / (double)View.ModifierSettings.Interval)); } } private void PeerSceneLoaded(Scene scene, LoadSceneMode mode) { QueuePeerSceneRefresh(); } private void QueuePeerSceneRefresh() { if (!NetworkServer.active && NetworkClient.active) { peerSceneRefreshFrame = Time.frameCount + 1; } } private void RefreshPeerScene() { if (peerSceneRefreshFrame < 0 || Time.frameCount < peerSceneRefreshFrame) { return; } peerSceneRefreshFrame = -1; if (!NetworkServer.active && NetworkClient.isConnected) { NetworkBridge networkBridge = bridge; if (((networkBridge != null) ? networkBridge.Replica.Current : null) != null) { ReceiveEffects(bridge.Replica.Current); NetLog("Rebound peer effects after scene load"); } } } private static GravityVector ReadGravity() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) return new GravityVector(Physics.gravity.x, Physics.gravity.y, Physics.gravity.z); } private ConfigEntry<float> Factor(string name, float value, float min, float max) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Expected O, but got Unknown return ((BaseUnityPlugin)this).Config.Bind<float>("Modifiers", name, value, new ConfigDescription("Multiplier captured from host config at run start.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(min, max), Array.Empty<object>())); } private unsafe void ConfigureModifiers() { //IL_0956: Unknown result type (might be due to invalid IL or missing references) //IL_095b: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Expected O, but got Unknown //IL_0120: Unknown result type (might be due to invalid IL or missing references) //IL_012a: Expected O, but got Unknown //IL_08ee: Unknown result type (might be due to invalid IL or missing references) //IL_08fb: Expected O, but got Unknown //IL_08fe: Unknown result type (might be due to invalid IL or missing references) //IL_0904: Unknown result type (might be due to invalid IL or missing references) //IL_033e: Unknown result type (might be due to invalid IL or missing references) //IL_0348: Expected O, but got Unknown //IL_0386: Unknown result type (might be due to invalid IL or missing references) //IL_0390: Expected O, but got Unknown //IL_03ce: Unknown result type (might be due to invalid IL or missing references) //IL_03d8: Expected O, but got Unknown //IL_01d4: 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_01ed: Unknown result type (might be due to invalid IL or missing references) //IL_01ef: Unknown result type (might be due to invalid IL or missing references) //IL_01f4: Unknown result type (might be due to invalid IL or missing references) //IL_021f: Unknown result type (might be due to invalid IL or missing references) //IL_0221: Unknown result type (might be due to invalid IL or missing references) //IL_0226: Unknown result type (might be due to invalid IL or missing references) //IL_023a: Unknown result type (might be due to invalid IL or missing references) //IL_0246: Unknown result type (might be due to invalid IL or missing references) //IL_0248: Unknown result type (might be due to invalid IL or missing references) //IL_0252: Unknown result type (might be due to invalid IL or missing references) //IL_0254: Unknown result type (might be due to invalid IL or missing references) //IL_0268: Unknown result type (might be due to invalid IL or missing references) //IL_0272: Expected O, but got Unknown //IL_04c0: Unknown result type (might be due to invalid IL or missing references) //IL_04ca: Expected O, but got Unknown //IL_0516: Unknown result type (might be due to invalid IL or missing references) //IL_0520: Expected O, but got Unknown //IL_055e: Unknown result type (might be due to invalid IL or missing references) //IL_0568: Expected O, but got Unknown //IL_05a6: Unknown result type (might be due to invalid IL or missing references) //IL_05b0: Expected O, but got Unknown //IL_05ee: Unknown result type (might be due to invalid IL or missing references) //IL_05f8: Expected O, but got Unknown //IL_073f: Unknown result type (might be due to invalid IL or missing references) //IL_0745: Unknown result type (might be due to invalid IL or missing references) //IL_076f: Unknown result type (might be due to invalid IL or missing references) //IL_0784: Unknown result type (might be due to invalid IL or missing references) //IL_0791: Expected O, but got Unknown //IL_0791: Expected O, but got Unknown //IL_07aa: Unknown result type (might be due to invalid IL or missing references) //IL_07b7: Expected O, but got Unknown //IL_0865: Unknown result type (might be due to invalid IL or missing references) //IL_0872: Expected O, but got Unknown //IL_0875: Unknown result type (might be due to invalid IL or missing references) //IL_087b: Unknown result type (might be due to invalid IL or missing references) gravity = new GravityLease((Func<GravityVector>)ReadGravity, (Action<GravityVector>)delegate(GravityVector v) { //IL_0000: 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_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) Physics.gravity = new Vector3(v.X, v.Y, v.Z); }); traceGravity = ((BaseUnityPlugin)this).Config.Bind<bool>("Diagnostics", "TraceGravity", false, "Read-only native physics and shot-speed samples for local verification."); powerFactor = Factor("PowerSurge", 1.2f, 1f, 3f); heavyFactor = Factor("HeavyBall", 2f, 1f, 4f); featherFactor = Factor("Featherweight", 0.5f, 0.25f, 1f); lowFactor = Factor("LowGravity", 0.6f, 0.1f, 1f); highFactor = Factor("HighGravity", 1.5f, 1f, 4f); ModifierDefinition[] definitions = ModifierRegistry.Create(new ModifierSettings(1.2f, 2f, 0.5f, 0.6f, 1.5f, (IEnumerable<KeyValuePair<string, float>>)null)).Definitions; foreach (ModifierDefinition val in definitions) { modifierEnabled[val.Id] = ((BaseUnityPlugin)this).Config.Bind<bool>("Modifiers.Enabled", val.Id, true, "Offer " + val.Name + " when supported and compatible."); if (!new string[5] { "power_surge", "heavy_ball", "featherweight", "low_gravity", "high_gravity" }.Contains(val.Id)) { Contribution[] contributions = val.Contributions; foreach (Contribution val2 in contributions) { Dictionary<string, ConfigEntry<float>> dictionary = tuning; string id = val.Id; Parameter parameter = val2.Parameter; string key = id + "." + ((object)(*(Parameter*)(¶meter))/*cast due to .constrained prefix*/).ToString(); ConfigFile config = ((BaseUnityPlugin)this).Config; string id2 = val.Id; parameter = val2.Parameter; dictionary[key] = config.Bind<float>("Tuning", id2 + "." + ((object)(*(Parameter*)(¶meter))/*cast due to .constrained prefix*/).ToString(), val2.Factor, new ConfigDescription("Per-run channel multiplier.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(CanonicalCatalog.Minimum(val2.Parameter), CanonicalCatalog.Maximum(val2.Parameter)), Array.Empty<object>())); } } } ConfigEntry<bool> val3 = ((BaseUnityPlugin)this).Config.Bind<bool>("Compatibility", "InfiniteWarfareFiveUses", false, "Tracks the one-time 0.10.0 ammo balance migration."); if (!val3.Value) { if (tuning["infinite_warfare.Ammo"].Value == 10f) { tuning["infinite_warfare.Ammo"].Value = 5f; } val3.Value = true; ((BaseUnityPlugin)this).Config.Save(); } tuning["LeaderTaxFactor"] = ((BaseUnityPlugin)this).Config.Bind<float>("Tuning", "LeaderTaxFactor", 0.8f, new ConfigDescription("Leader shot factor.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 1f), Array.Empty<object>())); tuning["LastPlaceFactor"] = ((BaseUnityPlugin)this).Config.Bind<float>("Tuning", "LastPlaceFactor", 1.2f, new ConfigDescription("Last place shot factor.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 1.5f), Array.Empty<object>())); tuning["ComebackMaximum"] = ((BaseUnityPlugin)this).Config.Bind<float>("Tuning", "ComebackMaximum", 1.3f, new ConfigDescription("Maximum rank assistance.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 1.5f), Array.Empty<object>())); var array = new[] { new { Key = "TimedLow", Value = 0.35f, Min = 0.1f, Max = 1f }, new { Key = "TimedHigh", Value = 2f, Min = 1f, Max = 3f }, new { Key = "RouletteMoon", Value = 0.2f, Min = 0.1f, Max = 1f }, new { Key = "RouletteLow", Value = 0.6f, Min = 0.1f, Max = 1f }, new { Key = "RouletteHigh", Value = 1.5f, Min = 1f, Max = 4f } }; foreach (var anon in array) { tuning[anon.Key] = ((BaseUnityPlugin)this).Config.Bind<float>("Tuning", anon.Key, anon.Value, new ConfigDescription("Timed physics preset factor.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(anon.Min, anon.Max), Array.Empty<object>())); } tuning["IntervalSeconds"] = ((BaseUnityPlugin)this).Config.Bind<float>("Tuning", "IntervalSeconds", 20f, new ConfigDescription("Host interval for timed effects.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(5f, 120f), Array.Empty<object>())); tuning["ImpactThreshold"] = ((BaseUnityPlugin)this).Config.Bind<float>("Tuning", "ImpactThreshold", 12f, new ConfigDescription("Minimum impact speed for explosions.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(5f, 80f), Array.Empty<object>())); tuning["ExplosionCooldown"] = ((BaseUnityPlugin)this).Config.Bind<float>("Tuning", "ExplosionCooldown", 2f, new ConfigDescription("Global explosion cooldown.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 30f), Array.Empty<object>())); tuning["ExplosionRadius"] = ((BaseUnityPlugin)this).Config.Bind<float>("Tuning", "ExplosionRadius", 5f, new ConfigDescription("Native explosion radius, Nuclear Golf doubles it up to 12.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 12f), Array.Empty<object>())); ConfigEntry<bool> val4 = ((BaseUnityPlugin)this).Config.Bind<bool>("Compatibility", "Balance0101", false, "Tracks the one-time 0.10.1 balance-default migration; custom values are preserved."); if (!val4.Value) { Dictionary<string, float> dictionary2 = tuning.ToDictionary((KeyValuePair<string, ConfigEntry<float>> p) => p.Key, (KeyValuePair<string, ConfigEntry<float>> p) => p.Value.Value); int num3 = BalanceMigration.Apply0101((IDictionary<string, float>)dictionary2, false); foreach (KeyValuePair<string, float> item in dictionary2) { tuning[item.Key].Value = item.Value; } val4.Value = true; ((BaseUnityPlugin)this).Config.Save(); ((BaseUnityPlugin)this).Logger.LogInfo((object)("[CONFIG] 0.10.1 balance migration updated " + num3 + " legacy defaults")); } MethodInfo methodInfo = AccessTools.DeclaredMethod(typeof(GolfBall), "ApplyGravity", Type.EmptyTypes, (Type[])null); MethodInfo methodInfo2 = AccessTools.DeclaredMethod(typeof(PlayerMovement), "GetBaseGravitySpeedDelta", Type.EmptyTypes, (Type[])null); if (methodInfo != null && methodInfo2 != null) { Capabilities = (EffectChannel)(Capabilities | 2); if (traceGravity.Value) { harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(RogueGolfPlugin), "BeforeBallGravity", (Type[])null), new HarmonyMethod(typeof(RogueGolfPlugin), "AfterBallGravity", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, new HarmonyMethod(typeof(RogueGolfPlugin), "AfterPlayerGravity", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } else { ((BaseUnityPlugin)this).Logger.LogWarning((object)"[MODIFIER] Gravity channel unavailable: missing native consumer hooks"); } try { swingSpeedMethod = AccessTools.DeclaredMethod(typeof(Hittable), "GetSwingHitSpeed", new Type[3] { typeof(bool), typeof(SwingType), typeof(float) }, (Type[])null); if (swingSpeedMethod == null || swingSpeedMethod.ReturnType != typeof(float)) { throw new MissingMethodException("GetSwingHitSpeed"); } harmony.Patch((MethodBase)swingSpeedMethod, (HarmonyMethod)null, new HarmonyMethod(typeof(RogueGolfPlugin), "ScaleShotSpeed", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Capabilities = (EffectChannel)(Capabilities | 1); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("[MODIFIER] Shot power unavailable: " + ex.Message)); } try { MethodInfo methodInfo3 = AccessTools.DeclaredMethod(typeof(GolfBall), "Awake", Type.EmptyTypes, (Type[])null); if (methodInfo3 == null) { throw new MissingMethodException("GolfBall.Awake"); } harmony.Patch((MethodBase)methodInfo3, (HarmonyMethod)null, new HarmonyMethod(typeof(RogueGolfPlugin), "BallCreated", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Capabilities = (EffectChannel)(Capabilities | 4); } catch (Exception ex2) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("[MODIFIER] Mass unavailable: " + ex2.Message)); } ConfigurePhysicsChannels(); ConfigureWorldChannels(); ConfigureActiveEvents(); ConfigureLab(); ConfigureTestingMenu(); ((BaseUnityPlugin)this).Logger.LogInfo((object)("[MODIFIER] Supported channels=" + ((object)Capabilities/*cast due to .constrained prefix*/).ToString())); } private void BeginModifiers(uint seedValue) { //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_0073: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Expected O, but got Unknown //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: Unknown result type (might be due to invalid IL or missing references) //IL_0127: Unknown result type (might be due to invalid IL or missing references) //IL_0131: Expected O, but got Unknown //IL_0139: Unknown result type (might be due to invalid IL or missing references) //IL_0143: Expected O, but got Unknown RestoreEffects(); gravityBaseline = ReadGravity(); modifierSettings = new ModifierSettings(powerFactor.Value, heavyFactor.Value, featherFactor.Value, lowFactor.Value, highFactor.Value, tuning.Select((KeyValuePair<string, ConfigEntry<float>> p) => new KeyValuePair<string, float>(p.Key, p.Value.Value))); modifiers = ((runStage != PrototypeStage.Modifiers) ? ((ModifierManager)null) : new ModifierManager(ModifierRegistry.Create(modifierSettings), from p in modifierEnabled where p.Value.Value select p.Key, Capabilities)); if (modifiers != null) { modifiers.ContextAllows = AllowsContext; } eventSeed = seedValue ^ 0x5E05; worldRandom = new RunRandom(seedValue ^ 0x91317); modifierRandom = new RunRandom(seedValue ^ 0xA511E9B3u); if (modifiers != null) { ((BaseUnityPlugin)this).Logger.LogInfo((object)("[MODIFIER] Available=" + string.Join(",", modifiers.Available))); } } private void ActivateModifier(string id) { //IL_0022: 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_0083: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) if (modifiers == null || !modifiers.Activate(id, Run.HoleIndex + 1, progression.Persistence)) { throw new InvalidOperationException("Winner is no longer eligible: " + id); } if (CanonicalCatalog.IsTimed(modifiers.Registry.Get(id).Rule)) { modifiers.SetVariant(id, worldRandom.Next(32)); } ApplyEffects(Run.RunId, gravityBaseline, modifiers.Values); ((BaseUnityPlugin)this).Logger.LogInfo((object)("[MODIFIER] Activated " + id + " active=" + string.Join(",", modifiers.Active.Select((ActiveModifier e) => e.Id)))); } private void ExpireModifiers(int hole) { //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) if (modifiers == null) { return; } ActiveModifier[] active = modifiers.Active; if (modifiers.CompleteHole(hole)) { ApplyEffects(Run.RunId, gravityBaseline, modifiers.Values); ((BaseUnityPlugin)this).Logger.LogInfo((object)("[MODIFIER] Expired=" + string.Join(",", active.Select((ActiveModifier e) => e.Id).Except(modifiers.Active.Select((ActiveModifier e) => e.Id))) + " hole=" + hole)); } } private void ApplyEffects(string runId, GravityVector baseline, EffectValues values) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_014a: Unknown result type (might be due to invalid IL or missing references) //IL_014f: Unknown result type (might be due to invalid IL or missing references) BindScoringTriggers(); dampingTraced = false; float num = ((EffectValues)(ref effects))[(Parameter)9]; effects = values; RefreshMechanics(); if (((EffectValues)(ref values))[(Parameter)9] < num) { TrimAmmo(); } if (((EffectValues)(ref values)).Gravity == 1f) { gravity.Restore(); } else { gravity.Apply(runId, baseline, ((EffectValues)(ref values)).Gravity); } Rigidbody[] array = massLeases.Keys.Where((Rigidbody b) => (Object)(object)b == (Object)null).ToArray(); foreach (Rigidbody key in array) { massLeases.Remove(key); } GolfBall[] array2 = Object.FindObjectsByType<GolfBall>((FindObjectsInactive)0, (FindObjectsSortMode)0); foreach (GolfBall ball in array2) { ApplyBallMass(ball); } ManualLogSource logger = ((BaseUnityPlugin)this).Logger; string[] obj = new string[8] { "[MODIFIER] Effective shot=", ((EffectValues)(ref values)).ShotPower.ToString(), " gravity=", ((EffectValues)(ref values)).Gravity.ToString(), " mass=", ((EffectValues)(ref values)).BallMass.ToString(), " vector=", null }; Vector3 val = Physics.gravity; obj[7] = ((Vector3)(ref val)).ToString("F4"); logger.LogInfo((object)string.Concat(obj)); } private void ApplyBallMass(GolfBall ball) { //IL_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Expected O, but got Unknown Rigidbody[] array = massLeases.Keys.Where((Rigidbody b) => (Object)(object)b == (Object)null).ToArray(); foreach (Rigidbody key in array) { massLeases.Remove(key); } if ((Object)(object)ball == (Object)null || (Object)(object)ball.Rigidbody == (Object)null) { return; } BindShape(ball); Rigidbody body = ball.Rigidbody; if (!massLeases.TryGetValue(body, out var value)) { if (((EffectValues)(ref effects)).BallMass == 1f) { return; } value = new ScalarLease((Func<float>)(() => body.mass), (Action<float>)delegate(float v) { if ((Object)(object)body != (Object)null) { body.mass = v; } }); massLeases.Add(body, value); } value.Apply(((EffectValues)(ref effects)).BallMass); if (traceGravity.Value) { ((BaseUnityPlugin)this).Logger.LogInfo((object)("[PHYSICS] Mass hole=" + CourseManager.CurrentHoleCourseIndex + " factor=" + ((EffectValues)(ref effects)).BallMass + " actual=" + body.mass)); } } private static void BallCreated(GolfBall __instance) { Instance?.ApplyBallMass(__instance); } private static void ScaleShotSpeed(Hittable __instance, ref float __result) { RogueGolfPlugin instance = Instance; if (!((Object)(object)instance == (Object)null) && ((Behaviour)instance).enabled && !((Object)(object)__instance.AsEntity == (Object)null) && __instance.AsEntity.IsGolfBall) { float num = __result; float num2 = __result; float shotPower = ((EffectValues)(ref instance.effects)).ShotPower; PlayerGolfer owner = __instance.AsEntity.AsGolfBall.Owner; __result = num2 * Mathf.Clamp(shotPower * instance.PlayerShotFactor((owner != null) ? owner.PlayerInfo : null), 0.25f, 3f); if (instance.traceGravity.Value) { ((BaseUnityPlugin)instance).Logger.LogInfo((object)("[PHYSICS] Shot native=" + num.ToString("F6") + " factor=" + ((EffectValues)(ref instance.effects)).ShotPower + " actual=" + __result.ToString("F6"))); } } } private void ProbePhysics() { //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) if (!traceGravity.Value) { return; } GolfBall[] array = Object.FindObjectsByType<GolfBall>((FindObjectsInactive)0, (FindObjectsSortMode)0); foreach (GolfBall val in array) { ManualLogSource logger = ((BaseUnityPlugin)this).Logger; string[] obj = new string[6] { "[PHYSICS] Probe hole=", CourseManager.CurrentHoleCourseIndex.ToString(), " mass=", val.Rigidbody.mass.ToString(), " gravity=", null }; Vector3 val2 = Physics.gravity; obj[5] = ((Vector3)(ref val2)).ToString("F4"); logger.LogInfo((object)string.Concat(obj)); if ((Capabilities & 1) != 0) { swingSpeedMethod.Invoke(val.AsEntity.AsHittable, new object[3] { false, (object)(SwingType)0, 0.5f }); } } } internal void ResetPeerEffects() { RestoreEffects(); } private void RestoreEffects() { //IL_002d: 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_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: Unknown result type (might be due to invalid IL or missing references) peerSceneRefreshFrame = -1; GravityLease obj = gravity; if (obj != null) { obj.Restore(); } bool num = ((EffectValues)(ref effects))[(Parameter)9] > 1f; effects = EffectValues.Normal; if (num) { TrimAmmo(); } foreach (KeyValuePair<Rigidbody, ScalarLease> massLease in massLeases) { if ((Object)(object)massLease.Key != (Object)null) { massLease.Value.Restore(); ConfigEntry<bool> obj2 = traceGravity; if (obj2 != null && obj2.Value) { ((BaseUnityPlugin)this).Logger.LogInfo((object)("[PHYSICS] Mass restored=" + massLease.Key.mass)); } } } massLeases.Clear(); ModifierManager obj3 = modifiers; if (obj3 != null) { obj3.Clear(); } RestoreShapes(); ResetWorldEffects(); ManualLogSource logger = ((BaseUnityPlugin)this).Logger; Vector3 val = Physics.gravity; logger.LogInfo((object)("[MODIFIER] Restored channels; gravity=" + ((Vector3)(ref val)).ToString("F4"))); } private void ReceiveEffects(RunSnapshot s) { //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) if (!NetworkServer.active) { remoteActive = s.ActiveModifiers; modifierSettings = s.ModifierSettings; playerShotFactors = new Dictionary<ulong, float>(s.PlayerShotFactors); if (!((Behaviour)this).enabled || s.ActiveModifiers.Length == 0) { RestoreEffects(); } else { ApplyEffects(s.RunId, s.GravityBaseline, ModifierRegistry.Create(s.ModifierSettings).Compose((IEnumerable<ActiveModifier>)s.ActiveModifiers)); } } } private static void BeforeBallGravity(Rigidbody ___rigidbody, out Vector3 __state) { //IL_0002: 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) __state = ___rigidbody.linearVelocity; } private static void AfterBallGravity(Rigidbody ___rigidbody, Vector3 __state) { //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) RogueGolfPlugin instance = Instance; if (!((Object)(object)instance == (Object)null) && !(instance.ballSample == instance.GravitySampleKey)) { instance.ballSample = instance.GravitySampleKey; ManualLogSource logger = ((BaseUnityPlugin)instance).Logger; string[] obj = new string[8] { "[PHYSICS] Ball hole=", CourseManager.CurrentHoleCourseIndex.ToString(), " gravity=", null, null, null, null, null }; Vector3 val = Physics.gravity; obj[3] = ((Vector3)(ref val)).ToString("F4"); obj[4] = " delta="; val = ___rigidbody.linearVelocity - __state; obj[5] = ((Vector3)(ref val)).magnitude.ToString("F6"); obj[6] = " expected="; val = Physics.gravity; obj[7] = (((Vector3)(ref val)).magnitude * Time.fixedDeltaTime).ToString("F6"); logger.LogInfo((object)string.Concat(obj)); } } private static void AfterPlayerGravity(float __result, float ___gravityFactor) { //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) RogueGolfPlugin instance = Instance; if (!((Object)(object)instance == (Object)null) && !(instance.playerSample == instance.GravitySampleKey)) { instance.playerSample = instance.GravitySampleKey; float num = Physics.gravity.y * GameManager.PlayerMovementSettings.BaseGravityFactor * ___gravityFactor * Time.fixedDeltaTime; ManualLogSource logger = ((BaseUnityPlugin)instance).Logger; string[] obj = new string[8] { "[PHYSICS] Player hole=", CourseManager.CurrentHoleCourseIndex.ToString(), " gravity=", null, null, null, null, null }; Vector3 val = Physics.gravity; obj[3] = ((Vector3)(ref val)).ToString("F4"); obj[4] = " delta="; obj[5] = __result.ToString("F6"); obj[6] = " expected="; obj[7] = num.ToString("F6"); logger.LogInfo((object)string.Concat(obj)); } } private void OpenRunSetup() { if (NetworkServer.active && MatchSetupVisible) { SetUiFocus(value: true); if (uiFocus) { setupOpen = true; pauseTab = 1; settingsPage = 0; selected = 0; uiKey = ""; } } } private void CloseRunSetup() { setupOpen = false; pauseTab = 0; SetUiFocus(value: false); selected = 0; uiKey = ""; } private void DrawRunSetup() { //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_00be: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_0136: Unknown result type (might be due to invalid IL or missing references) //IL_012f: Unknown result type (might be due to invalid IL or missing references) //IL_017d: Unknown result type (might be due to invalid IL or missing references) //IL_0176: Unknown result type (might be due to invalid IL or missing references) //IL_01b4: Unknown result type (might be due to invalid IL or missing references) Shape((Transform)(object)uiContent, 0f, 0f, 1280f, 720f, new Color(0f, 0f, 0f, 0.65f), 0f, block: true); RectTransform val = Panel((Transform)(object)uiContent, 390f, 60f, 500f, 600f, Paper); Label((Transform)(object)val, 20f, 17f, 310f, 45f, "RogueGolf setup", 30f, null, bold: true); Button((Transform)(object)val, 360f, 24f, 120f, 34f, "Back", CloseRunSetup, Gold); Label((Transform)(object)val, 20f, 64f, 460f, 26f, "Private chaos runs · ties resolved randomly", 16f, Muted); Button((Transform)(object)val, 20f, 97f, 225f, 36f, "Run settings", delegate { pauseTab = 1; settingsPage = 0; }, (pauseTab == 1) ? Gold : Green); Button((Transform)(object)val, 255f, 97f, 225f, 36f, "Weights", delegate { pauseTab = 2; settingsPage = 0; }, (pauseTab == 2) ? Gold : Green); DrawHostSettings(val); Label((Transform)(object)val, 20f, 561f, 460f, 32f, "Arrows / D-pad: select · Enter / A: use · Esc / B: back", 14f, Muted); } private SettingRow EnumRow<T>(string name, ConfigEntry<T> entry) where T : struct { T[] choices = (T[])Enum.GetValues(typeof(T)); return new SettingRow { Name = name, Value = SplitWords(entry.Value.ToString()), Previous = delegate { Step(-1); }, Next = delegate { Step(1); } }; void Step(int direction) { int num = Array.IndexOf(choices, entry.Value); entry.Value = choices[(num + direction + choices.Length) % choices.Length]; SaveUiConfig(); } } private SettingRow IntRow(string name, ConfigEntry<int> entry, int min, int max, int step = 1) { return new SettingRow { Name = name, Value = entry.Value.ToString(), Previous = delegate { Step(-1); }, Next = delegate { Step(1); } }; void Step(int d) { entry.Value = Math.Max(min, Math.Min(max, entry.Value + d * step)); SaveUiConfig(); } } private SettingRow BoolRow(string name, ConfigEntry<bool> entry) { return new SettingRow { Name = name, Value = (entry.Value ? "On" : "Off"), Previous = Toggle, Next = Toggle }; void Toggle() { entry.Value = !entry.Value; SaveUiConfig(); } } private SettingRow SecondsRow(string name, ConfigEntry<float> entry, int max) { return new SettingRow { Name = name, Value = EffectPresentation.Number(entry.Value) + "s", Previous = delegate { Step(-1); }, Next = delegate { Step(1); } }; void Step(int d) { entry.Value = Math.Max(1f, Math.Min(max, entry.Value + (float)d)); SaveUiConfig(); } } private static string SplitWords(string text) { return Regex.Replace(text, "([a-z])([A-Z])", "$1 $2"); } private void SaveUiConfig() { ((BaseUnityPlugin)this).Config.Save(); uiKey = ""; } private void DrawHostSettings(RectTransform panel) { //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0497: Unknown result type (might be due to invalid IL or missing references) //IL_0490: Unknown result type (might be due to invalid IL or missing references) //IL_02f9: Unknown result type (might be due to invalid IL or missing references) //IL_02fe: Unknown result type (might be due to invalid IL or missing references) //IL_0320: Unknown result type (might be due to invalid IL or missing references) //IL_0325: 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_034c: Unknown result type (might be due to invalid IL or missing references) //IL_036e: Unknown result type (might be due to invalid IL or missing references) //IL_0373: Unknown result type (might be due to invalid IL or missing references) //IL_04de: Unknown result type (might be due to invalid IL or missing references) //IL_04d7: Unknown result type (might be due to invalid IL or missing references) //IL_050e: 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_07da: Unknown result type (might be due to invalid IL or missing references) //IL_07df: Unknown result type (might be due to invalid IL or missing references) //IL_0855: Unknown result type (might be due to invalid IL or missing references) //IL_089a: Unknown result type (might be due to invalid IL or missing references) //IL_08d5: Unknown result type (might be due to invalid IL or missing references) bool active = NetworkServer.active; Label((Transform)(object)panel, 20f, 146f, 460f, 44f, active ? "Next-course settings. Disabling also stops the current run." : "Current host settings. Only the host can edit the next course.", 16f, Muted); List<SettingRow> list = new List<SettingRow>(); if (pauseTab == 1) { if (active) { list.Add(BoolRow("RogueGolf enabled", enabledMode)); list.Add(EnumRow<SelectionMode>("Selection", selectionMode)); list.Add(EnumRow<PersistenceMode>("Persistence", persistenceMode)); list.Add(IntRow("Candidate cards", candidateCount, 2, 5)); list.Add(EnumRow<SkipMode>("Skip budget", skipMode)); list.Add(IntRow("Starting skips", skipBudget, 0, 99)); list.Add(EnumRow<SkipBehavior>("Skip effect", skipBehavior)); list.Add(BoolRow("Boss selections", bossEnabled)); list.Add(IntRow("Boss interval (selections)", bossInterval, 1, 99)); list.Add(BoolRow("Automatic skips", randomSkips)); list.Add(IntRow("Automatic skip weight", randomSkipWeight, 0, 10000, 10)); list.Add(IntRow("Automatic modifier weight", randomModifierWeight, 1, 10000, 10)); list.Add(SecondsRow("Voting time", voteSeconds, 120)); list.Add(SecondsRow("Reveal time", revealSeconds, 15)); list.Add(BoolRow("Allow vote changes", voteChanges)); list.Add(BoolRow("Close when all vote", earlyClose)); list.Add(IntRow("Run seed (0 = random)", seed, 0, 999999999)); for (int i = 0; i < bandStarts.Count; i++) { int index = i; ConfigEntry<int> entry = bandStarts[i]; list.Add(new SettingRow { Name = (new string[3] { "Early-mid starts at hole", "Mid-late starts at hole", "Late starts at hole" })[i], Value = entry.Value.ToString(), Previous = delegate { Change(-1); }, Next = delegate { Change(1); } }); void Change(int d) { int val2 = ((index == 0) ? 3 : (bandStarts[index - 1].Value + 1)); int val3 = ((index == 2) ? 999 : (bandStarts[index + 1].Value - 1)); entry.Value = Math.Max(val2, Math.Min(val3, entry.Value + d)); SaveUiConfig(); } } } else if (View != null) { ProgressionSettings val = View.Progression; (string, string)[] array = new(string, string)[7] { ("Selection", ((object)val.Selection/*cast due to .constrained prefix*/).ToString()), ("Persistence", ((object)val.Persistence/*cast due to .constrained prefix*/).ToString()), ("Skip budget", ((object)val.Skips/*cast due to .constrained prefix*/).ToString()), ("Skip effect", ((object)val.SkipEffect/*cast due to .constrained prefix*/).ToString()), ("Boss interval", val.BossEnabled ? val.BossInterval.ToString() : "Off"), ("Seed", View.Seed.ToString()), ("Vote time", View.Settings.Duration + "s") }; for (int num = 0; num < array.Length; num++) { (string, string) tuple = array[num]; list.Add(new SettingRow { Name = tuple.Item1, Value = SplitWords(tuple.Item2) }); } } } else { Button((Transform)(object)panel, 20f, 190f, 148f, 32f, "Rarity weights", delegate { weightTab = 0; settingsPage = 0; }, (weightTab == 0) ? Gold : Green); Button((Transform)(object)panel, 176f, 190f, 165f, 32f, "Modifier weights", delegate { weightTab = 1; settingsPage = 0; }, (weightTab == 1) ? Gold : Green); Label((Transform)(object)panel, 355f, 196f, 125f, 25f, "0 excludes", 14f, Muted); if (weightTab == 0) { string[] array2 = new string[4] { "Early", "Early-mid", "Mid-late", "Late" }; for (int num2 = 0; num2 < 14; num2++) { string name = ((num2 < 12) ? (array2[num2 / 3] + " / " + ((object)(ModifierRarity)(num2 % 3)/*cast due to .constrained prefix*/).ToString()) : ("Boss selection / " + ((num2 == 12) ? "Legendary" : "Boss"))); object obj; if (!active) { obj = new SettingRow { Name = name }; object obj2 = obj; RunSnapshot view = View; ((SettingRow)obj2).Value = ((view != null) ? view.Progression.TierWeights[num2] : 0).ToString(); } else { obj = IntRow(name, tierWeights[num2], 0, 10000, 5); } list.Add((SettingRow)obj); } } else { foreach (ModifierDefinition item in from d in UiRegistry.Definitions orderby d.Rarity, d.Name select d) { object obj3; if (!active) { obj3 = new SettingRow { Name = item.Name }; object obj4 = obj3; RunSnapshot view2 = View; ((SettingRow)obj4).Value = ((view2 != null) ? view2.Progression.Weight(item.Id) : 100).ToString(); } else { obj3 = IntRow(item.Name + (modifierEnabled[item.Id].Value ? "" : " (disabled)"), modifierWeights[item.Id], 0, 10000, 25); } list.Add((SettingRow)obj3); } } } int num3 = ((pauseTab == 1) ? 6 : 5); int num4 = Math.Max(1, (list.Count + num3 - 1) / num3); settingsPage = Math.Max(0, Math.Min(settingsPage, num4 - 1)); for (int num5 = 0; num5 < num3 && settingsPage * num3 + num5 < list.Count; num5++) { SettingRow row = list[settingsPage * num3 + num5]; float num6 = ((pauseTab == 1) ? 197 : 236) + num5 * 49; Shape((Transform)(object)panel, 20f, num6, 460f, 44f, Color32.op_Implicit(new Color32((byte)244, (byte)239, (byte)210, byte.MaxValue)), 10f); Label((Transform)(object)panel, 30f, num6 + 5f, 220f, 36f, row.Name, 17f, null, bold: true); ((TMP_Text)Label((Transform)(object)panel, 294f, num6 + 6f, 140f, 34f, row.Value, 16f, Muted)).alignment = (TextAlignmentOptions)514; Button((Transform)(object)panel, 256f, num6 + 6f, 34f, 32f, "−", delegate { if (NetworkServer.active) { row.Previous?.Invoke(); } }, Gold, active); Button((Transform)(object)panel, 438f, num6 + 6f, 34f, 32f, "+", delegate { if (NetworkServer.active) { row.Next?.Invoke(); } }, Gold, active); } Pager(panel, settingsPage, num4, delegate { settingsPage--; }, delegate { settingsPage++; }); } private void ConfigureTestingMenu() { testingKey = ((BaseUnityPlugin)this).Config.Bind<Key>("Diagnostics", "TestingMenuKey", (Key)99, "Host testing menu key, only while the native console is open with Console Commands enabled in a private driving range or playing hole."); try { NativeHook(typeof(DevConsoleGui), "OnGUI", "AllowConsoleGui"); testingUiReady = true; } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("[TEST] Testing menu unavailable: " + ex.Message)); } } private static bool AllowConsoleGui() { if (!((Object)(object)Instance == (Object)null)) { if (!Instance.testingOpen) { return Time.frameCount != Instance.consoleSuppressFrame; } return false; } return true; } private bool CanUseTestingMenu(out string reason) { //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Invalid comparison between Unknown and I4 reason = ""; if (!testingUiReady || !((Behaviour)this).enabled || !enabledMode.Value || !NetworkServer.active) { reason = "Only the active RogueGolf host can use the testing menu."; return false; } if (!DevConsoleGui.Active || !MatchSetupRules.IsCheatsEnabled()) { reason = "Enable Console Commands and open the console first."; return false; } if (bridge == null || !bridge.CanStart(out reason)) { return false; } if (InDrivingRange && stage.Value == PrototypeStage.Modifiers && !Run.Active) { return true; } if (runStage != PrototypeStage.Modifiers || (int)Run.State != 3 || modifiers == null) { reason = "Start a RogueGolf course and enter a playing hole first."; return false; } return true; } private void BeginRangeTesting() { if (InDrivingRange && !RangeTesting) { uint seedValue = (uint)DateTime.UtcNow.Ticks; runStage = PrototypeStage.Modifiers; BeginProgression(seedValue); BeginModifiers(seedValue); testingRangeId = "range-test-" + Guid.NewGuid().ToString("N"); StartHoleEffects(0); Publish(); } } private void EndRangeTesting() { if (RangeTesting) { ClearRunResources(); Publish(); ((BaseUnityPlugin)this).Logger.LogInfo((object)"[TEST] Driving-range effects cleared"); } } private void UpdateRangeTesting() { if (RangeTesting && (!NetworkServer.active || !InDrivingRange || Run.Active || !enabledMode.Value || !MatchSetupRules.IsCheatsEnabled() || !bridge.CanStart(out var _))) { EndRangeTesting(); } } private void CloseTestingMenu() { testingOpen = false; consoleSuppressFrame = Time.frameCount; uiKey = ""; selected = 0; } private void OpenTestingMenu() { if (!CanUseTestingMenu(out var reason)) { ((BaseUnityPlugin)this).Logger.LogInfo((object)("[TEST] " + reason)); return; } try { BeginRangeTesting(); } catch (Exception ex) { EndRangeTesting(); ((BaseUnityPlugin)this).Logger.LogWarning((object)("[TEST] " + ex.Message)); return; } SetUiFocus(value: false); setupOpen = false; testingOpen = true; testingDraft.Clear(); ActiveModifier[] active = modifiers.Active; foreach (ActiveModifier val in active) { testingDraft.Add(val.Id); } testingPage = 0; testingDetail = ""; testingMessage = "Choose modifiers, then Apply stack. Changes affect everyone."; selected = 0; uiKey = ""; } private bool UpdateTestingInput() { //IL_002c: 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_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) if (testingOpen && !CanUseTestingMenu(out var _)) { CloseTestingMenu(); } if (!Application.isFocused) { return testingOpen; } if ((InputManager.CurrentModeMask & 0xC80) != 0) { return testingOpen; } Keyboard current = Keyboard.current; if (testingKey != null && (int)testingKey.Value != 0 && current != null && ((ButtonControl)current[testingKey.Value]).wasPressedThisFrame && DevConsoleGui.Active) { if (testingOpen) { CloseTestingMenu(); } else { OpenTestingMenu(); } return true; } if (!testingOpen) { return false; } Gamepad current2 = Gamepad.current; if ((current != null && ((ButtonControl)current.escapeKey).wasPressedThisFrame) || (current2 != null && current2.buttonEast.wasPressedThisFrame)) { CloseTestingMenu(); return true; } Mouse mouse = Mouse.current; if (mouse != null && mouse.leftButton.wasPressedThisFrame) { UiAction uiAction = uiActions.LastOrDefault((UiAction a) => RectTransformUtility.RectangleContainsScreenPoint(a.Rect, ((InputControl<Vector2>)(object)((Pointer)mouse).position).ReadValue())); if (uiAction != null) { uiAction.Run(); uiKey = ""; return true; } } if ((current != null && ((ButtonControl)current.rightArrowKey).wasPressedThisFrame) || (current != null && ((ButtonControl)current.tabKey).wasPressedThisFrame) || (current2 != null && current2.dpad.down.wasPressedThisFrame)) { selected++; } if ((current != null && ((ButtonControl)current.leftArrowKey).wasPressedThisFrame) || (current2 != null && current2.dpad.up.wasPressedThisFrame)) { selected--; } if (uiActions.Count > 0) { selected = (selected % uiActions.Count + uiActions.Count) % uiActions.Count; if ((current != null && ((ButtonControl)current.enterKey).wasPressedThisFrame) || (current2 != null && current2.buttonSouth.wasPressedThisFrame)) { uiActions[selected].Run(); uiKey = ""; } } return true; } private string TestUnavailable(ModifierDefinition d) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) if ((EffectChannel)(d.Channel & Capabilities) != d.Channel) { return "Required game hooks unavailable"; } if (!AllowsContext(d)) { return "Requires a course with native traffic"; } return ""; } private void TestAction(Action action, string success) { if (!CanUseTestingMenu(out var reason)) { testingMessage = reason; CloseTestingMenu(); return; } try { action(); testingMessage = success; ((BaseUnityPlugin)this).Logger.LogInfo((object)("[TEST] " + success)); Publish(); } catch (Exception ex) { testingMessage = ex.Message; ((BaseUnityPlugin)this).Logger.LogWarning((object)("[TEST] " + ex.Message)); } uiKey = ""; } private void ApplyTestingStack(IEnumerable<string> ids) { string[] copy = ids.ToArray(); TestAction(delegate { //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) ModifierManager val = TestStack.Build(ModifierRegistry.Create(modifierSettings), (IEnumerable<string>)copy, TestHole, progression.Persistence, Capabilities, (Func<ModifierDefinition, bool>)AllowsContext); RestoreEffects(); modifiers = val; ApplyEffects(EffectRunId, gravityBaseline, modifiers.Values); StartHoleEffects(TestHole); TickWorldEffects(); testingDraft.Clear(); string[] array = copy; foreach (string item in array) { testingDraft.Add(item); } }, "Applied test stack (" + copy.Length + "). Durations, cycles and starting items restarted."); } private void DrawTestingMenu() { //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_0115: 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_022f: Unknown result type (might be due to invalid IL or missing references) //IL_0234: Unknown result type (might be due to invalid IL or missing references) //IL_0286: Unknown result type (might be due to invalid IL or missing references) //IL_02c4: Unknown result type (might be due to invalid IL or missing references) //IL_02c9: Unknown result type (might be due to invalid IL or missing references) //IL_02dd: Unknown result type (might be due to invalid IL or missing references) //IL_0319: Unknown result type (might be due to invalid IL or missing references) //IL_0607: Unknown result type (might be due to invalid IL or missing references) //IL_063e: Unknown result type (might be due to invalid IL or missing references) //IL_0675: Unknown result type (might be due to invalid IL or missing references) //IL_06ac: Unknown result type (might be due to invalid IL or missing references) //IL_06e3: Unknown result type (might be due to invalid IL or missing references) //IL_071a: Unknown result type (might be due to invalid IL or missing references) //IL_0751: Unknown result type (might be due to invalid IL or missing references) //IL_0782: Unknown result type (might be due to invalid IL or missing references) //IL_05bb: Unknown result type (might be due to invalid IL or missing references) //IL_0374: Unknown result type (might be due to invalid IL or missing references) //IL_036d: Unknown result type (might be due to invalid IL or missing references) Shape((Transform)(object)uiContent, 0f, 0f, 1280f, 720f, new Color(0f, 0f, 0f, 0.8f), 0f, block: true); RectTransform parent = Panel((Transform)(object)uiContent, 100f, 40f, 1080f, 640f, Paper); Label((Transform)(object)parent, 24f, 16f, 800f, 45f, "RogueGolf · Host testing", 32f, null, bold: true); Button((Transform)(object)parent, 888f, 23f, 168f, 34f, "Back to console", CloseTestingMenu, Gold); Label((Transform)(object)parent, 24f, 67f, 1020f, 40f, (RangeTesting ? "Driving range" : "Private course") + " · applies to all players · test selections bypass offer weights and enable switches", 16f, Muted); ModifierDefinition[] array = (from d in ModifierRegistry.Create(modifierSettings).Definitions orderby d.Rarity, d.Name select d).ToArray(); int num = (array.Length + 7) / 8; testingPage = Math.Max(0, Math.Min(testingPage, num - 1)); for (int num2 = 0; num2 < 8 && testingPage * 8 + num2 < array.Length; num2++) { ModifierDefinition val = array[testingPage * 8 + num2]; float num3 = 115 + num2 * 49; string id = val.Id; bool chosen = testingDraft.Contains(id); string text = TestUnavailable(val); Shape((Transform)(object)parent, 20f, num3, 508f, 45f, Color32.op_Implicit(new Color32((byte)244, (byte)239, (byte)210, byte.MaxValue)), 8f); Icon((Transform)(object)parent, 28f, num3 + 6f, 32f, val); Label((Transform)(object)parent, 72f, num3 + 3f, 265f, 25f, val.Name, 18f, Ink, bold: true); Label((Transform)(object)parent, 72f, num3 + 26f, 265f, 18f, (text == "") ? ((object)val.Rarity/*cast due to .constrained prefix*/).ToString() : text, 12f, Muted); Button((Transform)(object)parent, 340f, num3 + 7f, 82f, 31f, "Info", delegate { testingDetail = id; }, Gold); Button((Transform)(object)parent, 430f, num3 + 7f, 88f, 31f, chosen ? "Remove" : "Add", delegate { if (chosen) { testingDraft.Remove(id); } else { testingDraft.Add(id); } }, chosen ? Gold : Green, chosen || text == ""); } Button((Transform)(object)parent, 24f, 517f, 112f, 32f, "Previous", delegate { testingPage--; selected = 0; }, null, testingPage > 0); Label((Transform)(object)parent, 154f, 522f, 225f, 26f, "Page " + (testingPage + 1) + " / " + num + " · " + testingDraft.Count + " selected", 16f, Muted); Button((Transform)(object)parent, 406f, 517f, 112f, 32f, "Next", delegate { testingPage++; selected = 0; }, null, testingPage < num - 1); ModifierDefinition detail = ((IEnumerable<ModifierDefinition>)array).FirstOrDefault((Func<ModifierDefinition, bool>)((ModifierDefinition d) => d.Id == testingDetail)); ModifierDefinition obj = detail; Label((Transform)(object)parent, 553f, 112f, 490f, 45f, ((obj != null) ? obj.Name : null) ?? "Choose an effect", 27f, null, bold: true); Label((Transform)(object)parent, 553f, 165f, 480f, 210f, (detail == null) ? "Add/remove effects to build a draft stack. Apply stack replaces the current effects.\n\nConflicting groups are rejected before any change. Unsupported effects stay listed with a reason." : EffectPresentation.Details(detail, modifierSettings, ((IEnumerable<ActiveModifier>)modifiers.Active).FirstOrDefault((Func<ActiveModifier, bool>)((ActiveModifier e) => e.Id == detail.Id))), 18f); if (detail != null) { Button((Transform)(object)parent, 553f, 389f, 240f, 35f, "Apply this effect only", delegate { ApplyTestingStack(new string[1] { detail.Id }); }, Gold, TestUnavailable(detail) == ""); } Button((Transform)(object)parent, 553f, 439f, 232f, 38f, "Apply stack", delegate { ApplyTestingStack(testingDraft); }, Green); Button((Transform)(object)parent, 803f, 439f, 232f, 38f, "Clear all effects", delegate { ApplyTestingStack(Array.Empty<string>()); }, Gold); Button((Transform)(object)parent, 553f, 491f, 232f, 35f, "Trigger cycle", delegate { TestAction(delegate { nextWorldTick = 0.0; TickWorldEffects(); }, "Triggered host cycle"); }, Gold); Button((Transform)(object)parent, 803f, 491f, 232f, 35f, "Reload active stack", delegate { testingDraft.Clear(); ActiveModifier[] active = modifiers.Active; foreach (ActiveModifier val2 in active) { testingDraft.Add(val2.Id); } testingMessage = "Draft copied from active effects."; }, Green); Button((Transform)(object)parent, 553f, 535f, 152f, 28f, "Thunder nearby", delegate { TestAction(delegate { SpawnThunderProof(here: false); }, "One vanilla storm spawned nearby. Its lightning can hit players."); }, Gold); Button((Transform)(object)parent, 718f, 535f, 152f, 28f, "Thunder here", delegate { TestAction(delegate { SpawnThunderProof(here: true); }, "One vanilla storm centered here. Its lightning can hit players."); }, Gold); Button((Transform)(object)parent, 883f, 535f, 152f, 28f, "Stop thunder", delegate { TestAction(StopThunderProof, "Test storm cancelled."); }, Green); Label((Transform)(object)parent, 24f, 570f, 1020f, 55f, testingMessage, 17f, Ink); } private void ConfigureIntermission() { //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Expected O, but got Unknown stage = ((BaseUnityPlugin)this).Config.Bind<PrototypeStage>("General", "Stage", PrototypeStage.Modifiers, "Modifiers (canonical catalogue), Voting (dummy cards), Intermission, or Lifecycle."); holdSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("Intermission", "DurationSeconds", 5f, new ConfigDescription("Phase 2 automatic hold duration.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 60f), Array.Empty<object>())); nextMethod = AccessTools.DeclaredMethod(typeof(CourseManager), "ServerStartNextMatch", new Type[1] { typeof(bool) }, (Type[])null); } private bool InterceptNext(CourseManager manager, bool skipInventories) { //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Invalid comparison between Unknown and I4 if (releasing || !NetworkServer.active || !Run.Active || runStage == PrototypeStage.Lifecycle || Run.HoleIndex < 0) { return true; } if (gate.WasIntercepted(Run.RunId, Run.HoleIndex)) { return false; } if ((int)Run.State != 4) { return true; } if (!bridge.CanStart(out notice)) { ShowNotice(notice); ClearRunResources(); Run.Stop("PeerNotCompatible"); return true; } if (runStage == PrototypeStage.Modifiers && modifiers == null) { return true; } string id = Run.RunId; int hole = Run.HoleIndex; gate.Hold(id, hole, (Action)delegate { releasing = true; try { nextMethod.Invoke(manager, new object[1] { skipInventories }); } finally { releasing = false; } }, (Func<bool>)(() => (Object)(object)manager != (Object)null && NetworkServer.active && Run.RunId == id && Run.HoleIndex == hole && Run.Active)); releaseAt = NetworkTime.time + (double)holdSeconds.Value; NextMatchCountdown.Hide(); VoteCoordinator obj = voting; if (obj != null) { obj.ClearVote(); } Run.Transition((RunState)4, (RunState)5, "IntermissionHeld"); if (runStage == PrototypeStage.Voting || runStage == PrototypeStage.Modifiers) { OpenVote(); } return false; } private void ReleaseIntermission() { //IL_0021: Unknown result type (might be due to invalid IL or missing references) if (gate.Held && NetworkServer.active) { Run.Transition(Run.State, (RunState)2, "IntermissionReleased"); if (!gate.Release()) { ClearRunResources(); Run.Stop("InvalidTransitionTarget"); } } } private void Update() { UpdateRangeTesting(); bridge?.Update(); UpdateUIInput(); TickWorldEffects(); TickThunderProof(); TickActiveEvents(); TickLab(); if (NetworkServer.active && Run.Active && !enabledMode.Value) { ReleaseIntermission(); ClearRunResources(); Run.Stop("Disabled"); } if (runStage == PrototypeStage.Voting || runStage == PrototypeStage.Modifiers) { TickVote(); } else if (gate.Held && NetworkTime.time >= releaseAt) { ReleaseIntermission(); } } private void ConfigureLab() { labCommands = ((BaseUnityPlugin)this).Config.Bind<bool>("Diagnostics", "LabCommands", false, "Solo private host only: process RogueGolf.lab.txt in this profile config directory. Testing only."); } private void TickLab() { //IL_05db: Unknown result type (might be due to invalid IL or missing references) //IL_05e9: Unknown result type (might be due to invalid IL or missing references) //IL_0a7d: Unknown result type (might be due to invalid IL or missing references) //IL_0a86: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Invalid comparison between Unknown and I4 //IL_0107: Unknown result type (might be due to invalid IL or missing references) //IL_010d: 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) //IL_0115: Unknown result type (might be due to invalid IL or missing references) //IL_0288: Unknown result type (might be due to invalid IL or missing references) //IL_028d: Unknown result type (might be due to invalid IL or missing references) //IL_0294: Expected O, but got Unknown //IL_04a2: Unknown result type (might be due to invalid IL or missing references) //IL_04a7: Unknown result type (might be due to invalid IL or missing references) //IL_04b1: 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_04ce: Unknown result type (might be due to invalid IL or missing references) //IL_04df: Unknown result type (might be due to invalid IL or missing references) //IL_0184: Unknown result type (might be due to invalid IL or missing references) //IL_0189: Unknown result type (might be due to invalid IL or missing references) //IL_0193: Expected O, but got Unknown //IL_052b: Unknown result type (might be due to invalid IL or missing references) //IL_0530: Unknown result type (might be due to invalid IL or missing references) //IL_0534: Unknown result type (might be due to invalid IL or missing references) //IL_0536: Unknown result type (might be due to invalid IL or missing references) //IL_0540: Unknown result type (might be due to invalid IL or missing references) //IL_0545: 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_01e2: Unknown result type (might be due to invalid IL or missing references) //IL_01ed: Unknown result type (might be due to invalid IL or missing references) //IL_0573: Unknown result type (might be due to invalid IL or missing references) //IL_0578: Unknown result type (might be due to invalid IL or missing references) //IL_057a: Unknown result type (might be due to invalid IL or missing references) //IL_0584: Unknown result type (might be due to invalid IL or missing references) //IL_0589: Unknown result type (might be due to invalid IL or missing references) //IL_03c6: 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_05a1: Unknown result type (might be due to invalid IL or missing references) //IL_05a6: Unknown result type (might be due to invalid IL or missing references) //IL_031b: Unknown result type (might be due to invalid IL or missing references) //IL_0326: Unknown result type (might be due to invalid IL or missing references) //IL_0780: Unknown result type (might be due to invalid IL or missing references) //IL_0785: Unknown result type (might be due to invalid IL or missing references) //IL_078a: Unknown result type (might be due to invalid IL or missing references) //IL_0851: Unknown result type (might be due to invalid IL or missing references) //IL_0856: Unknown result type (might be due to invalid IL or missing references) //IL_085a: Unknown result type (might be due to invalid IL or missing references) //IL_085f: Unknown result type (might be due to invalid IL or missing references) //IL_0863: Unknown result type (might be due to invalid IL or missing references) //IL_0868: Unknown result type (might be due to invalid IL or missing references) //IL_07e9: Unknown result type (might be due to invalid IL or missing references) //IL_07ee: Unknown result type (might be due to invalid IL or missing references) //IL_07f3: Unknown result type (might be due to invalid IL or missing references) //IL_07f8: Unknown result type (might be due to invalid IL or missing references) //IL_0add: Unknown result type (might be due to invalid IL or missing references) //IL_0ae2: Unknown result type (might be due to invalid IL or missing references) //IL_0aec: Unknown result type (might be due to invalid IL or missing references) //IL_0af1: Unknown result type (might be due to invalid IL or missing references) //IL_0b09: Unknown result type (might be due to invalid IL or missing references) //IL_0b1a: Unknown result type (might be due to invalid IL or missing references) //IL_0b24: Unknown result type (might be due to invalid IL or missing references) //IL_0b35: Unknown result type (might be due to invalid IL or missing references) //IL_08e6: Unknown result type (might be due to invalid IL or missing references) //IL_08e8: Unknown result type (might be due to invalid IL or missing references) //IL_08ed: Unknown result type (might be due to invalid IL or missing references) //IL_0905: Unknown result type (might be due to invalid IL or missing references) //IL_0916: Unknown result type (might be due to invalid IL or missing references) //IL_0920: Unknown result type (might be due to invalid IL or missing references) //IL_093b: Unknown result type (might be due to invalid IL or missing references) //IL_097c: Unknown result type (might be due to invalid IL or missing references) ConfigEntry<bool> obj = labCommands; if (obj == null || !obj.Value || NetworkTime.time < nextLabRead) { return; } nextLabRead = NetworkTime.time + 0.25; string path = Path.Combine(Paths.ConfigPath, "RogueGolf.lab.txt"); if (!File.Exists(path)) { return; } string text = File.ReadAllText(path).Trim(); File.Delete(path); try { if (!NetworkServer.active || NetworkServer.connections.Count != 1 || bridge == null || !bridge.CanStart(out var _) || (int)Run.State != 3) { throw new InvalidOperationException("Lab requires a playing hole in a solo private host run"); } if (text.Length > 256) { throw new ArgumentException("Lab request too long"); } string[] array = text.Split(' '); string text2 = array[0]; if (text2 == "apply" && array.Length == 2) { ModifierRegistry val = ModifierRegistry.Create(modifierSettings); ModifierDefinition val2 = val.Get(array[1]); if (val2 == null || (EffectChannel)(val2.Channel & Capabilities) != val2.Channel) { throw new ArgumentException("Unsupported lab modifier"); } if (val2.Id == "golf_kart_warfare" && Object.FindObjectsByType<TrafficSpawner>((FindObjectsInactive)0, (FindObjectsSortMode)0).Length == 0) { throw new ArgumentException("No native traffic in current scene"); } RestoreEffects(); modifiers = new ModifierManager(val, val.Definitions.Select((ModifierDefinition d) => d.Id), Capabilities); modifiers.ContextAllows = AllowsContext; if (!modifiers.Activate(val2.Id, Run.HoleIndex, (PersistenceMode)0)) { throw new InvalidOperationException("Lab activation rejected"); } ApplyEffects(Run.RunId, gravityBaseline, modifiers.Values); StartHoleEffects(Run.HoleIndex); Publish(); ((BaseUnityPlugin)this).Logger.LogInfo((object)("[LAB] Applied " + val2.Id)); } else if (text2 == "stack" && array.Length > 1 && array.Length <= 13) { ModifierRegistry obj2 = ModifierRegistry.Create(modifierSettings); ModifierManager val3 = new ModifierManager(obj2, obj2.Definitions.Select((ModifierDefinition d) => d.Id), Capabilities); val3.ContextAllows = AllowsContext; foreach (string item in array.Skip(1)) { if (!val3.Activate(item, Run.HoleIndex, (PersistenceMode)0)) { throw new ArgumentException("Incompatible lab stack: " + item); } } RestoreEffects(); modifiers = val3; ApplyEffects(Run.RunId, gravityBaseline, modifiers.Values); StartHoleEffects(Run.HoleIndex); Publish(); ((BaseUnityPlugin)this).Logger.LogInfo((object)("[LAB] Applied stack " + string.Join(",", array.Skip(1)))); } else { switch (text2) { case "clear": RestoreEffects(); Publish(); break; case "empty": { PlayerInfo[] array2 = Players(); foreach (PlayerInfo val6 in array2) { for (int num3 = 0; num3 < val6.Inventory.slots.Count; num3++) { if ((int)val6.Inventory.slots[num3].itemType != 0) { removeItem.Invoke(val6.Inventory, new object[3] { num3, false, false }); } } } break; } case "tick": nextWorldTick = 0.0; TickWorldEffects(); break; case "cup": { GolfBall ownBall4 = Players().First().AsGolfer.OwnBall; ownBall4.AsEntity.InformWillTeleport(); ownBall4.Rigidbody.position = ((Component)GolfHoleManager.MainHole).transform.position + Vector3.up * 2f; ((Component)ownBall4).transform.position = ownBall4.Rigidbody.position; ownBall4.Rigidbody.linearVelocity = Vector3.zero; ownBall4.AsEntity.InformTeleported(); break; } case "swap_probe": { PlayerGolfer asGolfer = Players().First().AsGolfer; GolfBall ownBall = asGolfer.OwnBall; Vector3 position = ownBall.Rigidbody.position; GolfBall val4 = asGolfer.ServerSpawnBall(position + Vector3.right * 2f, Quaternion.identity); try { RotateBalls((GolfBall[])(object)new GolfBall[2] { ownBall, val4 }); if (Vector3.Distance(ownBall.Rigidbody.position, position + Vector3.right * 2f) > 0.01f || Vector3.Distance(val4.Rigidbody.position, position) > 0.01f) { throw new InvalidOperationException("Native swap failed"); } ((BaseUnityPlugin)this).Logger.LogInfo((object)"[LAB] Two native ball swap passed"); } finally { ownBall.Rigidbody.position = position; ((Component)ownBall).transform.position = position; asGolfer.NetworkownBall = ownBall; NetworkServer.Destroy(((Component)val4).gameObject); } break; } case "equip_pistol": { PlayerInventory inventory = Players().First().Inventory; Grant(inventory, (ItemType)2, replaceIfFull: true); inventory.TrySelectItemSlot(0, false); break; } case "use_item": { bool flag2 = default(bool); bool flag = Players().First().Inventory.TryUseItem(false, ref flag2); ((BaseUnityPlugin)this).Logger.LogInfo((object)("[LAB] Native item use=" + flag + " ateInput=" + flag2)); break; } case "thunder": SpawnThunderProof(here: false); break; case "thunder_here": SpawnThunderProof(here: true); break; case "thunder_stop": StopThunderProof(); break; case "thunder_status": ((BaseUnityPlugin)this).Logger.LogInfo((object)("[THUNDER] proof=" + (thunderProof != null) + " storms=" + Object.FindObjectsByType<Thunderstorm>((FindObjectsInactive)0, (FindObjectsSortMode)0).Length + " strikes=" + Object.FindObjectsByType<ThunderstormLightningStrike>((FindObjectsInactive)0, (FindObjectsSortMode)0).Length)); break; case "explode": TryExplosion(((Component)Players().First().AsGolfer.OwnBall).transform.position + Vector3.up); break; case "spawn_blast_neighbor": { PlayerGolfer asGolfer3 = Players().First().AsGolfer; GolfBall ownBall5 = asGolfer3.OwnBall; if ((Object)(object)labBlastNeighbor != (Object)null) { NetworkServer.Destroy(((Component)labBlastNeighbor).gameObject); } labBlastNeighbor = asGolfer3.ServerSpawnBall(ownBall5.Rigidbody.position + Vector3.right, Quaternion.identity); asGolfer3.NetworkownBall = ownBall5; break; } case "source_blast_probe": { PlayerGolfer asGolfer2 = Players().First().AsGolfer; GolfBall ownBall3 = asGolfer2.OwnBall; Rigidbody rigidbody2 = ownBall3.Rigidbody; Vector3 position2 = rigidbody2.position; Vector3 linearVelocity = rigidbody2.linearVelocity; Vector3 angularVelocity = rigidbody2.angularVelocity; GolfBall val5 = labBlastNeighbor; if ((Object)(object)val5 == (Object)null) { throw new InvalidOperationException("Run spawn_blast_neighbor first, then let native initialization finish"); } int sourceHits = 0; float neighborImpulse = 0f; Action<PlayerInventory, ItemType, ItemUseId, Vector3, float, Vector3, bool, bool, ProtectiveState> action = delegate { sourceHits++; }; Action<PlayerInventory, ItemType, ItemUseId, Vector3, float, Vector3, bool, bool, ProtectiveState> action2 = delegate(PlayerInventory a, ItemType b, ItemUseId c, Vector3 d, float e, Vector3 f, bool g, bool h, ProtectiveState i) { neighborImpulse += ((Vector3)(ref f)).magnitude; }; ownBall3.AsEntity.AsHittable.WillApplyItemHitPhysics += action; val5.AsEntity.AsHittable.WillApplyItemHitPhysics += action2; try { val5.Rigidbody.position = position2 + Vector3.right; ((Component)val5).transform.position = val5.Rigidbody.position; val5.Rigidbody.linearVelocity = Vector3.right * 5f; rigidbody2.linearVelocity = new Vector3(1f, 2f, 3f); MethodInfo methodInfo = AccessTools.DeclaredMethod(typeof(GolfBall), "UpdateIsStationary", (Type[])null, (Type[])null); methodInfo.Invoke(ownBall3, null); methodInfo.Invoke(val5, null); Physics.SyncTransforms(); int num = explosionsThisHole; TryExplosion(position2, ownBall3.AsEntity.AsHittable); if (explosionsThisHole != num + 1 || sourceHits != 0 || neighborImpulse < 0.01f) { throw new InvalidOperationException("Blast