WIP: Add SchemSearch #467
@ -28,6 +28,7 @@ import de.steamwar.bungeecore.listeners.ping.PingListener;
|
||||
import de.steamwar.bungeecore.network.BungeeNetworkHandler;
|
||||
import de.steamwar.bungeecore.network.NetworkReceiver;
|
||||
import de.steamwar.bungeecore.network.SWScriptSyntaxForwarder;
|
||||
import de.steamwar.bungeecore.util.SchematicSearch;
|
||||
import de.steamwar.sql.Punishment;
|
||||
import de.steamwar.sql.SteamwarUser;
|
||||
import de.steamwar.sql.UserElo;
|
||||
@ -122,6 +123,7 @@ public class BungeeCore extends Plugin {
|
||||
new Fabric();
|
||||
new SubserverProtocolFixer();
|
||||
new PingListener();
|
||||
new SchematicSearchListener();
|
||||
|
||||
local = new Node.LocalNode();
|
||||
if(MAIN_SERVER) {
|
||||
@ -233,6 +235,7 @@ public class BungeeCore extends Plugin {
|
||||
tablistManager.disable();
|
||||
errorLogger.unregister();
|
||||
Statement.closeAll();
|
||||
SchematicSearch.abortAll();
|
||||
}
|
||||
|
||||
public static BungeeCore get() {
|
||||
|
@ -21,6 +21,7 @@ package de.steamwar.bungeecore.commands;
|
||||
|
||||
import de.steamwar.bungeecore.Message;
|
||||
import de.steamwar.bungeecore.Node;
|
||||
import de.steamwar.bungeecore.util.SchematicSearch;
|
||||
import de.steamwar.command.SWCommand;
|
||||
import net.md_5.bungee.api.CommandSender;
|
||||
|
||||
@ -37,6 +38,7 @@ public class StatCommand extends SWCommand {
|
||||
|
||||
@Register
|
||||
public void genericCommand(CommandSender sender) {
|
||||
Message.send("STAT_SEARCH_QUEUE", sender, SchematicSearch.getQueueSize());
|
||||
Map<String, Integer> serverCount = new HashMap<>();
|
||||
try {
|
||||
Process process = new ProcessBuilder("ps", "x").start();
|
||||
|
41
src/de/steamwar/bungeecore/listeners/SchematicSearchListener.java
Normale Datei
@ -0,0 +1,41 @@
|
||||
/*
|
||||
Lixfel markierte diese Unterhaltung als gelöst
Veraltet
|
||||
* This file is a part of the SteamWar software.
|
||||
*
|
||||
* Copyright (C) 2023 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.bungeecore.listeners;
|
||||
|
||||
import de.steamwar.bungeecore.Message;
|
||||
import de.steamwar.bungeecore.util.SchematicSearch;
|
||||
import net.md_5.bungee.api.event.PlayerDisconnectEvent;
|
||||
import net.md_5.bungee.api.event.ServerSwitchEvent;
|
||||
import net.md_5.bungee.event.EventHandler;
|
||||
|
||||
public class SchematicSearchListener extends BasicListener {
|
||||
|
||||
@EventHandler
|
||||
public void onServerSwitch(ServerSwitchEvent event) {
|
||||
if(SchematicSearch.removeFromQueue(event.getPlayer())) {
|
||||
Message.send("SCHEMATIC_SEARCH_REMOVED_FROM_QUEUE", event.getPlayer());
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onQuit(PlayerDisconnectEvent event) {
|
||||
SchematicSearch.removeFromQueue(event.getPlayer());
|
||||
}
|
||||
}
|
@ -31,5 +31,6 @@ public class BungeeNetworkHandler {
|
||||
new ImALobbyHandler().register();
|
||||
new InventoryCallbackHandler().register();
|
||||
new PrepareSchemHandler().register();
|
||||
new SchematicSearchRequestHandler().register();
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,47 @@
|
||||
/*
|
||||
Lixfel markierte diese Unterhaltung als gelöst
Veraltet
Lixfel
hat
. .
|
||||
* This file is a part of the SteamWar software.
|
||||
*
|
||||
* Copyright (C) 2023 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.bungeecore.network.handlers;
|
||||
|
||||
import de.steamwar.bungeecore.BungeeCore;
|
||||
import de.steamwar.bungeecore.util.SchematicSearch;
|
||||
import de.steamwar.network.packets.PacketHandler;
|
||||
import de.steamwar.network.packets.client.RequestSchematicSearchPacket;
|
||||
import de.steamwar.sql.SchematicNode;
|
||||
import de.steamwar.sql.SteamwarUser;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
|
||||
public class SchematicSearchRequestHandler extends PacketHandler {
|
||||
|
||||
@Handler
|
||||
public void handle(RequestSchematicSearchPacket packet) {
|
||||
SteamwarUser user = SteamwarUser.get(packet.getPlayerId());
|
||||
SchematicNode node = SchematicNode.getSchematicNode(packet.getSchematicId());
|
||||
ProxiedPlayer proxiedPlayer = BungeeCore.get().getProxy().getPlayer(user.getUUID());
|
||||
if(proxiedPlayer == null) {
|
||||
return;
|
||||
}
|
||||
SchematicSearch.SchematicSearchBehavior behavior = SchematicSearch.SchematicSearchBehavior.builder()
|
||||
.airAsAny(packet.isAirAsAny())
|
||||
.ignoreAir(packet.isIgnoreAir())
|
||||
.ignoreBlockData(packet.isIgnoreBlockData())
|
||||
.build();
|
||||
SchematicSearch.queueSearch(proxiedPlayer, node, behavior);
|
||||
}
|
||||
}
|
307
src/de/steamwar/bungeecore/util/SchematicSearch.java
Normale Datei
@ -0,0 +1,307 @@
|
||||
/*
|
||||
Lixfel markierte diese Unterhaltung als gelöst
Veraltet
Lixfel
hat
. .
|
||||
* This file is a part of the SteamWar software.
|
||||
*
|
||||
* Copyright (C) 2023 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.bungeecore.util;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import de.steamwar.bungeecore.BungeeCore;
|
||||
import de.steamwar.bungeecore.Message;
|
||||
import de.steamwar.sql.NodeData;
|
||||
import de.steamwar.sql.SchematicNode;
|
||||
import de.steamwar.sql.SteamwarUser;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import net.md_5.bungee.api.ChatMessageType;
|
||||
import net.md_5.bungee.api.chat.ClickEvent;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.api.scheduler.ScheduledTask;
|
||||
import org.apache.commons.lang3.time.DurationFormatUtils;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.file.Files;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.zip.GZIPOutputStream;
|
||||
|
||||
public class SchematicSearch {
|
||||
private static final String SEARCH_BINARY = "/home/chaoscaot/schemsearch/target/release/schemsearch-cli";
|
||||
private static final LinkedBlockingQueue<SchematicSearch> searchQueue = new LinkedBlockingQueue<>();
|
||||
private static SchematicSearch currentSearch;
|
||||
|
||||
Lixfel
hat
Falscher Pfad. Falscher Pfad.
|
||||
private static void startNext() {
|
||||
if(currentSearch != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
synchronized (searchQueue) {
|
||||
Lixfel
hat
Warum den Task ständig starten und stoppen und nicht den Thread ständig offen lassen und einfach eventbasiert auf notifyAll() warten lassen? Warum den Task ständig starten und stoppen und nicht den Thread ständig offen lassen und einfach eventbasiert auf notifyAll() warten lassen?
|
||||
if(searchQueue.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
currentSearch = searchQueue.poll();
|
||||
currentSearch.start();
|
||||
}
|
||||
}
|
||||
|
||||
public static void queueSearch(ProxiedPlayer player, SchematicNode node, SchematicSearchBehavior behavior) {
|
||||
synchronized (searchQueue) {
|
||||
searchQueue.add(new SchematicSearch(player, node, behavior));
|
||||
startNext();
|
||||
if (!searchQueue.isEmpty()) {
|
||||
Message.send("SCHEMATIC_SEARCH_QUEUED", player);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void abortAll() {
|
||||
Lixfel markierte diese Unterhaltung als gelöst
Veraltet
Lixfel
hat
Lass solche Spezialberechtigungen raus. Lass solche Spezialberechtigungen raus.
|
||||
synchronized (searchQueue) {
|
||||
searchQueue.stream().map(schematicSearch -> schematicSearch.player).collect(Collectors.toSet()).forEach(player -> {
|
||||
Message.send("SCHEMATIC_SEARCH_REMOVED_FROM_QUEUE_SOFTRELOAD", player);
|
||||
});
|
||||
searchQueue.clear();
|
||||
}
|
||||
if(currentSearch != null) {
|
||||
Message.send("SCHEMATIC_SEARCH_CANCELED_SOFTRELOAD", currentSearch.player);
|
||||
currentSearch.end();
|
||||
}
|
||||
}
|
||||
|
||||
public static int getQueueSize() {
|
||||
synchronized (searchQueue) {
|
||||
return searchQueue.size();
|
||||
}
|
||||
}
|
||||
|
||||
private static List<String> constructArguments(SteamwarUser user, File pattern, SchematicSearchBehavior behavior) {
|
||||
List<String> args = new ArrayList<>(Arrays.asList(SEARCH_BINARY, "-T", "2", "-s", "-u", String.valueOf(user.getId()), "-o", "json:std", "-m", "50"));
|
||||
if(behavior.isAirAsAny()) {
|
||||
args.add("-A");
|
||||
}
|
||||
if(behavior.isIgnoreAir()) {
|
||||
args.add("-a");
|
||||
}
|
||||
if(behavior.isIgnoreBlockData()) {
|
||||
args.add("-d");
|
||||
}
|
||||
args.add(pattern.getAbsolutePath());
|
||||
return args;
|
||||
}
|
||||
|
||||
public static boolean removeFromQueue(ProxiedPlayer player) {
|
||||
boolean removed;
|
||||
synchronized (searchQueue) {
|
||||
removed = searchQueue.removeIf(search -> search.player.equals(player));
|
||||
}
|
||||
|
||||
if (currentSearch != null && currentSearch.player.equals(player)) {
|
||||
currentSearch.end();
|
||||
removed = true;
|
||||
}
|
||||
|
||||
return removed;
|
||||
}
|
||||
|
||||
private static File schematicNodeToTempFile(NodeData node) {
|
||||
try {
|
||||
File f = File.createTempFile("schemsearch", ".schem");
|
||||
f.deleteOnExit();
|
||||
OutputStream os = new GZIPOutputStream(Files.newOutputStream(f.toPath()));
|
||||
InputStream is = node.schemData();
|
||||
|
||||
byte[] buffer = new byte[1024];
|
||||
int read;
|
||||
while((read = is.read(buffer)) != -1) {
|
||||
os.write(buffer, 0, read);
|
||||
}
|
||||
|
||||
os.close();
|
||||
Lixfel
hat
Der Code in der Funktion (und den folgenden Methoden) wirkt sehr C-mäßig geschrieben. Evtl. kann man das nochmal etwas eleganter formulieren. Der Code in der Funktion (und den folgenden Methoden) wirkt sehr C-mäßig geschrieben. Evtl. kann man das nochmal etwas eleganter formulieren.
|
||||
is.close();
|
||||
return f;
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private final ProxiedPlayer player;
|
||||
private final SchematicNode node;
|
||||
private final SchematicSearchBehavior behavior;
|
||||
private File pattern;
|
||||
private ScheduledTask task;
|
||||
private Process process;
|
||||
|
||||
private SchematicSearch(ProxiedPlayer player, SchematicNode node, SchematicSearchBehavior behavior) {
|
||||
this.player = player;
|
||||
this.node = node;
|
||||
this.behavior = behavior;
|
||||
}
|
||||
|
||||
private static String readInputStream(InputStream is) throws IOException {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
byte[] buffer = new byte[1024];
|
||||
long total = 0;
|
||||
while (is.available() > 0) {
|
||||
int read = is.read(buffer);
|
||||
total += read;
|
||||
if (read > 0) {
|
||||
sb.append(new String(buffer, 0, read));
|
||||
}
|
||||
if (total > 1024 * 128) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Lixfel markierte diese Unterhaltung als gelöst
Lixfel
hat
BufferedInputStream.readline()? BufferedInputStream.readline()?
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private void start() {
|
||||
task = BungeeCore.get().getProxy().getScheduler().runAsync(BungeeCore.get(), () -> {
|
||||
try {
|
||||
NodeData data = NodeData.get(node);
|
||||
if (!data.getNodeFormat()) {
|
||||
Message.send("SCHEMATIC_SEARCH_NOT_SUPPORTED", player);
|
||||
end();
|
||||
return;
|
||||
}
|
||||
Message.send("SCHEMATIC_SEARCH_STARTED", player, node.getName());
|
||||
pattern = schematicNodeToTempFile(data);
|
||||
ProcessBuilder builder = new ProcessBuilder(constructArguments(SteamwarUser.get(player.getUniqueId()), pattern, behavior));
|
||||
process = builder.start();
|
||||
InputStream stdout = process.getInputStream();
|
||||
InputStream stderr = process.getErrorStream();
|
||||
Lixfel
hat
Warum hier zwei verschiedene Tasks und nicht diesen Task mit dem "watchdog" konsolidieren? Warum hier zwei verschiedene Tasks und nicht diesen Task mit dem "watchdog" konsolidieren?
|
||||
|
||||
String bar = "";
|
||||
while (!process.waitFor(200, TimeUnit.MILLISECONDS)) {
|
||||
String s = readInputStream(stderr);
|
||||
Lixfel markierte diese Unterhaltung als gelöst
Lixfel
hat
Man könnte auch den Grund (altes Format) mit angeben, um dem User zu helfen. Man könnte auch den Grund (altes Format) mit angeben, um dem User zu helfen.
|
||||
if (s.length() > 0) {
|
||||
if (s.contains("s[")) {
|
||||
s = s.substring(s.lastIndexOf("s[") + 1);
|
||||
}
|
||||
s = s.replace("█", "§e█§7");
|
||||
bar = s;
|
||||
}
|
||||
BungeeCore.send(player, ChatMessageType.ACTION_BAR, "§7" + bar);
|
||||
}
|
||||
String s = readInputStream(stderr);
|
||||
if (s.contains("s[")) {
|
||||
s = s.substring(s.lastIndexOf("s[") + 1);
|
||||
Lixfel
hat
Ich weiß nicht, was das harte Timing hier soll, und wie zuverlässig das hier so ist... (und ob das überhaupt nötig ist) Ich weiß nicht, was das harte Timing hier soll, und wie zuverlässig das hier so ist... (und ob das überhaupt nötig ist)
|
||||
}
|
||||
BungeeCore.send(player, ChatMessageType.ACTION_BAR, "§7" + s.replace("█", "§e█§7"));
|
||||
s = readInputStream(stdout);
|
||||
String[] outputs = s.split("\n");
|
||||
|
||||
List<JsonObject> elements = Arrays.stream(outputs).map(JsonParser::parseString).map(JsonElement::getAsJsonObject).collect(Collectors.toList());
|
||||
|
||||
int searchCount = 0;
|
||||
long searchTime = 0;
|
||||
List<Match> matches = new ArrayList<>();
|
||||
|
||||
for (JsonObject element : elements) {
|
||||
switch (element.get("event").getAsString()) {
|
||||
case "Init":
|
||||
searchCount = element.get("total").getAsInt();
|
||||
break;
|
||||
case "Found":
|
||||
matches.add(new Match(element.get("x").getAsInt(), element.get("y").getAsInt(), element.get("z").getAsInt(), element.get("percent").getAsFloat() * 100, element.get("name").getAsString()));
|
||||
break;
|
||||
case "End":
|
||||
searchTime = element.get("end_time").getAsLong();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Message.send("SCHEMATIC_SEARCH_RESULT_HEADER", player, searchCount, DurationFormatUtils.formatDuration(searchTime, "' 'm'm 's's 'S'ms'")
|
||||
.replace(" 0ms", "")
|
||||
.replace(" 0s", "")
|
||||
.replace(" 0m", ""));
|
||||
|
||||
if (matches.size() <= 1) {
|
||||
Message.send("SCHEMATIC_SEARCH_NO_RESULTS", player);
|
||||
end();
|
||||
return;
|
||||
}
|
||||
|
||||
SteamwarUser user = SteamwarUser.get(player.getUniqueId());
|
||||
for (Match match : matches) {
|
||||
String[] nameSplit = match.name.split(" ");
|
||||
String name = nameSplit[0];
|
||||
int id = Integer.parseInt(nameSplit[1].substring(1, nameSplit[1].length() - 1));
|
||||
if (id == node.getId()) continue;
|
||||
Message.sendPrefixless("SCHEMATIC_SEARCH_RESULT", player, Message.parse("SCHEMATIC_SEARCH_RESULT_HOVER", player, name),
|
||||
new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/schem info " + SchematicNode.byIdAndUser(user, id).generateBreadcrumbs()),
|
||||
name, match.percent, match.x + 1, match.y + 1, match.z + 1);
|
||||
}
|
||||
|
||||
if (matches.size() >= 49) {
|
||||
Message.send("SCHEMATIC_SEARCH_TOO_MANY_RESULTS", player);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new SecurityException(e);
|
||||
} finally {
|
||||
end();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void end() {
|
||||
if(process != null && process.isAlive()) {
|
||||
process.destroy();
|
||||
}
|
||||
|
||||
if(pattern != null) {
|
||||
pattern.delete();
|
||||
}
|
||||
|
||||
if(task != null) {
|
||||
task.cancel();
|
||||
Lixfel markierte diese Unterhaltung als gelöst
Lixfel
hat
Wie wäre es mit einem finally { end(); }? Und welche ungenannten Exception können da noch auftreten, dass die nochmal separat in einer SecurityException gewrappt werden müssen? Wie wäre es mit einem finally { end(); }? Und welche ungenannten Exception können da noch auftreten, dass die nochmal separat in einer SecurityException gewrappt werden müssen?
|
||||
}
|
||||
|
||||
synchronized (searchQueue) {
|
||||
if(currentSearch == this) {
|
||||
currentSearch = null;
|
||||
}
|
||||
}
|
||||
startNext();
|
||||
}
|
||||
|
||||
@AllArgsConstructor
|
||||
private static class Match {
|
||||
int x;
|
||||
int y;
|
||||
int z;
|
||||
float percent;
|
||||
String name;
|
||||
}
|
||||
|
||||
@Builder
|
||||
@Getter
|
||||
public static class SchematicSearchBehavior {
|
||||
@Builder.Default
|
||||
boolean airAsAny = false;
|
||||
@Builder.Default
|
||||
boolean ignoreAir = false;
|
||||
@Builder.Default
|
||||
boolean ignoreBlockData = false;
|
||||
}
|
||||
}
|
@ -121,6 +121,7 @@ MOD_USE_MODSENDER=§cPlease use the §c§lFabricModSender§c (https://steamwar.d
|
||||
#Various commands
|
||||
Lixfel markierte diese Unterhaltung als gelöst
Lixfel
hat
Deutsche Fassung fehlt noch. Deutsche Fassung fehlt noch.
|
||||
ALERT=§f{0}
|
||||
STAT_SERVER=§7Server §e{0}§8: §7Below limit §e{1} §7Server count §e{2}
|
||||
STAT_SEARCH_QUEUE=§7Schematic search queue: §e{0}
|
||||
|
||||
#Ban&Mute-Command
|
||||
PUNISHMENT_USAGE=§8/§7{0} §8[§eplayer§8] [§edd§8.§emm§8.§eyyyy §7or §edd§8.§emm§8.§eyyyy§8_§ehh§8:§emm §7or §enumber§8[§eh§7our|§ed§7ay|§ew§7eek|§em§7onth|§ey§7ear§8] §7or §eperma§8] [§ereason§8]
|
||||
@ -690,3 +691,16 @@ MOD_FORBIDDEN=§eForbidden
|
||||
MOD_AUTOBAN=§cAutoban
|
||||
MOD_YT=§5YT Only
|
||||
MOD_ITEM_BACK=§7Back
|
||||
|
||||
#Schematic Search
|
||||
SCHEMATIC_SEARCH_QUEUED=§7Your search has been queued and will be executed shortly.
|
||||
Lixfel markierte diese Unterhaltung als gelöst
Lixfel
hat
Fehlt ein Leerzeichen? Und (ka was dabei gesucht wird) ist es grammatikalisch korrekt? (Searched {0} schematics in {1})? Fehlt ein Leerzeichen? Und (ka was dabei gesucht wird) ist es grammatikalisch korrekt? (Searched {0} schematics in {1})?
|
||||
SCHEMATIC_SEARCH_STARTED=§7Your search for "§e{0}§7" has started.
|
||||
SCHEMATIC_SEARCH_NO_RESULTS=§cNo results found.
|
||||
Lixfel markierte diese Unterhaltung als gelöst
Lixfel
hat
for more info? for more info?
|
||||
SCHEMATIC_SEARCH_RESULT_HEADER=§7Searched §e{0} §7schematics in§e{1}.
|
||||
SCHEMATIC_SEARCH_RESULT=§7{0}: §e{1}§7% §8(§e{2}§7,§e{3},§e{4}§8)
|
||||
SCHEMATIC_SEARCH_RESULT_HOVER=§7Click for more info about {0}.
|
||||
Lixfel markierte diese Unterhaltung als gelöst
Veraltet
Lixfel
hat
software update software update
|
||||
SCHEMATIC_SEARCH_NOT_SUPPORTED=§cThis schematic is not supported because it is on an old format.
|
||||
Lixfel markierte diese Unterhaltung als gelöst
Veraltet
Lixfel
hat
cancelled? cancelled?
|
||||
SCHEMATIC_SEARCH_REMOVED_FROM_QUEUE=§cYour search has been removed from the queue because you switched servers.
|
||||
SCHEMATIC_SEARCH_REMOVED_FROM_QUEUE_SOFTRELOAD=§cYour search has been removed from the queue because of a software update.
|
||||
SCHEMATIC_SEARCH_CANCELED_SOFTRELOAD=§cYour search has been cancelled because of a software update.
|
||||
SCHEMATIC_SEARCH_TOO_MANY_RESULTS=§cToo many results found. Please be more specific.
|
@ -647,4 +647,16 @@ ADVENT_CALENDAR_TITLE=§eAdventskalender
|
||||
ADVENT_CALENDAR_DAY=§7Tag§8: §e{0}
|
||||
ADVENT_CALENDAR_MESSAGE=§eHast du heute schon dein Geschenk geholt?
|
||||
ADVENT_CALENDAR_MESSAGE_HOVER=§eKlicken zum öffnen!
|
||||
ADVENT_CALENDAR_OPEN=§7Du hast §e{0}§7 aus dem Adventskalender erhalten!
|
||||
ADVENT_CALENDAR_OPEN=§7Du hast §e{0}§7 aus dem Adventskalender erhalten!
|
||||
|
||||
#Schematic Search
|
||||
SCHEMATIC_SEARCH_QUEUED=§7Deine Suche wurde in die Warteschlange eingereiht.
|
||||
SCHEMATIC_SEARCH_STARTED=§7Deine Suche nach §e{0} §7wurde gestartet.
|
||||
SCHEMATIC_SEARCH_NO_RESULTS=§cEs wurden keine Ergebnisse gefunden.
|
||||
SCHEMATIC_SEARCH_RESULT_HEADER=§e{0} §7Schematics in §e{1} §7durchsucht
|
||||
SCHEMATIC_SEARCH_RESULT_HOVER=§7Klicke für mehr Informationen
|
||||
SCHEMATIC_SEARCH_NOT_SUPPORTED=§cDiese Schematic ist in einem alten Format und kann nicht genutzt werden.
|
||||
SCHEMATIC_SEARCH_REMOVED_FROM_QUEUE=§cDeine Suche wurde aus der Warteschlange entfernt, weil du den Server gewechselt hast.
|
||||
SCHEMATIC_SEARCH_REMOVED_FROM_QUEUE_SOFTRELOAD=§cWegen eines Software Updates wurde deine Suche aus der Warteschlange entfernt.
|
||||
Lixfel
hat
Deutsch Softwareupdate statt Software Update Deutsch Softwareupdate statt Software Update
|
||||
SCHEMATIC_SEARCH_CANCELED_SOFTRELOAD=§cDeine Suche wurde wegen eines Software Updates abgebrochen.
|
||||
SCHEMATIC_SEARCH_TOO_MANY_RESULTS=§cEs wurden zu viele Ergebnisse gefunden. Bitte spezifiziere deine Suche.
|
License header