### Example Configuration File Format Source: https://github.com/risk-of-thunder/r2wiki/wiki/Mod-Creation_C Shows the typical structure of a BepInEx configuration file. It includes sections defined by `[SectionName]` and key-value pairs with optional comments for descriptions. ```plaintext [MySection] # This is the description. MyKey = 10 ``` -------------------------------- ### SyncVar Hook Example in C# Source: https://github.com/risk-of-thunder/r2wiki/wiki/Mod-Creation_C This C# code demonstrates how to implement a SyncVar hook in Unity Netcode. The hook `OnSyncTarget` is called when the `target` SyncVar is updated. It shows how to handle the old and new values, update the SyncVar itself, and perform additional logic like getting a component. The example also covers manually calling the hook on client start if necessary. ```csharp public class SyncVarHookExample : NetworkBehaviour { [SyncVar(hook = nameof(OnSyncTarget)] private GameObject target; // A component cannot be a SyncVar so we will update it ourselves private CharacterBody targetBody; [Server] public void UpdateTarget(GameObject newTarget) { OnSyncTarget(target); } private void OnSyncTarget(GameObject newTarget) { // At this point in the code, we have access to both the old target // and the new target in case we need to do any handing-off operations. // Here we just want to update the value - DON'T OMIT THIS target = newTarget; // Any further logic if (target) { targetBody = target.GetComponent(); } } public override void OnStartClient() { base.OnStartClient(); // While a client is guaranteed to have received the up-to-date value before // this point, SyncVar hooks are not called when data is deserialized during // initialization. If the hook is something that you always want to run every // time the value is assigned to something, it can be manually called here. OnSyncTarget(target); } } ``` -------------------------------- ### Example Modded Launch Parameters Source: https://github.com/risk-of-thunder/r2wiki/wiki/Mod-Creation_Testing_Testing-Multiplayer-Alone An example of launch parameters used to direct the game executable to load a specific BepInEx DLL, typically used for modded profiles. This is copied from the r2modman settings. ```powershell # Example --doorstop-enabled true --doorstop-target-assembly "C:\Users\user\AppData\Roaming\r2modmanPlus-local\RiskOfRain2\profiles\Default\BepInEx\core\BepInEx.Preloader.dll ``` -------------------------------- ### Install RoR2EditorKit using Git URL in Unity Package Manager Source: https://github.com/risk-of-thunder/r2wiki/wiki/Mod-Creation_Unity-Editor-Usage_RoR2-Editor-Kit-Utilization This snippet demonstrates how to add the RoR2EditorKit package to a Unity project using a Git URL in the Package Manager. It includes an example of how to specify a version tag for installation. ```text https://github.com/risk-of-thunder/RoR2EditorKit.git#5.4.0 ``` -------------------------------- ### Implement IContentPackProvider with ContentPack Source: https://github.com/risk-of-thunder/r2wiki/wiki/Mod-Creation_C This C# snippet shows how to implement the IContentPackProvider interface by directly managing a ContentPack object. It covers initialization, loading static content, generating the content pack, and finalization. This is suitable for simpler content pack structures. ```csharp public class MyModContent : IContentPackProvider { internal ContentPack contentPack = new ContentPack(); //Your unique identifier, setting this to your mod's name or mod's GUID is recommended. public string identifier => MainClass.GUID; private static void Init() { ContentManager.collectContentPackProviders += (addContentPackProvider) => { addContentPackProvider(new MyModContent()); }; } private static void AddContentPack(ContentManager.AddContentPackProviderDelegate addContentPackProvider) { addContentPackProvider(Singleton); } public IEnumerator LoadStaticContentAsync(LoadStaticContentAsyncArgs args) { //Write or call methods to load content to your ContentPack args.ReportProgress(1f); yield break; } //This is the simplest way of implementing this method, only add more stuff if you know what you're doing. public IEnumerator GenerateContentPackAsync(GetContentPackAsyncArgs args) { contentPack.identifier = identifier; //This is a static method ContentPack.Copy(contentPack, args.output); args.ReportProgress(1f); yield break; } public IEnumerator FinalizeAsync(FinalizeAsyncArgs args) { args.ReportProgress(1f); yield break; } } ``` -------------------------------- ### Example Difficulty Mod Setup (C#) Source: https://github.com/risk-of-thunder/r2wiki/blob/master/docs/Mod-Creation/Assets/Difficulty.md This is a comprehensive C# example demonstrating the setup for a Risk of Thunder difficulty mod. It includes BepInEx plugin attributes, configuration, R2API dependencies, and event subscriptions for modifying game mechanics like character stats and language tokens. ```csharp using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using R2API; using R2API.Utils; using RoR2; using UnityEngine; using RiskOfOptions; using RiskOfOptions.Options; using RiskOfOptions.OptionConfigs; using RoR2.CharacterAI; using System.Linq; namespace ExampleDifficultyMod { [BepInDependency(R2API.R2API.PluginGUID)] [BepInDependency("com.rune580.riskofoptions")] [BepInDependency(LanguageAPI.PluginGUID)] [BepInDependency(DifficultyAPI.PluginGUID)] [BepInDependency(RecalculateStatsAPI.PluginGUID)] [BepInPlugin(PluginGUID, PluginName, PluginVersion)] public class Main : BaseUnityPlugin { public const string PluginGUID = PluginAuthor + "." + PluginName; public const string PluginAuthor = "ExampleModAuthor"; public const string PluginName = "ExampleDifficultyMod"; public const string PluginVersion = "1.0.0"; public static ManualLogSource EDMLogger; public static DifficultyDef ExampleModDiffDef; public static DifficultyIndex ExampleModDiffIndex; public static ConfigEntry Scaling { get; set; } public static bool shouldRun = false; public void Awake() { EDMLogger = Logger; Scaling = Config.Bind("General", "Difficulty Scaling", 150f, "Percentage of difficulty scaling, 150% is +50%. Difficulty order does not update visually. Vanilla is 150"); ModSettingsManager.AddOption(new StepSliderOption(Scaling, new StepSliderConfig() { increment = 25f, min = 0f, max = 300f })); AddDifficulty(); FillTokens(); On.RoR2.Language.GetLocalizedStringByToken += Language_GetLocalizedStringByToken; Run.onRunSetRuleBookGlobal += Run_onRunSetRuleBookGlobal; Run.onRunStartGlobal += (Run run) => { shouldRun = false; if (run.selectedDifficulty == ExampleModDiffIndex) { shouldRun = true; CharacterMaster.onStartGlobal += CharacterMaster_onStartGlobal; CharacterBody.onBodyAwakeGlobal += CharacterBody_onBodyAwakeGlobal; On.EntityStates.LemurianMonster.ChargeFireball.OnEnter += ChargeFireball_OnEnter; RecalculateStatsAPI.GetStatCoefficients += RecalculateStatsAPI_GetStatCoefficients; /* Here is where our logic should run. This is nice, because even On.Hooks work greatly and there's not much hassle after setting up this base. It's worth to note that some things don't properly unhook, leaving changes after playing the difficulty and switching off of it. ``` -------------------------------- ### Configure Custom Music State Transitions in WWise Source: https://github.com/risk-of-thunder/r2wiki/wiki/Mod-Creation_Assets_Stage This section details how to set up game syncs and state groups in WWise to manage music transitions, such as switching between boss music and ambient tracks based on game events like teleporter charging. It involves creating a 'bossStatus' game sync with 'alive' and 'dead' states and assigning appropriate music tracks to these states within the BossSongChoice and GameplaySongChoice containers. -------------------------------- ### Implement Custom Editor Setting Provider (C#) Source: https://github.com/risk-of-thunder/r2wiki/blob/master/docs/Mod-Creation/Unity-Editor-Usage/RoR2-Editor-Kit-Utilization.md This C# code demonstrates how to implement a custom editor setting provider by extending ScriptableSingleton and the IEditorSettingProvider interface. It includes saving settings on application quit and registering the provider with the EditorSettingManager. ```csharp using RoR2EditorKit.Settings; using UnityEditor; using UnityEngine; using System.Collections.Generic; public sealed class CustomEditorSettings : ScriptableSingleton, EditorSettingManager.IEditorSettingProvider { [SerializeField] private List _projectSettings = new List(); List EditorSettingManager.IEditorSettingProvider.editorSettings => _projectSettings; string EditorSettingManager.IEditorSettingProvider.providerName => nameof(CustomEditorSettings); public void SaveSettings() => Save(true); private void Awake() { EditorApplication.quitting += EditorApplication_quitting; } private void EditorApplication_quitting() { Save(true); } private void OnDestroy() { EditorApplication.quitting -= EditorApplication_quitting; } [InitializeOnLoadMethod] private static void Init() { EditorSettingManager.RegisterProvider(instance, () => instance); } } ``` -------------------------------- ### C# Artifact Implementation Example Source: https://github.com/risk-of-thunder/r2wiki/wiki/Mod-Creation_Assets_Artifacts Defines a sample artifact named 'Artifact of Example' that prints a message to the chat at the start of a run. It includes configuration for the number of messages and icon loading. ```csharp using BepInEx.Configuration; using System; using System.Collections.Generic; using System.Text; using UnityEngine; using UnityEngine.Networking; using RoR2; using static YourNameSpaceHere.BaseUnityPluginInheritedClass; namespace YourNameSpaceHere { class ExampleArtifact : ArtifactBase { public static ConfigEntry TimesToPrintMessageOnStart; public override string ArtifactName => "Artifact of Example"; public override string ArtifactLangTokenName => "ARTIFACT_OF_EXAMPLE"; public override string ArtifactDescription => "When enabled, print a message to the chat at the start of the run."; public override Sprite ArtifactEnabledIcon => MainAssets.LoadAsset("ExampleArtifactEnabledIcon.png"); public override Sprite ArtifactDisabledIcon => MainAssets.LoadAsset("ExampleArtifactDisabledIcon.png"); public override void Init(ConfigFile config) { CreateConfig(config); CreateLang(); CreateArtifact(); Hooks(); } private void CreateConfig(ConfigFile config) { TimesToPrintMessageOnStart = config.Bind("Artifact: " + ArtifactName, "Times to Print Message in Chat", 5, "How many times should a message be printed to the chat on run start?"); } public override void Hooks() { Run.onRunStartGlobal += PrintMessageToChat; } private void PrintMessageToChat(Run run) { if(NetworkServer.active && ArtifactEnabled) { for(int i = 0; i < TimesToPrintMessageOnStart.Value; i++) { Chat.AddMessage("Example Artifact has been Enabled."); } } } } } ``` -------------------------------- ### Example .language File Structure for Translations Source: https://github.com/risk-of-thunder/r2wiki/wiki/Mod-Creation_Assets_Translation This example shows the structure of a typical .language file used for mod translations in Risk of Rain 2. It uses a nested JSON format with language codes as keys and key-value pairs for translated strings. ```json { "RU": { "ITEM_SANDSWEPT_BLEEDING_WITNESS_DESCRIPTION" : "Ваши периодические эффекты восстанавливают здоровье всех союзников.", "ITEM_SANDSWEPT_CEREMONIAL_JAR_DESCRIPTION" : "Соедините врагов при попадании. Соединённые враги получают взрывной урон." } } ``` -------------------------------- ### Embed IMGUI in Visual Elements (C#) Source: https://github.com/risk-of-thunder/r2wiki/wiki/Mod-Creation_Unity-Editor-Usage_Editor-Scripts This example shows how to integrate IMGUI drawing methods into a Visual Elements UI using the `IMGUIContainer`. This is useful for leveraging existing IMGUI features or when UIElements lack specific functionalities. The `IMGUIContainer` takes a delegate pointing to the IMGUI drawing method. ```csharp public void SetupElement(VisualElement rootElement) { rootElement.Add(new IMGUIContainer(MyIMGUIMethod)); } private void MyIMGUIMethod() { EditorGUILayout.LabelField("This label was written using IMGUI!"); } ``` -------------------------------- ### Setup Game Event Hooks in C# Source: https://context7.com/risk-of-thunder/r2wiki/llms.txt This C# code demonstrates how to subscribe to and handle global game events in Risk of Rain 2. It covers events for run start, stage start, character death, and inventory changes. Dependencies include RoR2 and UnityEngine namespaces. ```csharp using RoR2; using UnityEngine; public class GameComponentsExample { public void SetupEventHooks() { // Run start event - fires when a new run begins Run.onRunStartGlobal += OnRunStart; // Stage start event Stage.onStageStartGlobal += OnStageStart; // Character death event GlobalEventManager.onCharacterDeathGlobal += OnCharacterDeath; // Inventory changed event (item pickup/drop) CharacterBody.onBodyInventoryChangedGlobal += OnInventoryChanged; } private void OnRunStart(Run run) { // Access run properties float difficulty = run.difficultyCoefficient; int stageCount = run.stageClearCount; // Access RNG for consistent randomness Xoroshiro128Plus rng = run.stageRng; } private void OnStageStart(Stage stage) { // Access scene information SceneInfo sceneInfo = SceneInfo.instance; if (sceneInfo && sceneInfo.sceneDef) { string sceneName = sceneInfo.sceneDef.cachedName; } } private void OnCharacterDeath(DamageReport damageReport) { CharacterBody victimBody = damageReport.victimBody; CharacterBody attackerBody = damageReport.attackerBody; if (attackerBody && attackerBody.isPlayerControlled) { // Player killed something int itemCount = attackerBody.inventory.GetItemCount(RoR2Content.Items.Crowbar); } } private void OnInventoryChanged(CharacterBody body) { // Access body components HealthComponent health = body.healthComponent; CharacterMaster master = body.master; Inventory inventory = body.inventory; // Check for specific items int hoofCount = inventory.GetItemCount(RoR2Content.Items.Hoof); // Access network user for player characters if (body.isPlayerControlled) { PlayerCharacterMasterController pcmc = master.GetComponent(); NetworkUser networkUser = pcmc?.networkUser; } } } ``` -------------------------------- ### Implement IContentPackProvider with SerializableContentPack Source: https://github.com/risk-of-thunder/r2wiki/wiki/Mod-Creation_C This C# snippet demonstrates implementing IContentPackProvider using a SerializableContentPack. This approach is useful when content needs to be loaded from an AssetBundle or constructed dynamically before being converted into a ContentPack. It includes loading from an asset bundle and creating the ContentPack. ```csharp public class MyModContent : IContentPackProvider { private ContentPack contentPack; internal SerializableContentPack serializableContentPack; public string identifier => MainClass.GUID; private static void Init() { //Load your serializable content pack from your assetbundle, or create it directly, Commented out so you implement it serializableContentPack = //Assets.MainAssetBundle.LoadAsset("ContentPack"); ContentManager.collectContentPackProviders += (addContentPackProvider) => { addContentPackProvider(new MyModContent()); }; } public IEnumerator LoadStaticContentAsync(LoadStaticContentAsyncArgs args) { //Write or call methods to load the rest of your content to your SerializableContentPack //Once all the content is loaded in the SerializableContentPack, Create the ContentPack with the method. contentPack = serializableContentPack.CreateContentPack(); args.ReportProgress(1f); yield break; } //This is the simplest way of implementing this method, only add more stuff if you know what you're doing. public IEnumerator GenerateContentPackAsync(GetContentPackAsyncArgs args) { contentPack.identifier = identifier; //This is a static method ContentPack.Copy(contentPack, args.output); args.ReportProgress(1f); yield break; } public IEnumerator FinalizeAsync(FinalizeAsyncArgs args) { args.ReportProgress(1f); yield break; } } ``` -------------------------------- ### Complex Async Content Loading with Progress Mapping (C#) Source: https://github.com/risk-of-thunder/r2wiki/blob/master/docs/Mod-Creation/C Demonstrates a more advanced asynchronous loading pattern using a helper class and mapping progress. This allows for granular control over the loading screen's progress indicator by remapping the completion of individual loading actions. ```csharp public IEnumerator LoadStaticContentAsync(LoadStaticContentAsyncArgs args) { contentPack.identifier = identifier; ContentLoadHelper contentLoadHelper = new ContentLoadHelper(); contentLoadHelper.assetRepository = Resources.Load("ResourcesDirectory"); Action[] loadDispatchers = new Action[foo] { delegate { Foo.Bar(); } delegate { Foo2.Bar() } //etc etc... } int j = 0; while (j < loadDispatchers2.Length) { loadDispatchers2[j](); args.ReportProgress(Util.Remap(j + 1, 0f, loadDispatchers2.Length, 0f, 0.05f)); yield return null; int num = j + 1; j = num; } ``` -------------------------------- ### Full Difficulty Configuration and Real-time Update Example (C#) Source: https://github.com/risk-of-thunder/r2wiki/blob/master/docs/Mod-Creation/Assets/Difficulty.md A comprehensive C# code example demonstrating the setup of difficulty scaling configuration, including binding the ConfigEntry, adding difficulty definitions, filling language tokens, and implementing a real-time update hook for GetLocalizedStringByToken. This snippet integrates all the previously mentioned concepts. ```csharp public static ConfigEntry Scaling { get; set; } public void Awake() { EDMLogger = Logger; Scaling = Config.Bind("General", "Difficulty Scaling", 150f, "Percentage of difficulty scaling, 150% is +50%. Difficulty order does not update visually. Vanilla is 150"); AddDifficulty(); FillTokens(); On.RoR2.Language.GetLocalizedStringByToken += Language_GetLocalizedStringByToken; } private string Language_GetLocalizedStringByToken(On.RoR2.Language.orig_GetLocalizedStringByToken orig, Language self, string token) { ExampleModDiffDef.scalingValue = Scaling.Value / 50f; if (token == "EXAMPLEDIFFICULTYMOD_DESCRIPTION") { return "My example difficulty description.\n\n" + ">Player Health Regeneration: -40%\n" + ">Difficulty Scaling: +" + (Scaling.Value - 100f) + "%"; } return orig(self, token); } public void FillTokens() { LanguageAPI.Add("EXAMPLEDIFFICULTYMOD_NAME", "Example Difficulty"); LanguageAPI.Add("EXAMPLEDIFFICULTYMOD_DESCRIPTION", "My example difficulty description.\n\n" + ">Player Health Regeneration: -40%\n" + ">Difficulty Scaling: +" + (Scaling.Value - 100f) + "%"); } public void AddDifficulty() { ExampleModDiffDef = new(Scaling.Value / 50f, "EXAMPLEDIFFICULTYMOD_NAME", "EXAMPLEDIFFICULTYMOD_ICON", "EXAMPLEDIFFICULTYMOD_DESCRIPTION", new Color32(255, 255, 255, 255), "ed", true); ExampleModDiffDef.iconSprite = null; ExampleModDiffDef.foundIconSprite = true; ExampleModDiffIndex = DifficultyAPI.AddDifficulty(ExampleModDiffDef); } ``` -------------------------------- ### Creating a Custom Named ConfigFile Source: https://github.com/risk-of-thunder/r2wiki/wiki/Mod-Creation_C Shows how to create a `ConfigFile` instance with a custom name and path. The second parameter, when set to true, ensures the file is created even if no `ConfigWrapper` objects are defined. ```csharp private static ConfigFile CustomConfigFile { get; set; } // Initialize it: CustomConfigFile = new ConfigFile(Paths.ConfigPath + "\CustomNamedFile.cfg", true); ``` -------------------------------- ### Conditional Logic with Run Events for Modding Source: https://github.com/risk-of-thunder/r2wiki/blob/master/docs/Mod-Creation/Assets/Difficulty.md This C# code sets up a boolean flag 'shouldRun' to control logic execution based on the selected difficulty at the start of a run. It also includes hooks for run start and destroy events, allowing for setup and cleanup of modded behaviors. Be mindful of potential unhooking issues and consider cleanup hooks. ```csharp public static bool shouldRun = false; Run.onRunStartGlobal += (Run run) => { shouldRun = false; if (run.selectedDifficulty == ExampleModDiffIndex) { shouldRun = true; /* Here is where our logic should run. This is nice, because even On.Hooks work greatly and there's not much hassle after setting up this base. It's worth to note that some things don't properly unhook, leaving changes after playing the difficulty and switching off of it. However, you can run a cleanup hook in destroy that resets the values to vanilla (though you will have to know them, either by logging or a unity rip of the game. You also remove the cleanup hooks here with -=. */ } }; Run.onRunDestroyGlobal += (Run run) => { shouldRun = false; // Here is where we can remove our hooks with -= for example. // We can also run our cleanup hooks here, with +=. }; ``` -------------------------------- ### Execute Code on Game Startup with InitDuringStartupPhase Attribute (C#) Source: https://github.com/risk-of-thunder/r2wiki/blob/master/docs/Mod-Creation/Updating-Your-Mods/1.3.9-Memory-Optimization/Core-API-Changelog-1.3.9.md The `[InitDuringStartupPhase]` attribute allows methods to be executed during specific phases of the game's startup sequence. It takes a `GameInitPhase` enum to define the phase and an integer `order` for execution priority. Lower order values execute earlier. ```csharp namespace RoR2 { public class ExampleClass { [InitDuringStartupPhase(GameInitPhase.Init, 0)] public void InitializeSomething() { // Code to execute during the Init phase } } public enum GameInitPhase { Unassigned, PreGame, Init, PostGame } } ``` -------------------------------- ### Soft Dependency Declaration in C# Source: https://github.com/risk-of-thunder/r2wiki/wiki/Mod-Creation_Assets_Translation Declares a soft dependency on another mod using BepInDependency. This allows your mod to load after the specified mod without requiring it to be installed, ensuring compatibility with various mod combinations. ```csharp [BepInDependency("com.Bog.Deputy", BepInDependency.DependencyFlags.SoftDependency)] ``` -------------------------------- ### Execute Code on Game Startup with InitDuringStartupPhase Attribute (C#) Source: https://github.com/risk-of-thunder/r2wiki/wiki/Mod-Creation_Updating-Your-Mods_1.3.9-Memory-Optimization_Core-API-Changelog-1.3.9 The `[InitDuringStartupPhase(GameInitPhase initPhase, int order)]` attribute allows methods to be executed during specific phases of the game's startup sequence. The `initPhase` enum defines the startup stage, and `order` determines the execution sequence among methods within the same phase. Methods with a lower order value execute before those with higher values. ```csharp using RoR2; public class MyGameLogic { [InitDuringStartupPhase(GameInitPhase.Start, 0)] public void InitializeGameFeatures() { // Code to execute during the GameInitPhase.Start phase, with order 0 UnityEngine.Debug.Log("Initializing game features..."); } [InitDuringStartupPhase(GameInitPhase.Start, 2)] public void SetupGameSystems() { // Code to execute during the GameInitPhase.Start phase, with order 2 UnityEngine.Debug.Log("Setting up game systems..."); } } ``` -------------------------------- ### Manual Serialization Example in C# Source: https://github.com/risk-of-thunder/r2wiki/wiki/Mod-Creation_C Demonstrates manual serialization and deserialization for a NetworkBehaviour. It shows how to define custom dirty bits, serialize complex data like boolean arrays, and handle updates based on dirty bits. This is useful when simple SyncVars cannot capture the required data complexity. ```csharp class ManualSerializationExample : NetworkBehaviour { // Manually defining the dirty bits private const uint var1DirtyBit = 1u; private const uint var2DirtyBit = 2u; private int var1; // this behaves like a simple SyncVar private bool[] var2; // something complex to serialize [Server] private void SetVar1(int newValue) { if (var1 != newValue) { var1 = newValue; SetDirtyBit(var1DirtyBit); } } [Server] private SetVar2Value(int index, bool newValue) { if (index < var2.Length && var2[index] != newValue) { var2[index] = newValue; SetDirtyBit(var2DirtyBit); } } private void SerializeVar2(NetworkWriter writer) { writer.WritePackedUInt32(var2.Length); for (int i = 0; i < var2.Length; i++) { writer.Write(var2[i]); } } private void DeserializeVar2(NetworkReader reader) { int count = (int)reader.ReadPackedUInt32(); for (int i = 0; i < count; i++) { var2[i] = reader.ReadBoolean(); } } public override bool OnSerialize(NetworkWriter writer, bool forceAll) { // This is used when a network object is to be spawned on the client // so that all of the values are updated. if (forceAll) { // Write(int) writes 4 bytes to the buffer. Most integers tend // to not make use of all 4 bytes, in which case it will be more // space efficient to use WritePackedUInt32(uint) since it does a // bit of clever compression internally. However, if the value // is greater than 16777215u, it will end up writing 5 bytes, // in which case Write(int) is more preferred. writer.WritePackedUInt32((uint)var1); SerializeVar2(writer); return true; } writer.WritePackedUInt32(syncVarDirtyBits); if ((syncVarDirtyBits & var1DirtyBit) != 0u) { writer.WritePackedUInt32((uint)var1); } if ((syncVarDirtyBits & var2DirtyBit) != 0u) { SerializeVar2(writer); } return syncVarDirtyBits != 0u; } public override void OnDeserialize(NetworkReader reader, bool initialState) { if (initialState) { var1 = (int)reader.ReadPackedUInt32(); DeserializeVar2(reader); return; } uint dirtyBits = reader.ReadPackedUInt32(); if (dirtyBits & var1DirtyBit) != 0u) { var1 = (int)reader.ReadPackedUInt32(); } if (dirtyBits & var2DirtyBit) != 0u) { DeserializeVar2(reader); } } } ``` -------------------------------- ### Hooking into RoR2 HUD Awake Function (C#) Source: https://github.com/risk-of-thunder/r2wiki/wiki/Mod-Creation_Assets_UI This code snippet demonstrates how to hook into the Awake function of the RoR2.UI.HUD class to gain access to the HUD instance. It includes the necessary setup for hooking and unhooking the event. ```csharp private HUD hud = null; private void Awake() { On.RoR2.UI.HUD.Awake += MyFunc; } private void MyFunc(On.RoR2.UI.HUD.orig_Awake orig, RoR2.UI.HUD self) { orig(self); // Don't forget to call this, or the vanilla / other mods' codes will not execute! hud = self; //hud.mainContainer.transform // This will return the main container. You should put your UI elements under it or its children! // Rest of the code is to go here } private void OnDestroy() { On.RoR2.UI.HUD.Awake -= MyFunc; } ``` -------------------------------- ### C# Example: Extracting Item Pickup Description Source: https://github.com/risk-of-thunder/r2wiki/wiki/Mod-Creation_Assets_Translation This C# code snippet shows how to extract the pickup description for an item from a mod's DLL file using DnSpy. This information is crucial for translating item descriptions accurately. ```csharp public override string ItemPickupDesc { get { return "Your damage over time effects heal all allies."; } } ``` -------------------------------- ### Create and Configure SceneDef at Runtime (C#) Source: https://github.com/risk-of-thunder/r2wiki/wiki/Mod-Creation_Assets_Stage Demonstrates how to create a SceneDef object programmatically in C#. It covers setting essential properties like name, type, stage order, display tokens, music tracks, and portal material. This method is useful for dynamic stage creation and integration with R2API's stage registration utilities. ```csharp public void AddSceneDef(){ //The pseudocode below is only for those who are making their SceneDef in code ExampleModStageDef = ScriptableObject.CreateInstance(); string identificationStr = "example_examplestage"; ExampleModStageDef.cachedName = identification; ExampleModStageDef.baseSceneNameOverride = identification; ExampleModStageDef.sceneType = SceneType.Stage; ExampleModStageDef.isOfflineScene = false; ExampleModStageDef.stageOrder = 2; //Stage 2 ExampleModStageDef.nameToken = "EXAMPLE_MAP_EXAMPLESTAGE_NAME"; ExampleModStageDef.subtitleToken = "EXAMPLE_MAP_EXAMPLESTAGE_SUBTITLE"; ExampleModStageDef.previewTexture = YourLoadedPreviewTexture; ExampleModStageDef.portalSelectionMessageString = "EXAMPLE_BAZAAR_SEER_EXAMPLESTAGE"; ExampleModStageDef.shouldIncludeInLogbook = true; ExampleModStageDef.loreToken = "EXAMPLE_MAP_EXAMPLESTAGE_LORE"; ExampleModStageDef.dioramaPrefab = YourLoadedDioramaPrefab; ExampleModStageDef.suppressPlayerEntry = false; ExampleModStageDef.suppressNpcEntry = false; ExampleModStageDef.blockOrbitalSkills = false; ExampleModStageDef.validForRandomSelection = true; //The pseudocode below is applicable no matter where your SceneDef is created. //This is "The Raindrop that Fell to the Sky" ExampleModStageDef.mainTrack = Addressables.LoadAssetAsync("RoR2/Base/Common/muSong13.asset").WaitForCompletion(); //This is "Thermodynamic Equilibrium" ExampleModStageDef.bossTrack = Addressables.LoadAssetAsync("RoR2/Base/Common/muSong05.asset").WaitForCompletion(); ExampleModStageDef.portalMaterial = StageRegistration.MakeBazaarSeerMaterial(YourLoadedPreviewTexture.texture); StageRegistration.AddSceneDef(ExampleModStageDef, ExampleModPluginInfo) StageRegistration.RegisterSceneDefToNormalProgression(ExampleModStageDef); } ``` -------------------------------- ### Register Achievement with Lunar Coin Rewards Source: https://github.com/risk-of-thunder/r2wiki/wiki/Mod-Creation_Updating-Your-Mods_For-Seekers-of-the-Storm Shows how to register achievements with lunar coin rewards using the RegisterAchievementAttribute. This replaces the older UnlockableAPI for achievements. The attribute now includes a field for specifying lunar coin rewards, with examples for mastery and skill achievements. ```csharp // Example usage (assuming RegisterAchievementAttribute has a LunarCoinReward field) [RegisterAchievement("MyAchievement", "MyMod", "Description", "icon.png", LunarCoinReward = 5)] public class MyAchievement : AchievementBase { ... } ``` -------------------------------- ### Setup Empowering Elite Definition (C#) Source: https://github.com/risk-of-thunder/r2wiki/wiki/Mod-Creation_Assets_Elites Creates and configures an EliteDef for the empowering affix. This involves setting its color, associating it with the empowering equipment, defining a modifier token, and setting health and damage boost coefficients. It also links this EliteDef to the passive buff definition. ```csharp private void SetupElite() { AffixEmpoweringElite = ScriptableObject.CreateInstance(); AffixEmpoweringElite.color = AffixEmpoweringColor; AffixEmpoweringElite.eliteEquipmentDef = AffixEmpoweringEquipment; AffixEmpoweringElite.modifierToken = "ELITE_MODIFIER_EMPOWERING"; AffixEmpoweringElite.name = "EliteEmpowering"; AffixEmpoweringElite.healthBoostCoefficient = healthMult; AffixEmpoweringElite.damageBoostCoefficient = damageMult; AffixEmpoweringBuff.eliteDef = AffixEmpoweringElite; } ```