From 4e94cdde4a1ef802126f3929e94034717226cf3c Mon Sep 17 00:00:00 2001 From: mkwcat <26661008+mkwcat@users.noreply.github.com> Date: Wed, 11 Sep 2024 21:22:30 -0400 Subject: [PATCH] QR2: Add MKW course and engine class to groups API --- gpcm/report.go | 8 ++ qr2/group.go | 243 ++++++++++------------------------------------ qr2/group_info.go | 224 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 285 insertions(+), 190 deletions(-) create mode 100644 qr2/group_info.go diff --git a/gpcm/report.go b/gpcm/report.go index 3df6a8c..8a174b0 100644 --- a/gpcm/report.go +++ b/gpcm/report.go @@ -77,6 +77,14 @@ func (g *GameSpySession) handleWWFCReport(command common.GameSpyCommand) { profileId := g.User.ProfileId logging.Warn(g.ModuleName, "Kicking", aurora.BrightCyan(strconv.FormatUint(uint64(profileId), 10)), fmt.Sprintf("for dropping too many frames (%d)", framesDropped)) kickPlayer(profileId, "too_many_frames_dropped") + + case "mkw_select_course", "mkw_select_cc": + if g.GameName != "mariokartwii" { + logging.Warn(g.ModuleName, "Ignoring mkw_select_* from wrong game") + continue + } + + qr2.ProcessMKWSelectRecord(g.User.ProfileId, key, value) } } } diff --git a/qr2/group.go b/qr2/group.go index ac02801..bcb3b3e 100644 --- a/qr2/group.go +++ b/qr2/group.go @@ -6,7 +6,6 @@ import ( "encoding/gob" "fmt" "os" - "sort" "strconv" "strings" "time" @@ -26,6 +25,10 @@ type Group struct { LastJoinIndex int server *Session players map[*Session]bool + + MKWRaceNumber int + MKWCourseID int + MKWEngineClassID int } var groups = map[string]*Group{} @@ -460,204 +463,64 @@ func (g *Group) updateMatchType() { g.MatchType = g.server.Data["dwc_mtype"] } -type MiiInfo struct { - MiiData string `json:"data"` - MiiName string `json:"name"` -} - -type PlayerInfo struct { - Count string `json:"count"` - ProfileID string `json:"pid"` - InGameName string `json:"name"` - ConnMap string `json:"conn_map"` - ConnFail string `json:"conn_fail"` - Suspend string `json:"suspend"` - - // Mario Kart Wii-specific fields - FriendCode string `json:"fc,omitempty"` - VersusELO string `json:"ev,omitempty"` - BattleELO string `json:"eb,omitempty"` - Mii []MiiInfo `json:"mii,omitempty"` -} - -type GroupInfo struct { - GroupName string `json:"id"` - GameName string `json:"game"` - CreateTime time.Time `json:"created"` - MatchType string `json:"type"` - Suspend bool `json:"suspend"` - ServerIndex string `json:"host,omitempty"` - MKWRegion string `json:"rk,omitempty"` - Players map[string]PlayerInfo `json:"players"` - - PlayersRaw map[string]map[string]string `json:"-"` - SortedJoinIndex []string `json:"-"` -} - -func getGroupsRaw(gameNames []string, groupNames []string) []GroupInfo { - var groupsCopy []GroupInfo +func ProcessMKWSelectRecord(profileId uint32, key string, value string) { + moduleName := "QR2:MKWSelectRecord:" + strconv.FormatUint(uint64(profileId), 10) mutex.Lock() - defer mutex.Unlock() - - for _, group := range groups { - if len(gameNames) > 0 && !common.StringInSlice(group.GameName, gameNames) { - continue - } - - if len(groupNames) > 0 && !common.StringInSlice(group.GroupName, groupNames) { - continue - } - - groupInfo := GroupInfo{ - GroupName: group.GroupName, - GameName: group.GameName, - CreateTime: group.CreateTime, - MatchType: "", - Suspend: true, - ServerIndex: "", - MKWRegion: "", - Players: map[string]PlayerInfo{}, - PlayersRaw: map[string]map[string]string{}, - SortedJoinIndex: []string{}, - } - - if group.MatchType == "0" || group.MatchType == "1" { - groupInfo.MatchType = "anybody" - } else if group.MatchType == "2" || group.MatchType == "3" { - groupInfo.MatchType = "private" - } else { - groupInfo.MatchType = "unknown" - } - - if group.server != nil { - groupInfo.ServerIndex = group.server.Data["+joinindex"] - } - - if groupInfo.GameName == "mariokartwii" { - groupInfo.MKWRegion = group.MKWRegion - } - - for session := range group.players { - mapData := map[string]string{} - for k, v := range session.Data { - mapData[k] = v - } - - if login := session.login; login != nil { - mapData["+ingamesn"] = login.InGameName - } else { - mapData["+ingamesn"] = "" - } - - groupInfo.PlayersRaw[mapData["+joinindex"]] = mapData - - if mapData["dwc_hoststate"] == "2" && mapData["dwc_suspend"] == "0" { - groupInfo.Suspend = false - } - - // Add the join index to the sorted list - myJoinIndex, _ := strconv.Atoi(mapData["+joinindex"]) - added := false - - for i, joinIndex := range groupInfo.SortedJoinIndex { - if joinIndex == mapData["+joinindex"] { - added = true - break - } - - intJoinIndex, _ := strconv.Atoi(joinIndex) - if intJoinIndex > myJoinIndex { - groupInfo.SortedJoinIndex = append(groupInfo.SortedJoinIndex, "") - copy(groupInfo.SortedJoinIndex[i+1:], groupInfo.SortedJoinIndex[i:]) - groupInfo.SortedJoinIndex[i] = mapData["+joinindex"] - added = true - break - } - } - - if !added { - groupInfo.SortedJoinIndex = append(groupInfo.SortedJoinIndex, mapData["+joinindex"]) - } - } - - groupsCopy = append(groupsCopy, groupInfo) + login := logins[profileId] + if login == nil { + mutex.Unlock() + logging.Warn(moduleName, "Received SELECT record from non-existent profile ID", aurora.Cyan(profileId)) + return } - return groupsCopy -} + session := login.session + if session == nil { + mutex.Unlock() + logging.Warn(moduleName, "Received SELECT record from profile ID", aurora.Cyan(profileId), "but no session exists") + return + } + mutex.Unlock() -// GetGroups returns a copy of all online rooms -func GetGroups(gameNames []string, groupNames []string, sorted bool) []GroupInfo { - groupsCopy := getGroupsRaw(gameNames, groupNames) + group := session.groupPointer + if group == nil { + return + } - for i, group := range groupsCopy { - for joinIndex, rawPlayer := range group.PlayersRaw { - playerInfo := PlayerInfo{ - Count: rawPlayer["+localplayers"], - ProfileID: rawPlayer["dwc_pid"], - InGameName: rawPlayer["+ingamesn"], - } - - pid, err := strconv.ParseUint(rawPlayer["dwc_pid"], 10, 32) - if err == nil { - if fcGame := rawPlayer["+fcgameid"]; len(fcGame) == 4 { - playerInfo.FriendCode = common.CalcFriendCodeString(uint32(pid), fcGame) - } - } - - if rawPlayer["gamename"] == "mariokartwii" { - playerInfo.VersusELO = rawPlayer["ev"] - playerInfo.BattleELO = rawPlayer["eb"] - } - - for i := 0; i < 32; i++ { - miiData := rawPlayer["+mii"+strconv.Itoa(i)] - if miiData == "" { - continue - } - - playerInfo.Mii = append(playerInfo.Mii, MiiInfo{ - MiiData: miiData, - MiiName: rawPlayer["+mii_name"+strconv.Itoa(i)], - }) - } - - for _, newIndex := range group.SortedJoinIndex { - if newIndex == joinIndex { - continue - } - - if rawPlayer["+conn_"+newIndex] == "" { - playerInfo.ConnMap += "0" - continue - } - - playerInfo.ConnMap += rawPlayer["+conn_"+newIndex] - } - - playerInfo.ConnFail = rawPlayer["+conn_fail"] - if playerInfo.ConnFail == "" { - playerInfo.ConnFail = "0" - } - - playerInfo.Suspend = rawPlayer["dwc_suspend"] - - groupsCopy[i].Players[joinIndex] = playerInfo + switch key { + case "mkw_select_course": + courseId, err := strconv.ParseUint(value, 10, 32) + if err != nil { + logging.Error(moduleName, "Error decoding mkw_select_course:", err.Error()) + return } + + logging.Info(moduleName, "Selected course", aurora.BrightCyan(strconv.FormatUint(courseId, 10))) + + mutex.Lock() + defer mutex.Unlock() + + group.MKWRaceNumber++ + group.MKWCourseID = int(courseId) + group.MKWEngineClassID = -1 + return + + case "mkw_select_cc": + ccId, err := strconv.ParseUint(value, 10, 32) + if err != nil { + logging.Error(moduleName, "Error decoding mkw_select_cc:", err.Error()) + return + } + + logging.Info(moduleName, "Selected CC", aurora.BrightCyan(strconv.FormatUint(ccId, 10))) + + mutex.Lock() + defer mutex.Unlock() + + group.MKWEngineClassID = int(ccId) + return } - if sorted { - sort.Slice(groupsCopy, func(i, j int) bool { - if groupsCopy[i].CreateTime.Equal(groupsCopy[j].CreateTime) { - return groupsCopy[i].GroupName < groupsCopy[j].GroupName - } - - return groupsCopy[i].CreateTime.Before(groupsCopy[j].CreateTime) - }) - } - - return groupsCopy } // saveGroups saves the current groups state to disk. diff --git a/qr2/group_info.go b/qr2/group_info.go new file mode 100644 index 0000000..954c97f --- /dev/null +++ b/qr2/group_info.go @@ -0,0 +1,224 @@ +package qr2 + +import ( + "sort" + "strconv" + "time" + "wwfc/common" +) + +type MiiInfo struct { + MiiData string `json:"data"` + MiiName string `json:"name"` +} + +type PlayerInfo struct { + Count string `json:"count"` + ProfileID string `json:"pid"` + InGameName string `json:"name"` + ConnMap string `json:"conn_map"` + ConnFail string `json:"conn_fail"` + Suspend string `json:"suspend"` + + // Mario Kart Wii-specific fields + FriendCode string `json:"fc,omitempty"` + VersusELO string `json:"ev,omitempty"` + BattleELO string `json:"eb,omitempty"` + Mii []MiiInfo `json:"mii,omitempty"` +} + +type GroupInfo struct { + GroupName string `json:"id"` + GameName string `json:"game"` + CreateTime time.Time `json:"created"` + MatchType string `json:"type"` + Suspend bool `json:"suspend"` + ServerIndex string `json:"host,omitempty"` + MKWRegion string `json:"rk,omitempty"` + + Players map[string]PlayerInfo `json:"players"` + RaceInfo *RaceInfo `json:"race,omitempty"` + + PlayersRaw map[string]map[string]string `json:"-"` + SortedJoinIndex []string `json:"-"` +} + +type RaceInfo struct { + RaceNumber int `json:"num"` + CourseID int `json:"course"` + EngineClassID int `json:"cc"` +} + +func getGroupsRaw(gameNames []string, groupNames []string) []GroupInfo { + var groupsCopy []GroupInfo + + mutex.Lock() + defer mutex.Unlock() + + for _, group := range groups { + if len(gameNames) > 0 && !common.StringInSlice(group.GameName, gameNames) { + continue + } + + if len(groupNames) > 0 && !common.StringInSlice(group.GroupName, groupNames) { + continue + } + + groupInfo := GroupInfo{ + GroupName: group.GroupName, + GameName: group.GameName, + CreateTime: group.CreateTime, + MatchType: "", + Suspend: true, + ServerIndex: "", + MKWRegion: "", + Players: map[string]PlayerInfo{}, + PlayersRaw: map[string]map[string]string{}, + SortedJoinIndex: []string{}, + } + + if group.MatchType == "0" || group.MatchType == "1" { + groupInfo.MatchType = "anybody" + } else if group.MatchType == "2" || group.MatchType == "3" { + groupInfo.MatchType = "private" + } else { + groupInfo.MatchType = "unknown" + } + + if group.server != nil { + groupInfo.ServerIndex = group.server.Data["+joinindex"] + } + + if groupInfo.GameName == "mariokartwii" { + groupInfo.MKWRegion = group.MKWRegion + + if group.MKWRaceNumber != 0 { + groupInfo.RaceInfo = &RaceInfo{ + RaceNumber: group.MKWRaceNumber, + CourseID: group.MKWCourseID, + EngineClassID: group.MKWEngineClassID, + } + } + } + + for session := range group.players { + mapData := map[string]string{} + for k, v := range session.Data { + mapData[k] = v + } + + if login := session.login; login != nil { + mapData["+ingamesn"] = login.InGameName + } else { + mapData["+ingamesn"] = "" + } + + groupInfo.PlayersRaw[mapData["+joinindex"]] = mapData + + if mapData["dwc_hoststate"] == "2" && mapData["dwc_suspend"] == "0" { + groupInfo.Suspend = false + } + + // Add the join index to the sorted list + myJoinIndex, _ := strconv.Atoi(mapData["+joinindex"]) + added := false + + for i, joinIndex := range groupInfo.SortedJoinIndex { + if joinIndex == mapData["+joinindex"] { + added = true + break + } + + intJoinIndex, _ := strconv.Atoi(joinIndex) + if intJoinIndex > myJoinIndex { + groupInfo.SortedJoinIndex = append(groupInfo.SortedJoinIndex, "") + copy(groupInfo.SortedJoinIndex[i+1:], groupInfo.SortedJoinIndex[i:]) + groupInfo.SortedJoinIndex[i] = mapData["+joinindex"] + added = true + break + } + } + + if !added { + groupInfo.SortedJoinIndex = append(groupInfo.SortedJoinIndex, mapData["+joinindex"]) + } + } + + groupsCopy = append(groupsCopy, groupInfo) + } + + return groupsCopy +} + +// GetGroups returns a copy of all online rooms +func GetGroups(gameNames []string, groupNames []string, sorted bool) []GroupInfo { + groupsCopy := getGroupsRaw(gameNames, groupNames) + + for i, group := range groupsCopy { + for joinIndex, rawPlayer := range group.PlayersRaw { + playerInfo := PlayerInfo{ + Count: rawPlayer["+localplayers"], + ProfileID: rawPlayer["dwc_pid"], + InGameName: rawPlayer["+ingamesn"], + } + + pid, err := strconv.ParseUint(rawPlayer["dwc_pid"], 10, 32) + if err == nil { + if fcGame := rawPlayer["+fcgameid"]; len(fcGame) == 4 { + playerInfo.FriendCode = common.CalcFriendCodeString(uint32(pid), fcGame) + } + } + + if rawPlayer["gamename"] == "mariokartwii" { + playerInfo.VersusELO = rawPlayer["ev"] + playerInfo.BattleELO = rawPlayer["eb"] + } + + for i := 0; i < 32; i++ { + miiData := rawPlayer["+mii"+strconv.Itoa(i)] + if miiData == "" { + continue + } + + playerInfo.Mii = append(playerInfo.Mii, MiiInfo{ + MiiData: miiData, + MiiName: rawPlayer["+mii_name"+strconv.Itoa(i)], + }) + } + + for _, newIndex := range group.SortedJoinIndex { + if newIndex == joinIndex { + continue + } + + if rawPlayer["+conn_"+newIndex] == "" { + playerInfo.ConnMap += "0" + continue + } + + playerInfo.ConnMap += rawPlayer["+conn_"+newIndex] + } + + playerInfo.ConnFail = rawPlayer["+conn_fail"] + if playerInfo.ConnFail == "" { + playerInfo.ConnFail = "0" + } + + playerInfo.Suspend = rawPlayer["dwc_suspend"] + + groupsCopy[i].Players[joinIndex] = playerInfo + } + } + + if sorted { + sort.Slice(groupsCopy, func(i, j int) bool { + if groupsCopy[i].CreateTime.Equal(groupsCopy[j].CreateTime) { + return groupsCopy[i].GroupName < groupsCopy[j].GroupName + } + + return groupsCopy[i].CreateTime.Before(groupsCopy[j].CreateTime) + }) + } + + return groupsCopy +}