SteamWar/BungeeCore
Archiviert
13
2

Merge pull request 'Update to new ranked system' (#475) from NewRankedSystem into master
Alle Prüfungen waren erfolgreich
SteamWarCI Build successful

Reviewed-on: #475
Reviewed-by: Lixfel <lixfel@steamwar.de>
Dieser Commit ist enthalten in:
Lixfel 2023-05-12 22:56:41 +02:00
Commit ab207b2234
5 geänderte Dateien mit 161 neuen und 69 gelöschten Zeilen

@ -1 +1 @@
Subproject commit bf6f6b920d0a4c00c3e8f4ddcc4ef8a684f8d174 Subproject commit e3e1a1cfcd603754aec0ced8d0becce88eecd0e5

Datei anzeigen

@ -21,11 +21,11 @@ package de.steamwar.bungeecore.commands;
import de.steamwar.bungeecore.ArenaMode; import de.steamwar.bungeecore.ArenaMode;
import de.steamwar.bungeecore.Message; import de.steamwar.bungeecore.Message;
import de.steamwar.sql.SteamwarUser;
import de.steamwar.sql.UserElo;
import de.steamwar.command.SWCommand; import de.steamwar.command.SWCommand;
import de.steamwar.command.SWCommandUtils; import de.steamwar.command.SWCommandUtils;
import de.steamwar.command.TypeMapper; 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.BungeeCord;
import net.md_5.bungee.api.connection.ProxiedPlayer; import net.md_5.bungee.api.connection.ProxiedPlayer;
@ -67,18 +67,10 @@ public class RankCommand extends SWCommand {
} else { } else {
Message.send("RANK_UNPLACED", player); 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) @Mapper(value = "player", local = true)
public TypeMapper<String> playerTypeMapper() { public TypeMapper<String> playerTypeMapper() {
return SWCommandUtils.createMapper(s -> s, s -> BungeeCord.getInstance().getPlayers().stream().map(ProxiedPlayer::getName).collect(Collectors.toList())); return SWCommandUtils.createMapper(s -> s, s -> BungeeCord.getInstance().getPlayers().stream().map(ProxiedPlayer::getName).collect(Collectors.toList()));

Datei anzeigen

@ -1,7 +1,7 @@
/* /*
* This file is a part of the SteamWar software. * 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 * 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 * it under the terms of the GNU Affero General Public License as published by
@ -20,29 +20,35 @@
package de.steamwar.bungeecore.network.handlers; package de.steamwar.bungeecore.network.handlers;
import de.steamwar.bungeecore.ArenaMode; import de.steamwar.bungeecore.ArenaMode;
import de.steamwar.bungeecore.BungeeCore;
import de.steamwar.network.packets.PacketHandler; import de.steamwar.network.packets.PacketHandler;
import de.steamwar.network.packets.common.FightEndsPacket; import de.steamwar.network.packets.common.FightEndsPacket;
import de.steamwar.sql.*; import de.steamwar.sql.*;
import lombok.RequiredArgsConstructor; 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.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors; import java.util.stream.Collectors;
public class FightEndsHandler extends PacketHandler { public class FightEndsHandler extends PacketHandler {
private Map<String, LinkedList<Game>> 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 int K = 20;
private long defaultFightRange = 1000 /* Milliseconds */ * 60 /* Seconds */ * 15L /* Minutes */; private Map<String, LinkedList<Game>> gameModeGames = new HashMap<>();
private Map<String, Long> fightRanges = new HashMap<>();
private long defaultFightCount = 1;
private Map<String, Long> fightCounts = new HashMap<>();
{
fightRanges.put("miniwargear", 1000 /* Milliseconds */ * 60 /* Seconds */ * 30L /* Minutes */);
fightCounts.put("miniwargear", 3L);
}
@Handler @Handler
public void handle(FightEndsPacket fightEndsPacket) { public void handle(FightEndsPacket fightEndsPacket) {
@ -50,13 +56,6 @@ public class FightEndsHandler extends PacketHandler {
return; 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 bluePublic = SchematicNode.getSchematicNode(fightEndsPacket.getBlueSchem()).getOwner() == 0;
boolean redPublic = SchematicNode.getSchematicNode(fightEndsPacket.getRedSchem()).getOwner() == 0; boolean redPublic = SchematicNode.getSchematicNode(fightEndsPacket.getRedSchem()).getOwner() == 0;
@ -64,15 +63,6 @@ public class FightEndsHandler extends PacketHandler {
return; 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 // 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. // gleichen Team sind dieser ungewertet ist.
Set<Integer> teamsIds = fightEndsPacket.getBluePlayers().stream().map(SteamwarUser::get).map(SteamwarUser::getTeam).collect(Collectors.toSet()); Set<Integer> teamsIds = fightEndsPacket.getBluePlayers().stream().map(SteamwarUser::get).map(SteamwarUser::getTeam).collect(Collectors.toSet());
@ -82,60 +72,172 @@ public class FightEndsHandler extends PacketHandler {
} }
} }
try { calcSchemElo(fightEndsPacket);
if (teamComboExistedAlready(fightEndsPacket.getBluePlayers(), fightEndsPacket.getRedPlayers(), fightEndsPacket.getGameMode())) { calcUserElo(fightEndsPacket);
return;
}
} finally {
gameModeGames.computeIfAbsent(fightEndsPacket.getGameMode(), s -> new LinkedList<>()).add(new Game(fightEndsPacket.getBluePlayers(), fightEndsPacket.getRedPlayers()));
} }
int blueSchemElo = SchemElo.getCurrentElo(fightEndsPacket.getBlueSchem()); private void calcUserElo(FightEndsPacket fightEndsPacket) {
int redSchemElo = SchemElo.getCurrentElo(fightEndsPacket.getRedSchem()); int blueTeamSize = fightEndsPacket.getBluePlayers().size();
int redTeamSize = fightEndsPacket.getRedPlayers().size();
int blueTeamElo = fightEndsPacket.getBluePlayers().stream().mapToInt(player -> UserElo.getEloOrDefault(player, fightEndsPacket.getGameMode())).sum(); 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(); 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); double blueFactor = getBluePlayerFactor(blueTeamSize, redTeamSize) * getTimeFactor(fightEndsPacket.getDuration()) * getBlueEloFactor(blueTeamElo, redTeamElo) * getRematchFactor(fightEndsPacket);
calculateEloOfTeam(fightEndsPacket.getRedSchem(), redSchemElo, blueSchemElo, redTeamElo, blueTeamElo, 1 - blueResult, fightEndsPacket.getRedPlayers(), fightEndsPacket.getGameMode(), bluePublic || redPublic); 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;
}
} }
private void calculateEloOfTeam(int schemId, int eloSchemOwn, int eloSchemEnemy, int eloTeamOwn, int eloTeamEnemy, double result, List<Integer> players, String gameMode, boolean noPlayerRank) { update(fightEndsPacket.getBluePlayers(), fightEndsPacket.getGameMode(), blueFactor);
double winSchemExpectation = calsWinExpectation(eloSchemOwn, eloSchemEnemy); update(fightEndsPacket.getRedPlayers(), fightEndsPacket.getGameMode(), redFactor);
SchemElo.setElo(schemId, (int) Math.round(eloSchemOwn + K * (result - winSchemExpectation))); }
if (noPlayerRank) return; private void update(List<Integer> players, String gameMode, double factor) {
double winTeamExpectation = calsWinExpectation(eloTeamOwn, eloTeamEnemy);
for (int player : players) { for (int player : players) {
int playerElo = UserElo.getEloOrDefault(player, gameMode); int playerElo = UserElo.getEloOrDefault(player, gameMode);
int fights = UserElo.getFightsOfSeason(player, gameMode); int eloGain = (int) Math.round(MEDIAN_ELO_GAIN * factor);
UserElo.setElo(player, gameMode, (int) Math.round(playerElo + getK(fights) * (result - winTeamExpectation))); 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) { private double calsWinExpectation(int eloOwn, int eloEnemy) {
return 1 / (1 + Math.pow(10, (eloEnemy - eloOwn) / 600f)); return 1 / (1 + Math.pow(10, (eloEnemy - eloOwn) / 600f));
} }
private double getK(int fights) { private double getBluePlayerFactor(int blueTeam, int redTeam) {
return K * Math.max(1.3 - (fights / 200.0), 0.8); return redTeam / (double) blueTeam;
} }
private boolean teamComboExistedAlready(List<Integer> bluePlayers, List<Integer> redPlayers, String gameMode) { private double getRedPlayerFactor(int blueTeam, int redTeam) {
if (!gameModeGames.containsKey(gameMode)) { return blueTeam / (double) redTeam;
return false;
} }
LinkedList<Game> games = gameModeGames.get(gameMode);
long lifetime = fightRanges.getOrDefault(gameMode, defaultFightRange); private double getTimeFactor(int duration) {
if (duration <= 10) {
return 0.5;
}
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<Game> games = gameModeGames.get(fightEndsPacket.getGameMode());
while (!games.isEmpty()) { while (!games.isEmpty()) {
Game game = games.getFirst(); Game game = games.getFirst();
if (game.livedMillis() > lifetime) { if (game.livedMillis() > REMATCH_LIFETIME) {
games.removeFirst(); games.removeFirst();
} else { } else {
break; 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 @RequiredArgsConstructor

Datei anzeigen

@ -642,7 +642,6 @@ RANK_HEADER=§7§lMode {0}
RANK_UNPLACED=§eunranked RANK_UNPLACED=§eunranked
RANK_PLACED=§e{0}§8. §7with §e{1} §7Elo§8. RANK_PLACED=§e{0}§8. §7with §e{1} §7Elo§8.
RANK_EMBLEM=§eEmblem§8: {0} RANK_EMBLEM=§eEmblem§8: {0}
RANK_NEEDED_FIGHTS_LEFT={0} §8(§e{1}§7 fights needed§8)
#Fabric Mod Sender #Fabric Mod Sender
MODIFICATION_BAN_MESSAGE=You tried to bypass / modify the FabricModSender! MODIFICATION_BAN_MESSAGE=You tried to bypass / modify the FabricModSender!

Datei anzeigen

@ -619,7 +619,6 @@ RANK_HEADER=§7§lModus {0}
RANK_UNPLACED=§eunplatziert RANK_UNPLACED=§eunplatziert
RANK_PLACED=§e{0}§8. §7mit §e{1} §7Elo§8. RANK_PLACED=§e{0}§8. §7mit §e{1} §7Elo§8.
RANK_EMBLEM=§eEmblem§8: {0} RANK_EMBLEM=§eEmblem§8: {0}
RANK_NEEDED_FIGHTS_LEFT={0} §8(§7noch §e{1}§7 Kämpfe nötig§8)
#Fabric Mod Sender #Fabric Mod Sender
MODIFICATION_BAN_MESSAGE=Du hast probiert den FabricModSender zu umgehen / zu modifizieren! MODIFICATION_BAN_MESSAGE=Du hast probiert den FabricModSender zu umgehen / zu modifizieren!