From 3dd1cc064991e191eaf9f54264af58b33aa0d857 Mon Sep 17 00:00:00 2001 From: asquared31415 <34665709+asquared31415@users.noreply.github.com> Date: Wed, 20 Dec 2023 12:27:24 -0500 Subject: [PATCH] Add EventWeight consts to lib and dev tools to see weights --- ONITwitchCore/DefaultCommands.cs | 147 ++++++++------- ONITwitchCore/DevTools/Panels/WeightsPanel.cs | 168 ++++++++++++++++++ ONITwitchCore/DevTools/TwitchDevTool.cs | 31 ++-- ONITwitchCore/ONITwitchCore.csproj | 1 + ONITwitchLib/Consts.cs | 103 +++++++++++ 5 files changed, 378 insertions(+), 72 deletions(-) create mode 100644 ONITwitchCore/DevTools/Panels/WeightsPanel.cs create mode 100644 ONITwitchLib/Consts.cs diff --git a/ONITwitchCore/DefaultCommands.cs b/ONITwitchCore/DefaultCommands.cs index a960ee7..d1c2f97 100644 --- a/ONITwitchCore/DefaultCommands.cs +++ b/ONITwitchCore/DefaultCommands.cs @@ -8,6 +8,7 @@ using DataManager = ONITwitch.EventLib.DataManager; using EventGroup = ONITwitch.EventLib.EventGroup; using EventInfo = ONITwitch.EventLib.EventInfo; +using static ONITwitchLib.Consts; namespace ONITwitch; @@ -23,7 +24,7 @@ public static void SetupCommands() new SpawnDupeCommand(), new Dictionary { { "MaxDupes", 30.0d } }, Danger.Small, - 50 + EventWeight.Frequent ) ); RegisterCommand( @@ -46,7 +47,7 @@ public static void SetupCommands() { SimHashes.Sucrose.ToString(), 1400 }, }, Danger.Small, - 50, + EventWeight.Frequent, "core.spawn_element" ) ); @@ -71,7 +72,7 @@ public static void SetupCommands() { SimHashes.Resin.ToString(), 500 }, }, Danger.Small, - 10, + EventWeight.Uncommon, "core.spawn_element" ) ); @@ -91,7 +92,7 @@ public static void SetupCommands() { SimHashes.AluminumOre.ToString(), 1500 }, }, Danger.Small, - 50, + EventWeight.Frequent, "core.spawn_element" ) ); @@ -111,7 +112,7 @@ public static void SetupCommands() { SimHashes.EthanolGas.ToString(), 10 }, }, Danger.Small, - 50, + EventWeight.Frequent, "core.spawn_element" ) ); @@ -137,7 +138,7 @@ public static void SetupCommands() { SimHashes.SuperCoolantGas.ToString(), 1000 }, }, Danger.Small, - 10, + EventWeight.Uncommon, "core.spawn_element" ) ); @@ -169,7 +170,7 @@ public static void SetupCommands() { SimHashes.CrudeOil.ToString(), 500 }, }, Danger.Small, - 50, + EventWeight.Frequent, "core.spawn_element" ) ); @@ -180,7 +181,7 @@ public static void SetupCommands() new EffectCommand(), CustomEffects.AthleticsUpEffect.Id, Danger.None, - 20, + EventWeight.Uncommon, "core.attribute_mod" ) ); @@ -191,7 +192,7 @@ public static void SetupCommands() new EffectCommand(), CustomEffects.AthleticsDownEffect.Id, Danger.None, - 20, + EventWeight.Uncommon, "core.attribute_mod" ) ); @@ -202,7 +203,7 @@ public static void SetupCommands() new EffectCommand(), CustomEffects.ConstructionUpEffect.Id, Danger.None, - 20, + EventWeight.Uncommon, "core.attribute_mod" ) ); @@ -213,7 +214,7 @@ public static void SetupCommands() new EffectCommand(), CustomEffects.ConstructionDownEffect.Id, Danger.None, - 20, + EventWeight.Uncommon, "core.attribute_mod" ) ); @@ -224,7 +225,7 @@ public static void SetupCommands() new EffectCommand(), CustomEffects.ExcavationUpEffect.Id, Danger.None, - 20, + EventWeight.Uncommon, "core.attribute_mod" ) ); @@ -235,7 +236,7 @@ public static void SetupCommands() new EffectCommand(), CustomEffects.ExcavationDownEffect.Id, Danger.None, - 20, + EventWeight.Uncommon, "core.attribute_mod" ) ); @@ -246,7 +247,7 @@ public static void SetupCommands() new EffectCommand(), CustomEffects.StrengthUpEffect.Id, Danger.None, - 20, + EventWeight.Uncommon, "core.attribute_mod" ) ); @@ -257,7 +258,7 @@ public static void SetupCommands() new EffectCommand(), CustomEffects.StrengthDownEffect.Id, Danger.None, - 20, + EventWeight.Uncommon, "core.attribute_mod" ) ); @@ -268,7 +269,7 @@ public static void SetupCommands() new BansheeWailCommand(), null, Danger.Small, - 10 + EventWeight.Uncommon ) ); RegisterCommand( @@ -278,7 +279,7 @@ public static void SetupCommands() new FillBedroomCommand(), SimHashes.Snow.ToString(), Danger.Small, - 20, + EventWeight.Common, "core.bedroom_element" ) ); @@ -289,7 +290,7 @@ public static void SetupCommands() new FillBedroomCommand(), SimHashes.SlimeMold.ToString(), Danger.Small, - 20, + EventWeight.Common, "core.bedroom_element" ) ); @@ -300,7 +301,7 @@ public static void SetupCommands() new ElementFloodCommand(), SimHashes.Water.ToString(), Danger.Medium, - 30, + EventWeight.Common, "core.flood" ) ); @@ -311,7 +312,7 @@ public static void SetupCommands() new ElementFloodCommand(), SimHashes.DirtyWater.ToString(), Danger.Medium, - 30, + EventWeight.Common, "core.flood" ) ); @@ -322,7 +323,7 @@ public static void SetupCommands() new ElementFloodCommand(), SimHashes.Ethanol.ToString(), Danger.Medium, - 10, + EventWeight.Uncommon, "core.flood" ) ); @@ -333,7 +334,7 @@ public static void SetupCommands() new ElementFloodCommand(), SimHashes.CrudeOil.ToString(), Danger.Medium, - 10, + EventWeight.Uncommon, "core.flood" ) ); @@ -344,7 +345,7 @@ public static void SetupCommands() new ElementFloodCommand(), SimHashes.Magma.ToString(), Danger.Deadly, - 2, + EventWeight.VeryRare, "core.flood" ) ); @@ -355,7 +356,7 @@ public static void SetupCommands() new ElementFloodCommand(), SimHashes.MoltenGold.ToString(), Danger.Deadly, - 2, + EventWeight.VeryRare, "core.flood" ) ); @@ -366,12 +367,19 @@ public static void SetupCommands() new ElementFloodCommand(), SimHashes.NuclearWaste.ToString(), Danger.High, - 2, + EventWeight.VeryRare, "core.flood" ) ); RegisterCommand( - new CommandInfo("IceAge", STRINGS.ONITWITCH.EVENTS.ICE_AGE, new IceAgeCommand(), null, Danger.Extreme, 1) + new CommandInfo( + "IceAge", + STRINGS.ONITWITCH.EVENTS.ICE_AGE, + new IceAgeCommand(), + null, + Danger.Extreme, + EventWeight.AlmostNever + ) ); RegisterCommand( new CommandInfo( @@ -380,12 +388,28 @@ public static void SetupCommands() new GlobalWarmingCommand(), null, Danger.High, - 1 + EventWeight.AlmostNever + ) + ); + RegisterCommand( + new CommandInfo( + "Pee", + STRINGS.ONITWITCH.EVENTS.PEE, + new PeeCommand(), + null, + Danger.Small, + EventWeight.Frequent ) ); - RegisterCommand(new CommandInfo("Pee", STRINGS.ONITWITCH.EVENTS.PEE, new PeeCommand(), null, Danger.Small, 30)); RegisterCommand( - new CommandInfo("Kill", STRINGS.ONITWITCH.EVENTS.KILL_DUPE, new KillDupeCommand(), null, Danger.Deadly, 1) + new CommandInfo( + "Kill", + STRINGS.ONITWITCH.EVENTS.KILL_DUPE, + new KillDupeCommand(), + null, + Danger.Deadly, + EventWeight.AlmostNever + ) ); RegisterCommand( new CommandInfo( @@ -394,7 +418,7 @@ public static void SetupCommands() new TileTempCommand(), -40.0d, Danger.Medium, - 10, + EventWeight.Uncommon, "core.tile_temp" ) ); @@ -405,7 +429,7 @@ public static void SetupCommands() new TileTempCommand(), +40.0d, Danger.Medium, - 10, + EventWeight.Uncommon, "core.tile_temp" ) ); @@ -416,7 +440,7 @@ public static void SetupCommands() new PartyTimeCommand(), 300.0d, Danger.None, - 30 + EventWeight.Common ) ); RegisterCommand( @@ -426,7 +450,7 @@ public static void SetupCommands() new PoisonDupesCommand(), null, Danger.High, - 20 + EventWeight.Uncommon ) ); RegisterCommand( @@ -436,7 +460,7 @@ public static void SetupCommands() new PoopsplosionCommand(), null, Danger.Small, - 50 + EventWeight.Frequent ) ); RegisterCommand( @@ -446,7 +470,7 @@ public static void SetupCommands() new RainPrefabCommand(), new Dictionary { { "PrefabId", SimHashes.Gold.ToString() }, { "Count", 50.0d } }, Danger.None, - 30, + EventWeight.Common, "core.rain_prefab" ) ); @@ -459,7 +483,7 @@ public static void SetupCommands() new RainPrefabCommand(), new Dictionary { { "PrefabId", GlomConfig.ID }, { "Count", 10.0d } }, Danger.Small, - 30, + EventWeight.Common, "core.rain_prefab" ) ); @@ -468,9 +492,9 @@ public static void SetupCommands() "RainPrefabDiamond", STRINGS.ONITWITCH.EVENTS.RAIN_PREFAB_DIAMOND, new RainPrefabCommand(), - new Dictionary { { "PrefabId", SimHashes.Diamond.ToString() }, { "Count", 25.0d } }, + new Dictionary { { "PrefabId", SimHashes.Diamond.ToString() }, { "Count", 100.0d } }, Danger.None, - 10, + EventWeight.Rare, "core.rain_prefab" ) ); @@ -481,7 +505,7 @@ public static void SetupCommands() new RainPrefabCommand(), new Dictionary { { "PrefabId", OilFloaterConfig.ID }, { "Count", 10.0d } }, Danger.Small, - 20, + EventWeight.Uncommon, "core.rain_prefab" ) ); @@ -494,7 +518,7 @@ public static void SetupCommands() new RainPrefabCommand(), new Dictionary { { "PrefabId", PacuConfig.ID }, { "Count", 10.0d } }, Danger.Small, - 20, + EventWeight.Uncommon, "core.rain_prefab" ) ); @@ -505,7 +529,7 @@ public static void SetupCommands() new RainPrefabCommand(), new Dictionary { { "PrefabId", BeeConfig.ID }, { "Count", 10.0d } }, Danger.Medium, - 10, + EventWeight.Uncommon, "core.rain_prefab" ) ); @@ -516,7 +540,7 @@ public static void SetupCommands() new ReduceOxygenCommand(), 0.20d, Danger.High, - 10 + EventWeight.Uncommon ) ); RegisterCommand( @@ -526,7 +550,7 @@ public static void SetupCommands() new SkillCommand(), 0.33d, Danger.None, - 20 + EventWeight.Common ) ); RegisterCommand( @@ -536,7 +560,7 @@ public static void SetupCommands() new SleepyDupesCommand(), null, Danger.Medium, - 20 + EventWeight.Common ) ); RegisterCommand( @@ -546,7 +570,7 @@ public static void SetupCommands() new SnazzySuitCommand(), null, Danger.None, - 20 + EventWeight.Uncommon ) ); RegisterCommand( @@ -556,7 +580,7 @@ public static void SetupCommands() new SpawnPrefabCommand(), GlitterPuftConfig.Id, Danger.None, - 20 + EventWeight.Common ) ); RegisterCommand( @@ -566,7 +590,7 @@ public static void SetupCommands() new SpawnPrefabCommand(), GeneShufflerRechargeConfig.ID, Danger.None, - 5 + EventWeight.Rare ) ); RegisterCommand( @@ -576,7 +600,7 @@ public static void SetupCommands() new SpawnPrefabCommand(), AtmoSuitConfig.ID, Danger.None, - 5 + EventWeight.Rare ) ); RegisterCommand( @@ -586,7 +610,7 @@ public static void SetupCommands() new SpawnPrefabCommand(), CrabConfig.ID, Danger.None, - 10 + EventWeight.Uncommon ) ); RegisterCommand( @@ -596,7 +620,7 @@ public static void SetupCommands() new SpawnPrefabCommand(), GassyMooCometConfig.ID, Danger.None, - 10 + EventWeight.Rare ) ); RegisterCommand( @@ -606,7 +630,7 @@ public static void SetupCommands() new StressCommand(), +0.75d, Danger.Medium, - 10, + EventWeight.Common, "core.stress" ) ); @@ -617,7 +641,7 @@ public static void SetupCommands() new StressCommand(), -0.75d, Danger.None, - 20, + EventWeight.Common, "core.stress" ) ); @@ -630,6 +654,7 @@ public static void SetupCommands() // will only choose a command that is within the expected danger range // but this command should ignore danger settings null, + // This is an exception to the normal weights, it's twice as frequent as "frequent" because it's special. 80, "core.surprise" ) @@ -641,7 +666,7 @@ public static void SetupCommands() new UninsulateCommand(), null, Danger.Extreme, - 5 + EventWeight.Rare ) ); RegisterCommand( @@ -651,7 +676,7 @@ public static void SetupCommands() new ResearchTechCommand(), null, Danger.None, - 10 + EventWeight.Common ) ); @@ -662,7 +687,7 @@ public static void SetupCommands() new EclipseCommand(), (double) (3 * Constants.SECONDS_PER_CYCLE), Danger.Small, - 10 + EventWeight.Uncommon ) ); @@ -673,7 +698,7 @@ public static void SetupCommands() new PocketDimensionCommand(), null, Danger.None, - 10 + EventWeight.Rare ) ); @@ -684,7 +709,7 @@ public static void SetupCommands() new SurpriseBoxCommand(), null, Danger.None, - 20, + EventWeight.Common, "core.surprise" ) ); @@ -696,7 +721,7 @@ public static void SetupCommands() new GeyserModificationCommand(), null, Danger.Medium, - 10 + EventWeight.Uncommon ) ); @@ -707,7 +732,7 @@ public static void SetupCommands() new MorphCommand(), null, Danger.Small, - 5 + EventWeight.Rare ) ); @@ -718,7 +743,7 @@ public static void SetupCommands() new FartCommand(), 25.0d, Danger.Small, - 30 + EventWeight.Common ) ); @@ -729,7 +754,7 @@ public static void SetupCommands() new SpiceFoodCommand(), null, Danger.None, - 30 + EventWeight.Uncommon ) ); } diff --git a/ONITwitchCore/DevTools/Panels/WeightsPanel.cs b/ONITwitchCore/DevTools/Panels/WeightsPanel.cs new file mode 100644 index 0000000..5b02164 --- /dev/null +++ b/ONITwitchCore/DevTools/Panels/WeightsPanel.cs @@ -0,0 +1,168 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using ImGuiNET; +using JetBrains.Annotations; +using ONITwitch.EventLib; +using ONITwitchLib.Logger; +using UnityEngine; + +namespace ONITwitch.DevTools.Panels; + +internal class WeightsPanel : IDevToolPanel +{ + private const int NameColumnId = 1; + private const int WeightColumnId = 2; + + private int totalWeight; + [NotNull] private List weightsList = []; + + public void DrawPanel() + { + // Generate weights, then only update when sorting changes. + if (weightsList.Count == 0) + { + GenerateWeights(); + } + + ImGui.Text($"Total Weight: {totalWeight}"); + const ImGuiTableFlags flags = ImGuiTableFlags.Hideable | ImGuiTableFlags.Resizable | + ImGuiTableFlags.Reorderable | ImGuiTableFlags.Sortable | + ImGuiTableFlags.SortMulti | ImGuiTableFlags.SortTristate | ImGuiTableFlags.RowBg | + ImGuiTableFlags.Borders | ImGuiTableFlags.ScrollY; + if (ImGui.BeginTable("twitch_event_weights", 2, flags, new Vector2(0f, 20 * 12))) + { + // The first column should be fixed width and the second column should stretch to the right side of the table. + // The table should start with the weight column default sorted descending. + ImGui.TableSetupColumn( + "Name", + ImGuiTableColumnFlags.WidthFixed | ImGuiTableColumnFlags.PreferSortAscending, + 0f, + NameColumnId + ); + ImGui.TableSetupColumn( + "Weight", + ImGuiTableColumnFlags.WidthStretch | ImGuiTableColumnFlags.DefaultSort | + ImGuiTableColumnFlags.PreferSortDescending, + 0f, + WeightColumnId + ); + // Keeps labels always visible. + ImGui.TableSetupScrollFreeze(0, 1); + ImGui.TableHeadersRow(); + + // Update the sorting when the sort specs change. + unsafe + { + var sortSpecsPtr = ImGui.TableGetSortSpecs(); + if ((IntPtr) sortSpecsPtr.NativePtr != IntPtr.Zero) + { + if (sortSpecsPtr.SpecsDirty) + { + weightsList.Sort((a, b) => SortItemsWithSpecs(sortSpecsPtr.Specs, a, b)); + sortSpecsPtr.SpecsDirty = false; + } + } + } + + // Use clipper to only process the items that are actually being shown. + unsafe + { + var clipper = new ImGuiListClipper(); + var clipperPtr = new ImGuiListClipperPtr(&clipper); + clipperPtr.Begin(weightsList.Count); + while (clipperPtr.Step()) + { + for (var idx = clipperPtr.DisplayStart; idx < clipperPtr.DisplayEnd; idx++) + { + var eventWithWeight = weightsList[idx]; + ImGui.TableNextRow(); + + ImGui.TableNextColumn(); + ImGui.Text($"{eventWithWeight.EventInfo}"); + + ImGui.TableNextColumn(); + var fraction = (float) eventWithWeight.Weight / totalWeight; + ImGui.Text($"{eventWithWeight.Weight} ({fraction * 100:F2}%)"); + } + } + } + + ImGui.EndTable(); + } + } + + private void GenerateWeights() + { + var weightMap = new Dictionary(); + + foreach (var eventGroup in TwitchDeckManager.Instance.GetGroups()) + { + foreach (var (eventInfo, weight) in eventGroup.GetWeights()) + { + totalWeight += weight; + if (weightMap.ContainsKey(eventInfo)) + { + Log.Warn($"Event {eventInfo} appeared in groups more than once"); + weightMap[eventInfo] += weight; + } + else + { + weightMap.Add(eventInfo, weight); + } + } + } + + weightsList = weightMap.Select( + static pair => new EventWithWeight + { + EventInfo = pair.Key, + Weight = pair.Value, + } + ) + .ToList(); + } + + private static int SortItemsWithSpecs( + RangeAccessor specs, + EventWithWeight a, + EventWithWeight b + ) + { + // Iterate over all sort specs because they can contain multiple things to sort by. + // The first spec that has a difference between two items will return the ordering. + // This means it's "sort by first spec, then by second, then by...". + // Not a List or enumerable, so it must be manually indexed. + for (var idx = 0; idx < specs.Count; idx += 1) + { + var spec = specs[idx]; + var ordering = spec.ColumnUserID switch + { + NameColumnId => string.Compare(a.EventInfo.Id, b.EventInfo.Id, StringComparison.Ordinal), + WeightColumnId => a.Weight.CompareTo(b.Weight), + // ReSharper disable once NotResolvedInText (not actually an arg). + _ => throw new ArgumentOutOfRangeException("ColumnUserID"), + }; + + if (ordering != 0) + { + // Swap the ordering if it's sort descending. + if (spec.SortDirection == ImGuiSortDirection.Descending) + { + return -ordering; + } + + return ordering; + } + } + + // The items were equal for all specs. + return 0; + } + + private struct EventWithWeight + { + internal EventInfo EventInfo; + internal int Weight; + } +} diff --git a/ONITwitchCore/DevTools/TwitchDevTool.cs b/ONITwitchCore/DevTools/TwitchDevTool.cs index 84917cf..5d07c2c 100644 --- a/ONITwitchCore/DevTools/TwitchDevTool.cs +++ b/ONITwitchCore/DevTools/TwitchDevTool.cs @@ -18,6 +18,7 @@ internal class TwitchDevTool : DevTool // The primary style used by the dev tools. [NotNull] private readonly ImGuiStyle mainStyle; + [NotNull] private readonly WeightsPanel weightsPanel; // true if the camera path is currently being edited. internal bool EditingCamPath; @@ -31,6 +32,7 @@ public TwitchDevTool() cameraPathPanel = new CameraPathPanel(debugMarkers, cameraPath); debugInfoPanel = new DebugInfoPanel(debugMarkers); eventsPanel = new EventsPanel(); + weightsPanel = new WeightsPanel(); mainStyle = new ImGuiStyle(); mainStyle.AddStyle(ImGuiStyleVar.FrameRounding, 4); @@ -71,21 +73,28 @@ protected override void RenderTo(DevPanel panel) // Clear the markers at the start of each frame. debugMarkers.Clear(); - // Early out if the game isn't active, we can't do most things. - if (Game.Instance == null) - { - TwitchImGui.WithStyle(mainStyle, () => { ImGui.TextColored(Color.red, "Game not yet active"); }); - return; - } - - // ========================================================== - // Game is active at this point - // ========================================================== - TwitchImGui.WithStyle( mainStyle, () => { + if (ImGui.CollapsingHeader("Event Weights")) + { + weightsPanel.DrawPanel(); + } + + ImGui.Separator(); + + // Early out if the game isn't active, we can't do most things. + if (Game.Instance == null) + { + ImGui.TextColored(Color.red, "Game not yet active"); + return; + } + + // ========================================================== + // Game is active at this point + // ========================================================== + if (ImGui.CollapsingHeader("Debug Info", ImGuiTreeNodeFlags.DefaultOpen)) { debugInfoPanel.DrawPanel(); diff --git a/ONITwitchCore/ONITwitchCore.csproj b/ONITwitchCore/ONITwitchCore.csproj index 1d6d9ed..034737b 100644 --- a/ONITwitchCore/ONITwitchCore.csproj +++ b/ONITwitchCore/ONITwitchCore.csproj @@ -4,6 +4,7 @@ ONITwitch ONITwitch ONITwitch + true true diff --git a/ONITwitchLib/Consts.cs b/ONITwitchLib/Consts.cs new file mode 100644 index 0000000..17ac2a1 --- /dev/null +++ b/ONITwitchLib/Consts.cs @@ -0,0 +1,103 @@ +using JetBrains.Annotations; + +namespace ONITwitchLib; + +/// +/// Various constants for use across Twitch Integration and mods that depend on it. +/// +[PublicAPI] +public static class Consts +{ + /// + /// Constants to use for event weights. Using these constants is not mandatory, but is heavily suggested so that + /// events have a consistent relative frequency. + ///
+ /// Suggested usage is: + /// + /// using static ONITwitchLib.Consts; + /// // ... + /// var myWeight = EventWeight.Common; + /// + ///
+ [PublicAPI] + public static class EventWeight + { + /// + /// Literally 0 weight. Events with this weight will never be drawn. This is mostly here for consistency in + /// case someone finds it useful. + /// + [PublicAPI] public const int Never = 0; + + /// + /// Events that should be possible, but should be exceptionally rare, things that should only want to happen to a + /// colony once in hundreds or thousands of cycles. + ///
+ /// Examples of this weight in the base Twitch Integration mod are: + ///
    + ///
  • Kill Duplicant
  • + ///
  • Lava Flood
  • + ///
+ ///
+ [PublicAPI] public const int AlmostNever = 1; + + /// + /// Events that are very rare, for example because they create a unique item or cause significant damage. + ///
+ /// Examples of this weight in the base Twitch Integration mod are: + ///
    + ///
  • Lava Flood
  • + ///
+ ///
+ [PublicAPI] public const int VeryRare = 2; + + /// + /// Events that should occur rarely. Events in this category should not be colony ending if they are dangerous. + ///
+ /// Examples of this weight in the base Twitch Integration mod are: + ///
    + ///
  • Uninsulate Tiles
  • + ///
  • Morph Critters
  • + ///
+ ///
+ [PublicAPI] public const int Rare = 5; + + /// + /// Events that should occur infrequently. Events in this category should be a decent challenge for the player or + /// provide something useful. + ///
+ /// Examples of this weight in the base Twitch Integration mod are: + ///
    + ///
  • Tune Geyser
  • + ///
  • Spawn Deadly Element (note that they are encased in a perfect insulator)
  • + ///
  • Oil and Ethanol Flood
  • + ///
+ ///
+ [PublicAPI] public const int Uncommon = 10; + + /// + /// Events that should occur commonly and regularly. Events in this category should not cause significant harm to a + /// colony. + ///
+ /// Examples of this weight in the base Twitch Integration mod are: + ///
    + ///
  • Surprise Box
  • + ///
  • Glitter Puft
  • + ///
  • Sleepy Dupes
  • + ///
  • Fart
  • + ///
+ ///
+ [PublicAPI] public const int Common = 20; + + /// + /// Events that should occur very frequently. Events in this category should be mostly neutral, neither providing much + /// help nor causing much harm to a colony. + ///
+ /// Examples of this weight in the base Twitch Integration mod are: + ///
    + ///
  • Spawn Solid/Liquid
  • + ///
  • Spawn Dupe
  • + ///
+ ///
+ [PublicAPI] public const int Frequent = 40; + } +}