modding.md, test mod, template mod, better imgui context

This commit is contained in:
madeline
2026-04-20 07:52:35 -07:00
parent 8356eff4ce
commit e7081f770a
11 changed files with 493 additions and 68 deletions
+154
View File
@@ -0,0 +1,154 @@
// Tests every feature of the Dusk mod API. Results shown in the mod tab.
#include "d/actor/d_a_alink.h"
#include "dusk/hook.hpp"
#include "dusk/mod_api.h"
#include "imgui.h"
#include "m_Do/m_Do_controller_pad.h"
#include <cstdio>
#include <string>
static int g_ticks = 0;
static bool g_pre_fired = false;
static bool g_post_fired = false;
static bool g_replace_fired = false;
static bool g_arg_write_ok = false;
static int g_pre_cancel_count = 0;
static int g_post_count = 0;
static bool g_resource_ok = false;
static std::string g_resource_text;
static char g_mod_dir_snippet[64] = {};
// Pre-hook on posMove. Hold L to test argRef write and cancellation.
static int32_t on_posMove_pre(void* args) {
g_pre_fired = true;
if (mDoCPd_c::getHoldL(PAD_1)) {
dusk::argRef<daAlink_c*>(args, 0)->shape_angle.y = 0;
g_arg_write_ok = true;
++g_pre_cancel_count;
return 1; // cancel
}
return 0;
}
// Post-hook on posMove. Fires even when the pre-hook cancelled.
static void on_posMove_post(void* args) {
g_post_fired = true;
++g_post_count;
(void)args;
}
// Replace-hook on execute. Calls through to the original so gameplay is unaffected.
using ExecuteEntry = dusk::HookEntry<&daAlink_c::execute>;
static void on_execute_replace(void* args) {
g_replace_fired = true;
ExecuteEntry::g_orig(dusk::arg<daAlink_c*>(args, 0));
}
static void DrawPanel(void*) {
auto status = [](const char* label, bool ok) {
ImGui::TextColored(ok ? ImVec4(0, 1, 0, 1) : ImVec4(1, 0.35f, 0.35f, 1),
ok ? "[PASS]" : "[WAIT]");
ImGui::SameLine();
ImGui::Text("%s", label);
};
ImGui::SeparatorText("Hooks");
status("pre-hook fired (posMove)", g_pre_fired);
status("post-hook fired (posMove)", g_post_fired);
status("replace-hook fired (execute)", g_replace_fired);
status("argRef write + pre cancel (hold L)", g_arg_write_ok);
ImGui::Text(" pre cancels: %d post calls: %d", g_pre_cancel_count, g_post_count);
ImGui::SeparatorText("Resources");
status("load_resource (hello.txt)", g_resource_ok);
if (!g_resource_text.empty())
ImGui::TextWrapped(" \"%s\"", g_resource_text.c_str());
ImGui::SeparatorText("API Fields");
status("mod_dir non-empty", g_mod_dir_snippet[0] != '\0');
ImGui::TextWrapped(" %s", g_mod_dir_snippet);
ImGui::Spacing();
ImGui::Separator();
if (ImGui::Button("Reset results")) {
g_pre_fired = false;
g_post_fired = false;
g_replace_fired = false;
g_arg_write_ok = false;
g_pre_cancel_count = 0;
g_post_count = 0;
}
daAlink_c* link = daAlink_getAlinkActorClass();
if (link) {
ImGui::SameLine();
ImGui::Text("(Link y angle: %d)", (int)link->shape_angle.y);
}
}
static void DrawMenuEntry(void*) {
if (ImGui::MenuItem("Test: log all levels")) {
dusk::g_api->log_info("log_info test");
dusk::g_api->log_warn("log_warn test");
dusk::g_api->log_error("log_error test");
}
if (ImGui::MenuItem("Test: reset Link y angle")) {
daAlink_c* link = daAlink_getAlinkActorClass();
if (link) link->shape_angle.y = 0;
}
}
extern "C" {
void mod_init(DuskModAPI* api) {
dusk::init(api);
api->log_info("mod_test initializing");
api->log_warn("log_warn smoke test");
api->log_error("log_error smoke test");
std::snprintf(g_mod_dir_snippet, sizeof(g_mod_dir_snippet), "%.60s", api->mod_dir);
size_t size = 0;
void* data = api->load_resource("hello.txt", &size);
if (data) {
g_resource_text.assign(static_cast<char*>(data), size);
while (!g_resource_text.empty() && g_resource_text.back() == '\n')
g_resource_text.pop_back();
api->free_resource(data);
g_resource_ok = true;
api->log_info("load_resource OK: \"%s\"", g_resource_text.c_str());
} else {
api->log_error("load_resource FAILED for hello.txt");
}
// Missing file should return nullptr gracefully.
void* missing = api->load_resource("does_not_exist.bin", nullptr);
if (!missing)
api->log_info("load_resource missing-file: correctly returned nullptr");
else {
api->log_error("load_resource missing-file: unexpectedly returned data");
api->free_resource(missing);
}
dusk::hookAddPre <&daAlink_c::posMove>(on_posMove_pre);
dusk::hookAddPost<&daAlink_c::posMove>(on_posMove_post);
dusk::hookSetReplace<&daAlink_c::execute>(on_execute_replace);
api->register_tab_content(DrawPanel, nullptr);
api->register_menu_item(DrawMenuEntry, nullptr);
api->log_info("mod_test ready");
}
void mod_tick(DuskModAPI* api) {
++g_ticks;
(void)api;
}
void mod_cleanup(DuskModAPI* api) {
api->log_info("mod_test unloaded after %d ticks", g_ticks);
}
}