feat: implement splatoon ranking functions

This commit is contained in:
Maple Nebel 2026-07-29 00:29:18 +02:00
parent b8ef746870
commit 3ccda9ed26
10 changed files with 265 additions and 9 deletions

View File

@ -0,0 +1,38 @@
package database
import (
"github.com/PretendoNetwork/splatoon/globals"
ranking_splatoon_types "github.com/PretendoNetwork/splatoon/nex/ranking/types"
)
func GetScoreDataEntries(splatfestID uint32) ([]ranking_splatoon_types.CompetitionRankingScoreData, error) {
list := []ranking_splatoon_types.CompetitionRankingScoreData{}
stream, err := globals.Postgres.Query(`SELECT
uploader_pid,
score,
last_updated,
app_data
FROM ranking_splatoon.user_scores WHERE splatfest_id=$1`, uint64(splatfestID))
if err != nil {
return nil, err
}
for stream.Next() {
competitionRankingScoreData := ranking_splatoon_types.NewCompetitionRankingScoreData()
err = stream.Scan(&competitionRankingScoreData.PID, &competitionRankingScoreData.Score, &competitionRankingScoreData.Modified, &competitionRankingScoreData.AppData)
if err != nil {
stream.Close()
return nil, err
}
list = append(list, competitionRankingScoreData)
}
err = stream.Err()
if err != nil {
return nil, err
}
return list, nil
}

View File

@ -0,0 +1,23 @@
package database
import (
"github.com/PretendoNetwork/splatoon/globals"
)
func GetVoteCount(splatfestID uint32, team_id uint8) (uint32, error) {
row := globals.Postgres.QueryRow(`SELECT
COUNT(user_id)
FROM ranking_splatoon.user_scores WHERE splatfest_id=$1 AND team_id=$2`, uint64(splatfestID), int16(team_id))
if row.Err() != nil {
return 0, row.Err()
}
var count uint32
err := row.Scan(&count)
if err != nil {
return 0, err
}
return count, nil
}

View File

@ -0,0 +1,28 @@
package database
import (
"github.com/PretendoNetwork/splatoon/globals"
)
func GetWinCount(splatfestID uint32, team_id uint8) (uint32, error) {
row := globals.Postgres.QueryRow(`SELECT
COUNT(upload_id)
FROM ranking_splatoon.results
WHERE splatfest_id=$1
AND (
(has_won = false AND team_id NOT EQUAL $2) OR
(has_won = true AND team_id=$2)
)`, uint64(splatfestID), int16(team_id))
if row.Err() != nil {
return 0, row.Err()
}
var count uint32
err := row.Scan(&count)
if err != nil {
return 0, err
}
return count, nil
}

View File

@ -0,0 +1,37 @@
package database
import (
"github.com/PretendoNetwork/splatoon/globals"
)
func Setup() {
var err error
_, err = globals.Postgres.Exec(`CREATE SCHEMA IF NOT EXISTS ranking_splatoon`)
if err != nil {
globals.Logger.Error(err.Error())
return
}
_, err = globals.Postgres.Exec(`CREATE TABLE IF NOT EXISTS ranking_splatoon.results (
upload_id bigserial primary key not null,
uploader_pid int8 not null,
splatfest_id int8 not null,
team_id int2 not null,
has_won int2 not null,
app_data bytea not null,
)`)
_, err = globals.Postgres.Exec(`CREATE TABLE IF NOT EXISTS ranking_splatoon.user_scores (
uploader_pid int8 not null,
splatfest_id int8 not null,
score int8 not null,
team_id boolean not null,
user_data bytea not null,
last_updated int8 not null
PRIMARY KEY(uploader_pid, splatfest_id)
)`)
if err != nil {
globals.Logger.Error(err.Error())
return
}
}

View File

@ -0,0 +1,25 @@
package database
import (
"github.com/PretendoNetwork/nex-go/v2/types"
"github.com/PretendoNetwork/splatoon/globals"
ranking_splatoon_types "github.com/PretendoNetwork/splatoon/nex/ranking/types"
)
func StoreUserMatchResult(userPID types.PID, data ranking_splatoon_types.CompetitionRankingUploadScoreParam) error {
var err error
_, err = globals.Postgres.Exec(`
INSERT INTO ranking_splatoon.results(
uploader_pid,
splatfest_id,
score,
team_id,
has_won
) VALUES ($1, $2, $3, $4, $5)
`, int64(userPID), int64(data.SplatfestId), int64(data.Score), int16(data.TeamId), bool(data.HasWon))
if err != nil {
return err
}
return nil
}

View File

@ -0,0 +1,33 @@
package database
import (
"github.com/PretendoNetwork/nex-go/v2/types"
"github.com/PretendoNetwork/splatoon/globals"
ranking_splatoon_types "github.com/PretendoNetwork/splatoon/nex/ranking/types"
)
func StoreUserScore(userPID types.PID, data ranking_splatoon_types.CompetitionRankingUploadScoreParam) error {
var err error
time := types.NewDateTime(0).Now()
_, err = globals.Postgres.Exec(`
INSERT INTO ranking_splatoon.user_scores(
uploader_pid,
splatfest_id,
score,
team_id,
last_updated,
app_data
) VALUES ($1, $2, $3, $4, $5, $6)
ON CONFLICT (uploader_pid, splatfest_id)
DO UPDATE SET
score = EXCLUDED.score,
last_updated = EXCLUDED.last_updated
team_id = EXCLUDED.team_id
app_data = EXCLUDED.app_data
`, int64(userPID), int64(data.SplatfestId), int64(data.Score), int16(data.TeamId), int64(time), ([]byte)(data.AppData))
if err != nil {
return err
}
return nil
}

View File

@ -6,9 +6,54 @@ import (
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals"
ranking "github.com/PretendoNetwork/nex-protocols-go/v2/ranking/splatoon"
"github.com/PretendoNetwork/splatoon/globals"
"github.com/PretendoNetwork/splatoon/nex/ranking/database"
ranking_splatoon_types "github.com/PretendoNetwork/splatoon/nex/ranking/types"
)
func GetSingleCompetitionRankingScore(splatfestID uint32) (ranking_splatoon_types.CompetitionRankingScoreInfo, error) {
var err error
info := ranking_splatoon_types.NewCompetitionRankingScoreInfo()
info.FestId = types.UInt32(splatfestID)
var team_alpha_count uint32
team_alpha_count, err = database.GetVoteCount(splatfestID, 0)
if err != nil {
return info, err
}
info.TeamVotes = append(info.TeamVotes, types.UInt32(team_alpha_count))
var team_bravo_count uint32
team_bravo_count, err = database.GetVoteCount(splatfestID, 1)
if err != nil {
return info, err
}
info.TeamVotes = append(info.TeamVotes, types.UInt32(team_bravo_count))
var team_alpha_wins uint32
team_alpha_wins, err = database.GetWinCount(splatfestID, 0)
if err != nil {
return info, err
}
info.TeamWins = append(info.TeamWins, types.UInt32(team_alpha_wins))
var team_bravo_wins uint32
team_bravo_wins, err = database.GetVoteCount(splatfestID, 1)
if err != nil {
return info, err
}
info.TeamWins = append(info.TeamWins, types.UInt32(team_bravo_wins))
var scoreDataEntries []ranking_splatoon_types.CompetitionRankingScoreData
scoreDataEntries, err = database.GetScoreDataEntries(splatfestID)
if err != nil {
return info, err
}
info.ScoreData = scoreDataEntries
return info, nil
}
func GetCompetitionRankingScore(err error, packet nex.PacketInterface, callID uint32, packetPayload []byte) (*nex.RMCMessage, *nex.Error) {
rmcResponseStream := nex.NewByteStreamOut(globals.SecureServer.LibraryVersions, globals.SecureServer.ByteStreamSettings)
@ -24,8 +69,19 @@ func GetCompetitionRankingScore(err error, packet nex.PacketInterface, callID ui
return nil, nex.NewError(nex.ResultCodes.Core.InvalidArgument, err.Error())
}
// todo: actually implement this function
retVal := types.NewList[ranking_splatoon_types.CompetitionRankingScoreInfo]()
var festListIndex uint32
for festListIndex = 0; festListIndex < uint32(params.ResultRange.Length); festListIndex++ {
if festListIndex >= uint32(len(params.FestivalIds)) {
return nil, nex.NewError(nex.ResultCodes.Core.InvalidArgument, "out of resultrange is out of bounds")
}
festId := params.FestivalIds[festListIndex+uint32(params.ResultRange.Offset)]
info, err := GetSingleCompetitionRankingScore(uint32(festId))
if err != nil {
return nil, nex.NewError(nex.ResultCodes.Core.Exception, "error retrieving ranking scores")
}
retVal = append(retVal, info)
}
retVal.WriteTo(rmcResponseStream)

View File

@ -16,7 +16,7 @@ type CompetitionRankingUploadScoreParam struct {
Unknown2 types.UInt32
Score types.UInt32
TeamId types.UInt8
TeamWin types.UInt8
HasWon types.Bool
IsFirstUpload types.Bool
AppData types.QBuffer
}
@ -30,7 +30,7 @@ func (crusp CompetitionRankingUploadScoreParam) WriteTo(writable types.Writable)
crusp.Unknown2.WriteTo(contentWritable)
crusp.Score.WriteTo(contentWritable)
crusp.TeamId.WriteTo(contentWritable)
crusp.TeamWin.WriteTo(contentWritable)
crusp.HasWon.WriteTo(contentWritable)
crusp.IsFirstUpload.WriteTo(contentWritable)
crusp.AppData.WriteTo(contentWritable)
@ -62,7 +62,7 @@ func (crusp CompetitionRankingUploadScoreParam) ExtractFrom(readable types.Reada
if err := crusp.TeamId.ExtractFrom(readable); err != nil {
return fmt.Errorf("failed to extract CompetitionRankingUploadScoreParam.TeamId. %s", err.Error())
}
if err := crusp.TeamWin.ExtractFrom(readable); err != nil {
if err := crusp.HasWon.ExtractFrom(readable); err != nil {
return fmt.Errorf("failed to extract CompetitionRankingUploadScoreParam.TeamWin. %s", err.Error())
}
if err := crusp.IsFirstUpload.ExtractFrom(readable); err != nil {
@ -85,7 +85,7 @@ func (crusp CompetitionRankingUploadScoreParam) Copy() types.RVType {
copied.Unknown2 = crusp.Unknown2.Copy().(types.UInt32)
copied.Score = crusp.Score.Copy().(types.UInt32)
copied.TeamId = crusp.TeamId.Copy().(types.UInt8)
copied.TeamWin = crusp.TeamWin.Copy().(types.UInt8)
copied.HasWon = crusp.HasWon.Copy().(types.Bool)
copied.IsFirstUpload = crusp.IsFirstUpload.Copy().(types.Bool)
copied.AppData = crusp.AppData.Copy().(types.QBuffer)
@ -119,7 +119,7 @@ func (crusp CompetitionRankingUploadScoreParam) Equals(o types.RVType) bool {
if !crusp.TeamId.Equals(other.TeamId) {
return false
}
if !crusp.TeamWin.Equals(other.TeamWin) {
if !crusp.HasWon.Equals(other.HasWon) {
return false
}
if !crusp.IsFirstUpload.Equals(other.IsFirstUpload) {
@ -161,7 +161,7 @@ func (crusp CompetitionRankingUploadScoreParam) FormatToString(indentationLevel
fmt.Fprintf(&b, "%sUnknown2: %s,\n", indentationValues, crusp.Unknown2)
fmt.Fprintf(&b, "%sScore: %s,\n", indentationValues, crusp.Score)
fmt.Fprintf(&b, "%sTeamId: %s,\n", indentationValues, crusp.TeamId)
fmt.Fprintf(&b, "%sTeamWin: %s,\n", indentationValues, crusp.TeamWin)
fmt.Fprintf(&b, "%sHasWon: %s,\n", indentationValues, crusp.HasWon)
fmt.Fprintf(&b, "%sIsFirstUpload: %s,\n", indentationValues, crusp.IsFirstUpload)
fmt.Fprintf(&b, "%sAppData: %s,\n", indentationValues, crusp.AppData)
fmt.Fprintf(&b, "%s}", indentationEnd)
@ -177,7 +177,7 @@ func NewCompetitionRankingUploadScoreParam() CompetitionRankingUploadScoreParam
Unknown2: types.NewUInt32(0),
Score: types.NewUInt32(0),
TeamId: types.NewUInt8(0),
TeamWin: types.NewUInt8(0),
HasWon: types.NewBool(false),
IsFirstUpload: types.NewBool(false),
AppData: types.NewQBuffer([]byte{}),
}

View File

@ -6,6 +6,7 @@ import (
common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals"
ranking "github.com/PretendoNetwork/nex-protocols-go/v2/ranking/splatoon"
"github.com/PretendoNetwork/splatoon/globals"
"github.com/PretendoNetwork/splatoon/nex/ranking/database"
ranking_splatoon_types "github.com/PretendoNetwork/splatoon/nex/ranking/types"
)
@ -24,7 +25,10 @@ func UploadCompetitionRankingScore(err error, packet nex.PacketInterface, callID
return nil, nex.NewError(nex.ResultCodes.Core.InvalidArgument, err.Error())
}
types.NewBool(false).WriteTo(rmcResponseStream)
database.StoreUserMatchResult(packet.Sender().PID(), params)
database.StoreUserScore(packet.Sender().PID(), params)
types.NewBool(true).WriteTo(rmcResponseStream)
rmcResponse := nex.NewRMCSuccess(globals.SecureEndpoint, rmcResponseStream.Bytes())
rmcResponse.ProtocolID = ranking.ProtocolID

View File

@ -1,6 +1,9 @@
package nex
import (
"slices"
"github.com/PretendoNetwork/nex-go/v2"
"github.com/PretendoNetwork/nex-go/v2/types"
commonmatchmaking "github.com/PretendoNetwork/nex-protocols-common-go/v2/match-making"
commonmatchmakingext "github.com/PretendoNetwork/nex-protocols-common-go/v2/match-making-ext"
@ -17,6 +20,7 @@ import (
secure "github.com/PretendoNetwork/nex-protocols-go/v2/secure-connection"
"github.com/PretendoNetwork/splatoon/globals"
ranking_impl "github.com/PretendoNetwork/splatoon/nex/ranking"
"github.com/PretendoNetwork/splatoon/nex/ranking/database"
)
func CreateReportDBRecord(_ types.PID, _ types.UInt32, _ types.QBuffer) error {
@ -29,11 +33,19 @@ func cleanupMatchmakeSessionSearchCriteriasHandler(searchCriterias types.List[ma
}
}
func adjustPublicStationFirst(packet nex.PacketInterface, urls types.List[types.StationURL], dataHolder types.DataHolder) {
connection := packet.Sender().(*nex.PRUDPConnection)
slices.Reverse(connection.StationURLs)
}
func registerCommonSecureServerProtocols() {
database.Setup()
secureProtocol := secure.NewProtocol()
globals.SecureEndpoint.RegisterServiceProtocol(secureProtocol)
commonSecureProtocol := commonsecure.NewCommonProtocol(secureProtocol)
commonSecureProtocol.EnableInsecureRegister()
commonSecureProtocol.OnAfterRegisterEx = adjustPublicStationFirst
commonSecureProtocol.CreateReportDBRecord = CreateReportDBRecord
natTraversalProtocol := nattraversal.NewProtocol()