-
Notifications
You must be signed in to change notification settings - Fork 11
Code Documentation
Last updated March 3rd 2023
LunaKit is a C++ add-on to Super Mario Odyssey, built off of CraftyBoss's ImGui ExLaunch Base Repo. This program is designed to be extremely modular and customizable for adding or editing its features. Every major element of LunaKit will be covered in this documentation wiki page, please refer to the table of contents below OR click the links in the code itself to jump straight to that part of the documentation
The DevGuiManager (LunaKit Manager) is the root class that controls every other part of LunaKit. Edits to this class are required to add nearly any new features, including new windows and home bar tabs.
This class is a singleton, meaning there is only one static instance of it that exists at all times (initalized in the game's GameSystem::init function, hook found in main.cpp)
Jump to table of contents
LunaKit's current version is found at the very top of the DevGuiManager header as LUNAKITVERSION
// Current version of the application (change this if you make changes, especially changes that effect the save data!)
#define LUNAKITVERSION "V1"
It is recommended this string is updated to a new value any time a new feature/major change is added (new window, new feature, major overhaul, ect.) This is not just as a note of current version for developers!! The current version is used to make sure the LunaKit save data is never using outdated information and causing issues.
Jump to table of contents
void update(); // Update is always called every frame (on the sequence)
void updateDisplay(); // Update display is only used when the menu is currently open
The manager runs two seperate update functions during a frame, update() and updateDisplay(). Here are the key differences between these functions:
update(): Update is called every frame off of the HakoniwaSequence hook, mainly used to execute code that affects the game. A good example of this is setting a boolean on clicking a box in updateDisplay(), and then handling that interaction in update(). In the update function, you have no control over the ImGui display and it is not recommended to try and use anything from the ImGui library in this function. It's worth noting that home menu tabs do NOT have standard update functions, everything set by those tabs should be run during the display update. This function is always called every frame, even if the interface is closed
updateDisplay(): This function is called far later during ImGui's drawing process and is responsible for rendering every window, home menu tab, and everything else visual. Most features share updateDisplay functions that are called off of the manager including Windows, Home Tabs, and Categories. You can add your own code into this through classes that inherit from these classes and override their updateDisplay functions. This function is not called if the interface is closed (hiding the windows with the left stick does not count as closed, code will still be run)
Jump to table of contents
Anchoring is a system handled by the manager that controls how the windows are displayed.
There are four different anchor positions:
- Top
- Bottom
- Left
- Right
// All positions the windows can be placed on the screen
enum WinAnchorType {
ANC_TOP,
ANC_BOTTOM,
ANC_LEFT,
ANC_RIGHT,
ANC_TOTAL_SIZE
};
Window positions and sizes are refreshed any time that refreshAnchor() or setAnchorType() is called, the exact positioning of these windows is determined based on the Window's setupAnchor() function. Each window has can either be anchored or non-anchored, as well as having total anchor pages. The amount of pages determines the size of the window, the larger the number the more room will be given (proportional to total open windows/pages) More detail given at Anchored vs. Non-anchored in the Window Documentation.
Jump to table of contents
Every LunaKit window is a box inheriting from the WindowBase class and registered in the windows array of the DevGuiManger. Note this excludes the home bar despite technically being a window, and includes both anchored and non-anchored windows, essentially any window class registered in the manager's window PtrArray
Windows have two update functions, updateWin()
and tryUpdateWinDisplay()
, for more information on these, check the update() vs. updateDisplay() docs above!
Creating a window class is designed to be quite simple, only needing to override four functions for most functionality. Each window has four main functions intended to be overridden by child classes, the constructor, updateWin, tryUpdateWinDisplay, and setupAnchor
Here is a template for what a window class may look like:
#pragma once
// Do not have include directly to DevGuiManager to avoid issues
#include "devgui/windows/WindowBase.h"
#include "devgui/categories/CategoryBase.h"
class WindowCustom : public WindowBase {
public:
WindowCustom(DevGuiManager* parent, const char* winName, bool isActiveByDefault, bool isAnchor, int windowPages);
void updateWin() override;
bool tryUpdateWinDisplay() override;
void setupAnchor(int totalAnchoredWindows, int anchorIdx) override;
private:
// Custom parameters and information here
};
Please note that you don't need to override every single one of these functions if you aren't using them! For example, if your window is in the standard anchor list, there is no reason to override setupAnchor. If you want to see how these functions are implemented in the cpp files, look at the other already created windows as a reference.
Once you have your window created, you'll need to add it to the window list in the Manager! This can be done by going to the function DevGuiManager::createElements()
at the top of the cpp file. Inside here you can add your window by adding a new call to createWindow
like this!
createWindow<WindowBase>("Window Name", true/false: isActiveByDefault, true/false: isInAnchorList, int: anchorPages);
The class in <> will determine what type of window is being created
The window name string will determine the name of each window in the title bar (VERY IMPORTANT EVERY WINDOW HAS A UNIQUE NAME!!)
The isActiveByDefault
boolean determines if (on a blank/new LunaKit save file) if the window is already open
The isInAnchorList
boolean determines if space should be made for this window when creating the list of anchored windows. More info on anchors here
The anchorPages
integer sets how much space will be given for your window (IF ANCHORED). Most windows should use 1 here, but if you need more room, this can be increased.
There are two types of windows, anchored and non-anchored.
For more general information on the anchor system as a whole, read this first
Anchored Windows: These types of windows are always positioned in a straight bar on any edge of the screen and their size is calculated based on what other windows are open (and how many pages each window takes up). Creating an anchored window is as simple as setting the isAnchor
bool in the createWindow<>()
to true and not overriding the setupAnchor()
function in your child class.
Non-Anchored Windows: Creating a non-anchored window is a good bit more complex. These windows can be manually positioned anywhere on the screen, but this takes care to avoid overlap with the anchored section regardless of where the user decides to place their anchored windows. Start by adding your window to the createWindow<>()
list with the isAnchor
bool set to false. After this you'll need to override the setupAnchor()
function to calculate your windows position and size manually. There is any number of ways you can do this, but here is the FPS window's custom anchor setup as an example.
WinAnchorType type = mParent->getAnchorType();
// Setup window's position based on the anchor type
switch(type) {
case WinAnchorType::ANC_TOP:
mConfig.mTrans = ImVec2(0, mConfig.mScrSize.y - mConfig.mSize.y);
break;
case WinAnchorType::ANC_BOTTOM:
mConfig.mTrans = ImVec2(0, mConfig.mMinimumY);
break;
case WinAnchorType::ANC_LEFT:
mConfig.mTrans = ImVec2(mConfig.mScrSize.x - mConfig.mSize.x, mConfig.mMinimumY);
break;
case WinAnchorType::ANC_RIGHT:
mConfig.mTrans = ImVec2(0, mConfig.mMinimumY);
break;
default:
break;
}
ImGui::SetWindowPos(mConfig.mTrans);
ImGui::SetWindowSize(mConfig.mSize);
This example picks a corner not already occupied and sets the position and size based on where everything else has been positioned. If you (for some reason) wanted a small window in the dead center of the screen, you can easily do that by setting the position and scale to preset vectors and avoid calculations based on the anchor. Depends on a case by case basis.
Window Config is an additional struct included in every window (through the member mConfig
) WRITE MORE LATER OKAY BYE (THESE DOCS TAKE SO LONG TO WRITEEEEE)
LunaKit created by Amethyst-szs
A Mario Odyssey tool built off Dear ImGui for developers, level builders, glitch hunters, and everyone in-between