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 } score, err := models.PlayerCache.GetScore(player.ID) var statusCode int = http.StatusOK if err != nil || score == -1 { score, statusCode = GetPlayerScore(player.Name) if score == score && score != -1 { // not NaN if err := models.PlayerCache.SetScore(player.ID, score); err != nil { utils.Logger.Errorf("[SCORE] Failed to cache player score: %s", err.Error()) return } } } switch statusCode { case 200: c.String(200, fmt.Sprintf("%.2f", score)) case 404: c.String(200, "") case 503, 504: c.String(statusCode, "") default: c.String(statusCode, "") utils.Logger.Warnf("[SCORE] Invalid request! Player: %s, Score: %f", player.Name, score) } } // GetScoreByPlayerName POST /score/:player_name func GetScoreByPlayerName(c *gin.Context) { playerName := c.Param("player_name") score, statusCode := GetPlayerScore(playerName) switch statusCode { case 200: c.String(200, fmt.Sprintf("%.2f", score)) case 404: c.String(200, "Spieler nicht gefunden!") case 503, 504: c.String(statusCode, "Tracker nicht erreichbar!") default: c.String(statusCode, "Ungültige Abfrage!") utils.Logger.Warnf("[SCORE] Invalid request! Player: %s, Score: %f", playerName, score) } } func GetPlayerScore(playerName string) (float32, int) { playerData, statusCode := getPlayerData(playerName) if statusCode != 200 { return -1, statusCode } return calcPlayerScore(playerData), statusCode } func getPlayerData(playerName string) (*models.TrackerWeaponJSON, int) { c := http.Client{} 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 nil, 0 } 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 nil, 0 } defer func(Body io.ReadCloser) { _ = Body.Close() }(res.Body) if res.StatusCode == 404 { utils.Logger.Errorf("[SCORE] User does not exist!") return nil, res.StatusCode } else if res.StatusCode == 503 || res.StatusCode == 504 { utils.Logger.Errorf("[SCORE] Service unavailable!") return nil, res.StatusCode } else if res.StatusCode != 200 { utils.Logger.Errorf("[SCORE] Status code error: %d %s", res.StatusCode, res.Status) return nil, res.StatusCode } var response models.TrackerWeaponJSON body, err := io.ReadAll(res.Body) if err != nil { utils.Logger.Errorf("[SCORE] Failed to read response body: %s", err.Error()) return nil, 0 } if err := json.Unmarshal(body, &response); err != nil { utils.Logger.Errorf("[SCORE] Failed to deserialize tracker API response: %s", err) return nil, 0 } return &response, 200 } func calcPlayerScore(playerData *models.TrackerWeaponJSON) float32 { gameMetrics := GetGameMetric("BF2042") if gameMetrics == nil { utils.Logger.Errorf("[SCORE] No game metrics specified for '%s'", "BF2042") return -1 } normalizeFactor := gameMetrics.NormalizeFactor topWeaponCount := gameMetrics.TopWeaponCount sort.SliceStable(playerData.Weapons, func(i, j int) bool { return playerData.Weapons[i].Kills > playerData.Weapons[j].Kills }) var top []models.Weapon for _, weapon := range playerData.Weapons { if len(top) >= topWeaponCount || 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) }