Remove SchemElo and UserElo

This commit is contained in:
2025-12-20 21:19:20 +01:00
parent 9efe625603
commit ac5dda58a1
10 changed files with 1 additions and 662 deletions
@@ -38,7 +38,6 @@ import de.steamwar.persistent.ReloadablePlugin;
import de.steamwar.sql.Punishment;
import de.steamwar.sql.SteamwarUser;
import de.steamwar.sql.Team;
import de.steamwar.sql.UserElo;
import de.steamwar.sql.internal.Statement;
import de.steamwar.velocitycore.commands.PunishmentCommand;
import de.steamwar.velocitycore.commands.ServerSwitchCommand;
@@ -184,7 +183,6 @@ public class VelocityCore implements ReloadablePlugin {
schedule(() -> {
SteamwarUser.clear();
UserElo.clear();
Team.clear();
}).repeat(1, TimeUnit.HOURS).schedule();
@@ -1,68 +0,0 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 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
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.velocitycore.commands;
import de.steamwar.command.SWCommand;
import de.steamwar.linkage.Linked;
import de.steamwar.messages.Chatter;
import de.steamwar.messages.Message;
import de.steamwar.sql.GameModeConfig;
import de.steamwar.sql.Season;
import de.steamwar.sql.SteamwarUser;
import de.steamwar.sql.UserElo;
import de.steamwar.velocitycore.ArenaMode;
@Linked
public class RankCommand extends SWCommand {
public RankCommand() {
super("rank");
}
@Register
public void ownRank(Chatter sender) {
rank(sender, sender.user());
}
@Register
public void rank(Chatter sender, @ErrorMessage("RANK_PLAYER_NOT_FOUND") SteamwarUser user) {
if (!sender.user().equals(user)) {
sender.prefixless("RANK_PLAYER_FOUND", user, Season.convertSeasonToString(Season.getSeason()));
} else {
sender.prefixless("RANK_PLAYER_SELF", Season.convertSeasonToString(Season.getSeason()));
}
for(GameModeConfig<String, String> mode : ArenaMode.getAllModes()) {
if (!mode.Server.Ranked)
continue;
Integer elo = UserElo.getElo(user.getId(), mode.getSchemTypeOrInternalName());
Message eloMsg;
if (elo != null) {
int placement = UserElo.getPlacement(elo, mode.getSchemTypeOrInternalName());
eloMsg = new Message("RANK_PLACED", placement, elo);
} else {
eloMsg = new Message("RANK_UNPLACED");
}
sender.prefixless("RANK_DATA", UserElo.getEmblemProgression(mode.getChatName(), user.getId()), mode.getChatName(), eloMsg);
}
}
}
@@ -248,7 +248,7 @@ public class ChatListener extends BasicListener {
msgReceiver == null ? receiver : msgReceiver,
highlightMentions(message, chatColorCode, receiver),
sender.getTeam() == 0 ? "" : "§" + Team.byId(sender.getTeam()).getTeamColor() + Team.byId(sender.getTeam()).getTeamKuerzel() + " ",
UserElo.getEmblem(sender, rankedModes),
"",
prefix.getColorCode(),
prefix.getChatPrefix().length() == 0 ? "§f" : prefix.getChatPrefix() + " ",
chatColorCode);
@@ -1,261 +0,0 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 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
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.velocitycore.network.handlers;
import com.velocitypowered.api.proxy.Player;
import de.steamwar.sql.GameModeConfig;
import de.steamwar.linkage.Linked;
import de.steamwar.messages.Chatter;
import de.steamwar.network.packets.PacketHandler;
import de.steamwar.network.packets.common.FightEndsPacket;
import de.steamwar.sql.SchematicType;
import de.steamwar.sql.SteamwarUser;
import de.steamwar.sql.UserElo;
import de.steamwar.velocitycore.ArenaMode;
import de.steamwar.velocitycore.VelocityCore;
import lombok.RequiredArgsConstructor;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.title.Title;
import java.time.Duration;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.function.IntFunction;
import java.util.stream.Collectors;
@Linked
public class EloPlayerHandler extends PacketHandler {
private static final int MEDIAN_ELO_GAIN = 40;
private static final int MEDIAN_ELO_LOSE = 20;
private static final long REMATCH_LIFETIME = (long) 60 * 60 * 1000;
private final Map<String, LinkedList<Game>> gameModeGames = new HashMap<>();
/**
* {@link FightEndsPacket#getWin()} == 1 -> Blue won
* {@link FightEndsPacket#getWin()} == 2 -> Red won
*/
@Handler
public void handle(FightEndsPacket fightEndsPacket) {
SchematicType schematicType = SchematicType.fromDB(fightEndsPacket.getGameMode());
GameModeConfig<String, String> arenaMode;
if (schematicType == null) {
arenaMode = ArenaMode.getByInternal(fightEndsPacket.getGameMode());
} else {
arenaMode = ArenaMode.getBySchemType(schematicType);
}
if (arenaMode == null) return;
if (!arenaMode.Server.Ranked) return;
if (EloSchemHandler.publicVsPrivate(fightEndsPacket))
return;
// 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<Integer> teamsIds = fightEndsPacket.getBluePlayers().stream().map(SteamwarUser::byId).map(SteamwarUser::getTeam).collect(Collectors.toSet());
for (int redPlayer : fightEndsPacket.getRedPlayers()) {
int team = SteamwarUser.byId(redPlayer).getTeam();
if (team != 0 && teamsIds.contains(team)) {
return;
}
}
// Get sizes of both teams
int blueTeamSize = fightEndsPacket.getBluePlayers().size();
int redTeamSize = fightEndsPacket.getRedPlayers().size();
// Calculate the favored team
double bluePlayerFactor = 1 / (1 + Math.exp(0 - (fightEndsPacket.getWin() == 2 ? (double) redTeamSize / blueTeamSize : (double) blueTeamSize / redTeamSize) * 3 + 3)) * 2;
double redPlayerFactor = 1 / (1 + Math.exp(0 - (fightEndsPacket.getWin() == 1 ? (double) blueTeamSize / redTeamSize : (double) redTeamSize / blueTeamSize) * 3 + 3)) * 2;
// Calculate the time factor
double timeFactor = getTimeFactor(fightEndsPacket.getDuration());
// Get total elo of both teams
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();
// Adaptive elo bonus
int blueTeamEloBonus = 0;
int redTeamEloBonus = 0;
if (Math.abs(blueTeamElo / (double) fightEndsPacket.getBluePlayers().size() - redTeamElo / (double) fightEndsPacket.getRedPlayers().size()) > 400) {
int outlivedDuration = Math.max(fightEndsPacket.getDuration() - 60, 0);
if (fightEndsPacket.getWin() == 1 && blueTeamElo < redTeamElo) {
blueTeamEloBonus = outlivedDuration / 20;
redTeamEloBonus = -(blueTeamEloBonus / 2);
} else if (fightEndsPacket.getWin() == 2 && redTeamElo < blueTeamElo) {
redTeamEloBonus = outlivedDuration / 20;
blueTeamEloBonus = -(redTeamEloBonus / 2);
} else if (fightEndsPacket.getWin() == 0) {
if (redTeamElo < blueTeamElo) {
blueTeamEloBonus = outlivedDuration / 20;
redTeamEloBonus = -(blueTeamEloBonus / 2);
} else if (blueTeamElo < redTeamElo) {
redTeamEloBonus = outlivedDuration / 20;
blueTeamEloBonus = -(redTeamEloBonus / 2);
}
}
}
// Calculate the elo factor
double blueEloFactor = ((fightEndsPacket.getWin() == 1 ? 1 : 0) - 1 / (1 + Math.pow(10, (redTeamElo - blueTeamElo) / 600.0))) * (1 / (1 + Math.exp(-Math.abs(blueTeamElo - redTeamElo) / 1200.0))) * 4;
double redEloFactor = blueEloFactor * -1;
// Calculate favoured team on draw
if (fightEndsPacket.getWin() == 0) {
if (bluePlayerFactor > 1) {
blueEloFactor = Math.abs(blueEloFactor) * -1;
redEloFactor = Math.abs(redEloFactor);
} else if (bluePlayerFactor < 1) {
blueEloFactor = Math.abs(blueEloFactor);
redEloFactor = Math.abs(redEloFactor) * -1;
} else {
if (Math.abs(blueEloFactor) > 1) {
// Do nothing
} else if (Math.abs(blueEloFactor) < 1) {
blueEloFactor = -blueEloFactor;
redEloFactor = -blueEloFactor;
} else {
blueEloFactor = 0;
redEloFactor = 0;
}
}
}
// Calculate the rematch factor
double rematchFactor = getRematchFactor(fightEndsPacket);
// Calculate the win factor
double blueWinFactor = (fightEndsPacket.getWin() == 1 ? 1 : 0.7);
double redWinFactor = (fightEndsPacket.getWin() == 2 ? 1 : 0.7);
// Calculate division factor
double divisionFactor = 1D / Math.max(blueTeamSize, redTeamSize);
double blueFactor = bluePlayerFactor * timeFactor * blueEloFactor * rematchFactor * blueWinFactor * divisionFactor;
double redFactor = redPlayerFactor * timeFactor * redEloFactor * rematchFactor * redWinFactor * divisionFactor;
// Calculate the elo gain for each player
int blueEloGain = (int) Math.round((blueFactor < 0 ? MEDIAN_ELO_LOSE : MEDIAN_ELO_GAIN) * blueFactor) + blueTeamEloBonus;
int redEloGain = (int) Math.round((redFactor < 0 ? MEDIAN_ELO_LOSE : MEDIAN_ELO_GAIN) * redFactor) + redTeamEloBonus;
// BungeeCore.get().getLogger().info("Blue: " + fightEndsPacket.getBluePlayers() + " " + blueTeamSize + " " + bluePlayerFactor + " " + timeFactor + " " + blueEloFactor + " " + rematchFactor + " " + blueWinFactor + " " + divisionFactor + " " + blueEloGain);
// BungeeCore.get().getLogger().info("Red: " + fightEndsPacket.getRedPlayers() + " " + redTeamSize + " " + redPlayerFactor + " " + timeFactor + " " + redEloFactor + " " + rematchFactor + " " + redWinFactor + " " + divisionFactor + " " + redEloGain);
update(fightEndsPacket.getBluePlayers(), fightEndsPacket.getGameMode(), blueEloGain);
update(fightEndsPacket.getRedPlayers(), fightEndsPacket.getGameMode(), redEloGain);
}
private void update(List<Integer> players, String gameMode, int eloGain) {
for (int player : players) {
// BungeeCore.get().getLogger().log(Level.INFO, "Player: " + player + " Elo: " + UserElo.getEloOrDefault(player, gameMode) + " Factor: " + factor);
int playerElo = UserElo.getEloOrDefault(player, gameMode);
playerElo += eloGain;
if (playerElo < 0) playerElo = 0;
int oldProgression = UserElo.getProgression(player, gameMode);
UserElo.setElo(player, gameMode, playerElo);
int newProgression = UserElo.getProgression(player, gameMode);
animate(player(player), UserElo.toEmblem(oldProgression).trim(), UserElo.toEmblem(newProgression).trim(), (oldProgression < newProgression) ? "§a" : "§c", eloGain);
}
}
private void animate(Player player, String oldEmblem, String newEmblem, String arrowColor, int eloGain) {
if (player == null)
return;
IntFunction<String> getRankup = getEmblemTransition(oldEmblem, newEmblem, arrowColor);
String color = ((eloGain > 0) ? "§a+" : (eloGain == 0 ? "§7" : "§c"));
double eloStep = eloGain / 40.0;
for (int i = 0; i < 40; i++) {
Component eloGainComponent = Chatter.SERIALIZER.deserialize(color + (int) (eloStep * (i + 1)));
int finalI = i;
VelocityCore.schedule(() -> player.showTitle(Title.title(
Chatter.SERIALIZER.deserialize(getRankup.apply(finalI)),
eloGainComponent,
Title.Times.times(Duration.ofMillis(finalI == 0 ? 250 : 0), Duration.ofSeconds(2), Duration.ofMillis(finalI == 39 ? 250 : 0))
))).delay(i * 50L, TimeUnit.MILLISECONDS).schedule();
}
}
private static IntFunction<String> getEmblemTransition(String oldEmblem, String newEmblem, String arrowColor) {
String finalOldEmblem = (oldEmblem.isEmpty() ? "/" : oldEmblem).trim();
String finalNewEmblem = (newEmblem.isEmpty() ? "/" : newEmblem).trim();
return i -> {
if (oldEmblem.equals(newEmblem)) return "§8" + finalOldEmblem;
if (i < 8) return "§8" + finalOldEmblem;
if (i < 16) return "§8" + finalOldEmblem + arrowColor + " >";
if (i < 24) return "§8" + finalOldEmblem + arrowColor + " >>";
if (i < 32) return "§8" + finalOldEmblem + arrowColor + " >>>";
return "§8" + finalOldEmblem + arrowColor + " >>> §8" + finalNewEmblem;
};
}
private Player player(int userId) {
return VelocityCore.getProxy().getPlayer(SteamwarUser.byId(userId).getUUID()).orElse(null);
}
private double getTimeFactor(int duration) {
if (duration <= 10) {
return 0.5;
}
if (duration <= 60) {
return 0.8;
}
return 1.0;
}
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()) {
Game game = games.getFirst();
if (game.livedMillis() > REMATCH_LIFETIME) {
games.removeFirst();
} else {
break;
}
}
long rematchCount = games.stream().filter(game -> game.isSame(fightEndsPacket.getBluePlayers(), fightEndsPacket.getRedPlayers())).count();
return 1.0 / rematchCount;
}
@RequiredArgsConstructor
private static class Game {
private long time = System.currentTimeMillis();
private final List<Integer> bluePlayers;
private final List<Integer> redPlayers;
public long livedMillis() {
return System.currentTimeMillis() - time;
}
public boolean isSame(List<Integer> bluePlayers, List<Integer> redPlayers) {
return bluePlayers.containsAll(this.bluePlayers) && redPlayers.containsAll(this.redPlayers);
}
}
}
@@ -1,82 +0,0 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 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
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.velocitycore.network.handlers;
import de.steamwar.sql.GameModeConfig;
import de.steamwar.linkage.Linked;
import de.steamwar.network.packets.PacketHandler;
import de.steamwar.network.packets.common.FightEndsPacket;
import de.steamwar.sql.SchemElo;
import de.steamwar.sql.SchematicNode;
import de.steamwar.sql.SchematicType;
import de.steamwar.velocitycore.ArenaMode;
@Linked
public class EloSchemHandler extends PacketHandler {
private static final int K = 20;
public static boolean publicVsPrivate(FightEndsPacket packet) {
if (packet.getRedSchem() == -1 && packet.getBlueSchem() == -1) {
return false;
}
SchematicNode blueSchem = SchematicNode.getSchematicNode(packet.getBlueSchem());
SchematicNode redSchem = SchematicNode.getSchematicNode(packet.getRedSchem());
return (blueSchem.getOwner() == 0) != (redSchem.getOwner() == 0);
}
@Handler
public void handle(FightEndsPacket fightEndsPacket) {
SchematicType type = SchematicType.fromDB(fightEndsPacket.getGameMode());
if (type == null) return;
GameModeConfig<String, String> arenaMode = ArenaMode.getBySchemType(type);
if (!arenaMode.Server.Ranked) return;
if (publicVsPrivate(fightEndsPacket))
return;
calcSchemElo(fightEndsPacket);
}
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(redSchemElo, blueSchemElo, fightEndsPacket.getRedSchem(), blueResult);
calcSchemElo(blueSchemElo, redSchemElo, fightEndsPacket.getBlueSchem(), 1 - blueResult);
}
private void calcSchemElo(int eloSchemOwn, int eloSchemEnemy, int schemId, double result) {
double winSchemExpectation = calcWinExpectation(eloSchemOwn, eloSchemEnemy);
SchemElo.setElo(schemId, (int) Math.round(eloSchemOwn + K * (result - winSchemExpectation)));
}
private double calcWinExpectation(int eloOwn, int eloEnemy) {
return 1 / (1 + Math.pow(10, (eloEnemy - eloOwn) / 600f));
}
}