Football: every player picks their own team and position
Closes the gap left by the football commit. Members publish their picks as lobby member data (tm/ps) alongside their loadout, and the owner publishes the scenario (sc) so members know when the picks matter. The lobby room turns into a team sheet: each member row shows their team and position, and MY TEAM / MY POSITION buttons cycle the local pick and republish it live, so everyone watches the sides fill out before the host launches. The egg builder honors explicit picks and only fills gaps: - pilots who chose a team get it; the rest are spread across the host team and one other so the sides stay balanced - a team with no volunteer runner promotes its first UNPICKED member, never overriding someone who chose crusher or blocker - if two pilots on a team both claim runner, the first keeps it and the second lines up - colors stay derived: runner in the team runner color, everyone else in the team color Verified: a three-pilot egg puts the host on crusher in team color with an unpicked teammate promoted to runner in runner color, the other side gets its own runner, and the teams/pilots blocks group correctly; the lobby room cycles picks and repaints the roster; both scenarios still pass their single-player regressions. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+117
-7
@@ -45,6 +45,7 @@ namespace
|
||||
int gLastGoNonce = 0; // launches we already answered
|
||||
int gShownResultsNonce = 0; // score sheets we already displayed
|
||||
const char kResultsKey[] = "res";
|
||||
const char kScenarioKey[] = "sc";
|
||||
|
||||
// async call-result plumbing
|
||||
Logical gCallDone = False;
|
||||
@@ -120,6 +121,12 @@ namespace
|
||||
}
|
||||
}
|
||||
|
||||
Logical IsOwner()
|
||||
{
|
||||
return gInLobby &&
|
||||
SteamMatchmaking()->GetLobbyOwner(gLobby) == SteamUser()->GetSteamID();
|
||||
}
|
||||
|
||||
void PublishMemberData()
|
||||
{
|
||||
char value[64];
|
||||
@@ -140,12 +147,30 @@ namespace
|
||||
SteamMatchmaking()->SetLobbyMemberData(gLobby, "vh", vehicle);
|
||||
SteamMatchmaking()->SetLobbyMemberData(gLobby, "cl", color);
|
||||
SteamMatchmaking()->SetLobbyMemberData(gLobby, "bd", badge);
|
||||
|
||||
// football: this member's own team and position pick
|
||||
SteamMatchmaking()->SetLobbyMemberData(gLobby, "tm",
|
||||
RPL4FrontEnd_TeamKey(RPL4FrontEnd_GetTeamIndex()));
|
||||
SteamMatchmaking()->SetLobbyMemberData(gLobby, "ps",
|
||||
RPL4FrontEnd_PositionKey(RPL4FrontEnd_GetPositionIndex()));
|
||||
|
||||
// the owner also publishes the scenario, so members know
|
||||
// whether their team/position pick matters
|
||||
if (IsOwner())
|
||||
{
|
||||
SteamMatchmaking()->SetLobbyData(gLobby, kScenarioKey,
|
||||
RPL4FrontEnd_IsFootballSelected() ? "football" : "race");
|
||||
}
|
||||
}
|
||||
|
||||
Logical IsOwner()
|
||||
Logical FootballLobby()
|
||||
{
|
||||
return gInLobby &&
|
||||
SteamMatchmaking()->GetLobbyOwner(gLobby) == SteamUser()->GetSteamID();
|
||||
if (!gInLobby)
|
||||
{
|
||||
return False;
|
||||
}
|
||||
const char *scenario = SteamMatchmaking()->GetLobbyData(gLobby, kScenarioKey);
|
||||
return (scenario != NULL && strcmp(scenario, "football") == 0);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------
|
||||
@@ -162,6 +187,8 @@ namespace
|
||||
char vehicle[24];
|
||||
char color[16];
|
||||
char badge[24];
|
||||
char team[32]; // football pick
|
||||
char position[16];
|
||||
Logical published;
|
||||
};
|
||||
|
||||
@@ -196,6 +223,12 @@ namespace
|
||||
strncpy(member->badge,
|
||||
SteamMatchmaking()->GetLobbyMemberData(gLobby, member->id, "bd"),
|
||||
sizeof(member->badge) - 1);
|
||||
strncpy(member->team,
|
||||
SteamMatchmaking()->GetLobbyMemberData(gLobby, member->id, "tm"),
|
||||
sizeof(member->team) - 1);
|
||||
strncpy(member->position,
|
||||
SteamMatchmaking()->GetLobbyMemberData(gLobby, member->id, "ps"),
|
||||
sizeof(member->position) - 1);
|
||||
member->published =
|
||||
member->ip[0] != '\0' && member->consolePort > 0 && member->gamePort > 0;
|
||||
}
|
||||
@@ -241,6 +274,8 @@ namespace
|
||||
strncpy(pilot->vehicle, members[i].vehicle, sizeof(pilot->vehicle) - 1);
|
||||
strncpy(pilot->color, members[i].color, sizeof(pilot->color) - 1);
|
||||
strncpy(pilot->badge, members[i].badge, sizeof(pilot->badge) - 1);
|
||||
strncpy(pilot->team, members[i].team, sizeof(pilot->team) - 1);
|
||||
strncpy(pilot->position, members[i].position, sizeof(pilot->position) - 1);
|
||||
|
||||
if (pods[0] != '\0')
|
||||
{
|
||||
@@ -274,8 +309,11 @@ namespace
|
||||
HFONT titleFont;
|
||||
RECT launchRect; // owner only
|
||||
RECT leaveRect;
|
||||
RECT teamRect; // football: cycle my team
|
||||
RECT positionRect; // football: cycle my position
|
||||
Logical launchClicked;
|
||||
Logical leaveClicked;
|
||||
Logical pickChanged;
|
||||
Logical closed;
|
||||
MemberInfo members[kMaxLobbyMembers];
|
||||
int memberCount;
|
||||
@@ -303,8 +341,9 @@ namespace
|
||||
RECT title = client;
|
||||
title.top = client.bottom / 28;
|
||||
title.bottom = title.top + client.bottom / 10;
|
||||
DrawTextA(mem, "STEAM RACE LOBBY", -1, &title,
|
||||
DT_CENTER | DT_TOP | DT_SINGLELINE);
|
||||
DrawTextA(mem,
|
||||
FootballLobby() ? "STEAM FOOTBALL LOBBY" : "STEAM RACE LOBBY",
|
||||
-1, &title, DT_CENTER | DT_TOP | DT_SINGLELINE);
|
||||
|
||||
SelectObject(mem, room->textFont);
|
||||
int row_h = client.bottom / 16;
|
||||
@@ -328,7 +367,25 @@ namespace
|
||||
is_owner_row ? " [HOST]" : "");
|
||||
DrawTextA(mem, text, -1, &row, DT_LEFT | DT_VCENTER | DT_SINGLELINE);
|
||||
|
||||
if (member->vehicle[0] != '\0')
|
||||
if (FootballLobby())
|
||||
{
|
||||
// football: the roster is a team sheet
|
||||
if (member->team[0] != '\0')
|
||||
{
|
||||
const char *position_name = "?";
|
||||
for (int i = 0; i < RPL4FrontEnd_PositionCount(); ++i)
|
||||
{
|
||||
if (strcmp(member->position, RPL4FrontEnd_PositionKey(i)) == 0)
|
||||
{
|
||||
position_name = RPL4FrontEnd_PositionName(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
sprintf(text, "%s - %s", member->team, position_name);
|
||||
DrawTextA(mem, text, -1, &row, DT_RIGHT | DT_VCENTER | DT_SINGLELINE);
|
||||
}
|
||||
}
|
||||
else if (member->vehicle[0] != '\0')
|
||||
{
|
||||
sprintf(text, "%s / %s", member->vehicle, member->color);
|
||||
DrawTextA(mem, text, -1, &row, DT_RIGHT | DT_VCENTER | DT_SINGLELINE);
|
||||
@@ -347,11 +404,30 @@ namespace
|
||||
-1, &hint, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
|
||||
|
||||
HBRUSH frame = CreateSolidBrush(kGreenBright);
|
||||
|
||||
//
|
||||
// Football: my own team and position, click to cycle
|
||||
//
|
||||
if (FootballLobby())
|
||||
{
|
||||
FrameRect(mem, &room->teamRect, frame);
|
||||
SetTextColor(mem, kGreenBright);
|
||||
sprintf(text, "MY TEAM: %s",
|
||||
RPL4FrontEnd_TeamName(RPL4FrontEnd_GetTeamIndex()));
|
||||
DrawTextA(mem, text, -1, &room->teamRect,
|
||||
DT_CENTER | DT_VCENTER | DT_SINGLELINE);
|
||||
|
||||
FrameRect(mem, &room->positionRect, frame);
|
||||
sprintf(text, "MY POSITION: %s",
|
||||
RPL4FrontEnd_PositionName(RPL4FrontEnd_GetPositionIndex()));
|
||||
DrawTextA(mem, text, -1, &room->positionRect,
|
||||
DT_CENTER | DT_VCENTER | DT_SINGLELINE);
|
||||
}
|
||||
if (IsOwner())
|
||||
{
|
||||
FrameRect(mem, &room->launchRect, frame);
|
||||
SetTextColor(mem, kGreenBright);
|
||||
DrawTextA(mem, "L A U N C H R A C E", -1, &room->launchRect,
|
||||
DrawTextA(mem, "L A U N C H G A M E", -1, &room->launchRect,
|
||||
DT_CENTER | DT_VCENTER | DT_SINGLELINE);
|
||||
}
|
||||
FrameRect(mem, &room->leaveRect, frame);
|
||||
@@ -395,6 +471,18 @@ namespace
|
||||
{
|
||||
room->leaveClicked = True;
|
||||
}
|
||||
else if (FootballLobby() && PtInRect(&room->teamRect, point))
|
||||
{
|
||||
RPL4FrontEnd_SetTeamIndex(
|
||||
(RPL4FrontEnd_GetTeamIndex() + 1) % RPL4FrontEnd_TeamCount());
|
||||
room->pickChanged = True;
|
||||
}
|
||||
else if (FootballLobby() && PtInRect(&room->positionRect, point))
|
||||
{
|
||||
RPL4FrontEnd_SetPositionIndex(
|
||||
(RPL4FrontEnd_GetPositionIndex() + 1) % RPL4FrontEnd_PositionCount());
|
||||
room->pickChanged = True;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
@@ -465,6 +553,16 @@ namespace
|
||||
room.leaveRect.top = client.bottom - 3 * row_h;
|
||||
room.leaveRect.bottom = client.bottom - row_h;
|
||||
|
||||
// football pick buttons, above the launch button
|
||||
room.teamRect.left = (client.right - col_w) / 2;
|
||||
room.teamRect.right = room.teamRect.left + col_w;
|
||||
room.teamRect.top = client.bottom - 12 * row_h;
|
||||
room.teamRect.bottom = room.teamRect.top + 2 * row_h;
|
||||
room.positionRect.left = room.teamRect.left;
|
||||
room.positionRect.right = room.teamRect.right;
|
||||
room.positionRect.top = client.bottom - 9 * row_h;
|
||||
room.positionRect.bottom = room.positionRect.top + 2 * row_h;
|
||||
|
||||
gRoom = &room;
|
||||
PublishMemberData();
|
||||
|
||||
@@ -489,6 +587,18 @@ namespace
|
||||
outcome = LobbyRoomClosed;
|
||||
break;
|
||||
}
|
||||
//
|
||||
// A pick changed: republish so everyone's roster updates
|
||||
//
|
||||
if (room.pickChanged)
|
||||
{
|
||||
room.pickChanged = False;
|
||||
PublishMemberData();
|
||||
room.memberCount = CollectMembers(room.members);
|
||||
InvalidateRect(room.window, NULL, FALSE);
|
||||
RedrawWindow(room.window, NULL, NULL, RDW_UPDATENOW);
|
||||
}
|
||||
|
||||
if (room.leaveClicked)
|
||||
{
|
||||
SteamMatchmaking()->LeaveLobby(gLobby);
|
||||
|
||||
Reference in New Issue
Block a user