Files
AyeZeeBB 101e1af6e4 Official, staff and friends in the server browser; server tokens; Linux self-update
Server browser
- Official servers (the backend's "official_servers") sort first and are
  drawn in ReSkate blue
- Servers Steam friends are skating in sort next, drawn in green with the
  friends' names; searching a friend's name finds their server
- A player in a session they joined can join another server straight from
  the list; hosts still end their session first, and a Steam friend's join
  offer never replaces a session
- Joining a dedicated server from the list records it as the player's
  public session, so friends see it and the row shows HERE
- Blocked servers (the backend's "blocked_servers") are hidden and cannot
  be joined, by code either
- When the backend sets "server_tokens_required", dedicated servers that
  signed in without a login token are hidden, except on the player's own
  network
- A search that gets no internet list from Steam keeps the last list for
  two minutes, and waits six seconds before treating it as empty
Roles
- New Staff tag from the backend's "staff" list: green name, badge and
  item animation, ranked after Dev
- Staff can pick a rainbow for their items on the Special page; it is sent
  as a solid colour older builds can show, so the protocol is unchanged
Dedicated server
- steam_token: sign in with a Steam game server login token and keep the
  same Steam ID every start; empty signs in anonymously as before
- A listed server without a token says so at startup, and when the token
  rule starts or stops hiding it
- Linux servers update themselves from the release's "server_linux"
  tarball, with the machine's curl and tar, pinned by SHA-256, and carry
  on in the same process
Launcher
- Only create the MiniDumpWriteDump API-set shim under Wine/Proton
2026-10-07 00:19:56 -05:00

160 lines
8.7 KiB
C++

#pragma once
#include <imgui.h>
#include <algorithm>
#include <array>
#include <cmath>
#include <vector>
// skate.'s own menu look (HUB screen), shared by the launcher and the in-game
// overlay. Colours are sampled from the game; sizes take the caller's UI scale.
namespace dingosdk::skate_theme {
inline constexpr ImU32 tile = IM_COL32(26, 26, 26, 240);
inline constexpr ImU32 tile_grey = IM_COL32(45, 45, 47, 240);
inline constexpr ImU32 tile_light = IM_COL32(61, 62, 66, 240);
inline constexpr ImU32 blue = IM_COL32(1, 131, 255, 255);
inline constexpr ImU32 blue_hover = IM_COL32(40, 152, 255, 255);
inline constexpr ImU32 blue_active = IM_COL32(0, 110, 215, 255);
// The ReSkate team's own servers: the logo's blue, a server tile tinted with it, and their name on it.
inline constexpr ImU32 official = IM_COL32(0, 145, 255, 255);
inline constexpr ImU32 official_tile = IM_COL32(8, 44, 82, 240);
inline constexpr ImU32 official_text = IM_COL32(110, 190, 255, 255);
// A server Steam friends are skating in: a tile tinted green, and its name on it.
inline constexpr ImU32 friends_tile = IM_COL32(14, 58, 32, 240);
inline constexpr ImU32 friends_text = IM_COL32(120, 226, 156, 255);
inline constexpr ImU32 black = IM_COL32(0, 0, 0, 255);
inline constexpr ImU32 white = IM_COL32(245, 245, 245, 255);
inline constexpr ImU32 grey_text = IM_COL32(150, 152, 158, 255);
inline constexpr ImU32 bar = IM_COL32(255, 177, 11, 255);
inline constexpr ImU32 bar_stripe = IM_COL32(255, 140, 0, 255);
inline constexpr ImU32 good = IM_COL32(46, 184, 92, 255);
inline constexpr ImU32 danger = IM_COL32(255, 92, 92, 255);
inline constexpr ImU32 warning = IM_COL32(255, 177, 11, 255);
inline constexpr ImU32 avatar = IM_COL32(240, 122, 30, 255);
inline float noise(unsigned n) {
n = (n << 13) ^ n;
return static_cast<float>((n * (n * n * 15731u + 789221u) + 1376312589u) & 0x7fffffffu) / 2147483647.0f;
}
// Rotates everything drawn since `start` (a VtxBuffer size) around `centre`.
inline void rotate_since(ImDrawList* draw, int start, float degrees, ImVec2 centre) {
const float radians = degrees * 0.01745329f, s = std::sin(radians), c = std::cos(radians);
for (int index = start; index < draw->VtxBuffer.Size; ++index) {
auto& p = draw->VtxBuffer[index].pos;
const ImVec2 d(p.x - centre.x, p.y - centre.y);
p = ImVec2(centre.x + d.x * c - d.y * s, centre.y + d.x * s + d.y * c);
}
}
// Tiles in skate.'s menus have slightly rough, hand-cut edges.
inline void rough_rect(ImDrawList* draw, ImVec2 a, ImVec2 b, ImU32 colour, unsigned seed, float scale = 1) {
// Reused between calls: menus draw many tiles every frame.
thread_local std::vector<ImVec2> points;
points.clear();
const float step = 10 * scale, wobble = 1.4f * scale;
unsigned n = seed * 977u;
const auto jitter = [&] { return (noise(++n) - 0.5f) * wobble; };
for (float x = a.x; x < b.x; x += step) points.emplace_back(x, a.y + jitter());
for (float y = a.y; y < b.y; y += step) points.emplace_back(b.x + jitter(), y);
for (float x = b.x; x > a.x; x -= step) points.emplace_back(x, b.y + jitter());
for (float y = b.y; y > a.y; y -= step) points.emplace_back(a.x + jitter(), y);
draw->AddConvexPolyFilled(points.data(), static_cast<int>(points.size()), colour);
}
// The dry-brush scribble skate. draws over locked tiles.
inline void scribble(ImDrawList* draw, ImVec2 a, ImVec2 b, unsigned seed, float scale = 1) {
for (int stroke = 0; stroke < 3; ++stroke) {
std::array<ImVec2, 15> points;
const float y = a.y + (b.y - a.y) * (0.45f + 0.12f * static_cast<float>(stroke));
for (int i = 0; i < static_cast<int>(points.size()); ++i) {
const float t = static_cast<float>(i) / 14.0f;
const float swing = std::min(16 * scale, (b.y - a.y) * 0.3f);
points[i] = ImVec2(a.x + (b.x - a.x) * (0.08f + 0.84f * t),
y + (i % 2 ? -1.0f : 1.0f) * swing * (0.6f + 0.4f * noise(seed * 31u + static_cast<unsigned>(i + stroke * 17))));
}
draw->AddPolyline(points.data(), static_cast<int>(points.size()), IM_COL32(0, 0, 0, 72), 0, 5 * scale);
}
}
enum class Icon { check, busy, fail, warning };
// Round status icons, like the ticks on the HUB's BOUNTIES tile.
inline void status_icon(ImDrawList* draw, ImVec2 centre, Icon icon, float time, float scale = 1) {
const float r = 9 * scale;
switch (icon) {
case Icon::check: {
draw->AddCircleFilled(centre, r, good);
const ImVec2 tick[]{ImVec2(centre.x - r * 0.45f, centre.y), ImVec2(centre.x - r * 0.1f, centre.y + r * 0.38f),
ImVec2(centre.x + r * 0.5f, centre.y - r * 0.38f)};
draw->AddPolyline(tick, 3, white, 0, 2.2f * scale);
break;
}
case Icon::busy: {
draw->AddCircle(centre, r - scale, white, 0, 2 * scale);
const float angle = time * 3.0f;
draw->AddLine(centre, ImVec2(centre.x + std::cos(angle) * r * 0.6f, centre.y + std::sin(angle) * r * 0.6f), white, 2 * scale);
draw->AddLine(centre, ImVec2(centre.x, centre.y - r * 0.5f), white, 2 * scale);
break;
}
case Icon::fail:
draw->AddCircleFilled(centre, r, danger);
draw->AddLine(ImVec2(centre.x - r * 0.4f, centre.y - r * 0.4f), ImVec2(centre.x + r * 0.4f, centre.y + r * 0.4f), white, 2.2f * scale);
draw->AddLine(ImVec2(centre.x - r * 0.4f, centre.y + r * 0.4f), ImVec2(centre.x + r * 0.4f, centre.y - r * 0.4f), white, 2.2f * scale);
break;
case Icon::warning:
draw->AddCircleFilled(centre, r, warning);
draw->AddLine(ImVec2(centre.x, centre.y - r * 0.5f), ImVec2(centre.x, centre.y + r * 0.1f), black, 2.4f * scale);
draw->AddCircleFilled(ImVec2(centre.x, centre.y + r * 0.45f), 1.4f * scale, black);
break;
}
}
// Yellow striped meter with a black outline, like the RIP SCORE bar.
inline void striped_bar(ImDrawList* draw, ImVec2 a, ImVec2 b, float fill, float time, float scale = 1) {
draw->AddRectFilled(a, b, IM_COL32(10, 10, 10, 255));
const float end = a.x + (b.x - a.x) * std::clamp(fill, 0.0f, 1.0f);
if (end > a.x) {
draw->AddRectFilled(a, ImVec2(end, b.y), bar);
draw->PushClipRect(a, ImVec2(end, b.y), true);
const float h = b.y - a.y, spacing = 9 * scale, offset = std::fmod(time * 12 * scale, spacing);
for (float x = a.x - h - spacing + offset; x < end; x += spacing)
draw->AddQuadFilled(ImVec2(x, b.y), ImVec2(x + 3.5f * scale, b.y), ImVec2(x + h + 3.5f * scale, a.y),
ImVec2(x + h, a.y), bar_stripe);
draw->PopClipRect();
}
draw->AddRect(ImVec2(a.x - scale, a.y - scale), ImVec2(b.x + scale, b.y + scale), black, 0, 0, 2 * scale);
}
// ImGui colours for flat, square, high-contrast widgets. Returns the count pushed.
inline int push_widget_colours() {
const std::pair<ImGuiCol, ImU32> colours[]{
{ImGuiCol_Text, white}, {ImGuiCol_TextDisabled, grey_text},
{ImGuiCol_WindowBg, IM_COL32(18, 18, 19, 246)}, {ImGuiCol_ChildBg, IM_COL32(0, 0, 0, 0)},
{ImGuiCol_PopupBg, IM_COL32(26, 26, 26, 252)}, {ImGuiCol_Border, IM_COL32(61, 62, 66, 255)},
{ImGuiCol_FrameBg, tile_grey}, {ImGuiCol_FrameBgHovered, tile_light}, {ImGuiCol_FrameBgActive, IM_COL32(72, 73, 78, 255)},
{ImGuiCol_Button, tile_grey}, {ImGuiCol_ButtonHovered, tile_light}, {ImGuiCol_ButtonActive, blue_active},
{ImGuiCol_Header, tile_light}, {ImGuiCol_HeaderHovered, IM_COL32(72, 73, 78, 255)}, {ImGuiCol_HeaderActive, blue_active},
{ImGuiCol_CheckMark, blue}, {ImGuiCol_SliderGrab, blue}, {ImGuiCol_SliderGrabActive, white},
{ImGuiCol_TextSelectedBg, IM_COL32(1, 131, 255, 110)}, {ImGuiCol_NavCursor, blue},
{ImGuiCol_TableHeaderBg, tile_grey}, {ImGuiCol_TableRowBg, IM_COL32(0, 0, 0, 0)},
{ImGuiCol_TableRowBgAlt, IM_COL32(255, 255, 255, 9)}, {ImGuiCol_TableBorderLight, IM_COL32(52, 53, 56, 255)},
{ImGuiCol_TableBorderStrong, IM_COL32(61, 62, 66, 255)}, {ImGuiCol_Separator, IM_COL32(61, 62, 66, 255)},
{ImGuiCol_ScrollbarBg, IM_COL32(0, 0, 0, 0)}, {ImGuiCol_ScrollbarGrab, tile_light},
{ImGuiCol_ScrollbarGrabHovered, IM_COL32(104, 108, 112, 255)}, {ImGuiCol_ScrollbarGrabActive, blue}};
for (const auto& [id, colour] : colours) ImGui::PushStyleColor(id, colour);
return static_cast<int>(std::size(colours));
}
// Blue button with black text: the colours of skate.'s selected tile.
inline void push_primary_button() {
ImGui::PushStyleColor(ImGuiCol_Button, blue);
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, blue_hover);
ImGui::PushStyleColor(ImGuiCol_ButtonActive, blue_active);
ImGui::PushStyleColor(ImGuiCol_Text, black);
}
inline void pop_primary_button() { ImGui::PopStyleColor(4); }
} // namespace dingosdk::skate_theme