mirror of
https://github.com/Swordpdf/ProsperoEden.git
synced 2026-10-19 04:09:47 -05:00
Public rooms: the project's own rooms first, a scrolling list, L1/R1 a page at a time (#217)
* Public rooms: the project's own rooms first, a scrolling list, and L1/R1 a page at a time * Launcher preview: nlohmann/json from the prepared Eden source
This commit is contained in:
1 parent
2146a4ad2b
commit
beab231e15
7 files changed
+57
-13
No files matched your search
@@ -83,6 +83,7 @@ RoomDirectory ParseRoomDirectory(std::string_view body, unsigned protocol) {
|
||||
if (!room.port || !room.capacity) throw std::runtime_error("invalid room limits");
|
||||
room.password = row.at("hasPassword").get<bool>();
|
||||
room.compatible = Number(row.at("netVersion"), UINT32_MAX) == protocol;
|
||||
room.featured = FeaturedRoom(room.name, room.port);
|
||||
if (row.contains("players")) {
|
||||
if (!row.at("players").is_array() || row.at("players").size() > 65535)
|
||||
throw std::runtime_error("invalid player list");
|
||||
@@ -93,6 +94,9 @@ RoomDirectory ParseRoomDirectory(std::string_view body, unsigned protocol) {
|
||||
}
|
||||
if (!rows.empty() && result.rooms.empty()) result.error = "No usable rooms were returned.";
|
||||
std::stable_sort(result.rooms.begin(), result.rooms.end(), [](const auto& a, const auto& b) {
|
||||
// The project's own rooms first, in the order of their ports.
|
||||
if (a.featured != b.featured) return a.featured > b.featured;
|
||||
if (a.featured && a.port != b.port) return a.port < b.port;
|
||||
if (a.compatible != b.compatible) return a.compatible > b.compatible;
|
||||
if (a.players != b.players) return a.players > b.players; // where people are, first
|
||||
return a.name < b.name;
|
||||
|
||||
@@ -12,6 +12,7 @@ struct PublicRoom {
|
||||
std::uint16_t port{};
|
||||
unsigned players{}, capacity{};
|
||||
bool password{}, compatible{};
|
||||
bool featured{}; // one of the project's own rooms (FeaturedRoom)
|
||||
};
|
||||
struct RoomDirectory {
|
||||
std::vector<PublicRoom> rooms;
|
||||
@@ -19,6 +20,14 @@ struct RoomDirectory {
|
||||
unsigned rejected{};
|
||||
};
|
||||
inline constexpr std::size_t MaxDirectoryBytes = 2 * 1024 * 1024;
|
||||
// The project's own rooms come first in Public rooms, in the order of their ports. They are
|
||||
// known from the public list itself: a name that starts with the project's and one of its ports
|
||||
// (a copied name on another port is an ordinary room).
|
||||
inline constexpr std::string_view FeaturedRoomName = "BlackBearReloaded ";
|
||||
inline constexpr std::uint16_t FeaturedRoomFirstPort = 24001, FeaturedRoomLastPort = 24005;
|
||||
inline bool FeaturedRoom(std::string_view name, std::uint16_t port) {
|
||||
return name.starts_with(FeaturedRoomName) && port >= FeaturedRoomFirstPort && port <= FeaturedRoomLastPort;
|
||||
}
|
||||
RoomDirectory ParseRoomDirectory(std::string_view body, unsigned protocol);
|
||||
RoomDirectory FetchRoomDirectory(unsigned protocol, const std::atomic<bool>& stop);
|
||||
}
|
||||
@@ -47,6 +47,19 @@ int main(int argc, char** argv) {
|
||||
assert(sorted.rooms.size() == 3 && sorted.rooms[0].name == "Z busy" && sorted.rooms[0].players == 2);
|
||||
assert(sorted.rooms[1].name == "A quiet" && sorted.rooms[2].name == "A other protocol");
|
||||
}
|
||||
// The project's own rooms first, in port order, ahead of busier and earlier-named rooms; its name
|
||||
// on another port is an ordinary room.
|
||||
{
|
||||
auto own_two = room, own_one = room, busy = room, copy = room;
|
||||
own_two["name"] = "BlackBearReloaded 2 (NA)"; own_two["port"] = 24002;
|
||||
own_one["name"] = "BlackBearReloaded 1 (NA)"; own_one["port"] = 24001;
|
||||
busy["name"] = "A busy"; busy["players"] = json::array({json::object(), json::object()});
|
||||
copy["name"] = "BlackBearReloaded 0 (NA)"; copy["port"] = 30000;
|
||||
const auto listed = ParseRoomDirectory(json{{"rooms", json::array({busy, own_two, copy, own_one})}}.dump(), 1);
|
||||
assert(listed.rooms.size() == 4 && listed.rooms[0].port == 24001 && listed.rooms[1].port == 24002);
|
||||
assert(listed.rooms[0].featured && listed.rooms[1].featured && !listed.rooms[2].featured);
|
||||
assert(listed.rooms[2].name == "A busy" && listed.rooms[3].port == 30000 && !listed.rooms[3].featured);
|
||||
}
|
||||
wrong = room; wrong["players"] = "bad"; assert(parse(wrong).rooms.empty());
|
||||
wrong = room; wrong["hasPassword"] = "false"; assert(parse(wrong).rooms.empty());
|
||||
assert(!ParseRoomDirectory("{", 1).error.empty());
|
||||
|
||||
@@ -364,6 +364,7 @@ void Launcher::update(float dt)
|
||||
option_rows_.update(dt);
|
||||
mapping_rows_.update(dt);
|
||||
menu_mapping_rows_.update(dt);
|
||||
room_rows_.update(dt);
|
||||
hud_rows_.update(dt);
|
||||
profile_rows_.update(dt);
|
||||
source_rows_.update(dt);
|
||||
|
||||
@@ -522,6 +522,7 @@ class Launcher
|
||||
bool mapping_for_game_ = false; // the mapping dialog edits the game's own mapping
|
||||
ListView mapping_rows_;
|
||||
ListView menu_mapping_rows_; // the menu keybinds dialog's rows
|
||||
ListView room_rows_; // Multiplayer > Public rooms: the rooms (room_row_ - 1 is the one chosen)
|
||||
std::vector<Profile> profiles_; // the people who play on this console
|
||||
std::string playing_; // the one games start as
|
||||
ListView profile_rows_;
|
||||
|
||||
@@ -81,10 +81,13 @@ void Launcher::press_multiplayer(Key key) {
|
||||
static_cast<int>(room_directory_.rooms.size()));
|
||||
cue(Cue::focus);
|
||||
}
|
||||
if (key == Key::left || key == Key::right) { // a page at a time: public lists are long
|
||||
room_row_ = std::clamp(room_row_ + (key == Key::right ? kVisibleRooms : -kVisibleRooms), 0,
|
||||
static_cast<int>(room_directory_.rooms.size()));
|
||||
cue(Cue::focus);
|
||||
// A page at a time: public lists are long. L1 and R1, as in the other long lists; Left and
|
||||
// Right too.
|
||||
if (key == Key::l1 || key == Key::r1 || key == Key::left || key == Key::right) {
|
||||
const bool on = key == Key::r1 || key == Key::right;
|
||||
const int next = std::clamp(room_row_ + (on ? kVisibleRooms : -kVisibleRooms), 0,
|
||||
static_cast<int>(room_directory_.rooms.size()));
|
||||
if (next != room_row_) { room_row_ = next; cue(Cue::focus); }
|
||||
}
|
||||
if (key != Key::cross) return;
|
||||
if (room_row_ > 0) {
|
||||
@@ -251,13 +254,21 @@ void Launcher::draw_public_rooms(Canvas& c, const Rect& pane) {
|
||||
text_fit(c, fill(tr("Nickname: {0}"), {room_connection_.nickname}), nick.x + 72, baseline(nick.y, 72, t.size_text),
|
||||
t.size_text, t.title, nick.w - 96);
|
||||
const int visible = room_status_.error.empty() ? kVisibleRooms : kVisibleRooms - 2;
|
||||
const int first = std::max(0, room_row_ - visible);
|
||||
for (int i = 0; i < visible && first + i < static_cast<int>(room_directory_.rooms.size()); ++i) {
|
||||
const auto& room = room_directory_.rooms[first + i];
|
||||
const float top = pane.y + 130.0f + i * 84.0f;
|
||||
// The rooms scroll under the nickname row as the cursor moves (room_row_ 0 is the nickname).
|
||||
const int rooms = static_cast<int>(room_directory_.rooms.size());
|
||||
room_rows_.wrap = false;
|
||||
room_rows_.pitch = 84.0f;
|
||||
room_rows_.visible = visible;
|
||||
if (room_rows_.count != rooms)
|
||||
room_rows_.reset(rooms, std::max(0, room_row_ - 1));
|
||||
else
|
||||
room_rows_.select(std::max(0, room_row_ - 1));
|
||||
for (int index = room_rows_.first_row(); index <= room_rows_.last_row() && index < rooms; ++index) {
|
||||
const auto& room = room_directory_.rooms[index];
|
||||
const float top = pane.y + 130.0f + index * 84.0f - room_rows_.scroll();
|
||||
const Rect row{pane.x, top, pane.w, 76};
|
||||
const bool on = room_row_ == first + i + 1;
|
||||
c.list.push_opacity(room.compatible ? 1.0f : 0.55f);
|
||||
const bool on = room_row_ == index + 1;
|
||||
c.list.push_opacity((room.compatible ? 1.0f : 0.55f) * room_rows_.row_alpha(index, 76.0f));
|
||||
plate(c, row_plate(), row, on ? 1.0f : 0.0f);
|
||||
if (room.password) icon(c, Icon::lock, row.x + 40, row.y + 38, 28, t.label);
|
||||
else icon(c, Icon::multiplayer, row.x + 40, row.y + 38, 28, on ? t.accent_pale : t.label);
|
||||
@@ -279,6 +290,7 @@ void Launcher::draw_public_rooms(Canvas& c, const Rect& pane) {
|
||||
text_fit(c, detail, row.x + 72, baseline(top + 40, 28, 18), 18, t.meta, room_w);
|
||||
c.list.pop_opacity();
|
||||
}
|
||||
scrollbar(c, room_rows_, pane.x + pane.w + 12.0f, pane.y + 130.0f, visible * 84.0f - 8.0f);
|
||||
std::string words;
|
||||
if (!message_.empty() && message_age_ < 6) words = message_;
|
||||
else if (!room_directory_.error.empty()) {
|
||||
@@ -381,7 +393,7 @@ void Launcher::draw_multiplayer(Canvas& c) {
|
||||
{
|
||||
kicker(c, tr("PUBLIC ROOMS"), kPanelLeft, baseline(80.0f, 30.0f, t.size_small), t.accent_pale, kPanelWidth);
|
||||
draw_public_rooms(c, {kPanelLeft, kPanelTop, kPanelWidth, 870.0f});
|
||||
static constexpr Hint kHints[]{{Pad::cross, TR("Select")}, {Pad::leftright, TR("Page")},
|
||||
static constexpr Hint kHints[]{{Pad::cross, TR("Select")}, {Pad::l1, TR("Page"), Pad::r1},
|
||||
{Pad::triangle, TR("Refresh")}, {Pad::circle, TR("Back")}};
|
||||
draw_footer(c, kHints, 4);
|
||||
}
|
||||
@@ -468,7 +480,7 @@ void Launcher::draw_multiplayer(Canvas& c) {
|
||||
if (room_page_ == RoomPage::public_rooms && room_status_.phase != Phase::Connecting &&
|
||||
room_status_.phase != Phase::Disconnecting) {
|
||||
draw_public_rooms(c, kPane);
|
||||
static constexpr Hint kHints[]{{Pad::cross, TR("Select")}, {Pad::leftright, TR("Page")},
|
||||
static constexpr Hint kHints[]{{Pad::cross, TR("Select")}, {Pad::l1, TR("Page"), Pad::r1},
|
||||
{Pad::triangle, TR("Refresh")}, {Pad::circle, TR("Back")}};
|
||||
draw_menu_hints(c, kHints, 4, layout::kLeft, layout::kFooter, t.copy, layout::kWidth);
|
||||
return;
|
||||
|
||||
@@ -23,6 +23,10 @@ harfbuzz=$(python3 -B "$root/tools/deps.py" path harfbuzz)
|
||||
harfbuzz=${harfbuzz%/harfbuzz.cc}
|
||||
# The UI kit's theme table: the launcher's themes start from its recipes (pe/ui/theme_kit.cpp).
|
||||
kit=$(python3 -B "$root/tools/deps.py" path ui-kit)/src
|
||||
# nlohmann/json (pe/ui/runtime_loading.hpp): the copy in the Eden source `make prepare` unpacks.
|
||||
json=$(ls -d "$(cat "$root/.local/headless-cache" 2>/dev/null)"/source/.cache/cpm/nlohmann_json/*/single_include \
|
||||
2>/dev/null | head -1)
|
||||
[[ -n $json ]] || { echo "nlohmann/json not found: run make prepare first" >&2; exit 1; }
|
||||
|
||||
sources=("$source_dir"/host/*.cpp "$source_dir"/pe/core/*.cpp "$source_dir"/pe/gfx/*.cpp "$source_dir"/pe/ui/*.cpp
|
||||
"$kit/ui/theme.cpp")
|
||||
@@ -39,7 +43,7 @@ for source in "${sources[@]}"; do
|
||||
warnings=(-Wall -Wextra)
|
||||
[[ $relative == pe/gfx/harfbuzz.cpp ]] && warnings=(-w)
|
||||
"$cxx" -std=c++20 -O2 "${warnings[@]}" -DGL_GLEXT_PROTOTYPES=1 -I"$source_dir" \
|
||||
-I"$source_dir/host" -I"$root/third_party/lua" -I"$root/tools/launcher/stb" -I"$root/third_party" -I"$harfbuzz" -I"$kit" \
|
||||
-I"$source_dir/host" -I"$root/third_party/lua" -I"$root/tools/launcher/stb" -I"$root/third_party" -I"$harfbuzz" -I"$kit" -I"$json" \
|
||||
-c "$source" -o "$object" &
|
||||
pids+=($!)
|
||||
if [[ ${#pids[@]} -ge ${HOST_BUILD_JOBS:-2} ]]; then
|
||||
|
||||
Reference in new issue
Block a user