geforkt von SteamWar/BungeeCore
Update to new ranked system
Dieser Commit ist enthalten in:
Ursprung
44e9c084c7
Commit
9039928eb4
@ -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()));
|
||||||
|
@ -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
|
||||||
|
@ -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!
|
||||||
|
In neuem Issue referenzieren
Einen Benutzer sperren