SteamWar/BungeeCore
Archiviert
13
2

Merge branch 'master' into Punishment++
Einige Prüfungen sind fehlgeschlagen
SteamWarCI Build failed

Dieser Commit ist enthalten in:
YoyoNow 2022-04-03 20:57:49 +02:00
Commit ba117806fb
13 geänderte Dateien mit 226 neuen und 170 gelöschten Zeilen

Datei anzeigen

@ -273,12 +273,6 @@ public class BungeeCore extends Plugin {
Persistent.setChatPrefix(CHAT_PREFIX);
Persistent.setLobbyServer(LOBBY_SERVER);
Statement.connect(
config.getString("db.url"),
config.getString("db.username"),
config.getString("db.password")
);
if (config.contains("discord")) {
SteamwarDiscordBotConfig.loadConfig(config.getSection("discord"));
}

Datei anzeigen

@ -20,7 +20,6 @@
package de.steamwar.bungeecore;
import de.steamwar.bungeecore.sql.SWException;
import de.steamwar.bungeecore.sql.Statement;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
@ -63,7 +62,7 @@ public class ErrorLogger extends Handler {
SWException.log("Bungee", "DDOS", ddosRate + "");
}
return;
} else if (!Statement.connectionStable()) {
} else if (stacktrace.contains("ErrorLogger")) {
return;
}
@ -85,13 +84,10 @@ public class ErrorLogger extends Handler {
static {
List<String> contains = new ArrayList<>();
contains.add("ServerConnector");
contains.add("InitialHandler");
contains.add("UpstreamBridge");
contains.add("DownstreamBridge");
contains.add(" took ");
contains.add("No client connected for pending server!");
contains.add("Error authenticating ");
contains.add("read timed out");
contains.add("Connection reset by peer");
contains.add("No client connected for pending server");
ignoreContains = Collections.unmodifiableList(contains);
}
}

Datei anzeigen

@ -59,23 +59,27 @@ public class EventStarter implements Runnable {
EventFight next;
while((next = nextFight(fights)) != null){
if(!BungeeCore.EVENT_MODE && Event.get(next.getEventID()).spectateSystem())
break; //Don't start EventServer if not the event bungee
Team blue = Team.get(next.getTeamBlue());
Team red = Team.get(next.getTeamRed());
ServerStarter starter = new ServerStarter().event(next);
ProxiedPlayer leiter = ProxyServer.getInstance().getPlayer(SteamwarUser.get(next.getKampfleiter()).getUuid());
if(leiter != null)
starter.send(leiter);
//Don't start EventServer if not the event bungee
if(BungeeCore.EVENT_MODE || !Event.get(next.getEventID()).spectateSystem()) {
ServerStarter starter = new ServerStarter().event(next);
Subserver subserver = starter.start();
ProxiedPlayer leiter = ProxyServer.getInstance().getPlayer(SteamwarUser.get(next.getKampfleiter()).getUuid());
if(leiter != null)
starter.send(leiter);
eventServer.put(blue.getTeamId(), subserver);
eventServer.put(red.getTeamId(), subserver);
Message.broadcast("EVENT_FIGHT_BROADCAST", "EVENT_FIGHT_BROADCAST_HOVER",
new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/event " + blue.getTeamKuerzel()), blue.getTeamColor(), blue.getTeamName(), red.getTeamColor(), red.getTeamName());
Subserver subserver = starter.start();
eventServer.put(blue.getTeamId(), subserver);
eventServer.put(red.getTeamId(), subserver);
Message.broadcast("EVENT_FIGHT_BROADCAST", "EVENT_FIGHT_BROADCAST_HOVER",
new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/event " + blue.getTeamKuerzel()), blue.getTeamColor(), blue.getTeamName(), red.getTeamColor(), red.getTeamName());
} else {
Message.broadcast("EVENT_FIGHT_BROADCAST", "EVENT_FIGHT_BROADCAST_HOVER",
new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/spectate"), blue.getTeamColor(), blue.getTeamName(), red.getTeamColor(), red.getTeamName());
}
}
}

Datei anzeigen

@ -35,6 +35,7 @@ import net.dv8tion.jda.api.JDABuilder;
import net.dv8tion.jda.api.OnlineStatus;
import net.dv8tion.jda.api.entities.Activity;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.exceptions.ErrorResponseException;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import net.dv8tion.jda.api.requests.restaction.CommandListUpdateAction;
import net.dv8tion.jda.api.utils.MemberCachePolicy;
@ -93,9 +94,13 @@ public class SteamwarDiscordBot {
EventManager.update();
SchematicsManager.update();
ProxyServer.getInstance().getScheduler().schedule(BungeeCore.get(), () -> {
activity();
EventManager.update();
SchematicsManager.update();
try {
activity();
EventManager.update();
SchematicsManager.update();
} catch (ErrorResponseException e) {
//ignored
}
}, 30, 30, TimeUnit.SECONDS);
DiscordRolesMessage.sendMessage();
DiscordRulesMessage.sendMessage();

Datei anzeigen

@ -36,6 +36,7 @@ import net.md_5.bungee.config.Configuration;
import java.sql.Timestamp;
import java.time.Instant;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
@ -127,15 +128,22 @@ public class CheckCommand extends BasicCommand {
for (SchematicNode schematic : schematicList) {
CheckSession current = currentSchems.get(schematic.getId());
long waitedMillis = Timestamp.from(Instant.now()).getTime() - schematic.getLastUpdate().getTime();
String color = waitedMillis > 14400000 ? (waitedMillis > 86400000 ? "§c" : "§e") : "§a";
long hours = waitedMillis / 3600000;
long minutes = (waitedMillis - hours * 3600000) / 60000;
String waitTime = color + Message.parse("CHECK_LIST_WAIT", player, hours, (minutes < 10) ? "0" + minutes : minutes);
if (current == null) {
Message.sendPrefixless("CHECK_LIST_TO_CHECK", player,
Message.parse("CHECK_LIST_TO_CHECK_HOVER", player),
new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/check schematic " + schematic.getId()),
waitTime,
schematic.getSchemtype().getKuerzel(), SteamwarUser.get(schematic.getOwner()).getUserName(), schematic.getName());
} else {
Message.sendPrefixless("CHECK_LIST_CHECKING", player,
Message.parse("CHECK_LIST_CHECKING_HOVER", player),
new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/join " + current.checker.getName()),
waitTime,
schematic.getSchemtype().getKuerzel(), SteamwarUser.get(schematic.getOwner()).getUserName(), schematic.getName(), current.checker.getName());
}
}

Datei anzeigen

@ -21,9 +21,10 @@ package de.steamwar.bungeecore.comms;
import com.google.common.io.ByteArrayDataInput;
import com.google.common.io.ByteStreams;
import de.steamwar.bungeecore.BungeeCore;
import de.steamwar.bungeecore.comms.handlers.*;
import de.steamwar.bungeecore.comms.packets.FightEndsPacket;
import de.steamwar.bungeecore.listeners.BasicListener;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.connection.Server;
import net.md_5.bungee.api.event.PluginMessageEvent;
import net.md_5.bungee.event.EventHandler;
@ -46,7 +47,7 @@ public class SpigotReceiver extends BasicListener {
ByteArrayDataInput in = ByteStreams.newDataInput(event.getData());
Byte handler = in.readByte();
handlerMap.get(handler).handle(in, ((Server) event.getSender()).getInfo());
ProxyServer.getInstance().getScheduler().runAsync(BungeeCore.get(), () -> handlerMap.get(handler).handle(in, ((Server) event.getSender()).getInfo()));
}
public static void registerHandler(Byte id, SpigotHandler handler) {

Datei anzeigen

@ -19,6 +19,7 @@
package de.steamwar.bungeecore.listeners;
import de.steamwar.bungeecore.BungeeCore;
import de.steamwar.bungeecore.Message;
import de.steamwar.bungeecore.sql.BannedUserIPs;
import de.steamwar.bungeecore.sql.Punishment;
@ -40,54 +41,58 @@ public class BanListener extends BasicListener {
@EventHandler
public void onLogin(LoginEvent event) {
SteamwarUser user = SteamwarUser.getOrCreate(event.getConnection());
if (user.isPunished(Punishment.PunishmentType.Ban)) {
user.updateBanIP(event.getConnection().getAddress().getAddress().getHostAddress());
event.setCancelled(true);
event.setCancelReason(user.punishmentMessage(Punishment.PunishmentType.Ban, ProxyServer.getInstance().getPlayer(event.getConnection().getUniqueId())));
return;
}
event.registerIntent(BungeeCore.get());
ProxyServer.getInstance().getScheduler().runAsync(BungeeCore.get(), () -> {
SteamwarUser user = SteamwarUser.getOrCreate(event.getConnection());
if (user.isPunished(Punishment.PunishmentType.Ban)) {
user.updateBanIP(event.getConnection().getAddress().getAddress().getHostAddress());
event.setCancelled(true);
event.setCancelReason(user.punishmentMessage(Punishment.PunishmentType.Ban, ProxyServer.getInstance().getPlayer(event.getConnection().getUniqueId())));
return;
}
List<BannedUserIPs> ips = BannedUserIPs.get(event.getConnection().getAddress().getAddress().getHostAddress());
if(!ips.isEmpty()){
List<BannedUserIPs> ips = BannedUserIPs.get(event.getConnection().getAddress().getAddress().getHostAddress());
if(!ips.isEmpty()){
Timestamp highestBan = ips.get(0).getTimestamp();
boolean perma = false;
for(BannedUserIPs banned : ips) {
SteamwarUser bannedUser = SteamwarUser.get(banned.getUserID());
if (bannedUser.isPunished(Punishment.PunishmentType.Ban)) {
Punishment ban = bannedUser.getPunishment(Punishment.PunishmentType.Ban);
if(ban.isPerma()) {
perma = true;
break;
Timestamp highestBan = ips.get(0).getTimestamp();
boolean perma = false;
for(BannedUserIPs banned : ips) {
SteamwarUser bannedUser = SteamwarUser.get(banned.getUserID());
if (bannedUser.isPunished(Punishment.PunishmentType.Ban)) {
Punishment ban = bannedUser.getPunishment(Punishment.PunishmentType.Ban);
if(ban.isPerma()) {
perma = true;
break;
}
if(ban.getEndTime().after(highestBan))
highestBan = ban.getEndTime();
}
}
ClickEvent clickEvent = new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/ban " + user.getUserName() + " "
+ (perma?"perma":highestBan.toLocalDateTime().format(DateTimeFormatter.ofPattern("dd.MM.yyyy_HH:mm")))
+ " Bannumgehung");
for (ProxiedPlayer target : ProxyServer.getInstance().getPlayers()){
if ((target.hasPermission("bungeecore.teamchat"))
&& (target.getChatMode() == ProxiedPlayer.ChatMode.COMMANDS_ONLY
|| target.getChatMode() == ProxiedPlayer.ChatMode.SHOWN)){
StringBuilder potentialBan = new StringBuilder();
potentialBan.append(Message.parsePrefixed("BAN_AVOIDING_ALERT", target, user.getUserName()));
for(BannedUserIPs banned : ips) {
SteamwarUser bannedUser = SteamwarUser.get(banned.getUserID());
potentialBan.append(Message.parse("BAN_AVOIDING_LIST", target, bannedUser.getUserName(),
banned.getTimestamp().toLocalDateTime().format(DateTimeFormatter.ofPattern(Message.parse("TIMEFORMAT", target)))));
}
TextComponent msg = new TextComponent(potentialBan.toString());
msg.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new ComponentBuilder(Message.parse("BAN_AVOIDING_BAN_HOVER", target)).create()));
msg.setClickEvent(clickEvent);
target.sendMessage(msg);
}
if(ban.getEndTime().after(highestBan))
highestBan = ban.getEndTime();
}
}
ClickEvent clickEvent = new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/ban " + user.getUserName() + " "
+ (perma?"perma":highestBan.toLocalDateTime().format(DateTimeFormatter.ofPattern("dd.MM.yyyy_HH:mm")))
+ " Bannumgehung");
for (ProxiedPlayer target : ProxyServer.getInstance().getPlayers()){
if ((target.hasPermission("bungeecore.teamchat"))
&& (target.getChatMode() == ProxiedPlayer.ChatMode.COMMANDS_ONLY
|| target.getChatMode() == ProxiedPlayer.ChatMode.SHOWN)){
StringBuilder potentialBan = new StringBuilder();
potentialBan.append(Message.parsePrefixed("BAN_AVOIDING_ALERT", target, user.getUserName()));
for(BannedUserIPs banned : ips) {
SteamwarUser bannedUser = SteamwarUser.get(banned.getUserID());
potentialBan.append(Message.parse("BAN_AVOIDING_LIST", target, bannedUser.getUserName(),
banned.getTimestamp().toLocalDateTime().format(DateTimeFormatter.ofPattern(Message.parse("TIMEFORMAT", target)))));
}
TextComponent msg = new TextComponent(potentialBan.toString());
msg.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new ComponentBuilder(Message.parse("BAN_AVOIDING_BAN_HOVER", target)).create()));
msg.setClickEvent(clickEvent);
target.sendMessage(msg);
}
}
}
event.completeIntent(BungeeCore.get());
});
}
}

Datei anzeigen

@ -167,14 +167,14 @@ public class ChatListener extends BasicListener {
return;
e.setCancelled(true);
publicChat(user, sender, e.getMessage());
ProxyServer.getInstance().getScheduler().runAsync(BungeeCore.get(), () -> publicChat(user, sender, e.getMessage()));
}
}
public static void discordChat(SteamwarUser user, String message){
String name = "§r" + user.getUserName() + "§r";
if (user.getUserGroup() != UserGroup.Member) {
name = user.getUserGroup().getColorCode() + user.getUserGroup().name() + " " + user.getUserName() + "§r";
name = user.getUserGroup().getColorCode() + user.getUserGroup().getChatPrefix() + " " + user.getUserName() + "§r";
}
String chatcolor = user.getUserGroup().getChatColorCode();
@ -196,7 +196,7 @@ public class ChatListener extends BasicListener {
}
}
private void publicChat(SteamwarUser user, ProxiedPlayer sender, String message){
private void publicChat(SteamwarUser user, ProxiedPlayer sender, String message) {
String name = UserElo.getEmblem(user) + sender.getDisplayName();
String chatcolor = user.getUserGroup().getChatColorCode();

Datei anzeigen

@ -19,8 +19,10 @@
package de.steamwar.bungeecore.listeners;
import de.steamwar.bungeecore.BungeeCore;
import de.steamwar.bungeecore.sql.Session;
import de.steamwar.bungeecore.sql.SteamwarUser;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.event.PlayerDisconnectEvent;
import net.md_5.bungee.api.event.PostLoginEvent;
import net.md_5.bungee.event.EventHandler;
@ -40,7 +42,8 @@ public class SessionManager extends BasicListener {
@EventHandler
public void onDisconnect(PlayerDisconnectEvent e){
Timestamp timestamp = sessions.remove(e.getPlayer());
if(timestamp != null)
Session.insertSession(SteamwarUser.get(e.getPlayer()).getId(), timestamp);
if(timestamp != null) {
ProxyServer.getInstance().getScheduler().runAsync(BungeeCore.get(), () -> Session.insertSession(SteamwarUser.get(e.getPlayer()).getId(), timestamp));
}
}
}

Datei anzeigen

@ -26,13 +26,13 @@ public class Season {
public static int getSeason() {
Calendar calendar = Calendar.getInstance();
int yearIndex = calendar.get(Calendar.MONTH) / 3;
int yearIndex = calendar.get(Calendar.MONTH) / 4;
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";
return calendar.get(Calendar.YEAR) + "-" + (calendar.get(Calendar.MONTH) / 4 * 3 + 1) + "-1";
}
public static String convertSeasonToString(int season){

Datei anzeigen

@ -21,128 +21,167 @@ package de.steamwar.bungeecore.sql;
import de.steamwar.bungeecore.BungeeCore;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.config.Configuration;
import net.md_5.bungee.config.ConfigurationProvider;
import net.md_5.bungee.config.YamlConfiguration;
import java.io.File;
import java.io.IOException;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
public class Statement implements AutoCloseable {
private static final Logger logger = BungeeCore.get().getLogger();
private static final List<Statement> statements = new ArrayList<>();
private static final Deque<Connection> connections = new ArrayDeque<>();
private static Connection con;
private static String url;
private static String user;
private static String password;
private static final String URL;
private static final String USER;
private static final String PASSWORD;
public static void connect(String url, String user, String password) {
Statement.url = url;
Statement.user = user;
Statement.password = password;
static {
File file = new File(BungeeCore.get().getDataFolder(), "MySQL.yml");
Configuration config;
try {
con = DriverManager.getConnection(url + "?autoReconnect=true&useServerPrepStmts=true", user, password);
} catch (SQLException e) {
config = ConfigurationProvider.getProvider(YamlConfiguration.class).load(file);
} catch (IOException e) {
ProxyServer.getInstance().stop();
throw new SecurityException("Could not start SQL-Connection", e);
throw new SecurityException("Could not load SQL connection", e);
}
}
private static void reset() {
closeAll();
connect(url, user, password);
try {
for (Statement statement : statements) {
statement.init();
}
} catch (SQLException e) {
throw new SecurityException("Could not reprepare SQL statements", e);
}
}
@Override
public void close() {
try {
st.close();
} catch (SQLException e) {
BungeeCore.get().getLogger().log(Level.INFO, "Could not close statement", e);
}
statements.remove(this);
URL = "jdbc:mysql://" + config.getString("HOST") + ":" + config.getString("PORT") + "/" + config.getString("DATABASE");
USER = config.getString("USER");
PASSWORD = config.getString("PASSWORD");
}
public static void closeAll() {
synchronized (statements) {
for (Statement statement : statements) {
synchronized (connections) {
for(Connection connection : connections) {
try {
statement.st.close();
synchronized (statements) {
statements.forEach(statement -> statement.close(connection));
}
connection.close();
} catch (SQLException e) {
BungeeCore.get().getLogger().log(Level.INFO, "Could not close statement", e);
logger.log(Level.INFO, "Could not close SQL connection", e);
}
}
try {
con.close();
} catch (SQLException e) {
BungeeCore.get().getLogger().log(Level.INFO, "Could not close SQL connection", e);
}
}
}
public static boolean connectionStable() {
try {
return !con.isClosed();
} catch (SQLException e) {
return false;
connections.clear();
}
}
private final String sql;
private PreparedStatement st;
private final Map<Connection, PreparedStatement> cachedStatements = new HashMap<>();
public Statement(String sql) {
this.sql = sql;
statements.add(this);
try {
init();
} catch (SQLException e) {
throw new SecurityException("Could not init SQL statement", e);
synchronized (statements) {
statements.add(this);
}
}
private void init() throws SQLException {
st = con.prepareStatement(sql);
}
public <T> T select(ResultSetUser<T> user, Object... objects) {
synchronized (statements) {
return prepare(() -> {
ResultSet rs = st.executeQuery();
T result = user.use(rs);
rs.close();
return result;
}, objects);
}
return withConnection(st -> {
ResultSet rs = st.executeQuery();
T result = user.use(rs);
rs.close();
return result;
}, objects);
}
public void update(Object... objects) {
synchronized (statements) {
prepare(st::executeUpdate, objects);
}
withConnection(PreparedStatement::executeUpdate, objects);
}
private <T> T prepare(SQLRunnable<T> runnable, Object... objects) {
private <T> T withConnection(SQLRunnable<T> runnable, Object... objects) {
Connection connection = aquireConnection();
T result;
try {
setObjects(objects);
return runnable.run();
result = tryWithConnection(connection, runnable, objects);
} catch (SQLException e) {
reset();
throw new SecurityException("Could not execute SQL statement", e);
closeConnection(connection);
connection = aquireConnection();
try {
result = tryWithConnection(connection, runnable, objects);
} catch (SQLException ex) {
closeConnection(connection);
throw new SecurityException("Could not execute statement", ex);
}
}
synchronized (connections) {
connections.push(connection);
}
return result;
}
private void setObjects(Object... objects) throws SQLException {
private <T> T tryWithConnection(Connection connection, SQLRunnable<T> runnable, Object... objects) throws SQLException {
PreparedStatement st = cachedStatements.get(connection);
if(st == null) {
st = connection.prepareStatement(sql);
cachedStatements.put(connection, st);
}
for (int i = 0; i < objects.length; i++) {
st.setObject(i + 1, objects[i]);
}
return runnable.run(st);
}
@Override
public void close() {
cachedStatements.values().forEach(st -> closeStatement(st, false));
cachedStatements.clear();
synchronized (statements) {
statements.remove(this);
}
}
private void close(Connection connection) {
PreparedStatement st = cachedStatements.remove(connection);
if(st != null)
closeStatement(st, true);
}
private static Connection aquireConnection() {
synchronized (connections) {
if(!connections.isEmpty())
return connections.pop();
}
try {
return DriverManager.getConnection(URL + "?autoReconnect=true&useServerPrepStmts=true", USER, PASSWORD);
} catch (SQLException e) {
throw new SecurityException("Could not open connection", e);
}
}
private static void closeConnection(Connection connection) {
synchronized (statements) {
for (Statement statement : statements) {
statement.close(connection);
}
}
try {
connection.close();
} catch (SQLException e) {
logger.log(Level.INFO, "Could not close connection", e);
}
}
private static void closeStatement(PreparedStatement st, boolean silent) {
try {
st.close();
} catch (SQLException e) {
if(!silent)
logger.log(Level.INFO, "Could not close statement", e);
}
}
public interface ResultSetUser<T> {
@ -150,6 +189,6 @@ public class Statement implements AutoCloseable {
}
private interface SQLRunnable<T> {
T run() throws SQLException;
T run(PreparedStatement st) throws SQLException;
}
}

Datei anzeigen

@ -24,12 +24,12 @@ import java.util.stream.Collectors;
public enum UserGroup {
Admin("§4", "§e", "Obererzieher", true, true, true, true),
Developer("§3", "§f", "Seelsorger", true, true, true, true),
Moderator("§c", "§f", "Schichtleiter", true, true, true, true),
Supporter("§9", "§f", "Kindergärtner", false, true, true, true),
Builder("§2", "§f", "Hausmeister", false, true, false, true),
YouTuber("§5", "§f", "Reporter", false, false, false, true),
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;

Datei anzeigen

@ -251,9 +251,10 @@ CHECK_HELP_NEXT=§8/§7check next §8- §7Nächste Prüffrage§8/§7freigeben
CHECK_HELP_DECLINE=§8/§7check decline §8[§eGrund§8] - §7Schematic ablehnen
CHECK_HELP_CANCEL=§8/§7check cancel §8- §7Bricht den Prüfvorgang ab
CHECK_LIST_HEADER=§e§lZu prüfen sind {0} Schematics
CHECK_LIST_TO_CHECK=§8{0} §7{1} §e{2}
CHECK_LIST_TO_CHECK={0} §8{1} §7{2} §e{3}
CHECK_LIST_WAIT={0}:{1}
CHECK_LIST_TO_CHECK_HOVER=§eSchematic prüfen
CHECK_LIST_CHECKING=§8{0} §7{1} §e{2} §7wird geprüft von §e{3}
CHECK_LIST_CHECKING={0} §8{1} §7{2} §e{3} §7wird geprüft von §e{4}
CHECK_LIST_CHECKING_HOVER=§eZum Prüfer
CHECK_SCHEMATIC_ALREADY_CHECKING=§cDu prüfst schon eine Schematic!
CHECK_SCHEMATIC_OWN=§cDu kannst nicht deine eigenen Schematics prüfen.