Skip to content

In a private match, hitting "Return to Lobby" on console will auto-group players into the same (new) social lobby#244

Open
Copilot wants to merge 7 commits intomainfrom
copilot/auto-group-social-lobby-rejoin
Open

In a private match, hitting "Return to Lobby" on console will auto-group players into the same (new) social lobby#244
Copilot wants to merge 7 commits intomainfrom
copilot/auto-group-social-lobby-rejoin

Conversation

Copy link
Contributor

Copilot AI commented Jan 4, 2026

When players exit a private Arena/Combat match via the "Social Lobby" button, they should automatically rejoin a new social lobby together if the match originated from one.

Implementation

Tracking origin

  • Added OriginSocialID field to MatchLabel and MatchSettings
  • lobbyCreate detects when private matches spawn from social lobbies and stores the origin ID

Auto-rejoin trigger

  • MatchLeave: Creates new social lobby when match empties
  • MatchTerminate: Creates new social lobby on match termination
  • Only applies to private Arena/Combat matches (excludes social lobbies)

Rejoin behavior

  • createSocialLobbyRejoin creates new social lobby with:
    • All match participants as 1-minute reservations
    • Same group ID to preserve party communication
    • Public social mode for seamless transition
// Match creation from social lobby now tracks origin
if (params.Mode == evr.ModeArenaPrivate || params.Mode == evr.ModeCombatPrivate) && 
   !params.CurrentMatchID.IsNil() {
    if currentLabel.Mode == evr.ModeSocialPublic {
        settings.OriginSocialID = &params.CurrentMatchID.UUID
    }
}

Files Changed

  • evr_match_label.go: Added OriginSocialID field
  • evr_match.go: Added rejoin logic in MatchLeave/MatchTerminate, implemented createSocialLobbyRejoin
  • evr_lobby_create.go: Origin tracking during match creation
  • evr_match_social_rejoin_test.go: Unit tests for new functionality
Original prompt

This section details on the original issue you should resolve

<issue_title>Auto-group social lobby rejoin after private match</issue_title>
<issue_description>Feature: When players exit a private match (triggered via the "Social Lobby" button on
the terminal), check if the match originated from a social lobby. If so, after the match
completes, automatically send all participants to the same new social lobby and create
one-minute reservations for each player in that lobby. If possible, ensure seamless
group transition so prior group communications persist within the new social lobby session.</issue_description>

Comments on the Issue (you are @copilot in this section)


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

Copilot AI and others added 4 commits January 4, 2026 16:01
- Added OriginSocialID field to MatchLabel to track social lobby origin
- Added OriginSocialID field to MatchSettings for initialization
- Modified lobbyCreate to detect and track when private matches are created from social lobbies
- Implemented createSocialLobbyRejoin function to create new social lobby with 1-minute reservations
- Updated MatchLeave to trigger auto-rejoin when match empties
- Updated MatchTerminate to trigger auto-rejoin on match termination
- Applied OriginSocialID from settings during match preparation

Co-authored-by: thesprockee <962164+thesprockee@users.noreply.github.com>
- Added missing evr package import to evr_lobby_create.go
- Fixed uuid.UUID handling in createSocialLobbyRejoin function
- Corrected LobbyGameServerAllocate parameter types

Co-authored-by: thesprockee <962164+thesprockee@users.noreply.github.com>
- Added unit tests for OriginSocialID in MatchLabel and MatchSettings
- Added tests for IsPrivateMatch helper method
- All tests pass successfully

Co-authored-by: thesprockee <962164+thesprockee@users.noreply.github.com>
- Fixed logic to explicitly exclude social lobbies and only handle Arena/Combat matches
- Added clarifying comments explaining that all participants rejoin together
- Improved readability of conditional checks

Co-authored-by: thesprockee <962164+thesprockee@users.noreply.github.com>
Copilot AI changed the title [WIP] Add auto-group rejoin feature for social lobby after private match Auto-rejoin social lobby after private match completion Jan 4, 2026
Copilot AI requested a review from thesprockee January 4, 2026 16:10
@thesprockee thesprockee marked this pull request as ready for review January 12, 2026 22:23
Copilot AI review requested due to automatic review settings January 12, 2026 22:23
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements an auto-rejoin feature that sends players back to a new social lobby after they complete a private Arena or Combat match that originated from a social lobby.

Changes:

  • Added origin tracking via OriginSocialID field in match metadata
  • Implemented auto-rejoin logic in MatchLeave and MatchTerminate handlers
  • Created createSocialLobbyRejoin() function to spawn new social lobbies with 1-minute player reservations

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 6 comments.

File Description
evr_match_social_rejoin_test.go Basic unit tests for new OriginSocialID field and IsPrivateMatch() helper
evr_match_label.go Added OriginSocialID field to track social lobby origin
evr_match.go Implemented rejoin logic in match lifecycle hooks and new createSocialLobbyRejoin() helper
evr_lobby_create.go Added origin detection when private matches are created from social lobbies

Comment on lines +743 to +752
// When a private match empties out, trigger auto-rejoin to social lobby if it originated from one
if state.OriginSocialID != nil {
leavingUserIDs := make([]string, 0, len(presences))
for _, p := range presences {
leavingUserIDs = append(leavingUserIDs, p.GetUserId())
}
if err := createSocialLobbyRejoin(ctx, logger, nk, state, leavingUserIDs); err != nil {
logger.WithField("error", err).Warn("Failed to create social lobby for auto-rejoin")
}
}
Copy link

Copilot AI Jan 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This logic has a critical flaw. When MatchLeave calls createSocialLobbyRejoin on line 749, the state.presenceMap is already empty (as checked on line 738). However, createSocialLobbyRejoin attempts to gather participants from state.presenceMap at lines 1607-1612, which will find no players. This means no participants will be added to the new social lobby, defeating the purpose of the auto-rejoin feature. The function should use the leavingUserIDs parameter to identify who needs rejoin reservations, not the empty presenceMap.

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +98
package server

import (
"testing"

"github.com/gofrs/uuid/v5"
"github.com/heroiclabs/nakama/v3/server/evr"
)

func TestMatchLabel_OriginSocialID(t *testing.T) {
// Test that OriginSocialID can be set and retrieved
originID := uuid.Must(uuid.NewV4())

label := &MatchLabel{
Mode: evr.ModeArenaPrivate,
LobbyType: PrivateLobby,
OriginSocialID: &originID,
}

if label.OriginSocialID == nil {
t.Fatal("OriginSocialID should not be nil")
}

if *label.OriginSocialID != originID {
t.Errorf("OriginSocialID = %v, want %v", *label.OriginSocialID, originID)
}
}

func TestMatchSettings_OriginSocialID(t *testing.T) {
// Test that OriginSocialID can be set in MatchSettings
originID := uuid.Must(uuid.NewV4())
groupID := uuid.Must(uuid.NewV4())

settings := &MatchSettings{
Mode: evr.ModeArenaPrivate,
Level: evr.LevelArena,
GroupID: groupID,
OriginSocialID: &originID,
}

if settings.OriginSocialID == nil {
t.Fatal("OriginSocialID should not be nil")
}

if *settings.OriginSocialID != originID {
t.Errorf("OriginSocialID = %v, want %v", *settings.OriginSocialID, originID)
}
}

func TestMatchLabel_IsPrivateMatch(t *testing.T) {
tests := []struct {
name string
mode evr.Symbol
want bool
}{
{
name: "Private Arena",
mode: evr.ModeArenaPrivate,
want: true,
},
{
name: "Private Combat",
mode: evr.ModeCombatPrivate,
want: true,
},
{
name: "Private Social",
mode: evr.ModeSocialPrivate,
want: true,
},
{
name: "Public Arena",
mode: evr.ModeArenaPublic,
want: false,
},
{
name: "Public Combat",
mode: evr.ModeCombatPublic,
want: false,
},
{
name: "Public Social",
mode: evr.ModeSocialPublic,
want: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
label := &MatchLabel{
Mode: tt.mode,
}
if got := label.IsPrivateMatch(); got != tt.want {
t.Errorf("IsPrivateMatch() = %v, want %v", got, tt.want)
}
})
}
}
Copy link

Copilot AI Jan 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tests only verify that the OriginSocialID field can be set and retrieved, but don't test the actual auto-rejoin functionality. Consider adding integration tests that verify: 1) A social lobby ID is correctly tracked when a private match is created from a social lobby, 2) The createSocialLobbyRejoin function successfully creates a new social lobby with participant reservations when a private match ends, 3) The rejoin doesn't trigger for matches that didn't originate from social lobbies.

Copilot uses AI. Check for mistakes.
Comment on lines +1604 to +1606
// Gather all participants from this match that need to rejoin
// We include all participants, not just those leaving, to ensure the whole group rejoins together
participants := make([]*EvrMatchPresence, 0)
Copy link

Copilot AI Jan 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment says "We include all participants, not just those leaving, to ensure the whole group rejoins together." However, this is misleading because in the MatchLeave case (line 749), state.presenceMap is already empty when this function is called, so no participants will be found. Additionally, the comment suggests intentional behavior of including everyone, but the leavingUserIDs parameter exists yet is unused, creating confusion about the intended design.

Copilot uses AI. Check for mistakes.
Comment on lines +35 to +43
if currentLabel, err := MatchLabelByID(ctx, nk, params.CurrentMatchID); err == nil {
if currentLabel.Mode == evr.ModeSocialPublic || currentLabel.Mode == evr.ModeSocialPrivate {
// This private match originated from a social lobby
originID := params.CurrentMatchID.UUID
settings.OriginSocialID = &originID
logger.Info("Private match originated from social lobby",
zap.String("origin_social_id", originID.String()),
zap.String("new_mode", params.Mode.String()))
}
Copy link

Copilot AI Jan 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error check silently ignores failures when trying to fetch the current match label. If MatchLabelByID returns an error (e.g., match not found, network issue), the code continues without setting OriginSocialID. While this might be intentional to avoid breaking lobby creation, consider logging the error at least at debug level so it's easier to diagnose why origin tracking might not work in some cases.

Suggested change
if currentLabel, err := MatchLabelByID(ctx, nk, params.CurrentMatchID); err == nil {
if currentLabel.Mode == evr.ModeSocialPublic || currentLabel.Mode == evr.ModeSocialPrivate {
// This private match originated from a social lobby
originID := params.CurrentMatchID.UUID
settings.OriginSocialID = &originID
logger.Info("Private match originated from social lobby",
zap.String("origin_social_id", originID.String()),
zap.String("new_mode", params.Mode.String()))
}
currentLabel, err := MatchLabelByID(ctx, nk, params.CurrentMatchID)
if err != nil {
logger.Debug("Failed to fetch current match label for origin tracking",
zap.Error(err),
zap.String("current_match_id", params.CurrentMatchID.UUID.String()),
zap.String("mode", params.Mode.String()))
} else if currentLabel.Mode == evr.ModeSocialPublic || currentLabel.Mode == evr.ModeSocialPrivate {
// This private match originated from a social lobby
originID := params.CurrentMatchID.UUID
settings.OriginSocialID = &originID
logger.Info("Private match originated from social lobby",
zap.String("origin_social_id", originID.String()),
zap.String("new_mode", params.Mode.String()))

Copilot uses AI. Check for mistakes.
Comment on lines +1585 to +1590
if !state.IsPrivateMatch() {
return nil
}

// Exclude social lobbies - only handle Arena and Combat
if state.Mode == evr.ModeSocialPrivate || state.Mode == evr.ModeSocialPublic {
Copy link

Copilot AI Jan 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logic in this function is redundant. The IsPrivateMatch() method on line 1585 already includes social private lobbies (ModeSocialPrivate), but then lines 1590-1592 explicitly exclude them. This creates confusion. Consider simplifying by removing the IsPrivateMatch() check and only checking for the specific modes you want to handle: ModeArenaPrivate and ModeCombatPrivate.

Suggested change
if !state.IsPrivateMatch() {
return nil
}
// Exclude social lobbies - only handle Arena and Combat
if state.Mode == evr.ModeSocialPrivate || state.Mode == evr.ModeSocialPublic {
if state.Mode != evr.ModeArenaPrivate && state.Mode != evr.ModeCombatPrivate {
return nil
}
// Check if this match originated from a social lobby
if state.OriginSocialID == nil {

Copilot uses AI. Check for mistakes.
}

// createSocialLobbyRejoin creates a new social lobby for players exiting a private match that originated from a social lobby
func createSocialLobbyRejoin(ctx context.Context, logger runtime.Logger, nk runtime.NakamaModule, state *MatchLabel, leavingUserIDs []string) error {
Copy link

Copilot AI Jan 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The leavingUserIDs parameter is defined but never used in the function body. The function instead gathers all participants from state.presenceMap (lines 1606-1612). Either remove this unused parameter or use it to filter which participants should be included in the rejoin lobby.

Copilot uses AI. Check for mistakes.
@thesprockee thesprockee changed the title Auto-rejoin social lobby after private match completion In a private match, hitting "Return to Lobby" on console will auto-group players into the same (new) social lobby Jan 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

In a private match, hitting "Return to Lobby" on console will auto-group players into the same (new) social lobby

2 participants