Compare commits
3 Commits
BauSystem/
...
RemoveElo
| Author | SHA1 | Date | |
|---|---|---|---|
| a1add4f997 | |||
| b517fe3ad0 | |||
| ac5dda58a1 |
@@ -1,74 +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.sql
|
||||
|
||||
import de.steamwar.sql.internal.useDb
|
||||
import org.jetbrains.exposed.v1.core.and
|
||||
import org.jetbrains.exposed.v1.core.dao.id.CompositeID
|
||||
import org.jetbrains.exposed.v1.core.dao.id.CompositeIdTable
|
||||
import org.jetbrains.exposed.v1.core.dao.id.EntityID
|
||||
import org.jetbrains.exposed.v1.core.eq
|
||||
import org.jetbrains.exposed.v1.dao.CompositeEntity
|
||||
import org.jetbrains.exposed.v1.dao.CompositeEntityClass
|
||||
import org.jetbrains.exposed.v1.jdbc.insertIgnore
|
||||
|
||||
object SchemEloTable: CompositeIdTable("SchemElo") {
|
||||
val schemId = reference("SchemId", SchematicNodeTable)
|
||||
val season = integer("Season").entityId()
|
||||
val elo = integer("Elo")
|
||||
|
||||
override val primaryKey = PrimaryKey(schemId, season)
|
||||
|
||||
init {
|
||||
addIdColumn(schemId)
|
||||
}
|
||||
}
|
||||
|
||||
class SchemElo(id: EntityID<CompositeID>): CompositeEntity(id) {
|
||||
companion object: CompositeEntityClass<SchemElo>(SchemEloTable) {
|
||||
@JvmStatic
|
||||
fun getElo(node: SchematicNode, season: Int, defaultElo: Int = 0) = useDb {
|
||||
find { (SchemEloTable.schemId eq node.id) and (SchemEloTable.season eq season) }.firstOrNull()?.elo ?: defaultElo
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getCurrentElo(schemId: Int) = getElo(SchematicNode.byId(schemId)!!, Season.getSeason())
|
||||
|
||||
@JvmStatic
|
||||
fun setElo(node: Int, elo: Int) = useDb {
|
||||
findByIdAndUpdate(CompositeID {
|
||||
it[SchemEloTable.schemId] = node
|
||||
it[SchemEloTable.season] = Season.getSeason()
|
||||
}) {
|
||||
it.elo = elo
|
||||
} ?: SchemEloTable.insertIgnore {
|
||||
it[SchemEloTable.schemId] = node
|
||||
it[SchemEloTable.season] = Season.getSeason()
|
||||
it[SchemEloTable.elo] = elo
|
||||
}
|
||||
|
||||
return@useDb
|
||||
}
|
||||
}
|
||||
|
||||
var node by SchemEloTable.schemId
|
||||
var season by SchemEloTable.season
|
||||
var elo by SchemEloTable.elo
|
||||
}
|
||||
@@ -365,8 +365,6 @@ class SchematicNode(id: EntityID<Int>) : IntEntity(id) {
|
||||
}
|
||||
}
|
||||
|
||||
fun getElo(season: Int) = SchemElo.getElo(this, season)
|
||||
|
||||
override fun delete() = useDb {
|
||||
super.delete()
|
||||
}
|
||||
|
||||
@@ -1,61 +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.sql;
|
||||
|
||||
import java.util.Calendar;
|
||||
|
||||
public class Season {
|
||||
private Season() {}
|
||||
|
||||
public static int getSeason() {
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
int yearIndex = calendar.get(Calendar.MONTH) / 4;
|
||||
return (calendar.get(Calendar.YEAR) * 3 + yearIndex);
|
||||
}
|
||||
|
||||
public static String getSeasonStart() {
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
int month = calendar.get(Calendar.MONTH);
|
||||
if (month <= 3) {
|
||||
return calendar.get(Calendar.YEAR) + "-1-1";
|
||||
} else if (month <= 7) {
|
||||
return calendar.get(Calendar.YEAR) + "-5-1";
|
||||
} else {
|
||||
return calendar.get(Calendar.YEAR) + "-9-1";
|
||||
}
|
||||
}
|
||||
|
||||
public static String convertSeasonToString(int season){
|
||||
if (season == -1) return "";
|
||||
int yearSeason = season % 3;
|
||||
int year = (season - yearSeason) / 3;
|
||||
return String.format("%d-%d", year, yearSeason + 1);
|
||||
}
|
||||
|
||||
public static int convertSeasonToNumber(String season){
|
||||
if (season.isEmpty()) return -1;
|
||||
String[] split = season.split("-");
|
||||
try {
|
||||
return Integer.parseInt(split[0]) * 3 + Integer.parseInt(split[1]) - 1;
|
||||
} catch (NumberFormatException e) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,170 +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.sql
|
||||
|
||||
import de.steamwar.sql.internal.useDb
|
||||
import org.jetbrains.exposed.v1.core.*
|
||||
import org.jetbrains.exposed.v1.core.dao.id.CompositeID
|
||||
import org.jetbrains.exposed.v1.core.dao.id.CompositeIdTable
|
||||
import org.jetbrains.exposed.v1.core.dao.id.EntityID
|
||||
import org.jetbrains.exposed.v1.dao.CompositeEntity
|
||||
import org.jetbrains.exposed.v1.dao.CompositeEntityClass
|
||||
import org.jetbrains.exposed.v1.jdbc.insertIgnore
|
||||
import org.jetbrains.exposed.v1.jdbc.select
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
object UserEloTable : CompositeIdTable("UserElo") {
|
||||
val season = integer("Season").entityId()
|
||||
val gameMode = varchar("GameMode", 16).entityId()
|
||||
val userId = reference("UserID", SteamwarUserTable)
|
||||
val elo = integer("Elo")
|
||||
|
||||
override val primaryKey = PrimaryKey(season, gameMode, userId)
|
||||
|
||||
init {
|
||||
addIdColumn(season)
|
||||
addIdColumn(gameMode)
|
||||
}
|
||||
}
|
||||
|
||||
class UserElo(id: EntityID<CompositeID>) : CompositeEntity(id) {
|
||||
companion object : CompositeEntityClass<UserElo>(UserEloTable) {
|
||||
private const val ELO_DEFAULT = 0
|
||||
|
||||
private val gameModeUserEloCache: MutableMap<String, MutableMap<Int, Int?>> = ConcurrentHashMap()
|
||||
private val emblemCache: MutableMap<Int, String> = ConcurrentHashMap()
|
||||
|
||||
@JvmStatic
|
||||
fun clear() {
|
||||
gameModeUserEloCache.clear()
|
||||
emblemCache.clear()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getEloOrDefault(userId: Int, gameMode: String) =
|
||||
getElo(userId, gameMode) ?: ELO_DEFAULT
|
||||
|
||||
@JvmStatic
|
||||
fun getElo(userId: Int, gameMode: String) =
|
||||
gameModeUserEloCache.getOrPut(gameMode) { mutableMapOf() }
|
||||
.getOrPut(userId) { getEloFromDb(userId, gameMode)?.elo }
|
||||
|
||||
private fun getEloFromDb(userId: Int, gameMode: String) = useDb {
|
||||
find { (UserEloTable.userId eq userId) and (UserEloTable.gameMode eq gameMode) and (UserEloTable.season eq Season.getSeason()) }.firstOrNull()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getFightsOfSeason(userId: Int, gamemode: String) = useDb {
|
||||
exec(
|
||||
"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(?)",
|
||||
args = listOf(
|
||||
IntegerColumnType() to userId,
|
||||
VarCharColumnType() to gamemode,
|
||||
VarCharColumnType() to Season.getSeasonStart()
|
||||
)
|
||||
) {
|
||||
return@exec if (it.next()) {
|
||||
it.getInt("Fights")
|
||||
} else {
|
||||
0
|
||||
}
|
||||
} ?: 0
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun setElo(userId: Int, gameMode: String, elo: Int) {
|
||||
emblemCache.remove(userId)
|
||||
gameModeUserEloCache.getOrDefault(gameMode, mutableMapOf()).remove(userId)
|
||||
useDb {
|
||||
findByIdAndUpdate(CompositeID {
|
||||
it[UserEloTable.userId] = userId
|
||||
it[UserEloTable.gameMode] = gameMode
|
||||
it[UserEloTable.season] = Season.getSeason()
|
||||
}) {
|
||||
it.elo = elo
|
||||
} ?: UserEloTable.insertIgnore {
|
||||
it[UserEloTable.userId] = userId
|
||||
it[UserEloTable.gameMode] = gameMode
|
||||
it[UserEloTable.season] = Season.getSeason()
|
||||
it[UserEloTable.elo] = elo
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getPlacement(elo: Int, gamemode: String) = useDb {
|
||||
UserEloTable.select(UserEloTable.userId.count()).where {
|
||||
(UserEloTable.gameMode eq gamemode) and (UserEloTable.elo greater elo) and (UserEloTable.season eq Season.getSeason())
|
||||
}.firstOrNull()?.get(UserEloTable.userId.count())?.let { it + 1 }?.toInt() ?: -1
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getEmblem(user: SteamwarUser, rankedModes: List<String>) =
|
||||
emblemCache.getOrPut(user.id.value) {
|
||||
var emblemProgression = -1
|
||||
for (mode in rankedModes) {
|
||||
if (getFightsOfSeason(user.id.value, mode) == 0) continue
|
||||
val progression = getProgression(user.id.value, mode)
|
||||
if (progression > emblemProgression) {
|
||||
emblemProgression = progression
|
||||
}
|
||||
}
|
||||
return toEmblem(emblemProgression)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getEmblemProgression(gameMode: String, userId: Int): String =
|
||||
when (getProgression(userId, gameMode)) {
|
||||
-1 -> "§8❱❱❱❱ ❂"
|
||||
0 -> "§e❱§8❱❱❱ ❂"
|
||||
1 -> "§e❱❱§8❱❱ ❂"
|
||||
2 -> "§e❱❱❱§8❱ ❂"
|
||||
3 -> "§e❱❱❱❱§8 ❂"
|
||||
4 -> "§8❱❱❱❱ §5❂"
|
||||
else -> throw SecurityException("Progression is not in range")
|
||||
}
|
||||
|
||||
|
||||
@JvmStatic
|
||||
fun getProgression(userId: Int, gameMode: String) = useDb { getElo(userId, gameMode) ?: -1 }.let {
|
||||
when {
|
||||
it < 0 -> -1
|
||||
it < 150 -> 0
|
||||
it < 350 -> 1
|
||||
it < 600 -> 2
|
||||
it < 900 -> 3
|
||||
else -> 4
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun toEmblem(progression: Int) = when (progression) {
|
||||
-1 -> ""
|
||||
0 -> "§e❱ "
|
||||
1 -> "§e❱❱ "
|
||||
2 -> "§e❱❱❱ "
|
||||
3 -> "§e❱❱❱❱ "
|
||||
4 -> "§5❂ "
|
||||
else -> throw SecurityException("Progression out of range")
|
||||
}
|
||||
}
|
||||
|
||||
var elo by UserEloTable.elo
|
||||
}
|
||||
@@ -59,7 +59,6 @@ UTIL_INFO_TYPE_DIR=§eDIR
|
||||
UTIL_INFO_RANK=§7Rank: §e{0}
|
||||
UTIL_INFO_COLOR=§7Color translation: {0}
|
||||
UTIL_INFO_REPLAY=§7Replay playback: {0}
|
||||
UTIL_INFO_ELO=§7Elo: §e{0}
|
||||
UTIL_INFO_FORMAT=§7Format: §e{0}
|
||||
UTIL_INFO_STATUS=§cState: §c{0}: {1}
|
||||
UTIL_INFO_MEMBER=§7Members: §e{0}
|
||||
|
||||
@@ -248,7 +248,6 @@ public class SchematicCommandUtils {
|
||||
if (node.getSchemtype().fightType()) {
|
||||
SchematicSystem.MESSAGE.sendPrefixless("UTIL_INFO_COLOR", player, SchematicSystem.MESSAGE.parse(node.replaceColor() ? "ON" : "OFF", player));
|
||||
SchematicSystem.MESSAGE.sendPrefixless("UTIL_INFO_REPLAY", player, SchematicSystem.MESSAGE.parse(node.allowReplay() ? "ON" : "OFF", player));
|
||||
SchematicSystem.MESSAGE.sendPrefixless("UTIL_INFO_ELO", player, node.getElo(Season.getSeason()));
|
||||
}
|
||||
|
||||
SchematicSystem.MESSAGE.sendPrefixless("UTIL_INFO_FORMAT", player, node.getFileEnding());
|
||||
|
||||
@@ -656,14 +656,6 @@ HOURS_PLAYED=§7Your playtime is§8: §e{0}h
|
||||
#Arena command
|
||||
ARENA_NOT_FOUND=§cThe specified arena could not be found
|
||||
|
||||
#Rank
|
||||
RANK_PLAYER_NOT_FOUND=§cPlayer not found
|
||||
RANK_PLAYER_SELF=§eRank §7Season §e{0}
|
||||
RANK_PLAYER_FOUND=§eRank §7of §e{0} §7Season §e{1}
|
||||
RANK_DATA={0} §e{1} {2}
|
||||
RANK_UNPLACED=§7unranked
|
||||
RANK_PLACED=§e{0}§8. §7with §e{1} §7Elo§8.
|
||||
|
||||
#Fabric Mod Sender
|
||||
MODIFICATION_BAN_MESSAGE=You tried to bypass / modify the FabricModSender!
|
||||
MODIFICATION_BAN_LOG={0} has tried to edit / bypass the FabricModSender! Reason: {1}
|
||||
|
||||
@@ -624,14 +624,6 @@ HOURS_PLAYED=§7Deine Spielzeit beträgt§8: §e{0}h
|
||||
#Arena command
|
||||
ARENA_NOT_FOUND=§cDie angegebene Arena konnte nicht gefunden werden
|
||||
|
||||
#Rank
|
||||
RANK_PLAYER_NOT_FOUND=§cSpieler nicht gefunden
|
||||
RANK_PLAYER_SELF=§eRang §7Saison §e{0}
|
||||
RANK_PLAYER_FOUND=§eRang §7von §e{0} §7Saison §e{1}
|
||||
RANK_DATA={0} §e{1} {2}
|
||||
RANK_UNPLACED=§7unplatziert
|
||||
RANK_PLACED=§e{0}§8. §7mit §e{1} §7Elo§8.
|
||||
|
||||
#Fabric Mod Sender
|
||||
MODIFICATION_BAN_MESSAGE=Du hast probiert den FabricModSender zu umgehen / zu modifizieren!
|
||||
MODIFICATION_BAN_LOG={0} hat probiert den Fabric Mod Sender zu editieren / umzugehen! Grund: {1}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -61,17 +61,8 @@ private val fightCount = "SELECT COUNT(*) AS num FROM FightPlayer WHERE UserID =
|
||||
fun getFightCount(user: SteamwarUser) =
|
||||
useDb { exec(fightCount, args = listOf(IntegerColumnType() to user.getId())) { getNum(it) } }
|
||||
|
||||
private const val rankedList =
|
||||
"SELECT UserName, Elo FROM UserData, UserElo WHERE UserID = id AND GameMode = ? AND Season = ? ORDER BY Elo DESC"
|
||||
|
||||
fun getRankedList(gamemode: String): List<Pair<String, Int>> = useDb {
|
||||
exec(rankedList, args = listOf(VarCharColumnType() to gamemode, IntegerColumnType() to Season.getSeason())) {
|
||||
val list = mutableListOf<Pair<String, Int>>()
|
||||
while (it.next()) {
|
||||
list.add(it.getString("UserName") to it.getInt("Elo"))
|
||||
}
|
||||
list
|
||||
} ?: emptyList()
|
||||
emptyList()
|
||||
}
|
||||
|
||||
private const val fightList =
|
||||
|
||||
Reference in New Issue
Block a user