package controllers import ( "InfantrySkillCalculator/models" "InfantrySkillCalculator/utils" "encoding/json" "fmt" "github.com/gin-gonic/gin" "io" "math" "net/http" "sort" ) // GetScoreByPlayerID GET /score/:player_id func GetScoreByPlayerID(c *gin.Context) { var player models.Player if err := models.DB.Where("id = ?", c.Param("player_id")).First(&player).Error; err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Player not found!"}) utils.Logger.Errorf("[SCORE] Player not found: %s", err.Error()) return } game, err := GetActiveGame(c) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "No active game available!"}) utils.Logger.Errorf("[SCORE] No active game available: %s", err.Error()) return } score, err := models.PlayerCache.GetScore(player.ID, game.Tag) if err != nil || score == -1 { score = CalcPlayerScore(player.Name, game.Tag) if score == score && score != -1 { // not NaN if err := models.PlayerCache.SetScore(player.ID, game.Tag, score); err != nil { utils.Logger.Errorf("[SCORE] Failed to cache player score: %s", err.Error()) return } } } if score != score || score == -1 { // NaN c.String(http.StatusOK, "") } else if score != -1 { c.String(http.StatusOK, fmt.Sprintf("%.2f", score)) } else { c.JSON(http.StatusBadRequest, "Invalid request!") utils.Logger.Warnf("[SCORE] Invalid request! Player: %s, Game: %s | Score: %f", player.Name, game.Tag, score) } } // GetScoreByPlayerName POST /score/:player_name func GetScoreByPlayerName(c *gin.Context) { game, err := GetActiveGame(c) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "No active game available!"}) utils.Logger.Errorf("[SCORE] No active game available: %s", err.Error()) return } score := CalcPlayerScore(c.Param("player_name"), game.Tag) if score != score || score == -1 { // NaN c.String(http.StatusNotFound, "Spieler nicht gefunden!") } else if score != -1 { c.String(http.StatusOK, fmt.Sprintf("%.2f", score)) } else { c.String(http.StatusBadRequest, "Ungültige Abfrage!") utils.Logger.Warnf("[SCORE] Invalid request! Player: %s, Game: %s | Score: %f", c.Param("player_name"), game.Tag, score) } } func CalcPlayerScore(playerName string, gameTag string) float32 { if gameTag != "BF5" && gameTag != "BF2042" { utils.Logger.Errorf("[SCORE] Invalid game tag '%s'", gameTag) return -1 } gameMetrics := GetGameMetric(gameTag) if gameMetrics == nil { utils.Logger.Errorf("[SCORE] No game metrics specified for '%s'", gameTag) return -1 } normalizeFactor := gameMetrics.NormalizeFactor topWeaponCount := gameMetrics.TopWeaponCount c := http.Client{} var reqUri string if gameTag == "BF5" { reqUri = "https://api.gametools.network/bfv/weapons/?raw=false&format_values=false&name=" + playerName + "&platform=pc" } else if gameTag == "BF2042" { reqUri = "https://api.gametools.network/bf2042/stats/?raw=false&format_values=false&name=" + playerName + "&platform=pc" } req, err := http.NewRequest("GET", reqUri, nil) if err != nil { utils.Logger.Errorf("[SCORE] Failed to create request: %s", err.Error()) return -1 } req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36") res, err := c.Do(req) if err != nil { utils.Logger.Errorf("[SCORE] Failed to send request: %s", err.Error()) return -1 } defer func(Body io.ReadCloser) { _ = Body.Close() }(res.Body) if res.StatusCode == 404 { utils.Logger.Errorf("[SCORE] User '%s' does not exist!", playerName) return -1 } else if res.StatusCode != 200 { utils.Logger.Errorf("[SCORE] Status code error: %d %s", res.StatusCode, res.Status) return -1 } var data models.TrackerWeaponJSON var body, _ = io.ReadAll(res.Body) if err := json.Unmarshal(body, &data); err != nil { utils.Logger.Errorf("Failed to deserialize tracker API response: %s", err) return -1 } sort.SliceStable(data.Weapons, func(i, j int) bool { return data.Weapons[i].Kills > data.Weapons[j].Kills }) var top []models.Weapon for _, weapon := range data.Weapons { if len(top) >= topWeaponCount { break } if gameTag == "BF2042" && weapon.Kills < 100 { break } acc := (float64(weapon.ShotsHit) / float64(weapon.ShotsFired)) * 100 kpm := weapon.KPM weaponMetrics := FindWeaponMetric(gameMetrics.WeaponMetrics, weapon.Type) if weaponMetrics == nil { utils.Logger.Errorf("[SCORE] No weapon metrics specified for '%s'", weapon.Type) continue } accFactor := weaponMetrics.AccuracyFactor kpmFactor := weaponMetrics.KpmFactor acc /= accFactor kpm /= kpmFactor weapon.Accuracy = acc weapon.KPM = kpm top = append(top, weapon) } sumAcc := 0.0 sumKpm := 0.0 for _, w := range top { sumAcc += w.Accuracy sumKpm += w.KPM } accAvg := sumAcc / float64(len(top)) kpmAvg := sumKpm / float64(len(top)) score := math.Round(((accAvg*kpmAvg)/normalizeFactor)*100) / 100 return float32(score) }