Quickstart
Introduction
This showcases some example usage of the API.
Installation
Either git clone git clone --recurse-submodules git@gitlab.com:Rawra/rain-world-imgui-api.git
the project (with its submodules)
or (preferrably!) just use the steam workshop ready package
Then reference these libraries:
rain-world-imgui-api.dll
-
The mod itself, provides basic API to use Dear ImGUI in the context of the game window.
ImGui.NET.dll
-
Dear ImGUI .NET wrapper P/Invoke library.
ImPlot.NET.dll
-
Dear ImGUI/ImPlot .NET wrapper P/Invoke library.
ImNodes.NET.dll
-
Dear ImGUI/ImNodes .NET wrapper P/Invoke library.
Optionally, you may also reference the 3 System. Libraries aswell.
Make sure to not ship the mod itself with your mod, if you do so anyway, expect issues.
Example
In order for this project to render imgui stuff for you, you must create a static function signature matching the renderer function pointer we expect:
Menu Usage (Where your imgui code will be working inside a TabItem):
public static delegate* managed<ref IntPtr, ref uint, ref uint, void> UserMenuRenderingCallbackFnPtr;
Menu Usage (Custom windows)
public static delegate* managed<ref IntPtr, ref uint, ref uint, void> UserCustomMenuRenderingCallbackFnPtr;
Live Usage:
public static delegate* managed<ref IntPtr, ref uint, ref uint, void> UserAlwaysRenderingCallbackFnPtr;
The term menu here refers to the by default toggleable "VK_DELETE" key debug menu (UserMenuRendering).
While live refers to the stuff that is always being rendered (UserAlwaysRendering)
After which you can add your own renderer to the list of global rendering callbacks like in the following code snippet. Beware you don't need to do both, just register what you really use.
// Where "ImGUI_Menu_Callback.PresentCallback" is a function matching the signature above.
ImGUIAPI.AddMenuCallback(&ImGUI_Menu_Callback.PresentCallback); // For menu-rendering (VK_DELETE menu)
ImGUIAPI.AddCustomMenuCallback(&ImGUI_Custom_Menu_Callback.PresentCallback); // For custom menu-rendering (VK_DELETE menu)
ImGUIAPI.AddAlwaysCallback(&ImGUI_Always_Callback.PresentCallback); // For permanent-rendering (HUD stuff, etc)
Below is a example function callback implementation
[SuppressUnmanagedCodeSecurity]
internal sealed class ImGUI_Menu_Callback
{
public static void PresentCallback(ref IntPtr IDXGISwapChain, ref uint SyncInterval, ref uint Flags)
{
if (ImGui.BeginTabItem(Plugin.PLUGIN_NAME))
{
if (ImGui.BeginTabBar("Tools"))
{
if (ImGui.BeginTabItem("Entity List"))
{
EntityListComponent();
ImGui.EndTabItem();
}
if (ImGui.BeginTabItem("Relationship Viewer"))
{
RelationshipViewerComponent();
ImGui.EndTabItem();
}
ImGui.EndTabBar();
}
ImGui.EndTabItem();
}
}
private static byte[] inputBufferModule = new byte[256];
private static void RelationshipViewerComponent()
{
if (!(Custom.rainWorld.processManagerInitialized && Custom.rainWorld.processManager.currentMainLoop is RainWorldGame g))
{
ImGui.Text("Must be in-game");
return;
}
ImGui.InputText("INTERNAL_ID", inputBufferModule, (uint)inputBufferModule.Length);
ImGui.Separator();
// Output for every relationship a node from a selected creature
if (!int.TryParse(UTF8Encoding.UTF8.GetString(inputBufferModule), out parsedCreatureId))
{
ImGui.Text("STATE: Parsing failed");
return;
}
if (!TryGetAbstractCreatureById(parsedCreatureId, out AbstractCreature creature))
{
ImGui.Text("STATE: TryGetAbstractCreatureById failed");
return;
}
// sanity check (real)
if (
(creature.state == null) ||
(creature.state.socialMemory == null) ||
(creature.state.socialMemory.relationShips == null)
)
{
ImGui.Text("STATE: creature.State or child is null");
return;
}
ImGui.Text($"STATE: realtionships: {creature.state.socialMemory.relationShips.Count}");
ImGuiTableFlags flags = ImGuiTableFlags.BordersOuter | ImGuiTableFlags.BordersV | ImGuiTableFlags.RowBg | ImGuiTableFlags.Resizable | ImGuiTableFlags.Reorderable;
if (ImGui.BeginTable("##relationships", 6, flags, new System.Numerics.Vector2(-1, 0)))
{
ImGui.TableSetupColumn("SUBJECT.ID", ImGuiTableColumnFlags.WidthFixed);
ImGui.TableSetupColumn("LIKE", ImGuiTableColumnFlags.WidthFixed);
ImGui.TableSetupColumn("TEMPLIKE", ImGuiTableColumnFlags.WidthFixed);
ImGui.TableSetupColumn("FEAR", ImGuiTableColumnFlags.WidthFixed);
ImGui.TableSetupColumn("TEMPFEAR", ImGuiTableColumnFlags.WidthFixed);
ImGui.TableSetupColumn("KNOW", ImGuiTableColumnFlags.WidthFixed);
ImGui.TableHeadersRow();
foreach (SocialMemory.Relationship rel in creature.state.socialMemory.relationShips)
{
ImGui.TableNextRow();
ImGui.TableNextColumn();
ImGui.Text(rel.subjectID.ToString());
ImGui.TableNextColumn();
ImGui.SliderFloat(string.Empty, ref rel.like, 0.00f, 1.00f);
ImGui.TableNextColumn();
ImGui.SliderFloat(string.Empty, ref rel.tempLike, 0.00f, 1.00f);
ImGui.TableNextColumn();
ImGui.SliderFloat(string.Empty, ref rel.fear, 0.00f, 1.00f);
ImGui.TableNextColumn();
ImGui.SliderFloat(string.Empty, ref rel.tempFear, 0.00f, 1.00f);
ImGui.TableNextColumn();
ImGui.SliderFloat(string.Empty, ref rel.know, 0.00f, 1.00f);
}
ImGui.EndTable();
}
}
private static bool TryGetAbstractCreatureById(int id, out AbstractCreature creature)
{
creature = default;
if (!(Custom.rainWorld.processManagerInitialized && Custom.rainWorld.processManager.currentMainLoop is RainWorldGame g))
return false;
foreach (var ar in g.world.abstractRooms)
{
foreach (var ac in ar.creatures)
{
if (ac.ID.number == id)
{
creature = ac;
return true;
}
}
}
return false;
}
}
Its important to note, however, that it is expected for all registered functions to not mess-up the ImGUI rendering stack. Beware of forgetting to "End" imgui components that you begin.