diff --git a/src/de/steamwar/bungeecore/BungeeCore.java b/src/de/steamwar/bungeecore/BungeeCore.java index b7338af..dc35139 100644 --- a/src/de/steamwar/bungeecore/BungeeCore.java +++ b/src/de/steamwar/bungeecore/BungeeCore.java @@ -25,10 +25,7 @@ import de.steamwar.bungeecore.commands.*; import de.steamwar.bungeecore.comms.SpigotReceiver; import de.steamwar.bungeecore.listeners.*; import de.steamwar.bungeecore.listeners.mods.*; -import de.steamwar.bungeecore.sql.Punishment; -import de.steamwar.bungeecore.sql.Statement; -import de.steamwar.bungeecore.sql.SteamwarUser; -import de.steamwar.bungeecore.sql.Team; +import de.steamwar.bungeecore.sql.*; import net.md_5.bungee.api.ChatMessageType; import net.md_5.bungee.api.CommandSender; import net.md_5.bungee.api.ProxyServer; @@ -156,6 +153,7 @@ public class BungeeCore extends Plugin { getProxy().getScheduler().schedule(this, () -> { SteamwarUser.clearCache(); + UserElo.clearCache(); Team.clearCache(); }, 1, 1, TimeUnit.HOURS); diff --git a/src/de/steamwar/bungeecore/comms/handlers/FightEndsHandler.java b/src/de/steamwar/bungeecore/comms/handlers/FightEndsHandler.java index 1592fcc..50afa5d 100644 --- a/src/de/steamwar/bungeecore/comms/handlers/FightEndsHandler.java +++ b/src/de/steamwar/bungeecore/comms/handlers/FightEndsHandler.java @@ -20,15 +20,65 @@ package de.steamwar.bungeecore.comms.handlers; import com.google.common.io.ByteArrayDataInput; +import de.steamwar.bungeecore.ArenaMode; import de.steamwar.bungeecore.comms.SpigotHandler; import de.steamwar.bungeecore.comms.packets.FightEndsPacket; +import de.steamwar.bungeecore.sql.SchemElo; +import de.steamwar.bungeecore.sql.SchematicNode; +import de.steamwar.bungeecore.sql.SchematicType; +import de.steamwar.bungeecore.sql.UserElo; import net.md_5.bungee.api.config.ServerInfo; +import java.util.List; + public class FightEndsHandler implements SpigotHandler { + private int K = 20; + @Override public void handle(ByteArrayDataInput in, ServerInfo info) { FightEndsPacket fightEndsPacket = new FightEndsPacket(in); + if (!ArenaMode.getBySchemType(SchematicType.fromDB(fightEndsPacket.getGameMode())).isRanked()) { + 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()).getId() == 0; + boolean redPublic = SchematicNode.getSchematicNode(fightEndsPacket.getRedSchem()).getId() == 0; + + if (bluePublic ^ redPublic) { + return; + } + + double blueResult; + if (fightEndsPacket.getWin() == 0) { + blueResult = 0.5; + } else if (fightEndsPacket.getWin() == 1) { + blueResult = 1; + } else { + blueResult = 0; + } + + int blueSchemElo = SchemElo.getElo(fightEndsPacket.getBlueSchem()); + int redSchemElo = SchemElo.getElo(fightEndsPacket.getRedSchem()); + + calculateEloOfTeam(fightEndsPacket.getBlueSchem(), blueSchemElo, redSchemElo, blueResult, fightEndsPacket.getBluePlayers(), fightEndsPacket.getGameMode()); + calculateEloOfTeam(fightEndsPacket.getRedSchem(), redSchemElo, blueSchemElo, 1 - blueResult, fightEndsPacket.getRedPlayers(), fightEndsPacket.getGameMode()); + } + + private void calculateEloOfTeam(int schemId, int eloOwn, int eloEnemy, double result, List players, String gameMode) { + double winExpectation = 1 / (1 + Math.pow(10, (eloOwn - eloEnemy) / 600f)); + SchemElo.setElo(schemId, (int) Math.round(eloOwn + K * (result - winExpectation))); + for (int player : players) { + int playerElo = UserElo.getEloOrDefault(player, gameMode); + UserElo.setElo(player, gameMode, (int) Math.round(playerElo + K * (result - winExpectation))); + } } } diff --git a/src/de/steamwar/bungeecore/listeners/ChatListener.java b/src/de/steamwar/bungeecore/listeners/ChatListener.java index 96c628a..54b9bb4 100644 --- a/src/de/steamwar/bungeecore/listeners/ChatListener.java +++ b/src/de/steamwar/bungeecore/listeners/ChatListener.java @@ -23,10 +23,7 @@ import de.steamwar.bungeecore.*; import de.steamwar.bungeecore.bot.SteamwarDiscordBot; import de.steamwar.bungeecore.commands.TpCommand; import de.steamwar.bungeecore.comms.packets.PingPacket; -import de.steamwar.bungeecore.sql.Punishment; -import de.steamwar.bungeecore.sql.SteamwarUser; -import de.steamwar.bungeecore.sql.Team; -import de.steamwar.bungeecore.sql.UserGroup; +import de.steamwar.bungeecore.sql.*; import net.md_5.bungee.api.*; import net.md_5.bungee.api.chat.TextComponent; import net.md_5.bungee.api.connection.ProxiedPlayer; @@ -200,7 +197,7 @@ public class ChatListener extends BasicListener { } private void publicChat(SteamwarUser user, ProxiedPlayer sender, String message){ - String name = sender.getDisplayName(); + String name = UserElo.getEmblem(user) + sender.getDisplayName(); String chatcolor = user.getUserGroup().getChatColorCode(); if(user.getUserGroup() != UserGroup.Member || user.getTeam() == 12 || user.getTeam() == 285 || user.getTeam() == 54) diff --git a/src/de/steamwar/bungeecore/listeners/ConnectionListener.java b/src/de/steamwar/bungeecore/listeners/ConnectionListener.java index 65855b3..a2f2fb2 100644 --- a/src/de/steamwar/bungeecore/listeners/ConnectionListener.java +++ b/src/de/steamwar/bungeecore/listeners/ConnectionListener.java @@ -76,7 +76,7 @@ public class ConnectionListener extends BasicListener { if(user.getUserGroup() != UserGroup.Member) { player.setPermission(YOUTUBER_MODS, true); - player.setDisplayName(user.getUserGroup().getColorCode() + user.getUserGroup().name() + " " + player.getName() + "§r"); + player.setDisplayName(user.getUserGroup().getColorCode() + user.getUserGroup().getChatPrefix() + " " + player.getName() + "§r"); player.setPermission("bungeecore.group." + user.getUserGroup().name().toLowerCase(), true); }else { player.setDisplayName(player.getName()); diff --git a/src/de/steamwar/bungeecore/sql/Elo.java b/src/de/steamwar/bungeecore/sql/Elo.java deleted file mode 100644 index da1db90..0000000 --- a/src/de/steamwar/bungeecore/sql/Elo.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - This file is a part of the SteamWar software. - - Copyright (C) 2020 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 . -*/ - -package de.steamwar.bungeecore.sql; - -public class Elo { - - private static final Statement elo = new Statement("SELECT Elo FROM Elo WHERE UserID = ? AND GameMode = ? AND Season = ?"); - private static final Statement place = new Statement("SELECT COUNT(*) AS Place FROM Elo WHERE GameMode = ? AND Elo > ? AND Season = ?"); - - private Elo(){} - - public static int getElo(int userID, String gameMode){ - return getElo(Season.getSeason(), userID, gameMode); - } - - public static int getElo(int season, int userID, String gameMode){ - return elo.select(rs -> { - if(rs.next()) - return rs.getInt("Elo"); - return 1000; - }, userID, gameMode, season); - } - - public static int getPlacement(int elo, String gameMode){ - return getPlacement(Season.getSeason(), elo, gameMode); - } - - public static int getPlacement(int season, int elo, String gameMode){ - return place.select(rs -> { - if(rs.next()) - return rs.getInt("Place"); - return -1; - }, gameMode, elo, season); - } -} diff --git a/src/de/steamwar/bungeecore/sql/SchemElo.java b/src/de/steamwar/bungeecore/sql/SchemElo.java new file mode 100644 index 0000000..d14eaec --- /dev/null +++ b/src/de/steamwar/bungeecore/sql/SchemElo.java @@ -0,0 +1,49 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2020 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 . + */ + +package de.steamwar.bungeecore.sql; + +import static de.steamwar.bungeecore.sql.UserElo.ELO_DEFAULT; + +public class SchemElo { + private SchemElo() {} + + private static final Statement elo = new Statement("SELECT Elo FROM SchemElo WHERE SchemID = ? AND Season = ?"); + private static final Statement setElo = new Statement("UPDATE SchemElo SET Elo = ? WHERE Season = ? AND SchemID = ?"); + + public static int getElo(int schemID) { + return getElo(Season.getSeason(), schemID); + } + + public static int getElo(int season, int schemID) { + return elo.select(rs -> { + if (rs.next()) + return rs.getInt("Elo"); + return ELO_DEFAULT; + }, schemID, season); + } + + public static void setElo(int schemID, int elo) { + setElo(Season.getSeason(), schemID, elo); + } + + public static void setElo(int season, int schemID, int elo) { + setElo.update(elo, season, schemID); + } +} diff --git a/src/de/steamwar/bungeecore/sql/Season.java b/src/de/steamwar/bungeecore/sql/Season.java index b1d6bdc..86dd01c 100644 --- a/src/de/steamwar/bungeecore/sql/Season.java +++ b/src/de/steamwar/bungeecore/sql/Season.java @@ -30,6 +30,11 @@ public class Season { return (calendar.get(Calendar.YEAR) * 3 + yearIndex); } + public static String getSeasonStart() { + Calendar calendar = Calendar.getInstance(); + return calendar.get(Calendar.YEAR) + "-" + (calendar.get(Calendar.MONTH) / 3 * 3 + 1) + "-1"; + } + public static String convertSeasonToString(int season){ if (season == -1) return ""; int yearSeason = season % 3; diff --git a/src/de/steamwar/bungeecore/sql/UserElo.java b/src/de/steamwar/bungeecore/sql/UserElo.java new file mode 100644 index 0000000..a4367fa --- /dev/null +++ b/src/de/steamwar/bungeecore/sql/UserElo.java @@ -0,0 +1,142 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2020 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 . + */ + +package de.steamwar.bungeecore.sql; + +import de.steamwar.bungeecore.ArenaMode; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +public class UserElo { + private UserElo() { + } + + public static final int ELO_DEFAULT = 1000; + + private static final Map>> gameModeUserEloCache = new HashMap<>(); + private static final Map maxEloCache = new HashMap<>(); + private static final Map emblemCache = new HashMap<>(); + + private static final Statement elo = new Statement("SELECT Elo FROM UserElo WHERE UserID = ? AND GameMode = ? AND Season = ?"); + private static final Statement setElo = new Statement("UPDATE UserElo SET Elo = ? WHERE Season = ? AND UserID = ? AND GameMode = ?"); + private static final Statement place = new Statement("SELECT COUNT(*) AS Place FROM UserElo WHERE GameMode = ? AND Elo > ? AND Season = ?"); + private static final Statement maxElo = new Statement("SELECT MAX(Elo) AS MaxElo FROM UserElo WHERE Season = ? AND GameMode = ?"); + private static final Statement fightsOfSeason = new Statement("SELECT COUNT(*) AS Fights FROM FightPlayer INNER JOIN Fight F on FightPlayer.FightID = F.FightID WHERE UserID = ? AND GameMode = ? AND UNIX_TIMESTAMP(StartTime) + Duration >= UNIX_TIMESTAMP(?)"); + + public static int getEloOrDefault(int userID, String gameMode) { + return getElo(userID, gameMode).orElse(ELO_DEFAULT); + } + + public static Optional getElo(int userID, String gameMode) { + return gameModeUserEloCache.computeIfAbsent(gameMode, gm -> { + return new HashMap<>(); + }).computeIfAbsent(userID, uid -> { + return elo.select(rs -> { + if (rs.next()) + return Optional.of(rs.getInt("Elo")); + return Optional.empty(); + }, userID, gameMode, Season.getSeason()); + }); + } + + public static int getFightsOfSeason(int userID, String gameMode) { + return fightsOfSeason.select(rs -> { + if (rs.next()) + return rs.getInt("Fights"); + return 0; + }, userID, gameMode, Season.getSeasonStart()); + } + + public static int getMaxElo(String gameMode) { + return maxEloCache.computeIfAbsent(gameMode, gm -> { + return maxElo.select(rs -> { + if (rs.next()) + return rs.getInt("MaxElo"); + return 0; + }, Season.getSeason(), gameMode); + }); + } + + public static void setElo(int userId, String gameMode, int elo) { + emblemCache.remove(userId); + gameModeUserEloCache.computeIfAbsent(gameMode, gm -> new HashMap<>()).put(userId, Optional.of(elo)); + maxEloCache.compute(gameMode, (gm, max) -> { + if (max == null || max < elo) { + emblemCache.clear(); + return elo; + } + return max; + }); + setElo.update(elo, Season.getSeason(), userId, gameMode); + } + + public static int getPlacement(int elo, String gameMode) { + return place.select(rs -> { + if (rs.next()) + return rs.getInt("Place"); + return -1; + }, gameMode, elo, Season.getSeason()); + } + + public static String getEmblem(SteamwarUser user) { + if (emblemCache.containsKey(user.getId())) { + return emblemCache.get(user.getId()); + } + + int maxEloOfPlayer = 0; + ArenaMode arenaMode = null; + for (ArenaMode mode : ArenaMode.getAllModes()) { + if (!mode.isRanked()) continue; + if (UserElo.getFightsOfSeason(user.getId(), mode.getSchemType()) < 10) continue; + + Optional currentElo = UserElo.getElo(user.getId(), mode.getSchemType()); + if (currentElo.isPresent() && currentElo.get() > maxEloOfPlayer) { + maxEloOfPlayer = currentElo.get(); + arenaMode = mode; + } + } + + if (arenaMode == null) { + emblemCache.put(user.getId(), ""); + return ""; + } + int maxEloOfGameMode = UserElo.getMaxElo(arenaMode.getSchemType()); + String emblem = getEmblem(maxEloOfPlayer, maxEloOfGameMode); + emblemCache.put(user.getId(), emblem); + return emblem; + } + + private static String getEmblem(int maxEloOfPlayer, int maxEloOfGameMode) { + if (maxEloOfPlayer > maxEloOfGameMode * 0.99) return "§5❂ "; + if (maxEloOfPlayer > maxEloOfGameMode * 0.97) return "§c✹ "; + if (maxEloOfPlayer > maxEloOfGameMode * 0.94) return "§b✸ "; + if (maxEloOfPlayer > maxEloOfGameMode * 0.88) return "§a✷ "; + if (maxEloOfPlayer > maxEloOfGameMode * 0.76) return "§e✶ "; + if (maxEloOfPlayer > maxEloOfGameMode * 0.51) return "§f✦ "; + return "§7✧ "; + } + + public static void clearCache() { + gameModeUserEloCache.clear(); + maxEloCache.clear(); + emblemCache.clear(); + } +} diff --git a/src/de/steamwar/bungeecore/sql/UserGroup.java b/src/de/steamwar/bungeecore/sql/UserGroup.java index e6c30f9..6ca7a9a 100644 --- a/src/de/steamwar/bungeecore/sql/UserGroup.java +++ b/src/de/steamwar/bungeecore/sql/UserGroup.java @@ -24,24 +24,26 @@ import java.util.stream.Collectors; public enum UserGroup { - Admin("§4", "§e", true, true, true, true), - Developer("§3", "§f", true, true, true, true), - Moderator("§c", "§f", true, true, true, true), - Supporter("§9", "§f", false, true, true, true), - Builder("§2", "§f", false, true, false, true), - YouTuber("§5", "§f", false, false, false, true), - Member("§7", "§7", false, false, false, false); + Admin("§4", "§e", "Admin", true, true, true, true), + Developer("§3", "§f", "Dev", true, true, true, true), + Moderator("§c", "§f", "Mod", true, true, true, true), + Supporter("§9", "§f", "Sup", false, true, true, true), + Builder("§2", "§f", "Arch", false, true, false, true), + YouTuber("§5", "§f", "YT", false, false, false, true), + Member("§7", "§7", "", false, false, false, false); private final String colorCode; private final String chatColorCode; + private final String chatPrefix; private final boolean adminGroup; private final boolean teamGroup; private final boolean checkSchematics; private final boolean privilegedMods; - UserGroup(String colorCode, String chatColorCode, boolean adminGroup, boolean teamGroup, boolean checkSchematics, boolean privilegedMods) { + UserGroup(String colorCode, String chatColorCode, String chatPrefix, boolean adminGroup, boolean teamGroup, boolean checkSchematics, boolean privilegedMods) { this.colorCode = colorCode; this.chatColorCode = chatColorCode; + this.chatPrefix = chatPrefix; this.adminGroup = adminGroup; this.teamGroup = teamGroup; this.checkSchematics = checkSchematics; @@ -75,4 +77,8 @@ public enum UserGroup { public static UserGroup getUsergroup(String name) { return Arrays.stream(UserGroup.values()).filter(userGroup -> userGroup.name().equalsIgnoreCase(name)).collect(Collectors.toList()).get(0); } + + public String getChatPrefix() { + return chatPrefix; + } } \ No newline at end of file