This commit is contained in:
AyeZeeBB committed 2026-10-11 19:26:25 -05:00
commit 8f2c456a39
12 files changed
+300 -48

No files matched your search

+2 -1
View File
@@ -505,7 +505,8 @@ ApplyEffect preview(const mods::ModList &list) {
};
bool reload{};
for (const auto &name : before) {
if (named(now, name)) continue;
// One of a Board Break deck pack's decks stands for the pack (mod_catalog.h).
if (const auto pack = mods::board_break_pack_of(name); named(now, pack.empty() ? name : pack)) continue;
const auto *entry = entry_of(name);
// A folder deleted since: what it held is unknown, so assume the worst.
if (!entry) {
@@ -133,6 +133,21 @@ void a_truck_goes_with_its_piece() {
const auto up = truck_bone(lifted, mount, mount_at, level, front_truck_place);
check(near(up.at + away, front_truck_place + Vec{0, 0.25f, 0}), "A piece that is lifted takes its truck up with it");
}
void a_baseplate_stays_on_the_deck_while_its_truck_steers() {
// A baseplate sits on the truck's effector, a child of the truck's bone. Whatever the truck's
// bone does, the truck's bone followed by the effector's is what it is at rest.
for (const auto& rest : {back_truck_rest, front_truck_rest}) {
const auto still = baseplate_bone({rest.facing, rest.at}, rest);
check(near(still.at, rest.effector) && near(std::abs(still.facing.w), 1.0f), "At rest the effector is where the rig has it");
const Vec bolt{0.03f, 0.08f, rest.at.z + 0.02f};
const auto on_deck = moved({rest.facing, rest.at}, moved({{}, rest.effector}, bolt));
for (const float lean : {-0.4f, 0.15f, 0.6f}) {
const Rigid steered{unit(rest.facing * about({0, 0, 1}, lean)), rest.at};
const auto effector = baseplate_bone(steered, rest);
check(near(moved(steered, moved(effector, bolt)), on_deck, 1e-4f), "Steered, the baseplate has not moved on the deck");
}
}
}
} // namespace
int main() {
@@ -146,6 +161,7 @@ int main() {
a_folded_board_meets_at_the_break();
a_hanging_end_turns_about_the_break();
a_truck_goes_with_its_piece();
a_baseplate_stays_on_the_deck_while_its_truck_steers();
std::cout << "board break tests passed\n";
return 0;
} catch (const std::exception& error) {
+24 -3
View File
@@ -45,6 +45,9 @@ constexpr float hand_reach = 1.3f, hand_upright = 0.8f;
// deck's mount, 3 the deck, 4 and 8 the trucks, and the spare bones that carry the four stretches
// (nose, front, back, tail; the tail's hangs on the front's).
constexpr int root_bone = 1, mount_bone = 2, deck_bone = 3, back_truck_bone = 4, front_truck_bone = 8;
// The trucks' effectors, each right after its truck's bone: a deck pack's truck model has the
// baseplates on them (board_break_model.h, baseplate_bone).
constexpr int back_plate_bone = 5, front_plate_bone = 9;
constexpr std::array<int, 4> carrier{12, 13, 15, 14};
constexpr std::uint32_t rig_bones = 16;
@@ -152,6 +155,21 @@ void carry_truck(std::uintptr_t buffer, const Rigid& motion, int index, Vec plac
(void)store_bone(bone_at(buffer, index), bone.facing, bone.at);
}
// Keeps the baseplates on the deck (baseplate_bone), from each truck's bone as the animation left
// it: a truck that a piece has carried off already, in a pose nobody has written since, is left.
void hold_baseplates(std::uintptr_t buffer) noexcept {
const auto hold = [&](int truck_index, int plate_index, const TruckRest& rest) {
PoseBone truck{};
if (!load_bone(bone_at(buffer, truck_index), truck)) return;
const Rigid now{facing_of(truck), place_of(truck)};
if (!(length(now.at - rest.at) < 0.05f)) return;
const auto bone = baseplate_bone(now, rest);
(void)store_bone(bone_at(buffer, plate_index), bone.facing, bone.at);
};
hold(back_truck_bone, back_plate_bone, back_truck_rest);
hold(front_truck_bone, front_plate_bone, front_truck_rest);
}
// ---- the piece that comes off --------------------------------------------------------------------
// The client tick starts a flight (a new id) when the board breaks; the render pose hook moves the
// piece through the world from there (a kick, gravity, the ground) and writes it relative to
@@ -276,14 +294,15 @@ std::uint32_t fly_piece(std::uintptr_t buffer, Rigid& piece) {
}
// ---- what the hook writes ------------------------------------------------------------------------
// The local board, broken: the real deck goes away, the trucks come back, each stretch goes to
// where its piece lies and one that belongs to no piece goes away too.
// The local board, broken: the real deck goes away, the trucks come back (their baseplates with
// them), each stretch goes to where its piece lies and one that belongs to no piece goes away too.
void draw_broken(std::uintptr_t base, std::uintptr_t holder, std::uint32_t kept, Hinge hinge, float angle) {
const auto buffer = board_pose(base, holder);
PoseBone root{}, mount{}, deck{};
if (!buffer || !load_bone(bone_at(buffer, root_bone), root) || !load_bone(bone_at(buffer, mount_bone), mount) ||
!load_bone(bone_at(buffer, deck_bone), deck))
return;
hold_baseplates(buffer);
// From where its animation put it this frame; a pose nobody has written since the last move stays as it is.
if (const Vec at = place_of(deck); length(at) < 100.0f) (void)store_bone(bone_at(buffer, deck_bone), facing_of(deck), at + away);
auto layout = lay_out(kept, hinge, angle, turn(facing_of(root), {0, 1, 0}).y, facing_of(mount));
@@ -298,10 +317,12 @@ void draw_broken(std::uintptr_t base, std::uintptr_t holder, std::uint32_t kept,
const auto bones = bones_of(layout.stretch);
for (std::size_t i = 0; i < bones.size(); ++i) (void)store_bone(bone_at(buffer, carrier[i]), bones[i].facing, bones[i].at);
}
// A whole board, any player's: its four stretches go far away from it.
// A whole board, any player's: its four stretches go far away from it, and its baseplates sit on
// the deck.
void park_pieces(std::uintptr_t base, std::uintptr_t holder) {
const auto buffer = board_pose(base, holder);
if (!buffer) return;
hold_baseplates(buffer);
const auto bones = bones_of({});
for (std::size_t i = 0; i < bones.size(); ++i) (void)store_bone(bone_at(buffer, carrier[i]), bones[i].facing, bones[i].at);
}
+17
View File
@@ -203,4 +203,21 @@ inline Rigid truck_bone(const Rigid& motion, Quat mount, Vec mount_at, Quat stee
const auto at = turn(conj(mount), turn(motion.facing, mount_at) + motion.at - mount_at);
return {unit(facing * steer), turn(facing, place) + at - away};
}
// The trucks' baseplates. The game's own truck model has them on the deck's bone, which goes away
// with the real deck when the board breaks, and they went with it. A deck pack's truck model has
// each baseplate on its truck's effector instead, a bone under the truck's that no model uses:
// wherever a piece carries the truck, its baseplate goes along. This is where that effector goes
// so that the baseplate sits on the deck however the truck steers: the truck's own turn and place
// taken back, its rest put in. `truck` is the truck's bone as the animation left it this frame.
struct TruckRest {
Quat facing; // the truck's bone on the deck's, at rest
Vec at;
Vec effector; // its effector on the truck's bone, at rest (not turned)
};
constexpr TruckRest back_truck_rest{{-0.3641458f, -0.3641458f, -0.6061335f, 0.6061335f}, {0.0005361f, -0.0183003f, -0.2344986f}, {0.0395021f, 0.0f, 0.0f}};
constexpr TruckRest front_truck_rest{{-0.6061674f, 0.6061674f, 0.3640894f, 0.3640894f}, {0.0005361f, -0.0182917f, 0.2346680f}, {0.0395677f, 0.0f, 0.0f}};
inline Rigid baseplate_bone(const Rigid& truck, const TruckRest& rest) noexcept {
const auto back = conj(truck.facing);
return {unit(back * rest.facing), turn(back, rest.at + turn(rest.facing, rest.effector) - truck.at)};
}
} // namespace dingosdk::board_break
+70 -5
View File
@@ -12,8 +12,66 @@
#include <filesystem>
#include <optional>
#include <stdexcept>
#include <string>
#include <vector>
namespace dingosdk::overlay {
namespace menu {
// Board Break's decks (Extension/BoardBreak/board_break.h). The game has one deck model and each
// deck is a mod's copy of it, so picking one applies the mods with that deck first and loads the
// level again. A button for each deck the feature was made with, greyed out while it is not
// installed, and for any other deck that is; the Mods folder is read again every 1.5 s while the
// card shows.
void board_break_deck_card(SkateMenu& menu, const Model& model, const CallbacksV3& callbacks) {
static std::vector<std::string> installed;
static std::string chosen;
static ULONGLONG next_scan{};
if (const auto now = GetTickCount64(); now >= next_scan) {
installed = mods::installed_board_break_decks();
chosen = mods::wanted_board_break_deck();
next_scan = now + 1500;
}
if (!model.board_break.available || installed.empty()) return;
const auto there = [&](const std::string& deck) { return std::find(installed.begin(), installed.end(), deck) != installed.end(); };
// The known decks in their own order, then the others that are installed: the numbers are the
// ones `boardbreak deck` prints.
std::vector<std::string> decks(mods::board_break_decks.begin(), mods::board_break_decks.end());
for (const auto& deck : installed)
if (std::find(decks.begin(), decks.end(), deck) == decks.end()) decks.push_back(deck);
const auto label = [&](const std::string& deck) {
auto name = deck;
bool start = true;
for (auto& c : name) {
if (c == '_') c = ' ';
else if (start && c >= 'a' && c <= 'z') c = static_cast<char>(c - 'a' + 'A');
start = c == ' ';
}
const auto place = std::find(decks.begin(), decks.end(), deck) - decks.begin() + 1;
return std::to_string(place) + " " + name;
};
const auto in_play = mods::board_break_in_play();
begin_card(menu, "board-break-decks", "BOARD BREAK DECK", in_play.empty() ? "none in the game" : label(in_play).c_str());
const bool busy = live_mods::busy();
const float gap = ImGui::GetStyle().ItemSpacing.x;
const float width = (ImGui::GetContentRegionAvail().x - gap * 3) / 4;
for (std::size_t i = 0; i < decks.size(); ++i) {
if (i % 4) ImGui::SameLine();
const auto& deck = decks[i];
ImGui::PushID(static_cast<int>(i));
ImGui::BeginDisabled(!there(deck) || busy || deck == in_play || !callbacks.queue_console_command);
if (ImGui::Button((label(deck) + (deck == in_play ? " *" : "")).c_str(), ImVec2(width, 0)))
send_console(menu, callbacks, "boardbreak deck " + deck);
ImGui::EndDisabled();
ImGui::PopID();
}
if (busy) note("Applying mods...");
else if (!in_play.empty() && chosen != in_play && there(chosen))
note((label(chosen) + " is chosen and shows once the level has loaded again.").c_str());
note("The deck every board in the game has, and the one that breaks. Picking another loads the level again; "
"decks that are not installed are greyed out.");
end_card();
}
}
namespace {
std::filesystem::path game_directory() {
std::array<wchar_t, 32768> executable{};
@@ -44,8 +102,11 @@ void draw_installed_mods(SkateMenu& menu) {
}
const auto& launch = mods::catalog();
const auto applied = live_mods::applied_mods();
// A Board Break deck pack is in effect when one of the decks inside it is (mod_catalog.h).
const auto in_effect = [&](const std::string& name) {
return std::ranges::any_of(applied, [&](const std::string& other) { return _stricmp(other.c_str(), name.c_str()) == 0; });
return std::ranges::any_of(applied, [&](const std::string& other) {
return _stricmp(other.c_str(), name.c_str()) == 0 || _stricmp(mods::board_break_pack_of(other).c_str(), name.c_str()) == 0;
});
};
const auto excluded = [&](const std::string& name) {
return std::ranges::any_of(launch.excluded, [&](const mods::Mod& mod) { return _stricmp(mod.name.c_str(), name.c_str()) == 0; });
@@ -66,7 +127,8 @@ void draw_installed_mods(SkateMenu& menu) {
// Mods still in effect whose folder has been deleted: applying unloads them.
std::vector<std::string> removed;
for (const auto& name : applied)
if (std::ranges::none_of(list.entries, [&](const mods::ModEntry& entry) { return _stricmp(entry.mod.name.c_str(), name.c_str()) == 0; }))
if (mods::board_break_pack_of(name).empty() &&
std::ranges::none_of(list.entries, [&](const mods::ModEntry& entry) { return _stricmp(entry.mod.name.c_str(), name.c_str()) == 0; }))
removed.push_back(name);
pending += removed.size();
loaded += removed.size();
@@ -165,7 +227,7 @@ void draw_installed_mods(SkateMenu& menu) {
} else if (set_aside(entry)) {
status = "SET ASIDE";
hover = "Set aside while a Board Break deck is the one in the game: the game has one deck model, so one "
"mod's deck loads at a time. Pick another deck with Board Break deck, or disable Board Break's decks.";
"mod's deck loads at a time. Pick another deck under BOARD BREAK DECK, or disable Board Break's decks.";
} else if (entry.enabled && effect) {
status = "LOADED";
colour = skate_theme::good;
@@ -268,7 +330,7 @@ void draw_custom_scripts(SkateMenu& menu) {
menu::end_card();
}
}
void draw_modding_menu(SkateMenu& menu, int tab) {
void draw_modding_menu(SkateMenu& menu, int tab, const Model& model, const CallbacksV3& callbacks) {
// Loose files are switched in the launcher; custom scripts only need to know
// whether they are on.
if (!menu.loose_files_settings_loaded) {
@@ -277,6 +339,9 @@ void draw_modding_menu(SkateMenu& menu, int tab) {
catch (const std::exception&) {}
}
if (tab == 1) draw_custom_scripts(menu);
else draw_installed_mods(menu);
else {
menu::board_break_deck_card(menu, model, callbacks);
draw_installed_mods(menu);
}
}
}
+5 -2
View File
@@ -1,6 +1,9 @@
#pragma once
namespace dingosdk::overlay {
struct SkateMenu;
// The MODS page: tab 0 lists installed mods, tab 1 custom Lua scripts.
void draw_modding_menu(SkateMenu& menu, int tab);
struct Model;
struct CallbacksV3;
// The MODS page: tab 0 lists installed mods (with Board Break's deck above them, when one is
// installed), tab 1 custom Lua scripts.
void draw_modding_menu(SkateMenu& menu, int tab, const Model& model, const CallbacksV3& callbacks);
}
+1 -1
View File
@@ -435,7 +435,7 @@ void draw_skate_menu(SkateMenu& menu, const Model& model, const CallbacksV3& cal
menu.mods_tab = 0;
ImGui::PushID(menu.mods_tab);
ImGui::BeginChild("mods-tab", ImVec2(0, page_body_height(menu)));
draw_modding_menu(menu, menu.mods_tab);
draw_modding_menu(menu, menu.mods_tab, model, callbacks);
ImGui::EndChild();
ImGui::PopID();
break;
@@ -38,6 +38,9 @@ bool primary_button(SkateMenu& menu, const char* label, bool enabled = true);
float page_body_height(const SkateMenu& menu);
// Runs a console command and shows its reply as feedback.
void send_console(SkateMenu& menu, const CallbacksV3& callbacks, const std::string& command);
// Board Break's decks, a button for each (modding_menu.cpp): on the Mods page and under the
// feature's switch on the Skater page. Draws nothing while no deck is installed.
void board_break_deck_card(SkateMenu& menu, const Model& model, const CallbacksV3& callbacks);
void debug_request(SkateMenu& menu, const CallbacksV3& callbacks, DebugRequest request);
// HOST shows the host form until a session exists, then that session (SESSION).
inline constexpr int multiplayer_session_tab = 1, multiplayer_voice_tab = 2, multiplayer_bans_tab = 3;
@@ -185,6 +185,7 @@ void movement_controls(SkateMenu& menu, const Model& model, const CallbacksV3& c
board_break, model.board_break.available && callbacks.queue_console_command))
send_console(menu, callbacks, board_break ? "boardbreak 1" : "boardbreak 0");
end_card();
board_break_deck_card(menu, model, callbacks);
begin_card(menu, "boosts", "BOOSTS", "Buttons are set in Settings > Controls");
ImGui::BeginDisabled(!callbacks.queue_debug);