From 5f36476287c316ddccb024f39973102c8e801280 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20L=C3=B3pez=20Guimaraes?= Date: Sat, 16 Nov 2024 23:58:37 +0000 Subject: [PATCH] feat: Initial support for persistent gatherings/communities 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. --- globals/matchmaking_globals.go | 2 +- globals/utils.go | 30 ++++ .../database/disconnect_participant.go | 9 +- .../database/end_gathering_participation.go | 13 +- .../database/get_detailed_gathering_by_id.go | 2 +- match-making/database/register_gathering.go | 14 +- match-making/find_by_single_id.go | 2 +- .../auto_matchmake_postpone.go | 3 +- .../auto_matchmake_with_param_postpone.go | 3 +- ...matchmake_with_search_criteria_postpone.go | 3 +- matchmake-extension/create_community.go | 72 +++++++++ .../create_matchmake_session.go | 3 +- .../create_matchmake_session_with_param.go | 3 +- .../database/create_matchmake_session.go | 2 +- .../database/create_persistent_gathering.go | 54 +++++++ ...nd_matchmake_session_by_search_criteria.go | 11 +- .../get_created_persistent_gatherings.go | 24 +++ .../database/get_detailed_gathering_by_id.go | 48 ++++-- .../database/get_official_communities.go | 137 ++++++++++++++++++ .../get_persistent_gathering_by_gathering.go | 66 +++++++++ .../get_persistent_gathering_by_id.go | 115 +++++++++++++++ ...ersistent_gathering_participation_count.go | 31 ++++ .../get_persistent_gathering_session_count.go | 26 ++++ .../get_persistent_gatherings_by_id.go | 131 +++++++++++++++++ .../get_persistent_gatherings_by_owner_pid.go | 134 +++++++++++++++++ .../database/get_simple_communities.go | 57 ++++++++ .../database/join_matchmake_session.go | 41 ++++++ ...oin_matchmake_session_with_participants.go | 47 ++++++ ...ersistent_gathering_participation_count.go | 30 ++++ .../find_community_by_gathering_id.go | 61 ++++++++ .../find_community_by_participant.go | 56 +++++++ .../find_official_community.go | 56 +++++++ matchmake-extension/get_simple_community.go | 55 +++++++ matchmake-extension/join_matchmake_session.go | 3 +- .../join_matchmake_session_ex.go | 3 +- .../join_matchmake_session_with_param.go | 3 +- matchmake-extension/protocol.go | 37 +++++ .../tracking/log_participate_community.go | 33 +++++ 38 files changed, 1367 insertions(+), 53 deletions(-) create mode 100644 matchmake-extension/create_community.go create mode 100644 matchmake-extension/database/create_persistent_gathering.go create mode 100644 matchmake-extension/database/get_created_persistent_gatherings.go create mode 100644 matchmake-extension/database/get_official_communities.go create mode 100644 matchmake-extension/database/get_persistent_gathering_by_gathering.go create mode 100644 matchmake-extension/database/get_persistent_gathering_by_id.go create mode 100644 matchmake-extension/database/get_persistent_gathering_participation_count.go create mode 100644 matchmake-extension/database/get_persistent_gathering_session_count.go create mode 100644 matchmake-extension/database/get_persistent_gatherings_by_id.go create mode 100644 matchmake-extension/database/get_persistent_gatherings_by_owner_pid.go create mode 100644 matchmake-extension/database/get_simple_communities.go create mode 100644 matchmake-extension/database/join_matchmake_session.go create mode 100644 matchmake-extension/database/join_matchmake_session_with_participants.go create mode 100644 matchmake-extension/database/update_persistent_gathering_participation_count.go create mode 100644 matchmake-extension/find_community_by_gathering_id.go create mode 100644 matchmake-extension/find_community_by_participant.go create mode 100644 matchmake-extension/find_official_community.go create mode 100644 matchmake-extension/get_simple_community.go create mode 100644 matchmake-extension/tracking/log_participate_community.go diff --git a/globals/matchmaking_globals.go b/globals/matchmaking_globals.go index 2afdae2..c5e0ef2 100644 --- a/globals/matchmaking_globals.go +++ b/globals/matchmaking_globals.go @@ -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 diff --git a/globals/utils.go b/globals/utils.go index c5b367c..8fb009f 100644 --- a/globals/utils.go +++ b/globals/utils.go @@ -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? diff --git a/match-making/database/disconnect_participant.go b/match-making/database/disconnect_participant.go index c106cf0..c6679bb 100644 --- a/match-making/database/disconnect_participant.go +++ b/match-making/database/disconnect_participant.go @@ -56,7 +56,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" && flags & match_making.GatheringFlags.PersistentGatheringLeaveParticipation == 0 { + // + // TODO - Is the match_making.GatheringFlags.PersistentGathering check correct here? + if flags & match_making.GatheringFlags.PersistentGathering != 0 || (gatheringType == "PersistentGathering" && flags & match_making.GatheringFlags.PersistentGatheringLeaveParticipation == 0) { continue } @@ -85,6 +87,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 diff --git a/match-making/database/end_gathering_participation.go b/match-making/database/end_gathering_participation.go index 9f8d26f..82921c1 100644 --- a/match-making/database/end_gathering_participation.go +++ b/match-making/database/end_gathering_participation.go @@ -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 } @@ -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 @@ -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) diff --git a/match-making/database/get_detailed_gathering_by_id.go b/match-making/database/get_detailed_gathering_by_id.go index ed1de97..377b2be 100644 --- a/match-making/database/get_detailed_gathering_by_id.go +++ b/match-making/database/get_detailed_gathering_by_id.go @@ -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 diff --git a/match-making/database/register_gathering.go b/match-making/database/register_gathering.go index 8178050..dabeb89 100644 --- a/match-making/database/register_gathering.go +++ b/match-making/database/register_gathering.go @@ -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 @@ -38,8 +38,8 @@ func RegisterGathering(manager *common_globals.MatchmakingManager, pid types.PID $10, $11 ) RETURNING id`, - pid, - pid, + uint64(ownerPID), + uint64(hostPID), uint16(gathering.MinimumParticipants), uint16(gathering.MaximumParticipants), uint32(gathering.ParticipationPolicy), @@ -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 } diff --git a/match-making/find_by_single_id.go b/match-making/find_by_single_id.go index 101f8f4..2546989 100644 --- a/match-making/find_by_single_id.go +++ b/match-making/find_by_single_id.go @@ -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 diff --git a/matchmake-extension/auto_matchmake_postpone.go b/matchmake-extension/auto_matchmake_postpone.go index 39dd7f7..3555e92 100644 --- a/matchmake-extension/auto_matchmake_postpone.go +++ b/matchmake-extension/auto_matchmake_postpone.go @@ -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" ) @@ -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 diff --git a/matchmake-extension/auto_matchmake_with_param_postpone.go b/matchmake-extension/auto_matchmake_with_param_postpone.go index 97d3ffb..5d49aad 100644 --- a/matchmake-extension/auto_matchmake_with_param_postpone.go +++ b/matchmake-extension/auto_matchmake_with_param_postpone.go @@ -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" @@ -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 diff --git a/matchmake-extension/auto_matchmake_with_search_criteria_postpone.go b/matchmake-extension/auto_matchmake_with_search_criteria_postpone.go index b37d09f..c31355d 100644 --- a/matchmake-extension/auto_matchmake_with_search_criteria_postpone.go +++ b/matchmake-extension/auto_matchmake_with_search_criteria_postpone.go @@ -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" @@ -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 diff --git a/matchmake-extension/create_community.go b/matchmake-extension/create_community.go new file mode 100644 index 0000000..0e86d5f --- /dev/null +++ b/matchmake-extension/create_community.go @@ -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 +} diff --git a/matchmake-extension/create_matchmake_session.go b/matchmake-extension/create_matchmake_session.go index 4111838..f0997ff 100644 --- a/matchmake-extension/create_matchmake_session.go +++ b/matchmake-extension/create_matchmake_session.go @@ -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" @@ -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() diff --git a/matchmake-extension/create_matchmake_session_with_param.go b/matchmake-extension/create_matchmake_session_with_param.go index c3934ea..3872f2a 100644 --- a/matchmake-extension/create_matchmake_session_with_param.go +++ b/matchmake-extension/create_matchmake_session_with_param.go @@ -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" @@ -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() diff --git a/matchmake-extension/database/create_matchmake_session.go b/matchmake-extension/database/create_matchmake_session.go index 67d5155..a1b921e 100644 --- a/matchmake-extension/database/create_matchmake_session.go +++ b/matchmake-extension/database/create_matchmake_session.go @@ -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 } diff --git a/matchmake-extension/database/create_persistent_gathering.go b/matchmake-extension/database/create_persistent_gathering.go new file mode 100644 index 0000000..54cb0c1 --- /dev/null +++ b/matchmake-extension/database/create_persistent_gathering.go @@ -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 +} diff --git a/matchmake-extension/database/find_matchmake_session_by_search_criteria.go b/matchmake-extension/database/find_matchmake_session_by_search_criteria.go index d1082b4..4a7196b 100644 --- a/matchmake-extension/database/find_matchmake_session_by_search_criteria.go +++ b/matchmake-extension/database/find_matchmake_session_by_search_criteria.go @@ -10,7 +10,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" - "github.com/PretendoNetwork/nex-protocols-go/v2/globals" "github.com/PretendoNetwork/nex-protocols-go/v2/match-making/constants" match_making_types "github.com/PretendoNetwork/nex-protocols-go/v2/match-making/types" pqextended "github.com/PretendoNetwork/pq-extended" @@ -227,7 +226,7 @@ func FindMatchmakeSessionBySearchCriteria(manager *common_globals.MatchmakingMan // * Closest attribute attribute1, err := strconv.ParseUint(string(searchCriteria.Attribs[1]), 10, 32) if err != nil { - globals.Logger.Error(err.Error()) + common_globals.Logger.Error(err.Error()) continue } @@ -238,7 +237,7 @@ func FindMatchmakeSessionBySearchCriteria(manager *common_globals.MatchmakingMan // TODO - Actually implement ranked matchmaking, using closest attribute at the moment attribute1, err := strconv.ParseUint(string(searchCriteria.Attribs[1]), 10, 32) if err != nil { - globals.Logger.Error(err.Error()) + common_globals.Logger.Error(err.Error()) continue } @@ -264,7 +263,7 @@ func FindMatchmakeSessionBySearchCriteria(manager *common_globals.MatchmakingMan attribute1, err := strconv.ParseUint(string(searchCriteria.Attribs[1]), 10, 32) if err != nil { - globals.Logger.Error(err.Error()) + common_globals.Logger.Error(err.Error()) continue } @@ -294,7 +293,7 @@ func FindMatchmakeSessionBySearchCriteria(manager *common_globals.MatchmakingMan bool(searchCriteria.ExcludeSystemPasswordSet), ) if err != nil { - globals.Logger.Critical(err.Error()) + common_globals.Logger.Critical(err.Error()) continue } @@ -355,7 +354,7 @@ func FindMatchmakeSessionBySearchCriteria(manager *common_globals.MatchmakingMan &codeWord, ) if err != nil { - globals.Logger.Critical(err.Error()) + common_globals.Logger.Critical(err.Error()) continue } diff --git a/matchmake-extension/database/get_created_persistent_gatherings.go b/matchmake-extension/database/get_created_persistent_gatherings.go new file mode 100644 index 0000000..029f784 --- /dev/null +++ b/matchmake-extension/database/get_created_persistent_gatherings.go @@ -0,0 +1,24 @@ +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" +) + +// GetCreatedPersistentGatherings returns the number of active persistent gatherings that a given PID owns +func GetCreatedPersistentGatherings(manager *common_globals.MatchmakingManager, ownerPID types.PID) (int, *nex.Error) { + var createdPersistentGatherings int + err := manager.Database.QueryRow(`SELECT + COUNT(pg.id) + FROM matchmaking.persistent_gatherings AS pg + INNER JOIN matchmaking.gatherings AS g ON g.id = pg.id + WHERE + g.registered=true AND + g.owner_pid=$1`, uint64(ownerPID)).Scan(&createdPersistentGatherings) + if err != nil { + return 0, nex.NewError(nex.ResultCodes.Core.Unknown, err.Error()) + } + + return createdPersistentGatherings, nil +} diff --git a/matchmake-extension/database/get_detailed_gathering_by_id.go b/matchmake-extension/database/get_detailed_gathering_by_id.go index 399699e..676e643 100644 --- a/matchmake-extension/database/get_detailed_gathering_by_id.go +++ b/matchmake-extension/database/get_detailed_gathering_by_id.go @@ -8,7 +8,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, participants, startedTime, nexError := match_making_database.FindGatheringByID(manager, gatheringID) if nexError != nil { return nil, "", nexError @@ -18,19 +18,43 @@ func GetDetailedGatheringByID(manager *common_globals.MatchmakingManager, gather return gathering, gatheringType, nil } - // TODO - Add PersistentGathering - if gatheringType != "MatchmakeSession" { - return nil, "", nex.NewError(nex.ResultCodes.Core.Exception, "change_error") - } + if gatheringType == "MatchmakeSession" { + matchmakeSession, nexError := GetMatchmakeSessionByGathering(manager, manager.Endpoint, gathering, uint32(len(participants)), startedTime) + if nexError != nil { + return nil, "", nexError + } - matchmakeSession, nexError := GetMatchmakeSessionByGathering(manager, manager.Endpoint, gathering, uint32(len(participants)), startedTime) - if nexError != nil { - return nil, "", nexError + // * Scrap session key and user password + matchmakeSession.SessionKey = make([]byte, 0) + matchmakeSession.UserPassword = "" + + return matchmakeSession, gatheringType, nil } - // * Scrap session key and user password - matchmakeSession.SessionKey = make([]byte, 0) - matchmakeSession.UserPassword = "" + if gatheringType == "PersistentGathering" { + persistentGathering, nexError := GetPersistentGatheringByGathering(manager, gathering, sourcePID) + if nexError != nil { + return nil, "", nexError + } + + matchmakeSessionCount, nexError := GetPersistentGatheringSessionCount(manager, gatheringID) + if nexError != nil { + return nil, "", nexError + } + + participationCount, nexError := GetPersistentGatheringParticipationCount(manager, gatheringID, sourcePID) + if nexError != nil { + return nil, "", nexError + } + + persistentGathering.MatchmakeSessionCount = types.NewUInt32(matchmakeSessionCount) + persistentGathering.ParticipationCount = types.NewUInt32(participationCount) + + // * Scrap persistent gathering password + persistentGathering.Password = "" + + return persistentGathering, gatheringType, nil + } - return matchmakeSession, gatheringType, nil + return nil, "", nex.NewError(nex.ResultCodes.Core.Exception, "change_error") } diff --git a/matchmake-extension/database/get_official_communities.go b/matchmake-extension/database/get_official_communities.go new file mode 100644 index 0000000..80adec3 --- /dev/null +++ b/matchmake-extension/database/get_official_communities.go @@ -0,0 +1,137 @@ +package database + +import ( + "time" + + "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_types "github.com/PretendoNetwork/nex-protocols-go/v2/match-making/types" + pqextended "github.com/PretendoNetwork/pq-extended" +) + +// GetOfficialCommunities returns the official communities based on the given parameters +func GetOfficialCommunities(manager *common_globals.MatchmakingManager, sourcePID types.PID, isAvailableOnly bool, resultRange types.ResultRange) ([]match_making_types.PersistentGathering, *nex.Error) { + persistentGatherings := make([]match_making_types.PersistentGathering, 0) + currentTime := time.Now().UTC() + rows, err := manager.Database.Query(`SELECT + g.id, + g.owner_pid, + g.host_pid, + g.min_participants, + g.max_participants, + g.participation_policy, + g.policy_argument, + g.flags, + g.state, + g.description, + pg.community_type, + pg.password, + pg.attribs, + pg.application_buffer, + pg.participation_start_date, + pg.participation_end_date + FROM matchmaking.gatherings AS g + INNER JOIN matchmaking.persistent_gatherings AS pg ON g.id = pg.id + WHERE + g.registered=true AND + g.type='PersistentGathering' AND + pg.community_type=2 AND + (CASE WHEN $1 THEN $2 BETWEEN pg.participation_start_date AND pg.participation_end_date ELSE true END) + LIMIT $3 OFFSET $4`, + isAvailableOnly, + currentTime, + uint32(resultRange.Length), + uint32(resultRange.Offset), + ) + if err != nil { + return nil, nex.NewError(nex.ResultCodes.Core.Unknown, err.Error()) + } + + for rows.Next() { + var gatheringID uint32 + var ownerPID uint64 + var hostPID uint64 + var minimumParticipants uint16 + var maximumParticipants uint16 + var participationPolicy uint32 + var policyArgument uint32 + var flags uint32 + var state uint32 + var description string + var communityType uint32 + var password string + var resultAttribs []uint32 + var applicationBuffer []byte + var resultParticipationStartDate time.Time + var resultParticipationEndDate time.Time + + err = rows.Scan( + &gatheringID, + &ownerPID, + &hostPID, + &minimumParticipants, + &maximumParticipants, + &participationPolicy, + &policyArgument, + &flags, + &state, + &description, + &communityType, + &password, + pqextended.Array(&resultAttribs), + &applicationBuffer, + &resultParticipationStartDate, + &resultParticipationEndDate, + ) + if err != nil { + common_globals.Logger.Critical(err.Error()) + continue + } + + resultPersistentGathering := match_making_types.NewPersistentGathering() + resultPersistentGathering.ID = types.NewUInt32(gatheringID) + + resultMatchmakeSessionCount, nexError := GetPersistentGatheringSessionCount(manager, uint32(resultPersistentGathering.ID)) + if nexError != nil { + common_globals.Logger.Critical(nexError.Error()) + continue + } + + resultParticipationCount, nexError := GetPersistentGatheringParticipationCount(manager, uint32(resultPersistentGathering.ID), uint64(sourcePID)) + if nexError != nil { + common_globals.Logger.Critical(nexError.Error()) + continue + } + + resultPersistentGathering.OwnerPID = types.NewPID(ownerPID) + resultPersistentGathering.HostPID = types.NewPID(hostPID) + resultPersistentGathering.MinimumParticipants = types.NewUInt16(minimumParticipants) + resultPersistentGathering.MaximumParticipants = types.NewUInt16(maximumParticipants) + resultPersistentGathering.ParticipationPolicy = types.NewUInt32(participationPolicy) + resultPersistentGathering.PolicyArgument = types.NewUInt32(policyArgument) + resultPersistentGathering.Flags = types.NewUInt32(flags) + resultPersistentGathering.State = types.NewUInt32(state) + resultPersistentGathering.Description = types.NewString(description) + resultPersistentGathering.CommunityType = types.NewUInt32(communityType) + resultPersistentGathering.Password = types.NewString(password) + + attributesSlice := make([]types.UInt32, len(resultAttribs)) + for i, value := range resultAttribs { + attributesSlice[i] = types.NewUInt32(value) + } + resultPersistentGathering.Attribs = attributesSlice + + resultPersistentGathering.ApplicationBuffer = applicationBuffer + resultPersistentGathering.ParticipationStartDate = resultPersistentGathering.ParticipationStartDate.FromTimestamp(resultParticipationStartDate) + resultPersistentGathering.ParticipationEndDate = resultPersistentGathering.ParticipationEndDate.FromTimestamp(resultParticipationEndDate) + resultPersistentGathering.MatchmakeSessionCount = types.NewUInt32(resultMatchmakeSessionCount) + resultPersistentGathering.ParticipationCount = types.NewUInt32(resultParticipationCount) + + persistentGatherings = append(persistentGatherings, resultPersistentGathering) + } + + rows.Close() + + return persistentGatherings, nil +} diff --git a/matchmake-extension/database/get_persistent_gathering_by_gathering.go b/matchmake-extension/database/get_persistent_gathering_by_gathering.go new file mode 100644 index 0000000..4895ca8 --- /dev/null +++ b/matchmake-extension/database/get_persistent_gathering_by_gathering.go @@ -0,0 +1,66 @@ +package database + +import ( + "database/sql" + "time" + + "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_types "github.com/PretendoNetwork/nex-protocols-go/v2/match-making/types" + pqextended "github.com/PretendoNetwork/pq-extended" +) + +// GetPersistentGatheringByGathering gets a persistent gathering with the given gathering data +func GetPersistentGatheringByGathering(manager *common_globals.MatchmakingManager, gathering match_making_types.Gathering, sourcePID uint64) (match_making_types.PersistentGathering, *nex.Error) { + var communityType uint32 + var password string + var resultAttribs []uint32 + var applicationBuffer []byte + var resultParticipationStartDate time.Time + var resultParticipationEndDate time.Time + + err := manager.Database.QueryRow(`SELECT + community_type, + password, + attribs, + application_buffer, + participation_start_date, + participation_end_date + FROM matchmaking.persistent_gatherings + WHERE id=$1`, + uint32(gathering.ID), + ).Scan( + &communityType, + &password, + pqextended.Array(&resultAttribs), + &applicationBuffer, + &resultParticipationStartDate, + &resultParticipationEndDate, + ) + if err != nil { + if err == sql.ErrNoRows { + return match_making_types.NewPersistentGathering(), nex.NewError(nex.ResultCodes.RendezVous.InvalidGID, "change_error") + } else { + return match_making_types.NewPersistentGathering(), nex.NewError(nex.ResultCodes.Core.Unknown, err.Error()) + } + } + + resultPersistentGathering := match_making_types.NewPersistentGathering() + resultPersistentGathering.Gathering = gathering + + resultPersistentGathering.CommunityType = types.NewUInt32(communityType) + resultPersistentGathering.Password = types.NewString(password) + + attributesSlice := make([]types.UInt32, len(resultAttribs)) + for i, value := range resultAttribs { + attributesSlice[i] = types.NewUInt32(value) + } + resultPersistentGathering.Attribs = attributesSlice + + resultPersistentGathering.ApplicationBuffer = applicationBuffer + resultPersistentGathering.ParticipationStartDate = resultPersistentGathering.ParticipationStartDate.FromTimestamp(resultParticipationStartDate) + resultPersistentGathering.ParticipationEndDate = resultPersistentGathering.ParticipationEndDate.FromTimestamp(resultParticipationEndDate) + + return resultPersistentGathering, nil +} diff --git a/matchmake-extension/database/get_persistent_gathering_by_id.go b/matchmake-extension/database/get_persistent_gathering_by_id.go new file mode 100644 index 0000000..15a939d --- /dev/null +++ b/matchmake-extension/database/get_persistent_gathering_by_id.go @@ -0,0 +1,115 @@ +package database + +import ( + "time" + + "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_types "github.com/PretendoNetwork/nex-protocols-go/v2/match-making/types" + pqextended "github.com/PretendoNetwork/pq-extended" +) + +// GetPersistentGatheringByID gets the persistent gatherings from the given gathering IDs +func GetPersistentGatheringByID(manager *common_globals.MatchmakingManager, sourcePID types.PID, gatheringID uint32) (match_making_types.PersistentGathering, *nex.Error) { + var ownerPID uint64 + var hostPID uint64 + var minimumParticipants uint16 + var maximumParticipants uint16 + var participationPolicy uint32 + var policyArgument uint32 + var flags uint32 + var state uint32 + var description string + var communityType uint32 + var password string + var resultAttribs []uint32 + var applicationBuffer []byte + var resultParticipationStartDate time.Time + var resultParticipationEndDate time.Time + + err := manager.Database.QueryRow(`SELECT + g.id, + g.owner_pid, + g.host_pid, + g.min_participants, + g.max_participants, + g.participation_policy, + g.policy_argument, + g.flags, + g.state, + g.description, + pg.community_type, + pg.password, + pg.attribs, + pg.application_buffer, + pg.participation_start_date, + pg.participation_end_date + FROM matchmaking.gatherings AS g + INNER JOIN matchmaking.persistent_gatherings AS pg ON g.id = pg.id + WHERE + g.registered=true AND + g.type='PersistentGathering' AND + g.id=$1`, + gatheringID, + ).Scan( + &gatheringID, + &ownerPID, + &hostPID, + &minimumParticipants, + &maximumParticipants, + &participationPolicy, + &policyArgument, + &flags, + &state, + &description, + &communityType, + &password, + pqextended.Array(&resultAttribs), + &applicationBuffer, + &resultParticipationStartDate, + &resultParticipationEndDate, + ) + if err != nil { + return match_making_types.NewPersistentGathering(), nil + } + + resultPersistentGathering := match_making_types.NewPersistentGathering() + resultPersistentGathering.ID = types.NewUInt32(gatheringID) + + resultMatchmakeSessionCount, nexError := GetPersistentGatheringSessionCount(manager, uint32(resultPersistentGathering.ID)) + if nexError != nil { + return match_making_types.NewPersistentGathering(), nexError + } + + resultParticipationCount, nexError := GetPersistentGatheringParticipationCount(manager, uint32(resultPersistentGathering.ID), uint64(sourcePID)) + if nexError != nil { + return match_making_types.NewPersistentGathering(), nexError + } + + resultPersistentGathering.OwnerPID = types.NewPID(ownerPID) + resultPersistentGathering.HostPID = types.NewPID(hostPID) + resultPersistentGathering.MinimumParticipants = types.NewUInt16(minimumParticipants) + resultPersistentGathering.MaximumParticipants = types.NewUInt16(maximumParticipants) + resultPersistentGathering.ParticipationPolicy = types.NewUInt32(participationPolicy) + resultPersistentGathering.PolicyArgument = types.NewUInt32(policyArgument) + resultPersistentGathering.Flags = types.NewUInt32(flags) + resultPersistentGathering.State = types.NewUInt32(state) + resultPersistentGathering.Description = types.NewString(description) + resultPersistentGathering.CommunityType = types.NewUInt32(communityType) + resultPersistentGathering.Password = types.NewString(password) + + attributesSlice := make([]types.UInt32, len(resultAttribs)) + for i, value := range resultAttribs { + attributesSlice[i] = types.NewUInt32(value) + } + resultPersistentGathering.Attribs = attributesSlice + + resultPersistentGathering.ApplicationBuffer = applicationBuffer + resultPersistentGathering.ParticipationStartDate = resultPersistentGathering.ParticipationStartDate.FromTimestamp(resultParticipationStartDate) + resultPersistentGathering.ParticipationEndDate = resultPersistentGathering.ParticipationEndDate.FromTimestamp(resultParticipationEndDate) + resultPersistentGathering.MatchmakeSessionCount = types.NewUInt32(resultMatchmakeSessionCount) + resultPersistentGathering.ParticipationCount = types.NewUInt32(resultParticipationCount) + + return resultPersistentGathering, nil +} diff --git a/matchmake-extension/database/get_persistent_gathering_participation_count.go b/matchmake-extension/database/get_persistent_gathering_participation_count.go new file mode 100644 index 0000000..9ebc4e9 --- /dev/null +++ b/matchmake-extension/database/get_persistent_gathering_participation_count.go @@ -0,0 +1,31 @@ +package database + +import ( + "database/sql" + + "github.com/PretendoNetwork/nex-go/v2" + common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals" +) + +// GetPersistentGatheringParticipationCount gets the number of times a user has participated on a given persistent gathering +func GetPersistentGatheringParticipationCount(manager *common_globals.MatchmakingManager, gatheringID uint32, sourcePID uint64) (uint32, *nex.Error) { + var persistentGatheringParticipationCount uint32 + err := manager.Database.QueryRow(`SELECT + cp.participation_count + FROM matchmaking.community_participations AS cp + WHERE + cp.user_pid=$1 AND + cp.gathering_id=$2`, + sourcePID, + gatheringID, + ).Scan(&persistentGatheringParticipationCount) + if err != nil { + if err == sql.ErrNoRows { + return 0, nil + } else { + return 0, nex.NewError(nex.ResultCodes.Core.Unknown, err.Error()) + } + } + + return persistentGatheringParticipationCount, nil +} diff --git a/matchmake-extension/database/get_persistent_gathering_session_count.go b/matchmake-extension/database/get_persistent_gathering_session_count.go new file mode 100644 index 0000000..9dec9e7 --- /dev/null +++ b/matchmake-extension/database/get_persistent_gathering_session_count.go @@ -0,0 +1,26 @@ +package database + +import ( + "github.com/PretendoNetwork/nex-go/v2" + common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals" +) + +// GetPersistentGatheringSessionCount gets the number of active matchmake sessions attached to a given persistent gathering +func GetPersistentGatheringSessionCount(manager *common_globals.MatchmakingManager, gatheringID uint32) (uint32, *nex.Error) { + var persistentGatheringSessionCount uint32 + err := manager.Database.QueryRow(`SELECT + COUNT(ms.id) + FROM matchmaking.matchmake_sessions AS ms + INNER JOIN matchmaking.gatherings AS g ON ms.id = g.id + WHERE + g.registered=true AND + ms.matchmake_system_type=5 AND + ms.attribs[1]=$1`, // * matchmake_system_type=5 is only used in matchmake sessions attached to a persistent gathering + gatheringID, + ).Scan(&persistentGatheringSessionCount) + if err != nil { + return 0, nex.NewError(nex.ResultCodes.Core.Unknown, err.Error()) + } + + return persistentGatheringSessionCount, nil +} diff --git a/matchmake-extension/database/get_persistent_gatherings_by_id.go b/matchmake-extension/database/get_persistent_gatherings_by_id.go new file mode 100644 index 0000000..6754ccf --- /dev/null +++ b/matchmake-extension/database/get_persistent_gatherings_by_id.go @@ -0,0 +1,131 @@ +package database + +import ( + "time" + + "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_types "github.com/PretendoNetwork/nex-protocols-go/v2/match-making/types" + pqextended "github.com/PretendoNetwork/pq-extended" +) + +// GetPersistentGatheringsByID gets the persistent gatherings from the given gathering IDs +func GetPersistentGatheringsByID(manager *common_globals.MatchmakingManager, sourcePID types.PID, gatheringIDs []uint32) ([]match_making_types.PersistentGathering, *nex.Error) { + persistentGatherings := make([]match_making_types.PersistentGathering, 0) + rows, err := manager.Database.Query(`SELECT + g.id, + g.owner_pid, + g.host_pid, + g.min_participants, + g.max_participants, + g.participation_policy, + g.policy_argument, + g.flags, + g.state, + g.description, + pg.community_type, + pg.password, + pg.attribs, + pg.application_buffer, + pg.participation_start_date, + pg.participation_end_date + FROM matchmaking.gatherings AS g + INNER JOIN matchmaking.persistent_gatherings AS pg ON g.id = pg.id + WHERE + g.registered=true AND + g.type='PersistentGathering' AND + g.id=ANY($1)`, + pqextended.Array(gatheringIDs), + ) + if err != nil { + return nil, nex.NewError(nex.ResultCodes.Core.Unknown, err.Error()) + } + + for rows.Next() { + var gatheringID uint32 + var ownerPID uint64 + var hostPID uint64 + var minimumParticipants uint16 + var maximumParticipants uint16 + var participationPolicy uint32 + var policyArgument uint32 + var flags uint32 + var state uint32 + var description string + var communityType uint32 + var password string + var resultAttribs []uint32 + var applicationBuffer []byte + var resultParticipationStartDate time.Time + var resultParticipationEndDate time.Time + + err = rows.Scan( + &gatheringID, + &ownerPID, + &hostPID, + &minimumParticipants, + &maximumParticipants, + &participationPolicy, + &policyArgument, + &flags, + &state, + &description, + &communityType, + &password, + pqextended.Array(&resultAttribs), + &applicationBuffer, + &resultParticipationStartDate, + &resultParticipationEndDate, + ) + if err != nil { + common_globals.Logger.Critical(err.Error()) + continue + } + + resultPersistentGathering := match_making_types.NewPersistentGathering() + resultPersistentGathering.ID = types.NewUInt32(gatheringID) + + resultMatchmakeSessionCount, nexError := GetPersistentGatheringSessionCount(manager, uint32(resultPersistentGathering.ID)) + if nexError != nil { + common_globals.Logger.Critical(nexError.Error()) + continue + } + + resultParticipationCount, nexError := GetPersistentGatheringParticipationCount(manager, uint32(resultPersistentGathering.ID), uint64(sourcePID)) + if nexError != nil { + common_globals.Logger.Critical(nexError.Error()) + continue + } + + resultPersistentGathering.OwnerPID = types.NewPID(ownerPID) + resultPersistentGathering.HostPID = types.NewPID(hostPID) + resultPersistentGathering.MinimumParticipants = types.NewUInt16(minimumParticipants) + resultPersistentGathering.MaximumParticipants = types.NewUInt16(maximumParticipants) + resultPersistentGathering.ParticipationPolicy = types.NewUInt32(participationPolicy) + resultPersistentGathering.PolicyArgument = types.NewUInt32(policyArgument) + resultPersistentGathering.Flags = types.NewUInt32(flags) + resultPersistentGathering.State = types.NewUInt32(state) + resultPersistentGathering.Description = types.NewString(description) + resultPersistentGathering.CommunityType = types.NewUInt32(communityType) + resultPersistentGathering.Password = types.NewString(password) + + attributesSlice := make([]types.UInt32, len(resultAttribs)) + for i, value := range resultAttribs { + attributesSlice[i] = types.NewUInt32(value) + } + resultPersistentGathering.Attribs = attributesSlice + + resultPersistentGathering.ApplicationBuffer = applicationBuffer + resultPersistentGathering.ParticipationStartDate = resultPersistentGathering.ParticipationStartDate.FromTimestamp(resultParticipationStartDate) + resultPersistentGathering.ParticipationEndDate = resultPersistentGathering.ParticipationEndDate.FromTimestamp(resultParticipationEndDate) + resultPersistentGathering.MatchmakeSessionCount = types.NewUInt32(resultMatchmakeSessionCount) + resultPersistentGathering.ParticipationCount = types.NewUInt32(resultParticipationCount) + + persistentGatherings = append(persistentGatherings, resultPersistentGathering) + } + + rows.Close() + + return persistentGatherings, nil +} diff --git a/matchmake-extension/database/get_persistent_gatherings_by_owner_pid.go b/matchmake-extension/database/get_persistent_gatherings_by_owner_pid.go new file mode 100644 index 0000000..c69842d --- /dev/null +++ b/matchmake-extension/database/get_persistent_gatherings_by_owner_pid.go @@ -0,0 +1,134 @@ +package database + +import ( + "time" + + "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_types "github.com/PretendoNetwork/nex-protocols-go/v2/match-making/types" + pqextended "github.com/PretendoNetwork/pq-extended" +) + +// GetPersistentGatheringsByOwnerPID finds the active persistent gatherings owned by the given owner PID +func GetPersistentGatheringsByOwnerPID(manager *common_globals.MatchmakingManager, sourcePID types.PID, ownerPID types.PID, resultRange types.ResultRange) ([]match_making_types.PersistentGathering, *nex.Error) { + persistentGatherings := make([]match_making_types.PersistentGathering, 0) + rows, err := manager.Database.Query(`SELECT + g.id, + g.owner_pid, + g.host_pid, + g.min_participants, + g.max_participants, + g.participation_policy, + g.policy_argument, + g.flags, + g.state, + g.description, + pg.community_type, + pg.password, + pg.attribs, + pg.application_buffer, + pg.participation_start_date, + pg.participation_end_date + FROM matchmaking.gatherings AS g + INNER JOIN matchmaking.persistent_gatherings AS pg ON g.id = pg.id + WHERE + g.registered=true AND + g.type='PersistentGathering' AND + g.owner_pid=$1 + LIMIT $2 OFFSET $3`, + uint64(ownerPID), + uint32(resultRange.Length), + uint32(resultRange.Offset), + ) + if err != nil { + return nil, nex.NewError(nex.ResultCodes.Core.Unknown, err.Error()) + } + + for rows.Next() { + var gatheringID uint32 + var ownerPID uint64 + var hostPID uint64 + var minimumParticipants uint16 + var maximumParticipants uint16 + var participationPolicy uint32 + var policyArgument uint32 + var flags uint32 + var state uint32 + var description string + var communityType uint32 + var password string + var resultAttribs []uint32 + var applicationBuffer []byte + var resultParticipationStartDate time.Time + var resultParticipationEndDate time.Time + + err = rows.Scan( + &gatheringID, + &ownerPID, + &hostPID, + &minimumParticipants, + &maximumParticipants, + &participationPolicy, + &policyArgument, + &flags, + &state, + &description, + &communityType, + &password, + pqextended.Array(&resultAttribs), + &applicationBuffer, + &resultParticipationStartDate, + &resultParticipationEndDate, + ) + if err != nil { + common_globals.Logger.Critical(err.Error()) + continue + } + + resultPersistentGathering := match_making_types.NewPersistentGathering() + resultPersistentGathering.ID = types.NewUInt32(gatheringID) + + resultMatchmakeSessionCount, nexError := GetPersistentGatheringSessionCount(manager, uint32(resultPersistentGathering.ID)) + if nexError != nil { + common_globals.Logger.Critical(nexError.Error()) + continue + } + + resultParticipationCount, nexError := GetPersistentGatheringParticipationCount(manager, uint32(resultPersistentGathering.ID), uint64(sourcePID)) + if nexError != nil { + common_globals.Logger.Critical(nexError.Error()) + continue + } + + resultPersistentGathering.OwnerPID = types.NewPID(ownerPID) + resultPersistentGathering.HostPID = types.NewPID(hostPID) + resultPersistentGathering.MinimumParticipants = types.NewUInt16(minimumParticipants) + resultPersistentGathering.MaximumParticipants = types.NewUInt16(maximumParticipants) + resultPersistentGathering.ParticipationPolicy = types.NewUInt32(participationPolicy) + resultPersistentGathering.PolicyArgument = types.NewUInt32(policyArgument) + resultPersistentGathering.Flags = types.NewUInt32(flags) + resultPersistentGathering.State = types.NewUInt32(state) + resultPersistentGathering.Description = types.NewString(description) + resultPersistentGathering.CommunityType = types.NewUInt32(communityType) + resultPersistentGathering.Password = types.NewString(password) + + attributesSlice := make([]types.UInt32, len(resultAttribs)) + for i, value := range resultAttribs { + attributesSlice[i] = types.NewUInt32(value) + } + resultPersistentGathering.Attribs = attributesSlice + + resultPersistentGathering.ApplicationBuffer = applicationBuffer + resultPersistentGathering.ParticipationStartDate = resultPersistentGathering.ParticipationStartDate.FromTimestamp(resultParticipationStartDate) + resultPersistentGathering.ParticipationEndDate = resultPersistentGathering.ParticipationEndDate.FromTimestamp(resultParticipationEndDate) + resultPersistentGathering.MatchmakeSessionCount = types.NewUInt32(resultMatchmakeSessionCount) + resultPersistentGathering.ParticipationCount = types.NewUInt32(resultParticipationCount) + + persistentGatherings = append(persistentGatherings, resultPersistentGathering) + } + + rows.Close() + + return persistentGatherings, nil +} diff --git a/matchmake-extension/database/get_simple_communities.go b/matchmake-extension/database/get_simple_communities.go new file mode 100644 index 0000000..5abbe90 --- /dev/null +++ b/matchmake-extension/database/get_simple_communities.go @@ -0,0 +1,57 @@ +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_types "github.com/PretendoNetwork/nex-protocols-go/v2/match-making/types" + pqextended "github.com/PretendoNetwork/pq-extended" +) + +// GetSimpleCommunities returns a slice of SimpleCommunity using information from the given gathering IDs +func GetSimpleCommunities(manager *common_globals.MatchmakingManager, gatheringIDList []uint32) ([]match_making_types.SimpleCommunity, *nex.Error) { + simpleCommunities := make([]match_making_types.SimpleCommunity, 0) + + rows, err := manager.Database.Query(`SELECT + pg.id + FROM matchmaking.persistent_gatherings AS pg + INNER JOIN matchmaking.gatherings AS g ON g.id = pg.id + WHERE + g.registered=true AND + g.type='PersistentGathering' AND + pg.id=ANY($1)`, + pqextended.Array(gatheringIDList), + ) + if err != nil { + return nil, nex.NewError(nex.ResultCodes.Core.Unknown, err.Error()) + } + + for rows.Next() { + resultSimpleCommunity := match_making_types.NewSimpleCommunity() + var gatheringID uint32 + var resultMatchmakeSessionCount uint32 + + err = rows.Scan( + &gatheringID, + ) + if err != nil { + common_globals.Logger.Critical(err.Error()) + continue + } + + resultMatchmakeSessionCount, nexError := GetPersistentGatheringSessionCount(manager, gatheringID) + if nexError != nil { + common_globals.Logger.Critical(nexError.Error()) + continue + } + + resultSimpleCommunity.GatheringID = types.NewUInt32(gatheringID) + resultSimpleCommunity.MatchmakeSessionCount = types.NewUInt32(resultMatchmakeSessionCount) + + simpleCommunities = append(simpleCommunities, resultSimpleCommunity) + } + + rows.Close() + + return simpleCommunities, nil +} diff --git a/matchmake-extension/database/join_matchmake_session.go b/matchmake-extension/database/join_matchmake_session.go new file mode 100644 index 0000000..43cf59b --- /dev/null +++ b/matchmake-extension/database/join_matchmake_session.go @@ -0,0 +1,41 @@ +package database + +import ( + "github.com/PretendoNetwork/nex-go/v2" + 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/tracking" + match_making_types "github.com/PretendoNetwork/nex-protocols-go/v2/match-making/types" +) + +// JoinMatchmakeSession joins participants from the same connection into a MatchmakeSession. Returns the new number of participants +func JoinMatchmakeSession(manager *common_globals.MatchmakingManager, matchmakeSession match_making_types.MatchmakeSession, connection *nex.PRUDPConnection, vacantParticipants uint16, joinMessage string) (uint32, *nex.Error) { + newParticipants, nexError := match_making_database.JoinGathering(manager, uint32(matchmakeSession.ID), connection, vacantParticipants, joinMessage) + if nexError != nil { + return 0, nexError + } + + // TODO - Should we return the error in these cases? + if matchmakeSession.MatchmakeSystemType == 5 { // * Attached to a persistent gathering + persistentGatheringID := uint32(matchmakeSession.Attributes[0]) + _, nexError = GetPersistentGatheringByID(manager, connection.PID(), persistentGatheringID) + if nexError != nil { + common_globals.Logger.Error(nexError.Error()) + return newParticipants, nil + } + + participationCount, nexError := UpdatePersistentGatheringParticipationCount(manager, connection.PID(), persistentGatheringID) + if nexError != nil { + common_globals.Logger.Error(nexError.Error()) + return newParticipants, nil + } + + nexError = tracking.LogParticipateCommunity(manager.Database, connection.PID(), persistentGatheringID, uint32(matchmakeSession.ID), participationCount) + if nexError != nil { + common_globals.Logger.Error(nexError.Error()) + return newParticipants, nil + } + } + + return newParticipants, nil +} diff --git a/matchmake-extension/database/join_matchmake_session_with_participants.go b/matchmake-extension/database/join_matchmake_session_with_participants.go new file mode 100644 index 0000000..4a7223a --- /dev/null +++ b/matchmake-extension/database/join_matchmake_session_with_participants.go @@ -0,0 +1,47 @@ +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" + "github.com/PretendoNetwork/nex-protocols-common-go/v2/matchmake-extension/tracking" + "github.com/PretendoNetwork/nex-protocols-go/v2/match-making/constants" + match_making_types "github.com/PretendoNetwork/nex-protocols-go/v2/match-making/types" +) + +// JoinMatchmakeSessionWithParticipants joins participants into a gathering. Returns the new number of participants +func JoinMatchmakeSessionWithParticipants(manager *common_globals.MatchmakingManager, matchmakeSession match_making_types.MatchmakeSession, connection *nex.PRUDPConnection, additionalParticipants []types.PID, joinMessage string, joinMatchmakeSessionBehavior constants.JoinMatchmakeSessionBehavior) (uint32, *nex.Error) { + newParticipants, nexError := match_making_database.JoinGatheringWithParticipants(manager, uint32(matchmakeSession.ID), connection, additionalParticipants, joinMessage, joinMatchmakeSessionBehavior) + if nexError != nil { + return 0, nexError + } + + // TODO - Should we return the error in these cases? + if matchmakeSession.MatchmakeSystemType == 5 { // * Attached to a persistent gathering + persistentGatheringID := uint32(matchmakeSession.Attributes[0]) + participantList := append(additionalParticipants, connection.PID()) + for _, participant := range participantList { + _, nexError = GetPersistentGatheringByID(manager, participant, persistentGatheringID) + if nexError != nil { + common_globals.Logger.Error(nexError.Error()) + continue + } + + participationCount, nexError := UpdatePersistentGatheringParticipationCount(manager, participant, persistentGatheringID) + if nexError != nil { + common_globals.Logger.Error(nexError.Error()) + continue + } + + nexError = tracking.LogParticipateCommunity(manager.Database, participant, persistentGatheringID, uint32(matchmakeSession.ID), participationCount) + if nexError != nil { + common_globals.Logger.Error(nexError.Error()) + continue + } + } + + } + + return newParticipants, nil +} diff --git a/matchmake-extension/database/update_persistent_gathering_participation_count.go b/matchmake-extension/database/update_persistent_gathering_participation_count.go new file mode 100644 index 0000000..60a07f8 --- /dev/null +++ b/matchmake-extension/database/update_persistent_gathering_participation_count.go @@ -0,0 +1,30 @@ +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" +) + +// UpdatePersistentGatheringParticipationCount updates the participation count of a user in a persistent gathering. Returns the participation count +func UpdatePersistentGatheringParticipationCount(manager *common_globals.MatchmakingManager, userPID types.PID, gatheringID uint32) (uint32, *nex.Error) { + var participationCount uint32 + err := manager.Database.QueryRow(`INSERT INTO matchmaking.community_participations AS cp ( + user_pid, + gathering_id, + participation_count + ) VALUES ( + $1, + $2, + 1 + ) ON CONFLICT (user_pid, gathering_id) DO UPDATE SET + participation_count=cp.participation_count+1 RETURNING participation_count`, + uint64(userPID), + gatheringID, + ).Scan(&participationCount) + if err != nil { + return 0, nex.NewError(nex.ResultCodes.Core.Unknown, err.Error()) + } + + return participationCount, nil +} diff --git a/matchmake-extension/find_community_by_gathering_id.go b/matchmake-extension/find_community_by_gathering_id.go new file mode 100644 index 0000000..195e6f5 --- /dev/null +++ b/matchmake-extension/find_community_by_gathering_id.go @@ -0,0 +1,61 @@ +package matchmake_extension + +import ( + "github.com/PretendoNetwork/nex-go/v2" + "github.com/PretendoNetwork/nex-go/v2/types" + match_making_types "github.com/PretendoNetwork/nex-protocols-go/v2/match-making/types" + common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals" + "github.com/PretendoNetwork/nex-protocols-common-go/v2/matchmake-extension/database" + matchmake_extension "github.com/PretendoNetwork/nex-protocols-go/v2/matchmake-extension" +) + +func (commonProtocol *CommonProtocol) findCommunityByGatheringID(err error, packet nex.PacketInterface, callID uint32, lstGID types.List[types.UInt32]) (*nex.RMCMessage, *nex.Error) { + if err != nil { + common_globals.Logger.Error(err.Error()) + return nil, nex.NewError(nex.ResultCodes.Core.InvalidArgument, "change_error") + } + + connection := packet.Sender().(*nex.PRUDPConnection) + endpoint := connection.Endpoint().(*nex.PRUDPEndPoint) + + commonProtocol.manager.Mutex.RLock() + + var gatheringIDs []uint32 + for _, gatheringID := range lstGID { + gatheringIDs = append(gatheringIDs, uint32(gatheringID)) + } + + communities, nexError := database.GetPersistentGatheringsByID(commonProtocol.manager, connection.PID(), gatheringIDs) + if nexError != nil { + commonProtocol.manager.Mutex.RUnlock() + return nil, nexError + } + + commonProtocol.manager.Mutex.RUnlock() + + lstCommunity := types.NewList[match_making_types.PersistentGathering]() + + for _, community := range communities { + // * Scrap persistent gathering password + community.Password = "" + } + + lstCommunity = communities + + rmcResponseStream := nex.NewByteStreamOut(endpoint.LibraryVersions(), endpoint.ByteStreamSettings()) + + lstCommunity.WriteTo(rmcResponseStream) + + rmcResponseBody := rmcResponseStream.Bytes() + + rmcResponse := nex.NewRMCSuccess(endpoint, rmcResponseBody) + rmcResponse.ProtocolID = matchmake_extension.ProtocolID + rmcResponse.MethodID = matchmake_extension.MethodFindCommunityByGatheringID + rmcResponse.CallID = callID + + if commonProtocol.OnAfterFindCommunityByGatheringID != nil { + go commonProtocol.OnAfterFindCommunityByGatheringID(packet, lstGID) + } + + return rmcResponse, nil +} diff --git a/matchmake-extension/find_community_by_participant.go b/matchmake-extension/find_community_by_participant.go new file mode 100644 index 0000000..88c809f --- /dev/null +++ b/matchmake-extension/find_community_by_participant.go @@ -0,0 +1,56 @@ +package matchmake_extension + +import ( + "github.com/PretendoNetwork/nex-go/v2" + "github.com/PretendoNetwork/nex-go/v2/types" + match_making_types "github.com/PretendoNetwork/nex-protocols-go/v2/match-making/types" + common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals" + "github.com/PretendoNetwork/nex-protocols-common-go/v2/matchmake-extension/database" + matchmake_extension "github.com/PretendoNetwork/nex-protocols-go/v2/matchmake-extension" +) + +func (commonProtocol *CommonProtocol) findCommunityByParticipant(err error, packet nex.PacketInterface, callID uint32, pid types.PID, resultRange types.ResultRange) (*nex.RMCMessage, *nex.Error) { + if err != nil { + common_globals.Logger.Error(err.Error()) + return nil, nex.NewError(nex.ResultCodes.Core.InvalidArgument, "change_error") + } + + connection := packet.Sender().(*nex.PRUDPConnection) + endpoint := connection.Endpoint().(*nex.PRUDPEndPoint) + + commonProtocol.manager.Mutex.RLock() + + communities, nexError := database.GetPersistentGatheringsByOwnerPID(commonProtocol.manager, connection.PID(), pid, resultRange) + if nexError != nil { + commonProtocol.manager.Mutex.RUnlock() + return nil, nexError + } + + commonProtocol.manager.Mutex.RUnlock() + + lstCommunity := types.NewList[match_making_types.PersistentGathering]() + + for _, community := range communities { + // * Scrap persistent gathering password + community.Password = "" + } + + lstCommunity = communities + + rmcResponseStream := nex.NewByteStreamOut(endpoint.LibraryVersions(), endpoint.ByteStreamSettings()) + + lstCommunity.WriteTo(rmcResponseStream) + + rmcResponseBody := rmcResponseStream.Bytes() + + rmcResponse := nex.NewRMCSuccess(endpoint, rmcResponseBody) + rmcResponse.ProtocolID = matchmake_extension.ProtocolID + rmcResponse.MethodID = matchmake_extension.MethodFindCommunityByParticipant + rmcResponse.CallID = callID + + if commonProtocol.OnAfterFindCommunityByParticipant != nil { + go commonProtocol.OnAfterFindCommunityByParticipant(packet, pid, resultRange) + } + + return rmcResponse, nil +} diff --git a/matchmake-extension/find_official_community.go b/matchmake-extension/find_official_community.go new file mode 100644 index 0000000..78549f3 --- /dev/null +++ b/matchmake-extension/find_official_community.go @@ -0,0 +1,56 @@ +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" + "github.com/PretendoNetwork/nex-protocols-common-go/v2/matchmake-extension/database" + matchmake_extension "github.com/PretendoNetwork/nex-protocols-go/v2/matchmake-extension" + match_making_types "github.com/PretendoNetwork/nex-protocols-go/v2/match-making/types" +) + +func (commonProtocol *CommonProtocol) findOfficialCommunity(err error, packet nex.PacketInterface, callID uint32, isAvailableOnly types.Bool, resultRange types.ResultRange) (*nex.RMCMessage, *nex.Error) { + if err != nil { + common_globals.Logger.Error(err.Error()) + return nil, nex.NewError(nex.ResultCodes.Core.InvalidArgument, "change_error") + } + + connection := packet.Sender().(*nex.PRUDPConnection) + endpoint := connection.Endpoint().(*nex.PRUDPEndPoint) + + commonProtocol.manager.Mutex.RLock() + + communities, nexError := database.GetOfficialCommunities(commonProtocol.manager, connection.PID(), bool(isAvailableOnly), resultRange) + if nexError != nil { + commonProtocol.manager.Mutex.RUnlock() + return nil, nexError + } + + commonProtocol.manager.Mutex.RUnlock() + + lstCommunity := types.NewList[match_making_types.PersistentGathering]() + + for _, community := range communities { + // * Scrap persistent gathering password + community.Password = "" + } + + lstCommunity = communities + + rmcResponseStream := nex.NewByteStreamOut(endpoint.LibraryVersions(), endpoint.ByteStreamSettings()) + + lstCommunity.WriteTo(rmcResponseStream) + + rmcResponseBody := rmcResponseStream.Bytes() + + rmcResponse := nex.NewRMCSuccess(endpoint, rmcResponseBody) + rmcResponse.ProtocolID = matchmake_extension.ProtocolID + rmcResponse.MethodID = matchmake_extension.MethodFindOfficialCommunity + rmcResponse.CallID = callID + + if commonProtocol.OnAfterFindOfficialCommunity != nil { + go commonProtocol.OnAfterFindOfficialCommunity(packet, isAvailableOnly, resultRange) + } + + return rmcResponse, nil +} diff --git a/matchmake-extension/get_simple_community.go b/matchmake-extension/get_simple_community.go new file mode 100644 index 0000000..a5d54b7 --- /dev/null +++ b/matchmake-extension/get_simple_community.go @@ -0,0 +1,55 @@ +package matchmake_extension + +import ( + "github.com/PretendoNetwork/nex-go/v2" + "github.com/PretendoNetwork/nex-go/v2/types" + match_making_types "github.com/PretendoNetwork/nex-protocols-go/v2/match-making/types" + common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals" + "github.com/PretendoNetwork/nex-protocols-common-go/v2/matchmake-extension/database" + matchmake_extension "github.com/PretendoNetwork/nex-protocols-go/v2/matchmake-extension" +) + +func (commonProtocol *CommonProtocol) getSimpleCommunity(err error, packet nex.PacketInterface, callID uint32, gatheringIDList types.List[types.UInt32]) (*nex.RMCMessage, *nex.Error) { + if err != nil { + common_globals.Logger.Error(err.Error()) + return nil, nex.NewError(nex.ResultCodes.Core.InvalidArgument, "change_error") + } + + connection := packet.Sender().(*nex.PRUDPConnection) + endpoint := connection.Endpoint().(*nex.PRUDPEndPoint) + + var gatheringIDs []uint32 + for _, gatheringID := range gatheringIDList { + gatheringIDs = append(gatheringIDs, uint32(gatheringID)) + } + + commonProtocol.manager.Mutex.RLock() + + simpleCommunities, nexError := database.GetSimpleCommunities(commonProtocol.manager, gatheringIDs) + if nexError != nil { + commonProtocol.manager.Mutex.RUnlock() + return nil, nexError + } + + commonProtocol.manager.Mutex.RUnlock() + + lstSimpleCommunityList := types.NewList[match_making_types.SimpleCommunity]() + lstSimpleCommunityList = simpleCommunities + + rmcResponseStream := nex.NewByteStreamOut(endpoint.LibraryVersions(), endpoint.ByteStreamSettings()) + + lstSimpleCommunityList.WriteTo(rmcResponseStream) + + rmcResponseBody := rmcResponseStream.Bytes() + + rmcResponse := nex.NewRMCSuccess(endpoint, rmcResponseBody) + rmcResponse.ProtocolID = matchmake_extension.ProtocolID + rmcResponse.MethodID = matchmake_extension.MethodGetSimpleCommunity + rmcResponse.CallID = callID + + if commonProtocol.OnAfterGetSimpleCommunity != nil { + go commonProtocol.OnAfterGetSimpleCommunity(packet, gatheringIDList) + } + + return rmcResponse, nil +} diff --git a/matchmake-extension/join_matchmake_session.go b/matchmake-extension/join_matchmake_session.go index ff8d27c..7d11075 100644 --- a/matchmake-extension/join_matchmake_session.go +++ b/matchmake-extension/join_matchmake_session.go @@ -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" matchmake_extension "github.com/PretendoNetwork/nex-protocols-go/v2/matchmake-extension" ) @@ -49,7 +48,7 @@ func (commonProtocol *CommonProtocol) joinMatchmakeSession(err error, packet nex return nil, nexError } - _, nexError = match_making_database.JoinGathering(commonProtocol.manager, uint32(joinedMatchmakeSession.Gathering.ID), connection, 1, string(strMessage)) + _, nexError = database.JoinMatchmakeSession(commonProtocol.manager, joinedMatchmakeSession, connection, 1, string(strMessage)) if nexError != nil { common_globals.Logger.Error(nexError.Error()) commonProtocol.manager.Mutex.Unlock() diff --git a/matchmake-extension/join_matchmake_session_ex.go b/matchmake-extension/join_matchmake_session_ex.go index 6b472e2..1aa6502 100644 --- a/matchmake-extension/join_matchmake_session_ex.go +++ b/matchmake-extension/join_matchmake_session_ex.go @@ -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" matchmake_extension "github.com/PretendoNetwork/nex-protocols-go/v2/matchmake-extension" ) @@ -49,7 +48,7 @@ func (commonProtocol *CommonProtocol) joinMatchmakeSessionEx(err error, packet n return nil, nexError } - _, nexError = match_making_database.JoinGathering(commonProtocol.manager, uint32(joinedMatchmakeSession.Gathering.ID), connection, uint16(participationCount), string(strMessage)) + _, nexError = database.JoinMatchmakeSession(commonProtocol.manager, joinedMatchmakeSession, connection, uint16(participationCount), string(strMessage)) if nexError != nil { common_globals.Logger.Error(nexError.Error()) commonProtocol.manager.Mutex.Unlock() diff --git a/matchmake-extension/join_matchmake_session_with_param.go b/matchmake-extension/join_matchmake_session_with_param.go index b688e37..2e5019d 100644 --- a/matchmake-extension/join_matchmake_session_with_param.go +++ b/matchmake-extension/join_matchmake_session_with_param.go @@ -3,7 +3,6 @@ package matchmake_extension import ( "github.com/PretendoNetwork/nex-go/v2" 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" @@ -63,7 +62,7 @@ func (commonProtocol *CommonProtocol) joinMatchmakeSessionWithParam(err error, p return nil, nexError } - _, nexError = match_making_database.JoinGatheringWithParticipants(commonProtocol.manager, uint32(joinedMatchmakeSession.Gathering.ID), connection, joinMatchmakeSessionParam.AdditionalParticipants, string(joinMatchmakeSessionParam.JoinMessage), constants.JoinMatchmakeSessionBehavior(joinMatchmakeSessionParam.JoinMatchmakeSessionBehavior)) + _, nexError = database.JoinMatchmakeSessionWithParticipants(commonProtocol.manager, joinedMatchmakeSession, connection, joinMatchmakeSessionParam.AdditionalParticipants, string(joinMatchmakeSessionParam.JoinMessage), constants.JoinMatchmakeSessionBehavior(joinMatchmakeSessionParam.JoinMatchmakeSessionBehavior)) if nexError != nil { common_globals.Logger.Error(nexError.Error()) commonProtocol.manager.Mutex.Unlock() diff --git a/matchmake-extension/protocol.go b/matchmake-extension/protocol.go index 2dda14e..caa0d2e 100644 --- a/matchmake-extension/protocol.go +++ b/matchmake-extension/protocol.go @@ -14,6 +14,7 @@ type CommonProtocol struct { endpoint nex.EndpointInterface protocol matchmake_extension.Interface manager *common_globals.MatchmakingManager + PersistentGatheringCreationMax int CanJoinMatchmakeSession func(manager *common_globals.MatchmakingManager, pid types.PID, matchmakeSession match_making_types.MatchmakeSession) *nex.Error CleanupSearchMatchmakeSession func(matchmakeSession *match_making_types.MatchmakeSession) CleanupMatchmakeSessionSearchCriterias func(searchCriterias types.List[match_making_types.MatchmakeSessionSearchCriteria]) @@ -24,6 +25,10 @@ type CommonProtocol struct { OnAfterAutoMatchmakePostpone func(packet nex.PacketInterface, anyGathering match_making_types.GatheringHolder, message types.String) OnAfterAutoMatchmakeWithParamPostpone func(packet nex.PacketInterface, autoMatchmakeParam match_making_types.AutoMatchmakeParam) OnAfterAutoMatchmakeWithSearchCriteriaPostpone func(packet nex.PacketInterface, lstSearchCriteria types.List[match_making_types.MatchmakeSessionSearchCriteria], anyGathering match_making_types.GatheringHolder, strMessage types.String) + OnAfterCreateCommunity func(packet nex.PacketInterface, community match_making_types.PersistentGathering, strMessage types.String) + OnAfterFindCommunityByGatheringID func(packet nex.PacketInterface, lstGID types.List[types.UInt32]) + OnAfterFindOfficialCommunity func(packet nex.PacketInterface, isAvailableOnly types.Bool, resultRange types.ResultRange) + OnAfterFindCommunityByParticipant func(packet nex.PacketInterface, pid types.PID, resultRange types.ResultRange) OnAfterUpdateProgressScore func(packet nex.PacketInterface, gid types.UInt32, progressScore types.UInt8) OnAfterCreateMatchmakeSessionWithParam func(packet nex.PacketInterface, createMatchmakeSessionParam match_making_types.CreateMatchmakeSessionParam) OnAfterUpdateApplicationBuffer func(packet nex.PacketInterface, gid types.UInt32, applicationBuffer types.Buffer) @@ -32,6 +37,7 @@ type CommonProtocol struct { OnAfterModifyCurrentGameAttribute func(packet nex.PacketInterface, gid types.UInt32, attribIndex types.UInt32, newValue types.UInt32) OnAfterBrowseMatchmakeSession func(packet nex.PacketInterface, searchCriteria match_making_types.MatchmakeSessionSearchCriteria, resultRange types.ResultRange) OnAfterJoinMatchmakeSessionEx func(packet nex.PacketInterface, gid types.UInt32, strMessage types.String, dontCareMyBlockList types.Bool, participationCount types.UInt16) + OnAfterGetSimpleCommunity func(packet nex.PacketInterface, gatheringIDList types.List[types.UInt32]) } // SetDatabase defines the matchmaking manager to be used by the common protocol @@ -81,6 +87,31 @@ func (commonProtocol *CommonProtocol) SetManager(manager *common_globals.Matchma return } + _, err = manager.Database.Exec(`CREATE TABLE IF NOT EXISTS matchmaking.community_participations ( + id bigserial PRIMARY KEY, + user_pid numeric(10), + gathering_id bigint, + participation_count bigint, + UNIQUE (user_pid, gathering_id) + )`) + if err != nil { + common_globals.Logger.Error(err.Error()) + return + } + + _, err = manager.Database.Exec(`CREATE TABLE IF NOT EXISTS tracking.participate_community ( + id bigserial PRIMARY KEY, + date timestamp, + source_pid numeric(10), + community_gid bigint, + gathering_id bigint, + participation_count bigint + )`) + if err != nil { + common_globals.Logger.Error(err.Error()) + return + } + // * In case the server is restarted, unregister any previous matchmake sessions _, err = manager.Database.Exec(`UPDATE matchmaking.gatherings SET registered=false WHERE type='MatchmakeSession'`) if err != nil { @@ -94,6 +125,7 @@ func NewCommonProtocol(protocol matchmake_extension.Interface) *CommonProtocol { commonProtocol := &CommonProtocol{ endpoint: protocol.Endpoint(), protocol: protocol, + PersistentGatheringCreationMax: 4, // * Default of 4 active persistent gatherings per user } protocol.SetHandlerOpenParticipation(commonProtocol.openParticipation) @@ -103,6 +135,10 @@ func NewCommonProtocol(protocol matchmake_extension.Interface) *CommonProtocol { protocol.SetHandlerAutoMatchmakePostpone(commonProtocol.autoMatchmakePostpone) protocol.SetHandlerAutoMatchmakeWithParamPostpone(commonProtocol.autoMatchmakeWithParamPostpone) protocol.SetHandlerAutoMatchmakeWithSearchCriteriaPostpone(commonProtocol.autoMatchmakeWithSearchCriteriaPostpone) + protocol.SetHandlerCreateCommunity(commonProtocol.createCommunity) + protocol.SetHandlerFindCommunityByGatheringID(commonProtocol.findCommunityByGatheringID) + protocol.SetHandlerFindOfficialCommunity(commonProtocol.findOfficialCommunity) + protocol.SetHandlerFindCommunityByParticipant(commonProtocol.findCommunityByParticipant) protocol.SetHandlerUpdateProgressScore(commonProtocol.updateProgressScore) protocol.SetHandlerCreateMatchmakeSessionWithParam(commonProtocol.createMatchmakeSessionWithParam) protocol.SetHandlerUpdateApplicationBuffer(commonProtocol.updateApplicationBuffer) @@ -111,6 +147,7 @@ func NewCommonProtocol(protocol matchmake_extension.Interface) *CommonProtocol { protocol.SetHandlerModifyCurrentGameAttribute(commonProtocol.modifyCurrentGameAttribute) protocol.SetHandlerBrowseMatchmakeSession(commonProtocol.browseMatchmakeSession) protocol.SetHandlerJoinMatchmakeSessionEx(commonProtocol.joinMatchmakeSessionEx) + protocol.SetHandlerGetSimpleCommunity(commonProtocol.getSimpleCommunity) return commonProtocol } diff --git a/matchmake-extension/tracking/log_participate_community.go b/matchmake-extension/tracking/log_participate_community.go new file mode 100644 index 0000000..cf61723 --- /dev/null +++ b/matchmake-extension/tracking/log_participate_community.go @@ -0,0 +1,33 @@ +package tracking + +import ( + "database/sql" + "time" + + "github.com/PretendoNetwork/nex-go/v2" + "github.com/PretendoNetwork/nex-go/v2/types" +) + +// LogParticipateCommunity logs a persistent gathering participation event on the given database +func LogParticipateCommunity(db *sql.DB, sourcePID types.PID, communityGID uint32, gatheringID uint32, participationCount uint32) *nex.Error { + eventTime := time.Now().UTC() + + _, err := db.Exec(`INSERT INTO tracking.participate_community ( + date, + source_pid, + community_gid, + gathering_id, + participation_count + ) VALUES ( + $1, + $2, + $3, + $4, + $5 + )`, eventTime, uint64(sourcePID), communityGID, gatheringID, participationCount) + if err != nil { + return nex.NewError(nex.ResultCodes.Core.Unknown, err.Error()) + } + + return nil +}