mirror of
https://github.com/Dingo-Shenanigans/ReSkate.git
synced 2026-10-23 23:26:06 +02:00
- overlay: reskate.on("draw", function(d) ...) each tick, with text, rect, line, circle, poly
and world_text, world_line, world_circle (projected through the camera as the frame draws),
size and world_to_screen; 4000 shapes a frame. reskate.ui.toast through ReSkate's notices, three
in 5 s. Images are left for later: they need texture slots in the overlay's renderer.
- menu: reskate.menu.panel(title, build) on a new PLUGINS tab of the ADDONS page, with heading,
label, separator, button, toggle, slider and dropdown, built ten times a second while it shows.
- audio.ui: the game's menu sounds by name, ten a second. mods.read: installed(name) and list(),
from the discovery thread's scan.
- session.read: active, is_dedicated, players, me, host, and player_joined / player_left.
net.session: send(to, message) to the same plugin on other games (by its manifest.json name) in
the session's throwdown packets, tagged 0xD1 (plugin_wire.h); 4 KB each, 16 KB a second.
- The overlay and the host share plugin_ui.h (shapes, panels, actions) and plugin_camera.h.
- Fixed: the overlay asked ImGui for the window's size before its context existed, which crashed
on the first frames; it now reads the size from the context.
- reskate-test has overlay (ptest hud), menu, audio, mods and session (ptest ping) modules;
reskate-hostile has draw-flood, toast-spam, sound-spam, message-flood and panel-loop.
Checked in game: ptest all 62 passed, 0 failed; the test HUD, the PLUGINS tab and its panel; every
hostile attack stopped or refused, trick-loop and panel-loop included. Not yet: ptest ping with a
second player.
103 lines
4.0 KiB
C++
103 lines
4.0 KiB
C++
#include "skate_menu_internal.h"
|
|
#include "Extension/Plugins/plugin_ui.h"
|
|
|
|
#include <map>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
// The ADDONS page's PLUGINS tab: each plugin's panels (reskate.menu.panel), drawn with the menu's
|
|
// own widgets. Plugins build them on the client thread ten times a second while this tab shows
|
|
// (plugin_ui.h), and what the player does goes back to them as actions.
|
|
namespace dingosdk::overlay::menu {
|
|
namespace {
|
|
// What the player last set on a widget, shown until the plugin's next build agrees, or a second
|
|
// passes: the plugin's answer comes a few frames later.
|
|
struct Edit {
|
|
bool on{};
|
|
float value{};
|
|
int chosen{};
|
|
double at{};
|
|
};
|
|
std::map<std::string, Edit>& edits() {
|
|
static auto* value = new std::map<std::string, Edit>;
|
|
return *value;
|
|
}
|
|
}
|
|
|
|
void plugins_addon(SkateMenu& menu) {
|
|
plugin_ui::note_panels_shown();
|
|
const auto panels = plugin_ui::latest_panels();
|
|
const double now = ImGui::GetTime();
|
|
std::erase_if(edits(), [&](const auto& item) { return now - item.second.at > 1.0; });
|
|
if (!panels || panels->empty()) {
|
|
begin_card(menu, "plugins-none", "PLUGINS");
|
|
note("No plugin has a panel here. A plugin adds one with reskate.menu.panel (it needs the \"menu\" permission). "
|
|
"plugin list in the console shows every plugin.");
|
|
end_card();
|
|
return;
|
|
}
|
|
int index = 0;
|
|
for (const auto& panel : *panels) {
|
|
ImGui::PushID(index++);
|
|
begin_card(menu, "plugin-panel", panel.title.c_str(), panel.plugin.c_str());
|
|
int item = 0;
|
|
for (const auto& w : panel.widgets) {
|
|
ImGui::PushID(item++);
|
|
const auto key = panel.plugin + '\n' + panel.title + '\n' + w.id;
|
|
auto& all = edits();
|
|
const auto edit = all.find(key);
|
|
const auto send = [&](plugin_ui::Action action, Edit remembered) {
|
|
action.plugin = panel.plugin;
|
|
action.panel = panel.title;
|
|
action.widget = w.id;
|
|
plugin_ui::act(std::move(action));
|
|
remembered.at = now;
|
|
all[key] = remembered;
|
|
};
|
|
using Kind = plugin_ui::Widget::Kind;
|
|
switch (w.kind) {
|
|
case Kind::heading: section(menu, w.text.c_str()); break;
|
|
case Kind::label: ImGui::TextWrapped("%s", w.text.c_str()); break;
|
|
case Kind::separator: ImGui::Separator(); break;
|
|
case Kind::button:
|
|
if (ImGui::Button(w.text.c_str(), ImVec2(-FLT_MIN, px(34)))) send({}, {});
|
|
break;
|
|
case Kind::toggle: {
|
|
bool on = edit != all.end() ? edit->second.on : w.on;
|
|
if (edit != all.end() && edit->second.on == w.on) all.erase(edit);
|
|
if (toggle_row(menu, w.text.c_str(), "", on)) {
|
|
plugin_ui::Action action;
|
|
action.on = on;
|
|
send(action, {on});
|
|
}
|
|
break;
|
|
}
|
|
case Kind::slider: {
|
|
float value = edit != all.end() ? edit->second.value : w.value;
|
|
field(menu, w.text.c_str());
|
|
if (slider_float("##slider", &value, w.minimum, w.maximum, "%.2f", ImGuiSliderFlags_AlwaysClamp)) {
|
|
plugin_ui::Action action;
|
|
action.value = value;
|
|
send(action, {false, value});
|
|
}
|
|
break;
|
|
}
|
|
case Kind::dropdown: {
|
|
int selected = (edit != all.end() ? edit->second.chosen : w.chosen) - 1;
|
|
if (edit != all.end() && edit->second.chosen == w.chosen) all.erase(edit);
|
|
if (option_row(menu, w.text.c_str(), "", selected, w.choices)) {
|
|
plugin_ui::Action action;
|
|
action.chosen = selected + 1;
|
|
send(action, {false, 0, selected + 1});
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
ImGui::PopID();
|
|
}
|
|
end_card();
|
|
ImGui::PopID();
|
|
}
|
|
}
|
|
}
|