mirror of
https://github.com/Dingo-Shenanigans/ReSkate.git
synced 2026-10-21 08:26:43 +02:00
An FPV drone flown from the free camera: real quad physics (thrust, gravity, drag, Betaflight "Actual" rates), collisions with the world, and a goggle-style display. Open it with `dronemenu` (or the drone window's Start flying). Flying - Keyboard and mouse, gamepads, or USB RC radios (EdgeTX etc. in Joystick mode). "Set up controller" learns each stick and switch by asking for one at a time, reversed axes and springing gamepad throttles included. While a controller flies the drone, the game is kept off it, as freecam's controller mode does. - Presets (Tinywhoop, Toothpick, 5 inch), arm/disarm, back to takeoff, and every setting live through `dronetune`, saved in the profile. - Optional range: the picture breaks up and is lost with distance from the takeoff point; past it, failsafe cuts the motors. - OSD, on-screen sticks, an analogue video look, synthesised motor sound. - Goggles: the pilot's skater wears eyewear from the game's catalog while flying, and gets their own back on landing (Road Rash aware). - Start flying hides the game UI, Stop flying brings it back. Multiplayer - New packet `drone` (36): where a player's drone is and which way it faces, 15 times a second, unreliable, relayed like effects. Markers glide between updates. Protocol version 52. - FLYING DRONE above the name of a player who is flying. - Players choose whether to share their drone and whether to see others'. - Dedicated servers: `sync_drones` (players section) and `drones on|off`. Off: no drone packets are relayed, and players are told in the roster. Changes to existing code - `flyspeed` takes any speed from 0.6 to 1500, not only the seven listed (the drone's presets and top speed slider use speeds of their own). - Overlay: begin/end_input_access, so the drone's controller read sees the device while the game is kept off it. - Profile: local_surface_hit, the park editor's ray from one point to another, for the drone's collisions. Tests: Skater/Test/drone_radio_tests (controller setup), and the drone packet and roster rule in Multiplayer/Test/protocol_tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MArBL6UbYZPsp9rMnq2Ks5
72 lines
5.3 KiB
C++
72 lines
5.3 KiB
C++
// Guided controller setup (drone_radio.h: Calibration): which axis each stick is on, which way
|
|
// round, and the arm and reset switches, from a simulated radio.
|
|
#include "Extension/Skater/drone_radio.h"
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
using namespace dingosdk::drone::radio;
|
|
using Step = Calibration::Step;
|
|
static int fails = 0;
|
|
#define CHECK(c, msg) do { if (!(c)) { std::printf("FAIL: %s\n", msg); ++fails; } } while (0)
|
|
// Hold axes for `t` seconds at 60 fps.
|
|
static void hold(Calibration& c, std::array<float,6> a, std::uint32_t b, float t) { for (float s = 0; s < t; s += 1.f/60) c.update(a, b, 1.f/60); }
|
|
int main() {
|
|
// A Mode 2 EdgeTX radio in AETR order: roll 1, pitch 2, throttle 3, yaw 4; switches on 5 and 6.
|
|
{ Calibration c; c.start(); CHECK(c.step()==Step::rest, "starts at rest");
|
|
std::array<float,6> rest{0,0,-1,0,-1,-1};
|
|
c.capture_rest(rest, 0); CHECK(c.step()==Step::throttle, "throttle after rest");
|
|
auto a = rest; a[2] = 1; hold(c, a, 0, 0.1f); CHECK(c.step()==Step::throttle, "short push is not enough");
|
|
hold(c, a, 0, 0.4f); CHECK(c.waiting_for_return(), "waits for throttle down");
|
|
a[0] = 0.3f; hold(c, a, 0, 0.5f); // a small wobble elsewhere is ignored
|
|
hold(c, rest, 0, 0.1f); CHECK(c.step()==Step::yaw, "yaw next");
|
|
a = rest; a[3] = 1; hold(c, a, 0, 0.4f); hold(c, rest, 0, 0.1f); CHECK(c.step()==Step::pitch, "pitch next");
|
|
a = rest; a[1] = 1; hold(c, a, 0, 0.4f); hold(c, rest, 0, 0.1f);
|
|
a = rest; a[0] = 1; hold(c, a, 0, 0.4f); hold(c, rest, 0, 0.1f); CHECK(c.step()==Step::arm, "arm next");
|
|
a = rest; a[4] = 1; hold(c, a, 0, 0.4f); CHECK(c.step()==Step::reset, "arm stays on, reset next");
|
|
auto b = a; b[5] = 1; hold(c, b, 0, 0.4f); CHECK(c.step()==Step::reset && c.waiting_for_return(), "reset must be let go");
|
|
hold(c, a, 0, 0.1f); CHECK(c.step()==Step::done, "done");
|
|
auto m = c.mapping(); CHECK(m==(std::array<int,4>{1,2,3,4}), "AETR mapping");
|
|
CHECK(c.arm_axis()==5 && c.reset_axis()==6, "switch axes");
|
|
CHECK(pick_axis(a, c.arm_axis()) > 0.5f, "armed side reads high");
|
|
}
|
|
// Shuffled and reversed: throttle on 1 reversed, yaw 2, pitch 4 reversed, roll 3; buttons for switches.
|
|
{ Calibration c; c.start(); std::array<float,6> rest{1,0,0,0,0,0}; c.capture_rest(rest, 0);
|
|
auto a = rest; a[0] = -1; hold(c, a, 0, 0.4f); hold(c, rest, 0, 0.1f);
|
|
a = rest; a[1] = 0.9f; hold(c, a, 0, 0.4f); hold(c, rest, 0, 0.1f);
|
|
a = rest; a[3] = -0.95f; hold(c, a, 0, 0.4f); hold(c, rest, 0, 0.1f);
|
|
a = rest; a[2] = 1; hold(c, a, 0, 0.4f); hold(c, rest, 0, 0.1f);
|
|
auto m = c.mapping(); CHECK(m==(std::array<int,4>{3,-4,-1,2}), "shuffled mapping");
|
|
auto cmd = map_axes(std::array<float,6>{-1,0,0,0,0,0}, m); CHECK(cmd.throttle > 0.99f, "reversed throttle reads full when pushed");
|
|
cmd = map_axes(rest, m); CHECK(cmd.throttle < 0.01f, "reversed throttle reads zero at rest");
|
|
hold(c, rest, 1u<<6, 0.4f); CHECK(c.arm_button()==7 && c.step()==Step::reset, "arm button");
|
|
hold(c, rest, (1u<<6)|(1u<<1), 0.4f); CHECK(c.reset_button()==2, "reset button");
|
|
hold(c, rest, 1u<<6, 0.1f); CHECK(c.step()==Step::done, "done after reset released");
|
|
}
|
|
// Skips, and a button held at rest never counts.
|
|
{ Calibration c; c.start(); std::array<float,6> rest{0,0,-1,0,0,0}; c.capture_rest(rest, 1u);
|
|
for (int i : {2,3,1,0}) { auto a = rest; a[i] = 1.f; hold(c, a, 1u, 0.4f); hold(c, rest, 1u, 0.1f); }
|
|
hold(c, rest, 1u, 1.0f); CHECK(c.step()==Step::arm, "held-at-rest button ignored");
|
|
c.skip(); c.skip(); CHECK(c.step()==Step::done && c.arm_axis()==0 && c.arm_button()==0, "skipped switches");
|
|
c.cancel(); CHECK(!c.active() && c.step()==Step::idle, "cancel");
|
|
}
|
|
// An Xbox pad through Windows' joystick API: sticks rest in the middle, triggers on 5 and 6,
|
|
// and up on a stick reads negative. The throttle springs back, so the middle has to hover.
|
|
{ Calibration c; c.start(); std::array<float,6> rest{0,0,0,0,-1,-1}; c.capture_rest(rest, 0);
|
|
auto a = rest; a[1] = -1; hold(c, a, 0, 0.4f); hold(c, rest, 0, 0.1f); // throttle: left stick up
|
|
a = rest; a[0] = 1; hold(c, a, 0, 0.4f); hold(c, rest, 0, 0.1f); // yaw: left stick right
|
|
a = rest; a[3] = -1; hold(c, a, 0, 0.4f); hold(c, rest, 0, 0.1f); // pitch: right stick up
|
|
a = rest; a[2] = 1; hold(c, a, 0, 0.4f); hold(c, rest, 0, 0.1f); // roll: right stick right
|
|
c.skip(); c.skip();
|
|
CHECK(c.throttle_centred(), "a pad's throttle is found to spring back");
|
|
const auto m = c.mapping(); CHECK(m==(std::array<int,4>{3,-4,-2,1}), "pad mapping");
|
|
CHECK(std::abs(map_axes(rest, m, 0.12f, true, 0.35f).throttle - 0.35f) < 1e-4f, "middle of a spring throttle hovers");
|
|
a = rest; a[1] = -1; CHECK(map_axes(a, m, 0.12f, true, 0.35f).throttle > 0.999f, "stick up is full throttle");
|
|
a[1] = 1; CHECK(map_axes(a, m, 0.12f, true, 0.35f).throttle < 0.001f, "stick down is no throttle");
|
|
a[1] = -0.05f; CHECK(std::abs(map_axes(a, m, 0.12f, true, 0.35f).throttle - 0.35f) < 1e-4f, "drift inside the dead zone still hovers");
|
|
}
|
|
// A radio's throttle rests at the bottom: not a spring throttle.
|
|
{ Calibration c; c.start(); std::array<float,6> rest{0,0,-1,0,0,0}; c.capture_rest(rest, 0);
|
|
auto a = rest; a[2] = 1; hold(c, a, 0, 0.4f); CHECK(!c.throttle_centred(), "a radio throttle is not a spring throttle"); }
|
|
std::printf(fails ? "%d failure(s)\n" : "all calibration checks passed\n", fails);
|
|
return fails ? 1 : 0;
|
|
}
|