mirror of
https://github.com/WiiLink24/wfc-server.git
synced 2026-03-21 17:44:58 -05:00
Merge 81c6045f02 into 713826f30e
This commit is contained in:
commit
cb166edc8b
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -20,6 +20,8 @@ state
|
|||
# Executables
|
||||
*.exe
|
||||
*.exe~
|
||||
wwfc
|
||||
|
||||
# Editor files
|
||||
.vscode
|
||||
.github
|
||||
|
|
|
|||
58
api/mkw_rr.go
Normal file
58
api/mkw_rr.go
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"wwfc/qr2"
|
||||
)
|
||||
|
||||
type RaceResultInfo struct {
|
||||
Results map[int][]qr2.RaceResult `json:"results"`
|
||||
}
|
||||
|
||||
func HandleMKWRR(w http.ResponseWriter, r *http.Request) {
|
||||
u, err := url.Parse(r.URL.String())
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
query, err := url.ParseQuery(u.RawQuery)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
groupNames := query["id"]
|
||||
if len(groupNames) != 1 {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
results := qr2.GetRaceResultsForGroup(query["id"][0])
|
||||
if results == nil {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
var jsonData []byte
|
||||
if len(results) == 0 {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
} else {
|
||||
jsonData, err = json.Marshal(RaceResultInfo{
|
||||
Results: results,
|
||||
})
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
w.Header().Set("Content-Length", strconv.Itoa(len(jsonData)))
|
||||
w.Write(jsonData)
|
||||
}
|
||||
|
|
@ -56,9 +56,7 @@ func (user *User) CreateUser(pool *pgxpool.Pool, ctx context.Context) error {
|
|||
return pool.QueryRow(ctx, InsertUser, user.UserId, user.GsbrCode, "", user.NgDeviceId, user.Email, user.UniqueNick).Scan(&user.ProfileId)
|
||||
}
|
||||
|
||||
if user.ProfileId >= 1000000000 {
|
||||
return ErrReservedProfileIDRange
|
||||
}
|
||||
// Reserved profile ID check removed; all profile IDs allowed
|
||||
|
||||
var exists bool
|
||||
err := pool.QueryRow(ctx, IsProfileIDInUse, user.ProfileId).Scan(&exists)
|
||||
|
|
@ -75,9 +73,7 @@ func (user *User) CreateUser(pool *pgxpool.Pool, ctx context.Context) error {
|
|||
}
|
||||
|
||||
func (user *User) UpdateProfileID(pool *pgxpool.Pool, ctx context.Context, newProfileId uint32) error {
|
||||
if newProfileId >= 1000000000 {
|
||||
return ErrReservedProfileIDRange
|
||||
}
|
||||
// Reserved profile ID check removed; all profile IDs allowed
|
||||
|
||||
var exists bool
|
||||
err := pool.QueryRow(ctx, IsProfileIDInUse, newProfileId).Scan(&exists)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
package gpcm
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
"time"
|
||||
"wwfc/common"
|
||||
"wwfc/logging"
|
||||
"wwfc/qr2"
|
||||
|
|
@ -9,6 +11,19 @@ import (
|
|||
"github.com/logrusorgru/aurora/v3"
|
||||
)
|
||||
|
||||
type RaceResultPlayer struct {
|
||||
Pid int `json:"pid"`
|
||||
FinishTimeMs int `json:"finish_time_ms"`
|
||||
CharacterId int `json:"character_id"`
|
||||
KartId int `json:"kart_id"`
|
||||
PlayerCount int `json:"player_count"`
|
||||
}
|
||||
|
||||
type RaceResult struct {
|
||||
ClientReportVersion string `json:"client_report_version"`
|
||||
Player *RaceResultPlayer `json:"player"`
|
||||
}
|
||||
|
||||
func (g *GameSpySession) handleWWFCReport(command common.GameSpyCommand) {
|
||||
for key, value := range command.OtherValues {
|
||||
logging.Info(g.ModuleName, "WiiLink Report:", aurora.Yellow(key))
|
||||
|
|
@ -63,6 +78,69 @@ func (g *GameSpySession) handleWWFCReport(command common.GameSpyCommand) {
|
|||
}
|
||||
|
||||
qr2.ProcessMKWSelectRecord(g.User.ProfileId, key, value)
|
||||
|
||||
case "wl:mkw_race_result":
|
||||
if g.GameName != "mariokartwii" {
|
||||
logging.Warn(g.ModuleName, "Ignoring", keyColored, "from wrong game")
|
||||
continue
|
||||
}
|
||||
|
||||
logging.Info(g.ModuleName, "Received race result from profile", aurora.BrightCyan(strconv.FormatUint(uint64(g.User.ProfileId), 10)))
|
||||
|
||||
var raceResult RaceResult
|
||||
err := json.Unmarshal([]byte(value), &raceResult)
|
||||
if err != nil {
|
||||
logging.Error(g.ModuleName, "Error parsing race result JSON:", err.Error())
|
||||
logging.Info(g.ModuleName, "Raw payload:", aurora.BrightMagenta(value))
|
||||
continue
|
||||
}
|
||||
|
||||
logging.Info(g.ModuleName, "Race result version:", aurora.Yellow(raceResult.ClientReportVersion))
|
||||
|
||||
player := raceResult.Player
|
||||
|
||||
logging.Info(g.ModuleName,
|
||||
"Player",
|
||||
"- PID:", aurora.Cyan(strconv.Itoa(player.Pid)),
|
||||
"Time:", aurora.Cyan(strconv.Itoa(player.FinishTimeMs)), "ms",
|
||||
"Char:", aurora.Cyan(strconv.Itoa(player.CharacterId)),
|
||||
"Kart:", aurora.Cyan(strconv.Itoa(player.KartId)),
|
||||
"Count:", aurora.Cyan(strconv.Itoa(player.PlayerCount)))
|
||||
|
||||
// Hand off to qr2 for processing
|
||||
qr2.ProcessMKWRaceResult(g.User.ProfileId, player.Pid, player.FinishTimeMs, player.CharacterId, player.KartId, player.PlayerCount)
|
||||
|
||||
case "wl:mkw_race_start_time":
|
||||
serverTime := time.Now().UnixMilli()
|
||||
logging.Info(g.ModuleName,
|
||||
"Race start time:", aurora.Yellow(value),
|
||||
"Server time:", aurora.Yellow(strconv.FormatInt(serverTime, 10)))
|
||||
|
||||
// Parse client timestamp
|
||||
clientTime, err := strconv.ParseInt(value, 10, 64)
|
||||
if err != nil {
|
||||
logging.Error(g.ModuleName, "Failed to parse client timestamp:", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// Store timing data in qr2 module
|
||||
qr2.StoreRaceStartTime(g.User.ProfileId, clientTime, serverTime)
|
||||
|
||||
case "wl:mkw_race_finish_time":
|
||||
serverTime := time.Now().UnixMilli()
|
||||
logging.Info(g.ModuleName,
|
||||
"Race finish time:", aurora.Yellow(value),
|
||||
"Server time:", aurora.Yellow(strconv.FormatInt(serverTime, 10)))
|
||||
|
||||
// Parse client timestamp
|
||||
clientTime, err := strconv.ParseInt(value, 10, 64)
|
||||
if err != nil {
|
||||
logging.Error(g.ModuleName, "Failed to parse client finish timestamp:", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// Store timing data in qr2 module
|
||||
qr2.StoreRaceFinishTime(g.User.ProfileId, clientTime, serverTime)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -177,6 +177,12 @@ func handleRequest(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
// Check for /api/mkw_rr
|
||||
if r.URL.Path == "/api/mkw_rr" {
|
||||
api.HandleMKWRR(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
logging.Info("NAS", aurora.Yellow(r.Method), aurora.Cyan(r.URL), "via", aurora.Cyan(r.Host), "from", aurora.BrightCyan(r.RemoteAddr))
|
||||
replyHTTPError(w, 404, "404 Not Found")
|
||||
}
|
||||
|
|
|
|||
150
qr2/group.go
150
qr2/group.go
|
|
@ -31,6 +31,39 @@ type Group struct {
|
|||
MKWEngineClassID int
|
||||
}
|
||||
|
||||
type RaceResultPlayer struct {
|
||||
Pid int `json:"pid"`
|
||||
FinishTimeMs int `json:"finish_time_ms"`
|
||||
CharacterId int `json:"character_id"`
|
||||
KartId int `json:"kart_id"`
|
||||
}
|
||||
|
||||
type RaceResult struct {
|
||||
ProfileID uint32
|
||||
PlayerID int
|
||||
FinishTime uint32
|
||||
CharacterID uint32
|
||||
VehicleID uint32
|
||||
PlayerCount uint32
|
||||
FinishPos int
|
||||
CourseID int
|
||||
EngineClassID int
|
||||
Delta int
|
||||
}
|
||||
|
||||
var raceResults = map[string]map[int][]RaceResult{} // GroupName -> RaceNumber -> []RaceResult
|
||||
|
||||
// Timing storage for delta calculation using start/finish times
|
||||
var raceStartTimings = map[uint32]struct {
|
||||
ClientTime int64
|
||||
ServerTime int64
|
||||
}{}
|
||||
|
||||
var raceFinishTimings = map[uint32]struct {
|
||||
ClientTime int64
|
||||
ServerTime int64
|
||||
}{}
|
||||
|
||||
var groups = map[string]*Group{}
|
||||
|
||||
func processResvOK(moduleName string, matchVersion int, reservation common.MatchCommandDataReservation, resvOK common.MatchCommandDataResvOK, sender, destination *Session) bool {
|
||||
|
|
@ -310,6 +343,22 @@ func CheckGPReservationAllowed(senderIP uint64, senderPid uint32, destPid uint32
|
|||
return checkReservationAllowed(moduleName, from, to, joinType)
|
||||
}
|
||||
|
||||
// StoreRaceStartTime stores the start timing data for delta calculation
|
||||
func StoreRaceStartTime(profileId uint32, clientTime, serverTime int64) {
|
||||
raceStartTimings[profileId] = struct {
|
||||
ClientTime int64
|
||||
ServerTime int64
|
||||
}{ClientTime: clientTime, ServerTime: serverTime}
|
||||
}
|
||||
|
||||
// StoreRaceFinishTime stores the finish timing data for delta calculation
|
||||
func StoreRaceFinishTime(profileId uint32, clientTime, serverTime int64) {
|
||||
raceFinishTimings[profileId] = struct {
|
||||
ClientTime int64
|
||||
ServerTime int64
|
||||
}{ClientTime: clientTime, ServerTime: serverTime}
|
||||
}
|
||||
|
||||
func ProcessNATNEGReport(result byte, ip1 string, ip2 string) {
|
||||
moduleName := "QR2:NATNEGReport"
|
||||
|
||||
|
|
@ -525,6 +574,107 @@ func ProcessMKWSelectRecord(profileId uint32, key string, value string) {
|
|||
|
||||
}
|
||||
|
||||
func ProcessMKWRaceResult(profileId uint32, playerPid int, finishTimeMs int, characterId int, kartId int, playerCount int) {
|
||||
moduleName := "QR2:MKWRaceResult:" + strconv.FormatUint(uint64(profileId), 10)
|
||||
|
||||
mutex.Lock()
|
||||
login := logins[profileId]
|
||||
if login == nil {
|
||||
mutex.Unlock()
|
||||
logging.Warn(moduleName, "Received race result from non-existent profile ID", aurora.Cyan(profileId))
|
||||
return
|
||||
}
|
||||
|
||||
session := login.session
|
||||
if session == nil {
|
||||
mutex.Unlock()
|
||||
logging.Warn(moduleName, "Received race result from profile ID", aurora.Cyan(profileId), "but no session exists")
|
||||
return
|
||||
}
|
||||
mutex.Unlock()
|
||||
|
||||
group := session.groupPointer
|
||||
if group == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if group.MKWRaceNumber == 0 {
|
||||
logging.Error(moduleName, "Received race result but no races have been started")
|
||||
return
|
||||
}
|
||||
|
||||
// Calculate delta using start/finish times
|
||||
var delta int
|
||||
if startTiming, exists := raceStartTimings[profileId]; exists {
|
||||
if finishTiming, exists := raceFinishTimings[profileId]; exists {
|
||||
clientElapsedTime := finishTiming.ClientTime - startTiming.ClientTime
|
||||
serverElapsedTime := finishTiming.ServerTime - startTiming.ServerTime
|
||||
delta = int(serverElapsedTime - clientElapsedTime)
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate finish position based on current race results
|
||||
finishPos := 1
|
||||
if raceResults[group.GroupName] != nil && len(raceResults[group.GroupName][group.MKWRaceNumber]) > 0 {
|
||||
// Get current race results for this race number
|
||||
currentResults := raceResults[group.GroupName][group.MKWRaceNumber]
|
||||
|
||||
// Count how many players have finished with better times
|
||||
for _, existingResult := range currentResults {
|
||||
if existingResult.FinishTime < uint32(finishTimeMs) {
|
||||
finishPos++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Convert race result data to internal format
|
||||
raceResultData := RaceResult{
|
||||
ProfileID: profileId,
|
||||
PlayerID: playerPid,
|
||||
FinishTime: uint32(finishTimeMs),
|
||||
CharacterID: uint32(characterId),
|
||||
VehicleID: uint32(kartId),
|
||||
PlayerCount: uint32(playerCount),
|
||||
FinishPos: finishPos,
|
||||
CourseID: group.MKWCourseID,
|
||||
EngineClassID: group.MKWEngineClassID,
|
||||
Delta: delta,
|
||||
}
|
||||
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
|
||||
if raceResults[group.GroupName] == nil {
|
||||
raceResults[group.GroupName] = map[int][]RaceResult{}
|
||||
}
|
||||
|
||||
raceResults[group.GroupName][group.MKWRaceNumber] = append(raceResults[group.GroupName][group.MKWRaceNumber], raceResultData)
|
||||
|
||||
logging.Info(moduleName, "Stored race result for profile", aurora.BrightCyan(strconv.FormatUint(uint64(profileId), 10)),
|
||||
"Race #:", aurora.Cyan(strconv.Itoa(group.MKWRaceNumber)),
|
||||
"Course:", aurora.Cyan(strconv.Itoa(group.MKWCourseID)),
|
||||
"Delta:", aurora.Cyan(strconv.Itoa(delta)))
|
||||
}
|
||||
|
||||
func GetRaceResultsForGroup(groupName string) map[int][]RaceResult {
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
|
||||
groupResults, ok := raceResults[groupName]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Return a copy to prevent external modification
|
||||
copiedRaceResults := make(map[int][]RaceResult)
|
||||
for raceNumber, results := range groupResults {
|
||||
copiedRaceResults[raceNumber] = make([]RaceResult, len(results))
|
||||
copy(copiedRaceResults[raceNumber], results)
|
||||
}
|
||||
|
||||
return copiedRaceResults
|
||||
}
|
||||
|
||||
// saveGroups saves the current groups state to disk.
|
||||
// Expects the mutex to be locked.
|
||||
func saveGroups() error {
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user