Lobby browser, names and passwords, The Good Stuff, smarter queues, and a balance pass

- Lobbies: a name (default "Lobby <code>"), Unlisted or Public, and an optional password. The Multiplayer page
  scrolls: a Browser of public lobbies (name, count, padlock, code, little player previews, Join with a password
  field when locked), Join by code, Create, then Settings. Errors show under whatever caused them. Links take
  ?pass= (and --pass), and Copy Link includes it. Protocol 11.
- Queue areas center on their host along the floor's flat stretch, squeezing their slots to fit, and move to a nearby
  surface when the host's is too short.
- An airblast steals an enemy's flail ball onto its own chain for a while. Flail balls reel themselves back after 15
  seconds. Doubler echoes of the Grimoire teleport move you too.
- The Good Stuff: a new trinket, the inverse of Diazepam (more spread). Spread is now a multiplier, and it widens or
  narrows the Vacuum's cone.
- Balance: Sword basic slash 25% smaller and charged slash half the size, Whip basic lash half the damage and reach,
  Recoil Coil recoil halved, Speedometer no longer speeds you up.
- Fixes: fashions survive coming back from a game, Quit shows only in the sandbox menu, and the chat log keeps above
  the menu's tip.
This commit is contained in:
shweet committed 2026-11-10 23:01:32 -05:00
1 parent af41b53717
commit 37560547d4
17 files changed
+666 -277

No files matched your search

+54 -5
View File
@@ -100,11 +100,15 @@ enum LeaveReason
LEAVE_KICKED
};
// A lobby: its members in the order they joined (the first hosting), and its rooms, the sandbox first.
// A lobby: its name, the password to join it (empty for none), whether the lobby list shows it, its members in the
// order they joined (the first hosting), and its rooms, the sandbox first.
struct Lobby
{
std::string name;
std::string password;
std::vector<int> members;
std::vector<int> rooms;
bool isPublic = false;
};
// What the network's threads hand the game loop: a member's connection opening or closing, one of their data channels
@@ -329,7 +333,8 @@ static void lobby_enter(int id, const std::string& code)
list += (list.empty() ? "" : ",") + std::string("{\"id\":") + std::to_string(other) +
",\"name\":" + json_quote(members[other].name) + "}";
send(id, "{\"type\":\"welcome\",\"id\":" + std::to_string(id) + ",\"code\":" + json_quote(code) +
",\"host\":" + std::to_string(lobby.members.front()) + ",\"members\":[" + list + "]}");
",\"name\":" + json_quote(lobby.name) + ",\"host\":" + std::to_string(lobby.members.front()) +
",\"members\":[" + list + "]}");
for (int other : lobby.members)
if (other != id)
send(other, "{\"type\":\"member_join\",\"id\":" + std::to_string(id) +
@@ -360,7 +365,35 @@ static void lobby_exit(int id, LeaveReason leave = LEAVE_QUIT, const std::string
",\"host\":" + std::to_string(lobby.members.front()) + reason + "}");
}
// The lobby messages, in JSON: making, joining and leaving lobbies, kicking, and keeping the connection busy.
// Tells a member about every public lobby: its code and name, how many are in it, whether it needs a password, and each
// player's color and fashion (as their fighter in its sandbox has them, or as they brought it while in a game), for
// little previews of them.
static void lobbies_send(int id)
{
std::string list;
for (auto& [code, lobby] : lobbies)
{
if (!lobby.isPublic || lobby.rooms.empty())
continue;
Sim& sandbox = rooms[lobby.rooms.front()].sim;
std::string players;
for (int member : lobby.members)
{
int fighter = sandbox.match.fighter_find(sandbox, member);
Profile& profile = members[member].profile;
int color = fighter >= 0 ? sandbox.match.colors[fighter] : profile.color;
int fashion = fighter >= 0 ? sandbox.match.fashions[fighter] : profile.fashion;
players += (players.empty() ? "" : ",") + std::string("{\"color\":") + std::to_string(color) +
",\"fashion\":" + std::to_string(fashion) + "}";
}
list += (list.empty() ? "" : ",") + std::string("{\"code\":") + json_quote(code) +
",\"name\":" + json_quote(lobby.name) + ",\"locked\":" + (lobby.password.empty() ? "false" : "true") +
",\"players\":[" + players + "]}";
}
send(id, "{\"type\":\"lobbies\",\"lobbies\":[" + list + "]}");
}
// The lobby messages, in JSON: listing, making, joining and leaving lobbies, kicking, and keeping the connection busy.
static void text_handle(int id, const std::string& text)
{
simdjson::dom::element message;
@@ -371,12 +404,14 @@ static void text_handle(int id, const std::string& text)
// Answered, so proxies see the connection busy both ways.
if (type == "ping")
return send(id, "{\"type\":\"pong\"}");
if (type == "create" || type == "join")
if (type == "list" || type == "create" || type == "join")
{
int64_t version = 0;
std::string_view name;
if (message["version"].get(version) || version != PROTOCOL_VERSION)
return error_send(id, "Different game version");
if (type == "list")
return lobbies_send(id);
if (!members[id].code.empty())
return error_send(id, "Already in a lobby");
members[id].name = name_clean(message["name"].get(name) ? "" : name);
@@ -385,7 +420,15 @@ static void text_handle(int id, const std::string& text)
std::string code = code_make();
if (code.empty())
return error_send(id, "No free lobby codes");
std::printf("Lobby %s opened (%zu open)\n", code.c_str(), lobbies.size() + 1);
// A lobby's name defaults to its code, and it starts unlisted unless asked otherwise.
std::string_view field;
bool isPublic = false;
Lobby& lobby = lobbies[code];
lobby.name = text_clean(message["lobby"].get(field) ? "" : field, LOBBY_NAME_MAX);
lobby.name = lobby.name.empty() ? "Lobby " + code : lobby.name;
lobby.password = text_clean(message["password"].get(field) ? "" : field, LOBBY_PASSWORD_MAX);
lobby.isPublic = !message["public"].get(isPublic) && isPublic;
std::printf("Lobby %s opened (%zu open)\n", code.c_str(), lobbies.size());
return lobby_enter(id, code);
}
std::string_view raw;
@@ -399,6 +442,11 @@ static void text_handle(int id, const std::string& text)
return error_send(id, "No lobby with that code");
if ((int)lobby->second.members.size() >= LOBBY_MEMBERS_MAX)
return error_send(id, "That lobby is full");
std::string_view password;
if (message["password"].get(password))
password = "";
if (!lobby->second.password.empty() && password != lobby->second.password)
return error_send(id, password.empty() ? LOBBY_PASSWORD_NEEDED : "Wrong password");
return lobby_enter(id, code);
}
if (type == "leave")
@@ -634,6 +682,7 @@ static void game_open(int sandboxSerial, int queuer)
members[players[k]].profile = profile_get(sandbox.sim.fighters[seats[k]]);
std::memcpy(members[players[k]].profile.name, lobby.names[seats[k]], sizeof(lobby.names[seats[k]]));
members[players[k]].profile.color = lobby.colors[seats[k]];
members[players[k]].profile.fashion = lobby.fashions[seats[k]];
}
int serial = room_open(sandbox.code);
Room& game = rooms[serial];