Skip to content

Commit

Permalink
feat: Initial support for persistent gatherings/communities
Browse files Browse the repository at this point in the history
Implement methods that are needed for Mario Kart 7 communities to work.
Note that support for communities on MK7 is partial since the community
statistics don't load because legacy Ranking isn't implemented. Aside
from that, players can create communities and join others without
issues. Other games which use persistent gatherings may or may not work.

In order to support the `ParticipationCount`, we replace matchmake
session joins with a wrapper which checks if the session is attached to
a community, and if it is, it will increment the participation count of
the player in a new table named `community_participations`. The
`MatchmakeSessionCount` is handled more easily by checking the sessions
that belong to the corresponding community.

A new parameter is also added named `PersistentGatheringCreationMax`
with a default value of 4, as reported and tested on various games. This
allows game servers to change the maximum number of active persistent
gatherings that a player can create. For example, Mario Kart 7 supports
up to 8 persistent gatherings instead of the default of 4.

In Mario Kart 7 there is no limitation on the number of players that can
"join" to a community. That is because they don't really join to it but
they create matchmake sessions linked to the persistent gathering (in
fact, the `MaximumParticipants` parameter on persistent gatherings is
set to 0). Thus, the `participants` parameter is unused in communities
(at least on MK7) and we instead log community participations with a new
tracking table `tracking.participate_community`.

Some changes also had to be done in other places like participant
disconnection handling or gathering registrations in order to implement
persistent gatherings accurately.
  • Loading branch information
DaniElectra committed Jan 24, 2025
1 parent bcc53de commit 25c233f
Show file tree
Hide file tree
Showing 38 changed files with 1,367 additions and 53 deletions.
2 changes: 1 addition & 1 deletion globals/matchmaking_globals.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ type MatchmakingManager struct {
Endpoint *nex.PRUDPEndPoint
Mutex *sync.RWMutex
GetUserFriendPIDs func(pid uint32) []uint32
GetDetailedGatheringByID func(manager *MatchmakingManager, gatheringID uint32) (types.RVType, string, *nex.Error)
GetDetailedGatheringByID func(manager *MatchmakingManager, sourcePID uint64, gatheringID uint32) (types.RVType, string, *nex.Error)
}

// NewMatchmakingManager returns a new MatchmakingManager
Expand Down
30 changes: 30 additions & 0 deletions globals/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,36 @@ func CheckValidMatchmakeSession(matchmakeSession match_making_types.MatchmakeSes
return true
}

// CheckValidPersistentGathering checks if a PersistentGathering is valid
func CheckValidPersistentGathering(persistentGathering match_making_types.PersistentGathering) bool {
if !CheckValidGathering(persistentGathering.Gathering) {
return false
}

// * Only allow normal and password-protected community types
if persistentGathering.CommunityType != 0 && persistentGathering.CommunityType != 1 {
return false
}

// * All strings must have a length lower than 256
//
// TODO - Can the password actually be up to 256 characters?
if len(persistentGathering.Password) > 256 {
return false
}

if len(persistentGathering.Attribs) != 6 {
return false
}

// * All buffers must have a length lower than 512
if len(persistentGathering.ApplicationBuffer) > 512 {
return false
}

return true
}

// CanJoinMatchmakeSession checks if a PID is allowed to join a matchmake session
func CanJoinMatchmakeSession(manager *MatchmakingManager, pid types.PID, matchmakeSession match_making_types.MatchmakeSession) *nex.Error {
// TODO - Is this the right error?
Expand Down
9 changes: 8 additions & 1 deletion match-making/database/disconnect_participant.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,9 @@ func DisconnectParticipant(manager *common_globals.MatchmakingManager, connectio
}

// * If the gathering is a PersistentGathering and the gathering isn't set to leave when disconnecting, ignore and continue
if gatheringType == "PersistentGathering" && uint32(gathering.Flags) & match_making.GatheringFlags.PersistentGatheringLeaveParticipation == 0 {
//
// TODO - Is the match_making.GatheringFlags.PersistentGathering check correct here?
if uint32(gathering.Flags) & match_making.GatheringFlags.PersistentGathering != 0 || (gatheringType == "PersistentGathering" && uint32(gathering.Flags) & match_making.GatheringFlags.PersistentGatheringLeaveParticipation == 0) {
continue
}

Expand All @@ -64,6 +66,11 @@ func DisconnectParticipant(manager *common_globals.MatchmakingManager, connectio
continue
}

// * If the gathering is a persistent gathering and allows zero users, only remove the participant from the gathering
if uint32(gathering.Flags) & (match_making.GatheringFlags.PersistentGathering | match_making.GatheringFlags.PersistentGatheringAllowZeroUsers) != 0 {
continue
}

if len(participants) == 0 {
// * There are no more participants, so we only have to unregister the gathering
// * Since the participant is disconnecting, we don't send notification events
Expand Down
13 changes: 6 additions & 7 deletions match-making/database/end_gathering_participation.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (

// EndGatheringParticipation ends the participation of a connection within a gathering and performs any additional handling required
func EndGatheringParticipation(manager *common_globals.MatchmakingManager, gatheringID uint32, connection *nex.PRUDPConnection, message string) *nex.Error {
gathering, gatheringType, participants, _, nexError := FindGatheringByID(manager, gatheringID)
gathering, _, participants, _, nexError := FindGatheringByID(manager, gatheringID)
if nexError != nil {
return nexError
}
Expand All @@ -24,12 +24,6 @@ func EndGatheringParticipation(manager *common_globals.MatchmakingManager, gathe
return nex.NewError(nex.ResultCodes.RendezVous.NotParticipatedGathering, "change_error")
}

// * If the gathering is a PersistentGathering, only remove the participant from the gathering
if gatheringType == "PersistentGathering" {
_, nexError = RemoveParticipantFromGathering(manager, gatheringID, uint64(connection.PID()))
return nexError
}

newParticipants, nexError := RemoveParticipantFromGathering(manager, gatheringID, uint64(connection.PID()))
if nexError != nil {
return nexError
Expand All @@ -40,6 +34,11 @@ func EndGatheringParticipation(manager *common_globals.MatchmakingManager, gathe
return nexError
}

// * If the gathering is a persistent gathering and allows zero users, only remove the participant from the gathering
if uint32(gathering.Flags) & (match_making.GatheringFlags.PersistentGathering | match_making.GatheringFlags.PersistentGatheringAllowZeroUsers) != 0 {
return nil
}

if len(newParticipants) == 0 {
// * There are no more participants, so we just unregister the gathering
return UnregisterGathering(manager, connection.PID(), gatheringID)
Expand Down
2 changes: 1 addition & 1 deletion match-making/database/get_detailed_gathering_by_id.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
)

// GetDetailedGatheringByID returns a Gathering as an RVType by its gathering ID
func GetDetailedGatheringByID(manager *common_globals.MatchmakingManager, gatheringID uint32) (types.RVType, string, *nex.Error) {
func GetDetailedGatheringByID(manager *common_globals.MatchmakingManager, sourcePID uint64, gatheringID uint32) (types.RVType, string, *nex.Error) {
gathering, gatheringType, _, _, nexError := FindGatheringByID(manager, gatheringID)
if nexError != nil {
return nil, "", nexError
Expand Down
14 changes: 7 additions & 7 deletions match-making/database/register_gathering.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import (
match_making_types "github.com/PretendoNetwork/nex-protocols-go/v2/match-making/types"
)

// RegisterGathering registers a new gathering on the databse. No participants are added
func RegisterGathering(manager *common_globals.MatchmakingManager, pid types.PID, gathering *match_making_types.Gathering, gatheringType string) (types.DateTime, *nex.Error) {
// RegisterGathering registers a new gathering on the database. No participants are added
func RegisterGathering(manager *common_globals.MatchmakingManager, ownerPID types.PID, hostPID types.PID, gathering *match_making_types.Gathering, gatheringType string) (types.DateTime, *nex.Error) {
startedTime := types.NewDateTime(0).Now()
var gatheringID uint32

Expand Down Expand Up @@ -38,8 +38,8 @@ func RegisterGathering(manager *common_globals.MatchmakingManager, pid types.PID
$10,
$11
) RETURNING id`,
pid,
pid,
ownerPID,
hostPID,
gathering.MinimumParticipants,
gathering.MaximumParticipants,
gathering.ParticipationPolicy,
Expand All @@ -56,13 +56,13 @@ func RegisterGathering(manager *common_globals.MatchmakingManager, pid types.PID

gathering.ID = types.NewUInt32(gatheringID)

nexError := tracking.LogRegisterGathering(manager.Database, pid, uint32(gathering.ID))
nexError := tracking.LogRegisterGathering(manager.Database, ownerPID, uint32(gathering.ID))
if nexError != nil {
return types.NewDateTime(0), nexError
}

gathering.OwnerPID = pid
gathering.HostPID = pid
gathering.OwnerPID = ownerPID.Copy().(types.PID)
gathering.HostPID = hostPID.Copy().(types.PID)

return startedTime, nil
}
2 changes: 1 addition & 1 deletion match-making/find_by_single_id.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ func (commonProtocol *CommonProtocol) findBySingleID(err error, packet nex.Packe

commonProtocol.manager.Mutex.RLock()

gathering, _, nexError := commonProtocol.manager.GetDetailedGatheringByID(commonProtocol.manager, uint32(id))
gathering, _, nexError := commonProtocol.manager.GetDetailedGatheringByID(commonProtocol.manager, uint64(connection.PID()), uint32(id))
if nexError != nil {
commonProtocol.manager.Mutex.RUnlock()
return nil, nexError
Expand Down
3 changes: 1 addition & 2 deletions matchmake-extension/auto_matchmake_postpone.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals"
match_making_types "github.com/PretendoNetwork/nex-protocols-go/v2/match-making/types"
matchmake_extension "github.com/PretendoNetwork/nex-protocols-go/v2/matchmake-extension"
match_making_database "github.com/PretendoNetwork/nex-protocols-common-go/v2/match-making/database"
database "github.com/PretendoNetwork/nex-protocols-common-go/v2/matchmake-extension/database"
)

Expand Down Expand Up @@ -68,7 +67,7 @@ func (commonProtocol *CommonProtocol) autoMatchmakePostpone(err error, packet ne
}
}

participants, nexError := match_making_database.JoinGathering(commonProtocol.manager, uint32(resultSession.Gathering.ID), connection, 1, string(message))
participants, nexError := database.JoinMatchmakeSession(commonProtocol.manager, *resultSession, connection, 1, string(message))
if nexError != nil {
commonProtocol.manager.Mutex.Unlock()
return nil, nexError
Expand Down
3 changes: 1 addition & 2 deletions matchmake-extension/auto_matchmake_with_param_postpone.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"github.com/PretendoNetwork/nex-go/v2"
"github.com/PretendoNetwork/nex-go/v2/types"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals"
match_making_database "github.com/PretendoNetwork/nex-protocols-common-go/v2/match-making/database"
"github.com/PretendoNetwork/nex-protocols-common-go/v2/matchmake-extension/database"
"github.com/PretendoNetwork/nex-protocols-go/v2/match-making/constants"
match_making_types "github.com/PretendoNetwork/nex-protocols-go/v2/match-making/types"
Expand Down Expand Up @@ -77,7 +76,7 @@ func (commonProtocol *CommonProtocol) autoMatchmakeWithParamPostpone(err error,
}
}

participants, nexError := match_making_database.JoinGatheringWithParticipants(commonProtocol.manager, uint32(resultSession.ID), connection, autoMatchmakeParam.AdditionalParticipants, string(autoMatchmakeParam.JoinMessage), constants.JoinMatchmakeSessionBehaviorJoinMyself)
participants, nexError := database.JoinMatchmakeSessionWithParticipants(commonProtocol.manager, resultSession, connection, autoMatchmakeParam.AdditionalParticipants, string(autoMatchmakeParam.JoinMessage), constants.JoinMatchmakeSessionBehaviorJoinMyself)
if nexError != nil {
commonProtocol.manager.Mutex.Unlock()
return nil, nexError
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"github.com/PretendoNetwork/nex-go/v2"
"github.com/PretendoNetwork/nex-go/v2/types"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals"
match_making_database "github.com/PretendoNetwork/nex-protocols-common-go/v2/match-making/database"
"github.com/PretendoNetwork/nex-protocols-common-go/v2/matchmake-extension/database"
match_making_types "github.com/PretendoNetwork/nex-protocols-go/v2/match-making/types"
matchmake_extension "github.com/PretendoNetwork/nex-protocols-go/v2/matchmake-extension"
Expand Down Expand Up @@ -87,7 +86,7 @@ func (commonProtocol *CommonProtocol) autoMatchmakeWithSearchCriteriaPostpone(er
vacantParticipants = uint16(lstSearchCriteria[0].VacantParticipants)
}

participants, nexError := match_making_database.JoinGathering(commonProtocol.manager, uint32(resultSession.Gathering.ID), connection, vacantParticipants, string(strMessage))
participants, nexError := database.JoinMatchmakeSession(commonProtocol.manager, resultSession, connection, vacantParticipants, string(strMessage))
if nexError != nil {
commonProtocol.manager.Mutex.Unlock()
return nil, nexError
Expand Down
72 changes: 72 additions & 0 deletions matchmake-extension/create_community.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package matchmake_extension

import (
"github.com/PretendoNetwork/nex-go/v2"
"github.com/PretendoNetwork/nex-go/v2/types"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals"
match_making_database "github.com/PretendoNetwork/nex-protocols-common-go/v2/match-making/database"
"github.com/PretendoNetwork/nex-protocols-common-go/v2/matchmake-extension/database"
match_making_types "github.com/PretendoNetwork/nex-protocols-go/v2/match-making/types"
matchmake_extension "github.com/PretendoNetwork/nex-protocols-go/v2/matchmake-extension"
)

func (commonProtocol *CommonProtocol) createCommunity(err error, packet nex.PacketInterface, callID uint32, community match_making_types.PersistentGathering, strMessage types.String) (*nex.RMCMessage, *nex.Error) {
if err != nil {
common_globals.Logger.Error(err.Error())
return nil, nex.NewError(nex.ResultCodes.Core.InvalidArgument, "change_error")
}

if !common_globals.CheckValidPersistentGathering(community) {
return nil, nex.NewError(nex.ResultCodes.Core.InvalidArgument, "change_error")
}

connection := packet.Sender().(*nex.PRUDPConnection)
endpoint := connection.Endpoint().(*nex.PRUDPEndPoint)

commonProtocol.manager.Mutex.Lock()

createdPersistentGatherings, nexError := database.GetCreatedPersistentGatherings(commonProtocol.manager, connection.PID())
if nexError != nil {
commonProtocol.manager.Mutex.Unlock()
return nil, nexError
}

if createdPersistentGatherings >= commonProtocol.PersistentGatheringCreationMax {
commonProtocol.manager.Mutex.Unlock()
return nil, nex.NewError(nex.ResultCodes.RendezVous.PersistentGatheringCreationMax, "change_error")
}

nexError = database.CreatePersistentGathering(commonProtocol.manager, connection, &community)
if nexError != nil {
commonProtocol.manager.Mutex.Unlock()
return nil, nexError
}

// TODO - Is this right? Mario Kart 7 sets 0 max participants
if community.MaximumParticipants > 0 {
_, nexError = match_making_database.JoinGathering(commonProtocol.manager, uint32(community.ID), connection, 1, string(strMessage))
if nexError != nil {
commonProtocol.manager.Mutex.Unlock()
return nil, nexError
}
}

commonProtocol.manager.Mutex.Unlock()

rmcResponseStream := nex.NewByteStreamOut(endpoint.LibraryVersions(), endpoint.ByteStreamSettings())

community.ID.WriteTo(rmcResponseStream)

rmcResponseBody := rmcResponseStream.Bytes()

rmcResponse := nex.NewRMCSuccess(endpoint, rmcResponseBody)
rmcResponse.ProtocolID = matchmake_extension.ProtocolID
rmcResponse.MethodID = matchmake_extension.MethodCreateCommunity
rmcResponse.CallID = callID

if commonProtocol.OnAfterCreateCommunity != nil {
go commonProtocol.OnAfterCreateCommunity(packet, community, strMessage)
}

return rmcResponse, nil
}
3 changes: 1 addition & 2 deletions matchmake-extension/create_matchmake_session.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"github.com/PretendoNetwork/nex-go/v2"
"github.com/PretendoNetwork/nex-go/v2/types"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals"
match_making_database "github.com/PretendoNetwork/nex-protocols-common-go/v2/match-making/database"
"github.com/PretendoNetwork/nex-protocols-common-go/v2/matchmake-extension/database"
match_making_types "github.com/PretendoNetwork/nex-protocols-go/v2/match-making/types"
matchmake_extension "github.com/PretendoNetwork/nex-protocols-go/v2/matchmake-extension"
Expand Down Expand Up @@ -52,7 +51,7 @@ func (commonProtocol *CommonProtocol) createMatchmakeSession(err error, packet n
return nil, nexError
}

participants, nexError := match_making_database.JoinGathering(commonProtocol.manager, uint32(matchmakeSession.Gathering.ID), connection, uint16(participationCount), string(message))
participants, nexError := database.JoinMatchmakeSession(commonProtocol.manager, matchmakeSession, connection, uint16(participationCount), string(message))
if nexError != nil {
common_globals.Logger.Error(nexError.Error())
commonProtocol.manager.Mutex.Unlock()
Expand Down
3 changes: 1 addition & 2 deletions matchmake-extension/create_matchmake_session_with_param.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"github.com/PretendoNetwork/nex-go/v2"
"github.com/PretendoNetwork/nex-go/v2/types"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals"
match_making_database "github.com/PretendoNetwork/nex-protocols-common-go/v2/match-making/database"
"github.com/PretendoNetwork/nex-protocols-common-go/v2/matchmake-extension/database"
"github.com/PretendoNetwork/nex-protocols-go/v2/match-making/constants"
match_making_types "github.com/PretendoNetwork/nex-protocols-go/v2/match-making/types"
Expand Down Expand Up @@ -51,7 +50,7 @@ func (commonProtocol *CommonProtocol) createMatchmakeSessionWithParam(err error,
return nil, nexError
}

participants, nexError := match_making_database.JoinGatheringWithParticipants(commonProtocol.manager, uint32(joinedMatchmakeSession.Gathering.ID), connection, createMatchmakeSessionParam.AdditionalParticipants, string(createMatchmakeSessionParam.JoinMessage), constants.JoinMatchmakeSessionBehaviorJoinMyself)
participants, nexError := database.JoinMatchmakeSessionWithParticipants(commonProtocol.manager, joinedMatchmakeSession, connection, createMatchmakeSessionParam.AdditionalParticipants, string(createMatchmakeSessionParam.JoinMessage), constants.JoinMatchmakeSessionBehaviorJoinMyself)
if nexError != nil {
common_globals.Logger.Error(nexError.Error())
commonProtocol.manager.Mutex.Unlock()
Expand Down
2 changes: 1 addition & 1 deletion matchmake-extension/database/create_matchmake_session.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (

// CreateMatchmakeSession creates a new MatchmakeSession on the database. No participants are added
func CreateMatchmakeSession(manager *common_globals.MatchmakingManager, connection *nex.PRUDPConnection, matchmakeSession *match_making_types.MatchmakeSession) *nex.Error {
startedTime, nexError := match_making_database.RegisterGathering(manager, connection.PID(), &matchmakeSession.Gathering, "MatchmakeSession")
startedTime, nexError := match_making_database.RegisterGathering(manager, connection.PID(), connection.PID(), &matchmakeSession.Gathering, "MatchmakeSession")
if nexError != nil {
return nexError
}
Expand Down
54 changes: 54 additions & 0 deletions matchmake-extension/database/create_persistent_gathering.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package database

import (
"github.com/PretendoNetwork/nex-go/v2"
"github.com/PretendoNetwork/nex-go/v2/types"
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals"
match_making_database "github.com/PretendoNetwork/nex-protocols-common-go/v2/match-making/database"
match_making_types "github.com/PretendoNetwork/nex-protocols-go/v2/match-making/types"
pqextended "github.com/PretendoNetwork/pq-extended"
)

// CreatePersistentGathering creates a new PersistentGathering on the database. No participants are added
func CreatePersistentGathering(manager *common_globals.MatchmakingManager, connection *nex.PRUDPConnection, persistentGathering *match_making_types.PersistentGathering) *nex.Error {
_, nexError := match_making_database.RegisterGathering(manager, connection.PID(), types.NewPID(0), &persistentGathering.Gathering, "PersistentGathering")
if nexError != nil {
return nexError
}

attribs := make([]uint32, len(persistentGathering.Attribs))
for i, value := range persistentGathering.Attribs {
attribs[i] = uint32(value)
}

_, err := manager.Database.Exec(`INSERT INTO matchmaking.persistent_gatherings (
id,
community_type,
password,
attribs,
application_buffer,
participation_start_date,
participation_end_date
) VALUES (
$1,
$2,
$3,
$4,
$5,
$6,
$7
)`,
uint32(persistentGathering.Gathering.ID),
uint32(persistentGathering.CommunityType),
string(persistentGathering.Password),
pqextended.Array(attribs),
[]byte(persistentGathering.ApplicationBuffer),
persistentGathering.ParticipationStartDate.Standard(),
persistentGathering.ParticipationEndDate.Standard(),
)
if err != nil {
return nex.NewError(nex.ResultCodes.Core.Unknown, err.Error())
}

return nil
}
Loading

0 comments on commit 25c233f

Please sign in to comment.