From 9039928eb4598b789e48f49a718f322022e1a8bc Mon Sep 17 00:00:00 2001 From: yoyosource Date: Thu, 11 May 2023 16:06:27 +0200 Subject: [PATCH] Update to new ranked system --- .../bungeecore/commands/RankCommand.java | 14 +- .../network/handlers/FightEndsHandler.java | 212 +++++++++++++----- .../steamwar/messages/BungeeCore.properties | 1 - 3 files changed, 160 insertions(+), 67 deletions(-) diff --git a/src/de/steamwar/bungeecore/commands/RankCommand.java b/src/de/steamwar/bungeecore/commands/RankCommand.java index b009872..9c50600 100644 --- a/src/de/steamwar/bungeecore/commands/RankCommand.java +++ b/src/de/steamwar/bungeecore/commands/RankCommand.java @@ -21,11 +21,11 @@ package de.steamwar.bungeecore.commands; import de.steamwar.bungeecore.ArenaMode; import de.steamwar.bungeecore.Message; -import de.steamwar.sql.SteamwarUser; -import de.steamwar.sql.UserElo; import de.steamwar.command.SWCommand; import de.steamwar.command.SWCommandUtils; import de.steamwar.command.TypeMapper; +import de.steamwar.sql.SteamwarUser; +import de.steamwar.sql.UserElo; import net.md_5.bungee.BungeeCord; import net.md_5.bungee.api.connection.ProxiedPlayer; @@ -67,18 +67,10 @@ public class RankCommand extends SWCommand { } else { Message.send("RANK_UNPLACED", player); } - Message.send("RANK_EMBLEM", player, getEmblemProgression(player, mode.getChatName(), user.getId())); + Message.send("RANK_EMBLEM", player, UserElo.getEmblemProgression(mode.getChatName(), user.getId())); } } - private static String getEmblemProgression(ProxiedPlayer player, String gameMode, int userId) { - int fightsOfSeason = UserElo.getFightsOfSeason(userId, gameMode); - if (fightsOfSeason < 10) - return Message.parse("RANK_NEEDED_FIGHTS_LEFT", player, "§8✧ ✦ ✶ ✷ ✸ ✹ ❂", 10 - fightsOfSeason); - - return UserElo.getEmblemProgression(gameMode, userId); - } - @Mapper(value = "player", local = true) public TypeMapper playerTypeMapper() { return SWCommandUtils.createMapper(s -> s, s -> BungeeCord.getInstance().getPlayers().stream().map(ProxiedPlayer::getName).collect(Collectors.toList())); diff --git a/src/de/steamwar/bungeecore/network/handlers/FightEndsHandler.java b/src/de/steamwar/bungeecore/network/handlers/FightEndsHandler.java index 48de642..cb7eeac 100644 --- a/src/de/steamwar/bungeecore/network/handlers/FightEndsHandler.java +++ b/src/de/steamwar/bungeecore/network/handlers/FightEndsHandler.java @@ -1,7 +1,7 @@ /* * This file is a part of the SteamWar software. * - * Copyright (C) 2020 SteamWar.de-Serverteam + * Copyright (C) 2022 SteamWar.de-Serverteam * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by @@ -20,29 +20,35 @@ package de.steamwar.bungeecore.network.handlers; import de.steamwar.bungeecore.ArenaMode; +import de.steamwar.bungeecore.BungeeCore; import de.steamwar.network.packets.PacketHandler; import de.steamwar.network.packets.common.FightEndsPacket; import de.steamwar.sql.*; import lombok.RequiredArgsConstructor; +import net.md_5.bungee.BungeeTitle; +import net.md_5.bungee.api.ProxyServer; +import net.md_5.bungee.api.Title; +import net.md_5.bungee.api.chat.BaseComponent; +import net.md_5.bungee.api.chat.TextComponent; +import net.md_5.bungee.api.connection.ProxiedPlayer; +import net.md_5.bungee.api.scheduler.TaskScheduler; import java.util.*; +import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; public class FightEndsHandler extends PacketHandler { - private Map> gameModeGames = new HashMap<>(); + private static final int MEDIAN_ELO_GAIN = 20; + private static final double WIN_FACTOR = 1.0; + private static final double DRAW_WIN_FACTOR = 0.2; + private static final double LOSE_FACTOR = -1.0; + private static final double DRAW_LOSE_FACTOR = -0.2; + private static final long REMATCH_LIFETIME = 1L * 60 * 60 * 1000; private int K = 20; - private long defaultFightRange = 1000 /* Milliseconds */ * 60 /* Seconds */ * 15L /* Minutes */; - private Map fightRanges = new HashMap<>(); - private long defaultFightCount = 1; - private Map fightCounts = new HashMap<>(); - - { - fightRanges.put("miniwargear", 1000 /* Milliseconds */ * 60 /* Seconds */ * 30L /* Minutes */); - fightCounts.put("miniwargear", 3L); - } + private Map> gameModeGames = new HashMap<>(); @Handler public void handle(FightEndsPacket fightEndsPacket) { @@ -50,13 +56,6 @@ public class FightEndsHandler extends PacketHandler { return; } - int bluePlayerSize = fightEndsPacket.getBluePlayers().size(); - int redPlayerSize = fightEndsPacket.getRedPlayers().size(); - double playerRatio = bluePlayerSize > redPlayerSize ? (double) redPlayerSize / bluePlayerSize : (double) bluePlayerSize / redPlayerSize; - if (playerRatio < 0.6) { - return; - } - boolean bluePublic = SchematicNode.getSchematicNode(fightEndsPacket.getBlueSchem()).getOwner() == 0; boolean redPublic = SchematicNode.getSchematicNode(fightEndsPacket.getRedSchem()).getOwner() == 0; @@ -64,15 +63,6 @@ public class FightEndsHandler extends PacketHandler { return; } - double blueResult; - if (fightEndsPacket.getWin() == 0) { - blueResult = 0.5; - } else if (fightEndsPacket.getWin() == 1) { - blueResult = 1; - } else { - blueResult = 0; - } - // Die nächsten Zeilen filtern ein Fight innerhalb eines Teams nicht gewertet wird, bzw auch wenn nur Teile beider Teams im // gleichen Team sind dieser ungewertet ist. Set teamsIds = fightEndsPacket.getBluePlayers().stream().map(SteamwarUser::get).map(SteamwarUser::getTeam).collect(Collectors.toSet()); @@ -82,60 +72,172 @@ public class FightEndsHandler extends PacketHandler { } } - try { - if (teamComboExistedAlready(fightEndsPacket.getBluePlayers(), fightEndsPacket.getRedPlayers(), fightEndsPacket.getGameMode())) { - return; - } - } finally { - gameModeGames.computeIfAbsent(fightEndsPacket.getGameMode(), s -> new LinkedList<>()).add(new Game(fightEndsPacket.getBluePlayers(), fightEndsPacket.getRedPlayers())); - } + calcSchemElo(fightEndsPacket); + calcUserElo(fightEndsPacket); + } - int blueSchemElo = SchemElo.getCurrentElo(fightEndsPacket.getBlueSchem()); - int redSchemElo = SchemElo.getCurrentElo(fightEndsPacket.getRedSchem()); + private void calcUserElo(FightEndsPacket fightEndsPacket) { + int blueTeamSize = fightEndsPacket.getBluePlayers().size(); + int redTeamSize = fightEndsPacket.getRedPlayers().size(); int blueTeamElo = fightEndsPacket.getBluePlayers().stream().mapToInt(player -> UserElo.getEloOrDefault(player, fightEndsPacket.getGameMode())).sum(); int redTeamElo = fightEndsPacket.getRedPlayers().stream().mapToInt(player -> UserElo.getEloOrDefault(player, fightEndsPacket.getGameMode())).sum(); - calculateEloOfTeam(fightEndsPacket.getBlueSchem(), blueSchemElo, redSchemElo, blueTeamElo, redTeamElo, blueResult, fightEndsPacket.getBluePlayers(), fightEndsPacket.getGameMode(), bluePublic || redPublic); - calculateEloOfTeam(fightEndsPacket.getRedSchem(), redSchemElo, blueSchemElo, redTeamElo, blueTeamElo, 1 - blueResult, fightEndsPacket.getRedPlayers(), fightEndsPacket.getGameMode(), bluePublic || redPublic); + double blueFactor = getBluePlayerFactor(blueTeamSize, redTeamSize) * getTimeFactor(fightEndsPacket.getDuration()) * getBlueEloFactor(blueTeamElo, redTeamElo) * getRematchFactor(fightEndsPacket); + double redFactor = getRedPlayerFactor(blueTeamSize, redTeamSize) * getTimeFactor(fightEndsPacket.getDuration()) * getRedEloFactor(blueTeamElo, redTeamElo) * getRematchFactor(fightEndsPacket); + + if (fightEndsPacket.getWin() == 1) { + blueFactor *= WIN_FACTOR; + redFactor *= LOSE_FACTOR; + } else if (fightEndsPacket.getWin() == 2) { + blueFactor *= LOSE_FACTOR; + redFactor *= WIN_FACTOR; + } else { + if (redFactor == blueFactor) { + blueFactor *= 0; + redFactor *= 0; + } else if (blueFactor > redFactor) { + blueFactor *= DRAW_WIN_FACTOR; + redFactor *= DRAW_LOSE_FACTOR; + } else { + blueFactor *= DRAW_LOSE_FACTOR; + redFactor *= DRAW_WIN_FACTOR; + } + } + + update(fightEndsPacket.getBluePlayers(), fightEndsPacket.getGameMode(), blueFactor); + update(fightEndsPacket.getRedPlayers(), fightEndsPacket.getGameMode(), redFactor); } - private void calculateEloOfTeam(int schemId, int eloSchemOwn, int eloSchemEnemy, int eloTeamOwn, int eloTeamEnemy, double result, List players, String gameMode, boolean noPlayerRank) { - double winSchemExpectation = calsWinExpectation(eloSchemOwn, eloSchemEnemy); - SchemElo.setElo(schemId, (int) Math.round(eloSchemOwn + K * (result - winSchemExpectation))); - - if (noPlayerRank) return; - double winTeamExpectation = calsWinExpectation(eloTeamOwn, eloTeamEnemy); + private void update(List players, String gameMode, double factor) { for (int player : players) { int playerElo = UserElo.getEloOrDefault(player, gameMode); - int fights = UserElo.getFightsOfSeason(player, gameMode); - UserElo.setElo(player, gameMode, (int) Math.round(playerElo + getK(fights) * (result - winTeamExpectation))); + int eloGain = (int) Math.round(MEDIAN_ELO_GAIN * factor); + playerElo += eloGain; + if (playerElo < 0) playerElo = 0; + + int oldProgression = UserElo.getProgression(player, gameMode); + UserElo.setElo(player, gameMode, playerElo); + int newProgression = UserElo.getProgression(player, gameMode); + + BaseComponent[] eloGainComponent = TextComponent.fromLegacyText(((eloGain > 0) ? "§a+" : (eloGain == 0 ? "§7" : "§c")) + eloGain); + if (oldProgression == newProgression) { + send(player(player), UserElo.toEmblem(oldProgression).trim(), eloGainComponent); + continue; + } + animate(player(player), UserElo.toEmblem(oldProgression).trim(), UserElo.toEmblem(newProgression).trim(), (oldProgression < newProgression) ? "§a" : "§c", eloGainComponent); } } + private void animate(ProxiedPlayer player, String oldEmblem, String newEmblem, String arrowColor, BaseComponent[] eloGainComponent) { + String finalOldEmblem = (oldEmblem.isEmpty() ? "/" : oldEmblem); + String finalNewEmblem = (newEmblem.isEmpty() ? "/" : newEmblem); + + TaskScheduler scheduler = ProxyServer.getInstance().getScheduler(); + scheduler.schedule(BungeeCore.get(), () -> { + send(player, "§8" + finalOldEmblem, eloGainComponent); + }, 0, TimeUnit.SECONDS); + scheduler.schedule(BungeeCore.get(), () -> { + send(player, "§8" + finalOldEmblem + arrowColor + " >", eloGainComponent); + }, 500, TimeUnit.MILLISECONDS); + scheduler.schedule(BungeeCore.get(), () -> { + send(player, "§8" + finalOldEmblem + arrowColor + " >>", eloGainComponent); + }, 1000, TimeUnit.MILLISECONDS); + scheduler.schedule(BungeeCore.get(), () -> { + send(player, "§8" + finalOldEmblem + arrowColor + " >>>", eloGainComponent); + }, 1500, TimeUnit.MILLISECONDS); + scheduler.schedule(BungeeCore.get(), () -> { + send(player, "§8" + finalOldEmblem + arrowColor + " >>> §8" + finalNewEmblem, eloGainComponent); + }, 2000, TimeUnit.MILLISECONDS); + } + + private void send(ProxiedPlayer player, String text, BaseComponent[] eloGainComponent) { + Title title = new BungeeTitle().title(TextComponent.fromLegacyText(text)) + .subTitle(eloGainComponent) + .fadeIn(5) + .stay(40) + .fadeOut(5); + title.send(player); + } + + private ProxiedPlayer player(int userId) { + return ProxyServer.getInstance().getPlayer(SteamwarUser.get(userId).getUUID()); + } + + private void calcSchemElo(FightEndsPacket fightEndsPacket) { + double blueResult; + if (fightEndsPacket.getWin() == 0) { + blueResult = 0.5; + } else if (fightEndsPacket.getWin() == 1) { + blueResult = 1; + } else { + blueResult = 0; + } + + int blueSchemElo = SchemElo.getCurrentElo(fightEndsPacket.getBlueSchem()); + int redSchemElo = SchemElo.getCurrentElo(fightEndsPacket.getRedSchem()); + calcSchemElo(fightEndsPacket.getBlueSchem(), blueSchemElo, redSchemElo, blueResult); + calcSchemElo(fightEndsPacket.getRedSchem(), redSchemElo, blueSchemElo, 1 - blueResult); + } + + private void calcSchemElo(int eloSchemOwn, int eloSchemEnemy, int schemId, double result) { + double winSchemExpectation = calsWinExpectation(eloSchemOwn, eloSchemEnemy); + SchemElo.setElo(schemId, (int) Math.round(eloSchemOwn + K * (result - winSchemExpectation))); + } + private double calsWinExpectation(int eloOwn, int eloEnemy) { return 1 / (1 + Math.pow(10, (eloEnemy - eloOwn) / 600f)); } - private double getK(int fights) { - return K * Math.max(1.3 - (fights / 200.0), 0.8); + private double getBluePlayerFactor(int blueTeam, int redTeam) { + return redTeam / (double) blueTeam; } - private boolean teamComboExistedAlready(List bluePlayers, List redPlayers, String gameMode) { - if (!gameModeGames.containsKey(gameMode)) { - return false; + private double getRedPlayerFactor(int blueTeam, int redTeam) { + return blueTeam / (double) redTeam; + } + + private double getTimeFactor(int duration) { + if (duration <= 10) { + return 0.5; } - LinkedList games = gameModeGames.get(gameMode); - long lifetime = fightRanges.getOrDefault(gameMode, defaultFightRange); + if (duration <= 60) { + return 0.8; + } + return 1.0; + } + + private double getBlueEloFactor(int blueElo, int redElo) { + if (blueElo == 0) blueElo = 1; + if (redElo == 0) redElo = 1; + return sigmoid(redElo / (double) blueElo); + } + + private double getRedEloFactor(int blueElo, int redElo) { + if (blueElo == 0) blueElo = 1; + if (redElo == 0) redElo = 1; + return sigmoid(blueElo / (double) redElo); + } + + private double sigmoid(double x) { + return 1 / (1 + Math.exp(-2 * (x - 1))) * 2; + } + + private double getRematchFactor(FightEndsPacket fightEndsPacket) { + gameModeGames.computeIfAbsent(fightEndsPacket.getGameMode(), s -> new LinkedList<>()).add(new Game(fightEndsPacket.getBluePlayers(), fightEndsPacket.getRedPlayers())); + + LinkedList games = gameModeGames.get(fightEndsPacket.getGameMode()); while (!games.isEmpty()) { Game game = games.getFirst(); - if (game.livedMillis() > lifetime) { + if (game.livedMillis() > REMATCH_LIFETIME) { games.removeFirst(); } else { break; } } - return games.stream().filter(game -> game.isSame(bluePlayers, redPlayers)).count() > fightCounts.getOrDefault(gameMode, defaultFightCount); + + long rematchCount = games.stream().filter(game -> game.isSame(fightEndsPacket.getBluePlayers(), fightEndsPacket.getRedPlayers())).count(); + return 1.0 / rematchCount; } @RequiredArgsConstructor diff --git a/src/de/steamwar/messages/BungeeCore.properties b/src/de/steamwar/messages/BungeeCore.properties index 2e9241e..3a1feaf 100644 --- a/src/de/steamwar/messages/BungeeCore.properties +++ b/src/de/steamwar/messages/BungeeCore.properties @@ -642,7 +642,6 @@ RANK_HEADER=§7§lMode {0} RANK_UNPLACED=§eunranked RANK_PLACED=§e{0}§8. §7with §e{1} §7Elo§8. RANK_EMBLEM=§eEmblem§8: {0} -RANK_NEEDED_FIGHTS_LEFT={0} §8(§e{1}§7 fights needed§8) #Fabric Mod Sender MODIFICATION_BAN_MESSAGE=You tried to bypass / modify the FabricModSender!