Add VelocityCore Module

This commit is contained in:
2024-08-04 21:05:46 +02:00
parent 3e6f8c9b91
commit 28d4886584
145 changed files with 15181 additions and 0 deletions
@@ -0,0 +1,121 @@
/*
This file is a part of the SteamWar software.
Copyright (C) 2020 SteamWar.de-Serverteam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.velocitycore;
import de.steamwar.sql.SchematicType;
import lombok.Getter;
import java.util.*;
public class ArenaMode extends GameModeConfig {
private static final Random random = new Random();
private static final Map<String, ArenaMode> byChat = new HashMap<>();
private static final Map<String, ArenaMode> byInternal = new HashMap<>();
private static final Map<SchematicType, ArenaMode> bySchemType = new HashMap<>();
@Getter
private static final List<ArenaMode> allModes = new LinkedList<>();
static {
init();
}
public static void init() {
byChat.clear();
byInternal.clear();
bySchemType.clear();
allModes.clear();
GameModeConfig.loadAll(ArenaMode.class, (file, mode) -> {
if(mode.getServer() == null)
return;
mode.config = file.getName();
mode.internalName = file.getName().replace(".yml", "");
mode.getMaps().forEach(map -> mode.lowerToRealMapNames.put(map.toLowerCase(), map));
if(mode.getGameName() == null)
mode.setGameName(mode.internalName);
allModes.add(mode);
byInternal.put(mode.internalName, mode);
for(String name : mode.getServer().getChatNames()){
byChat.put(name.toLowerCase(), mode);
}
if(mode.getSchematic() != null && mode.getSchemType() != null)
bySchemType.put(SchematicType.fromDB(mode.getSchemType()), mode);
});
}
public static ArenaMode getByChat(String name){
return byChat.get(name.toLowerCase());
}
public static ArenaMode getByInternal(String name){
return byInternal.get(name);
}
public static List<String> getAllChatNames(boolean historic) {
List<String> chatNames = new LinkedList<>();
for(ArenaMode mode : byInternal.values()){
if(historic == mode.isHistoric())
chatNames.addAll(mode.getServer().getChatNames());
}
return chatNames;
}
public static ArenaMode getBySchemType(SchematicType schemType){
return bySchemType.get(schemType);
}
private final Map<String, String> lowerToRealMapNames = new HashMap<>();
@Getter
private String internalName;
@Getter
private String config;
public String hasMap(String map){
for(String m : getMaps()) {
if(m.equalsIgnoreCase(map))
return m;
}
return null;
}
public String getRandomMap(){
return getMaps().get(random.nextInt(getMaps().size()));
}
public String convertToRealMapName(String map){
return lowerToRealMapNames.get(map.toLowerCase());
}
public String getChatName(){
return getServer().getChatNames().get(0);
}
public boolean withoutChatName(){
return getServer().getChatNames().isEmpty();
}
}
@@ -0,0 +1,44 @@
/*
This file is a part of the SteamWar software.
Copyright (C) 2020 SteamWar.de-Serverteam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.velocitycore;
import de.steamwar.messages.Chatter;
import java.util.List;
import java.util.concurrent.TimeUnit;
class Broadcaster {
private final List<String> broadcasts = VelocityCore.get().getConfig().getBroadcasts();
private int lastBroadCast = 0;
Broadcaster() {
if(!broadcasts.isEmpty())
VelocityCore.schedule(this::broadcast).repeat(10, TimeUnit.MINUTES).schedule();
}
private void broadcast() {
if(!VelocityCore.getProxy().getAllPlayers().isEmpty())
Chatter.broadcast().system("PLAIN_STRING", broadcasts.get(lastBroadCast++));
if(lastBroadCast == broadcasts.size())
lastBroadCast = 0;
}
}
@@ -0,0 +1,88 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2024 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;
import com.velocitypowered.api.proxy.server.RegisteredServer;
import lombok.Getter;
import org.yaml.snakeyaml.LoaderOptions;
import org.yaml.snakeyaml.TypeDescription;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.Constructor;
import org.yaml.snakeyaml.introspector.BeanAccess;
import org.yaml.snakeyaml.representer.Representer;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
@Getter
public class Config {
public static <T> T load(Class<T> clazz, File file, Consumer<TypeDescription> description) {
TypeDescription typeDescription = new TypeDescription(Config.class);
description.accept(typeDescription);
Constructor constructor = new Constructor(clazz, new LoaderOptions());
constructor.addTypeDescription(typeDescription);
Representer representer = new Representer();
representer.getPropertyUtils().setSkipMissingProperties(true);
Yaml yaml = new Yaml(constructor, representer);
yaml.setBeanAccess(BeanAccess.FIELD);
try{
return yaml.load(new FileInputStream(file));
}catch(IOException e){
VelocityCore.getProxy().shutdown();
throw new SecurityException("Could not load config", e);
}
}
public static Config load() {
return load(Config.class, new File(VelocityCore.get().getDataDirectory().toFile(), "config.yml"), description -> description.addPropertyParameters("servers", String.class, Config.Server.class));
}
private String lobbyserver;
private boolean eventmode = false;
private Map<String, Server> servers = Collections.emptyMap();
private List<String> broadcasts = Collections.emptyList();
private Poll poll = null;
public RegisteredServer lobbyserver() {
return VelocityCore.getProxy().getServer(lobbyserver).orElseThrow();
}
@Getter
public static class Poll {
private String question;
private List<String> answers;
}
@Getter
public static class Server {
private int spectatePort = 0;
private List<String> commands;
}
}
@@ -0,0 +1,83 @@
/*
This file is a part of the SteamWar software.
Copyright (C) 2020 SteamWar.de-Serverteam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.velocitycore;
import de.steamwar.sql.SWException;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.appender.AbstractAppender;
import org.apache.logging.log4j.core.config.AbstractConfiguration;
import org.apache.logging.log4j.core.config.Property;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.util.List;
public class ErrorLogger extends AbstractAppender {
ErrorLogger() {
super("SW ErrorLogger", null, null, false, Property.EMPTY_ARRAY);
start();
LoggerContext context = (LoggerContext) LogManager.getContext(false);
context.getConfiguration().addAppender(this);
context.getRootLogger().addAppender(this);
context.updateLoggers();
}
void unregister() {
LoggerContext context = (LoggerContext) LogManager.getContext(false);
((AbstractConfiguration) context.getConfiguration()).removeAppender(getName());
context.getRootLogger().removeAppender(this);
context.updateLoggers();
}
@Override
public void append(LogEvent event) {
if(event.getLevel().isLessSpecificThan(Level.WARN))
return;
String message = event.getMessage().getFormattedMessage();
for(String reason : filteredMessages)
if(message.contains(reason))
return;
ByteArrayOutputStream stacktraceOutput = new ByteArrayOutputStream();
if(event.getThrown() != null)
event.getThrown().printStackTrace(new PrintStream(stacktraceOutput));
String stacktrace = stacktraceOutput.toString();
for(String reason : filteredStacktraces)
if (stacktrace.contains(reason))
return;
SWException.log(message, stacktrace);
}
private static final List<String> filteredMessages = List.of(
"read timed out",
"disconnected while connecting to Lobby"
);
private static final List<String> filteredStacktraces = List.of(
"ErrorLogger",
"Connection reset by peer"
);
}
@@ -0,0 +1,90 @@
/*
This file is a part of the SteamWar software.
Copyright (C) 2020 SteamWar.de-Serverteam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.velocitycore;
import de.steamwar.messages.Chatter;
import de.steamwar.persistent.Subserver;
import de.steamwar.sql.EventFight;
import de.steamwar.sql.Team;
import net.kyori.adventure.text.event.ClickEvent;
import java.sql.Timestamp;
import java.util.HashMap;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.TimeUnit;
import static de.steamwar.persistent.Storage.eventServer;
public class EventStarter {
private static final Map<Integer, String> spectatePorts = new HashMap<>();
public static void addSpectateServer(int port, String command) {
spectatePorts.put(port, command);
}
public EventStarter() {
EventFight.loadAllComingFights();
VelocityCore.schedule(this::run).delay(10, TimeUnit.SECONDS).schedule();
}
public static Map<Integer, Subserver> getEventServer() {
return eventServer;
}
private void run() {
eventServer.entrySet().removeIf(entry -> Subserver.getSubserver(entry.getValue().getServer()) == null);
Queue<EventFight> fights = EventFight.getFights();
EventFight next;
while((next = nextFight(fights)) != null){
Team blue = Team.get(next.getTeamBlue());
Team red = Team.get(next.getTeamRed());
//Don't start EventServer if not the event bungee
String command;
if(VelocityCore.get().getConfig().isEventmode() || next.getSpectatePort() == 0) {
ServerStarter starter = new ServerStarter().event(next);
starter.callback(subserver -> {
eventServer.put(blue.getTeamId(), subserver);
eventServer.put(red.getTeamId(), subserver);
}).start();
command = "/event " + blue.getTeamKuerzel();
} else {
command = "/" + spectatePorts.get(next.getSpectatePort());
}
Chatter.broadcast().system("EVENT_FIGHT_BROADCAST", "EVENT_FIGHT_BROADCAST_HOVER", ClickEvent.runCommand(command), blue.getTeamColor(), blue.getTeamName(), red.getTeamColor(), red.getTeamName());
}
}
private EventFight nextFight(Queue<EventFight> fights){
EventFight next = fights.peek();
if(next == null)
return null;
if(!next.getStartTime().before(new Timestamp(System.currentTimeMillis())))
return null;
return fights.poll();
}
}
@@ -0,0 +1,92 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2024 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;
import lombok.Getter;
import lombok.Setter;
import java.io.File;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.function.BiConsumer;
@Getter
public class GameModeConfig {
public static <T extends GameModeConfig> void loadAll(Class<T> config, BiConsumer<File, T> consumer) {
File folder = new File(VelocityCore.get().getDataDirectory().getParent().toFile(), "FightSystem");
if(!folder.exists())
return;
for(File file : Arrays.stream(folder.listFiles((file, name) -> name.endsWith(".yml") && !name.endsWith(".kits.yml"))).sorted().toList()) {
consumer.accept(file, Config.load(config, file, description -> {}));
}
}
private Server Server = null;
private List<String> CheckQuestions = Collections.emptyList();
private String deadline = null;
private Schematic Schematic = null;
@Setter
private String GameName;
@Getter
public static class Server {
private String Folder;
private String ServerJar;
private List<String> ChatNames = Collections.emptyList();
private List<String> Maps;
private boolean Ranked = false;
private boolean Historic = false;
}
@Getter
public static class Schematic {
private String Type;
private String Shortcut;
private String Material;
private boolean ManualCheck = true;
}
public String getServerJar() {
return getServer().getServerJar();
}
public String getFolder() {
return getServer().getFolder();
}
public List<String> getMaps() {
return getServer().getMaps();
}
public boolean isHistoric() {
return getServer().isHistoric();
}
public boolean isRanked() {
return getServer().isRanked();
}
public String getSchemType() {
return getSchematic().getType();
}
}
@@ -0,0 +1,218 @@
/*
This file is a part of the SteamWar software.
Copyright (C) 2020 SteamWar.de-Serverteam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.velocitycore;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.file.Files;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.logging.Level;
public abstract class Node {
//-Xquickstart Langzeitperformance testen!
private static final List<String> OPENJ9_ARGS = Arrays.asList(
"-XX:+EnableCRIUSupport", "-XX:-CRIURestoreNonPortableMode",
"-Xgc:excessiveGCratio=80", "-Xdisableexplicitgc", "-Xnoclassgc", "-Xmos128M", "-Xmns48M", "-XX:+ExitOnOutOfMemoryError", // initial heap half values of memory observed by 1.19 spectate server
"-Xsyslog:none", "-Xtrace:none", "-Xverify:none", "-Xdump:system:none", "-Xdump:jit:none", "-Xdump:snap:none",
"-XX:+EnableDynamicAgentLoading", "-Dlog4j.configurationFile=log4j2.xml"
);
private static final Set<String> JAVA_8 = new HashSet<>();
static {
JAVA_8.add("paper-1.8.8.jar");
JAVA_8.add("paper-1.10.2.jar");
JAVA_8.add("spigot-1.8.8.jar");
JAVA_8.add("spigot-1.9.4.jar");
JAVA_8.add("spigot-1.10.2.jar");
}
private static final long MIN_FREE_MEM = 4 * 1024 * 1024L; // 4 GiB
private static final List<Node> nodes = new ArrayList<>();
public static Node getNode() {
for(Node node : nodes) {
if(node.belowLoadLimit)
return node;
}
return null;
}
public static void forEach(Consumer<Node> consumer) {
nodes.forEach(consumer);
}
public abstract ProcessBuilder startServer(String serverJar, File directory, String worldDir, String levelName, int port, String xmx, String... dParams);
protected abstract ProcessBuilder prepareExecution(String... command);
protected abstract void calcLoadLimit();
protected final String hostname;
protected volatile boolean belowLoadLimit = true;
protected Node(String hostname) {
this.hostname = hostname;
nodes.add(this);
VelocityCore.schedule(this::calcLoadLimit).repeat(2, TimeUnit.SECONDS).schedule();
}
public void execute(String... command) {
try {
prepareExecution(command).start().waitFor();
} catch (IOException e) {
throw new SecurityException("Could not execute command", e);
} catch (InterruptedException e) {
VelocityCore.getLogger().log(Level.SEVERE, "Interrupted during execution", e);
Thread.currentThread().interrupt();
}
}
public boolean belowLoadLimit() {
return belowLoadLimit;
}
public String getName() {
return hostname;
}
protected void constructServerstart(File directory, List<String> cmd, String serverJar, String worldDir, String levelName, int port, String xmx, String... dParams) {
if (JAVA_8.contains(serverJar))
cmd.add("/usr/lib/jvm/java-8-openj9-amd64/bin/java");
else
cmd.add("/usr/lib/jvm/java-21-openj9-amd64/bin/java");
for(String param : dParams){
cmd.add("-D" + param);
}
cmd.add("-Xshareclasses:nonfatal,name=" + directory.getName());
cmd.add("-Xmx" + xmx);
cmd.addAll(OPENJ9_ARGS);
if (!JAVA_8.contains(serverJar)) {
cmd.add("--add-opens");
cmd.add("java.base/jdk.internal.misc=ALL-UNNAMED");
cmd.add("-XX:-CRIUSecProvider");
}
cmd.add("-jar");
cmd.add("/binarys/" + serverJar);
cmd.add("--log-strip-color");
cmd.add("--world-dir");
cmd.add(worldDir);
cmd.add("--level-name");
cmd.add(levelName);
cmd.add("--port");
cmd.add(String.valueOf(port));
cmd.add("nogui");
}
protected void calcLoadLimit(BufferedReader meminfo) throws IOException {
String line = meminfo.readLine();
while(!line.startsWith("MemAvailable")) {
line = meminfo.readLine();
}
long availableMem = Long.parseLong(line.replaceAll(" +", " ").split(" ")[1]);
belowLoadLimit = availableMem >= MIN_FREE_MEM;
}
public static class LocalNode extends Node {
private static final File MEMINFO = new File("/proc/meminfo");
public LocalNode() {
super("sw");
}
@Override
public ProcessBuilder startServer(String serverJar, File directory, String worldDir, String levelName, int port, String xmx, String... dParams) {
List<String> cmd = new ArrayList<>();
constructServerstart(directory, cmd, serverJar, worldDir, levelName, port, xmx, dParams);
ProcessBuilder builder = new ProcessBuilder(cmd);
builder.directory(directory);
return builder;
}
@Override
protected void calcLoadLimit() {
try (BufferedReader meminfo = new BufferedReader(new InputStreamReader(Files.newInputStream(MEMINFO.toPath())))) {
calcLoadLimit(meminfo);
} catch (IOException e) {
VelocityCore.getLogger().log(Level.SEVERE, "Could not read local load", e);
belowLoadLimit = false;
}
}
@Override
protected ProcessBuilder prepareExecution(String... command) {
return new ProcessBuilder(command);
}
}
public static class RemoteNode extends Node {
public RemoteNode(String hostname) {
super(hostname);
VelocityCore.getLogger().log(Level.INFO, "Added node {0}", hostname);
}
@Override
public ProcessBuilder startServer(String serverJar, File directory, String worldDir, String levelName, int port, String xmx, String... dParams) {
List<String> cmd = new ArrayList<>();
cmd.add("ssh");
cmd.add("-L");
cmd.add(port + ":localhost:" + port);
cmd.add(hostname);
cmd.add("cd");
cmd.add(directory.getPath());
cmd.add(";");
constructServerstart(directory, cmd, serverJar, worldDir, levelName, port, xmx, dParams);
return new ProcessBuilder(cmd);
}
@Override
protected ProcessBuilder prepareExecution(String... command) {
List<String> cmd = new ArrayList<>();
cmd.add("ssh");
cmd.add(hostname);
cmd.addAll(Arrays.asList(command));
return new ProcessBuilder(cmd);
}
@Override
protected void calcLoadLimit() {
try {
Process process = prepareExecution("cat /proc/meminfo").start();
if(!process.waitFor(1, TimeUnit.SECONDS))
throw new IOException(hostname + " timeout");
try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
calcLoadLimit(reader);
}
} catch (IOException e) {
if(belowLoadLimit)
VelocityCore.getLogger().log(Level.SEVERE, "Could read remote load", e);
belowLoadLimit = false;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
belowLoadLimit = false;
}
}
}
}
@@ -0,0 +1,373 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2024 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;
import com.velocitypowered.api.proxy.Player;
import de.steamwar.messages.Chatter;
import de.steamwar.persistent.Arenaserver;
import de.steamwar.persistent.Bauserver;
import de.steamwar.persistent.Builderserver;
import de.steamwar.persistent.Subserver;
import de.steamwar.sql.*;
import java.io.DataOutputStream;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.function.BooleanSupplier;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
public class ServerStarter {
private static final Portrange BAU_PORTS = VelocityCore.MAIN_SERVER ? new Portrange(10100, 20000) : new Portrange(2100, 2200);
private static final Portrange ARENA_PORTS = VelocityCore.MAIN_SERVER ? new Portrange(3000, 3100) : (VelocityCore.get().getConfig().isEventmode() ? new Portrange(4000, 5000) : BAU_PORTS);
public static final String SERVER_PATH = "/servers/";
private static final String USER_HOME = System.getProperty("user.home") + "/";
private static final String EVENT_PATH = USER_HOME + "event/";
public static final String TEMP_WORLD_PATH = USER_HOME + "arenaserver/";
public static final String TUTORIAL_PATH = USER_HOME + "tutorials/";
public static final String WORLDS_BASE_PATH = USER_HOME + "userworlds";
public static final String BUILDER_BASE_PATH = USER_HOME + "builder";
private File directory = null;
private String worldDir = null;
private Node node = null;
private String serverJar = "spigot-1.15.2.jar";
private String xmx = "768M";
private Portrange portrange = BAU_PORTS;
private Function<Integer, String> serverNameProvider = port -> node.getName() + port;
private BooleanSupplier startCondition = () -> true;
private Runnable worldSetup = () -> {};
private String worldName = null;
private Runnable worldCleanup = () -> {};
private boolean allowMerge = false;
private String fightMap = null;
private String gameMode = null;
private boolean checkpoint = false;
private ServerConstructor constructor = (serverName, port, builder, shutdownCallback, failureCallback) -> new Arenaserver(serverName, gameMode, fightMap, allowMerge, port, builder, shutdownCallback);
private Consumer<Subserver> callback = subserver -> {};
private final Set<Player> playersToSend = new HashSet<>();
private final Map<String, String> arguments = new HashMap<>();
public ServerStarter arena(ArenaMode mode, String map) {
portrange = ARENA_PORTS;
serverNameProvider = port -> mode.getGameName() + (port - portrange.start);
serverJar = mode.getServerJar();
allowMerge = true;
fightMap = map;
gameMode = mode.getInternalName();
directory = new File(SERVER_PATH, mode.getFolder());
arguments.put("config", mode.getConfig());
tempWorld(SERVER_PATH + mode.getFolder() + "/arenas/" + map);
return this;
}
public ServerStarter event(EventFight eventFight) {
arena(ArenaMode.getByInternal(eventFight.getSpielmodus()), eventFight.getMap());
node = VelocityCore.local;
worldDir = EVENT_PATH;
worldCleanup = () -> {};
arguments.put("fightID", String.valueOf(eventFight.getFightID()));
fightMap = eventFight.getMap();
gameMode = eventFight.getSpielmodus();
String serverName = Team.get(eventFight.getTeamBlue()).getTeamKuerzel() + " vs " + Team.get(eventFight.getTeamRed()).getTeamKuerzel();
serverNameProvider = port -> serverName;
worldName = serverToWorldName(serverName + eventFight.getStartTime().toLocalDateTime().format(DateTimeFormatter.ISO_TIME));
return this;
}
public ServerStarter test(ArenaMode mode, String map, Player owner) {
arena(mode, map);
buildWithTemp(owner);
portrange = BAU_PORTS;
arguments.put("fightID", String.valueOf(-1));
return send(owner);
}
public ServerStarter blueLeader(Player player) {
arguments.put("blueLeader", player.getUniqueId().toString());
return send(player);
}
public ServerStarter redLeader(Player player) {
arguments.put("redLeader", player.getUniqueId().toString());
return send(player);
}
public ServerStarter check(int schemID) {
arguments.put("checkSchemID", String.valueOf(schemID));
return this;
}
public ServerStarter prepare(int schemID) {
arguments.put("prepareSchemID", String.valueOf(schemID));
return this;
}
public ServerStarter replay(int replayID) {
arguments.put("replay", String.valueOf(replayID));
return this;
}
public ServerStarter build(ServerVersion version, UUID owner) {
directory = version.getServerDirectory("Bau");
serverJar = version.getServerJar();
worldDir = version.getWorldFolder(WORLDS_BASE_PATH);
worldName = version != ServerVersion.SPIGOT_12 ? String.valueOf(SteamwarUser.get(owner).getId()) : owner.toString();
checkpoint = true;
build(owner);
worldSetup = () -> {
File world = new File(worldDir, worldName);
if (!world.exists())
copyWorld(node, new File(directory, "Bauwelt").getPath(), world.getPath());
};
// Send players to existing server
startCondition = () -> {
Bauserver subserver = Bauserver.get(owner);
if(subserver != null) {
for(Player p : playersToSend)
SubserverSystem.sendPlayer(subserver, p);
return false;
}
boolean atLeastOneSupervisor = playersToSend.stream().anyMatch(player -> {
if (player.getUniqueId().equals(owner)) return true;
BauweltMember bauweltMember = BauweltMember.getBauMember(owner, player.getUniqueId());
return bauweltMember.isSupervisor();
});
if (!atLeastOneSupervisor) {
for (Player p : playersToSend) {
Chatter.of(p).system("BAU_START_NOT_ALLOWED");
}
}
return atLeastOneSupervisor;
};
return this;
}
public ServerStarter tutorial(Player owner, Tutorial tutorial) {
directory = new File(SERVER_PATH, "Tutorial");
buildWithTemp(owner);
tempWorld(TUTORIAL_PATH + tutorial.getTutorialId());
arguments.put("tutorial", String.valueOf(tutorial.getTutorialId()));
return send(owner);
}
private void tempWorld(String template) {
worldDir = TEMP_WORLD_PATH;
worldSetup = () -> copyWorld(node, template, worldDir + worldName);
worldCleanup = () -> SubserverSystem.deleteFolder(node, worldDir + worldName);
}
private void buildWithTemp(Player owner) {
build(owner.getUniqueId());
// Stop existing build server
startCondition = () -> {
if(startingBau(owner))
return false;
Bauserver subserver = Bauserver.get(owner.getUniqueId());
if(subserver != null && subserver.isStarted())
subserver.stop();
return !startingBau(owner);
};
}
private void build(UUID owner) {
constructor = (serverName, port, builder, shutdownCallback, failureCallback) -> new Bauserver(serverName, owner, port, builder, shutdownCallback, failureCallback);
serverNameProvider = port -> bauServerName(SteamwarUser.get(owner));
}
public ServerStarter builder(ServerVersion version, String map, File generator) {
serverJar = version.getServerJar();
directory = version.getServerDirectory("Builder");
worldDir = version.getWorldFolder(BUILDER_BASE_PATH);
worldName = map;
serverNameProvider = port -> "*" + map;
checkpoint = true;
constructor = (serverName, port, builder, shutdownCallback, failureCallback) -> new Builderserver(serverName, worldName, port, builder, shutdownCallback, failureCallback);
// Send players to existing server
startCondition = () -> {
Builderserver subserver = Builderserver.get(worldName);
if (subserver != null) {
for(Player p : playersToSend)
SubserverSystem.sendPlayer(subserver, p);
return false;
}
return true;
};
if(generator != null) {
worldSetup = () -> {
File leveldat = new File(new File(worldDir, worldName), "level.dat");
try {
Files.copy(generator.toPath(), leveldat.toPath());
} catch (IOException e) {
throw new SecurityException(e);
}
};
}
return this;
}
public ServerStarter send(Player player) {
playersToSend.add(player);
return this;
}
public ServerStarter callback(Consumer<Subserver> callback) {
this.callback = callback;
return this;
}
public boolean start() {
if(!startCondition.getAsBoolean())
return false;
int port = portrange.freePort();
String serverName = serverNameProvider.apply(port);
if(node == null) {
node = Node.getNode();
if(node == null) {
for (Player p : playersToSend)
Chatter.of(p).system("SERVER_START_OVERLOAD");
return false;
}
}
if(worldName == null)
worldName = serverToWorldName(serverName);
worldSetup.run();
arguments.put("logPath", worldName);
File checkpointDir = new File("/tmp/" + System.getProperty("user.name") + ".checkpoints/" + directory.getName() + "/" + worldName);
if(checkpoint)
arguments.put("checkpoint", checkpointDir.getPath());
if(checkpoint && checkpointDir.exists()) {
try(DataOutputStream out = new DataOutputStream(Files.newOutputStream(new File(checkpointDir, "port").toPath()))) {
out.writeInt(port);
} catch (IOException e) {
throw new SecurityException(e);
}
postStart(constructor.construct(serverName, port, node.prepareExecution(
"criu", "restore", "-D", checkpointDir.getPath(), "--auto-dedup", "--shell-job", "-v"
), worldCleanup, e -> regularStart(serverName, port)));
} else {
regularStart(serverName, port);
}
return true;
}
private void regularStart(String serverName, int port) {
postStart(constructor.construct(serverName, port, node.startServer(
serverJar, directory, worldDir, worldName, port, xmx, arguments.entrySet().stream().map(entry -> entry.getKey() + "=" + entry.getValue()).toArray(String[]::new)
), worldCleanup, null));
}
private void postStart(Subserver subserver) {
for(Player p : playersToSend)
SubserverSystem.sendPlayer(subserver, p);
callback.accept(subserver);
}
private static boolean startingBau(Player p) {
Bauserver subserver = Bauserver.get(p.getUniqueId());
if(subserver != null && !subserver.isStarted()) {
Chatter.of(p).system("BAU_START_ALREADY");
return true;
}
return false;
}
public static String bauServerName(SteamwarUser user) {
return user.getUserName() + "s Bau";
}
public static String serverToWorldName(String serverName) {
return serverName.replace(' ', '_').replace("[", "").replace("]", "");
}
public static void copyWorld(Node node, String template, String target) {
node.execute("cp", "-r", template, target);
}
private interface ServerConstructor {
Subserver construct(String serverName, int port, ProcessBuilder builder, Runnable shutdownCallback, Consumer<Exception> failureCallback);
}
private static class Portrange {
private final int start;
private final int end;
private int current;
private Portrange(int start, int end) {
this.start = start;
this.end = end;
current = start;
}
private void increment() {
current++;
if(current == end)
current = start;
}
private synchronized int freePort() {
Set<Integer> usedPorts;
synchronized (Subserver.getServerList()) {
usedPorts = Subserver.getServerList().stream().map(server -> server.getServer().getAddress().getPort()).collect(Collectors.toSet());
}
while(usedPorts.contains(current)) {
increment();
}
int result = current;
increment();
return result;
}
}
}
@@ -0,0 +1,87 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2024 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;
import lombok.Getter;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
@Getter
public enum ServerVersion {
SPIGOT_12("spigot-1.12.2.jar", 12),
SPIGOT_15("spigot-1.15.2.jar", 15),
PAPER_19("paper-1.19.3.jar", 19),
PAPER_20("paper-1.20.1.jar", 20);
private static final Map<String, ServerVersion> chatMap = new HashMap<>();
static {
chatMap.put("20", ServerVersion.PAPER_20);
chatMap.put("1.20", ServerVersion.PAPER_20);
chatMap.put("1.20.1", ServerVersion.PAPER_20);
chatMap.put("as", ServerVersion.PAPER_20);
chatMap.put("airship", ServerVersion.PAPER_20);
chatMap.put("wg", ServerVersion.PAPER_20);
chatMap.put("wargear", ServerVersion.PAPER_20);
chatMap.put("ws", ServerVersion.PAPER_20);
chatMap.put("warship", ServerVersion.PAPER_20);
chatMap.put("mwg", ServerVersion.PAPER_20);
chatMap.put("miniwargear", ServerVersion.PAPER_20);
chatMap.put("19", ServerVersion.PAPER_19);
chatMap.put("1.19", ServerVersion.PAPER_19);
chatMap.put("1.19.2", ServerVersion.PAPER_19);
chatMap.put("15", ServerVersion.SPIGOT_15);
chatMap.put("1.15", ServerVersion.SPIGOT_15);
chatMap.put("1.15.2", ServerVersion.SPIGOT_15);
chatMap.put("12", ServerVersion.SPIGOT_12);
chatMap.put("1.12", ServerVersion.SPIGOT_12);
chatMap.put("1.12.2", ServerVersion.SPIGOT_12);
}
public static ServerVersion get(String chat) {
return chatMap.get(chat.toLowerCase());
}
public static Set<String> chatVersions() {
return chatMap.keySet();
}
private final String serverJar;
private final int versionSuffix;
ServerVersion(String serverJar, int versionSuffix) {
this.serverJar = serverJar;
this.versionSuffix = versionSuffix;
}
public String getWorldFolder(String base) {
return base + versionSuffix + "/";
}
public File getServerDirectory(String base) {
return new File(ServerStarter.SERVER_PATH, base + versionSuffix);
}
}
@@ -0,0 +1,58 @@
/*
This file is a part of the SteamWar software.
Copyright (C) 2020 SteamWar.de-Serverteam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.velocitycore;
import com.velocitypowered.api.proxy.Player;
import de.steamwar.velocitycore.network.NetworkSender;
import de.steamwar.velocitycore.network.handlers.FightInfoHandler;
import de.steamwar.messages.Chatter;
import de.steamwar.messages.Message;
import de.steamwar.network.packets.server.StartingServerPacket;
import de.steamwar.persistent.Subserver;
import de.steamwar.sql.IgnoreSystem;
import de.steamwar.sql.SteamwarUser;
import net.kyori.adventure.text.event.ClickEvent;
import java.util.UUID;
public class SubserverSystem {
private SubserverSystem(){}
public static void deleteFolder(Node node, String worldName) {
node.execute("rm", "-r", worldName);
}
public static void sendDeniedMessage(Chatter p, UUID owner) {
if(IgnoreSystem.isIgnored(owner, p.user().getUUID())){
p.system("SERVER_IGNORED");
return;
}
Chatter o = Chatter.of(owner);
o.system("SERVER_ADD_MEMBER", p);
o.prefixless("SERVER_ADD_MESSAGE", new Message("SERVER_ADD_MESSAGE_HOVER"), ClickEvent.runCommand("/bau addmember " + p.user().getUserName()));
}
public static void sendPlayer(Subserver subserver, Player player) {
subserver.sendPlayer(player);
if(!subserver.isStarted() && FightInfoHandler.onLobby(player))
NetworkSender.send(player, new StartingServerPacket(SteamwarUser.get(player.getUniqueId()).getId()));
}
}
@@ -0,0 +1,282 @@
/*
This file is a part of the SteamWar software.
Copyright (C) 2020 SteamWar.de-Serverteam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.velocitycore;
import com.google.inject.Inject;
import com.velocitypowered.api.event.Subscribe;
import com.velocitypowered.api.event.proxy.ProxyInitializeEvent;
import com.velocitypowered.api.event.proxy.ProxyShutdownEvent;
import com.velocitypowered.api.plugin.Dependency;
import com.velocitypowered.api.plugin.Plugin;
import com.velocitypowered.api.plugin.annotation.DataDirectory;
import com.velocitypowered.api.proxy.ProxyServer;
import com.velocitypowered.api.scheduler.Scheduler;
import de.steamwar.command.*;
import de.steamwar.messages.Chatter;
import de.steamwar.network.packets.PacketHandler;
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.*;
import de.steamwar.velocitycore.discord.DiscordBot;
import de.steamwar.velocitycore.discord.DiscordConfig;
import de.steamwar.velocitycore.listeners.*;
import de.steamwar.velocitycore.mods.*;
import de.steamwar.velocitycore.network.handlers.*;
import de.steamwar.velocitycore.tablist.TablistManager;
import lombok.Getter;
import java.nio.file.Path;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
@Plugin(
id = "velocitycore",
name = "VelocityCore",
dependencies = { @Dependency(id = "persistentvelocitycore") }
)
public class VelocityCore implements ReloadablePlugin {
public static boolean MAIN_SERVER;
private static VelocityCore instance;
public static VelocityCore get() {
return instance;
}
public static Node local;
public static Scheduler.TaskBuilder schedule(Runnable runnable) {
return instance.proxyServer.getScheduler().buildTask(instance, runnable);
}
public static ProxyServer getProxy() {
return instance.proxyServer;
}
public static Logger getLogger() {
return instance.logger;
}
private final ProxyServer proxyServer;
private final Logger logger;
@Getter
private final Path dataDirectory;
private Driver sqlDriver;
@Getter
private Config config;
private ErrorLogger errorLogger;
private TablistManager tablistManager;
@Getter
private TeamCommand teamCommand;
@Inject
public VelocityCore(ProxyServer proxyServer, Logger logger, @DataDirectory Path dataDirectory) {
this.proxyServer = proxyServer;
this.logger = logger;
this.dataDirectory = dataDirectory;
}
@Subscribe
@Override
public void onProxyInitialization(ProxyInitializeEvent event) {
instance = this;
config = Config.load();
MAIN_SERVER = proxyServer.getBoundAddress().getPort() == 25565;
try {
sqlDriver = Statement.mysqlMode() ? new com.mysql.cj.jdbc.Driver() : new org.sqlite.JDBC();
DriverManager.registerDriver(sqlDriver);
} catch (SQLException e) {
throw new SecurityException(e);
}
errorLogger = new ErrorLogger();
SWCommandUtils.init((SWTypeMapperCreator<TypeMapper<Object>, Chatter, Object>) (mapper, tabCompleter) -> new TypeMapper<>() {
@Override
public Object map(Chatter sender, PreviousArguments previousArguments, String s) {
return mapper.apply(s);
}
@Override
public Collection<String> tabCompletes(Chatter sender, PreviousArguments previousArguments, String s) {
return tabCompleter.apply(sender, s);
}
});
schedule(TabCompletionCache::invalidateOldEntries).repeat(1, TimeUnit.SECONDS).schedule();
initStaticServers();
PollSystem.init();
new Hostname();
new PluginMessage();
new Schematica();
new Badlion();
new FabricModSender();
new ReplayMod();
new FML2();
new ConnectionListener();
new ChatListener();
new BanListener();
new CheckListener();
new IPSanitizer();
local = new Node.LocalNode();
if(MAIN_SERVER) {
//new Node.RemoteNode("lx");
}
new TeamchatCommand();
new MsgCommand();
new RCommand();
new PingCommand();
new AlertCommand();
new KickCommand();
new JoinmeCommand();
new TpCommand();
HelpCommand helpCommand = new HelpCommand();
teamCommand = new TeamCommand();
new ServerTeamchatCommand();
new DevCommand();
new EventCommand();
new EventreloadCommand();
new EventRescheduleCommand();
new PollCommand();
new BugCommand();
new WhoisCommand();
new RulesCommand();
new IgnoreCommand();
new UnIgnoreCommand();
new PollresultCommand();
new ListCommand();
new StatCommand();
new VerifyCommand();
new GDPRQuery();
new PlaytimeCommand();
new ArenaCommand();
new RankCommand();
new LocalCommand();
new SetLocaleCommand();
new BuilderCloudCommand();
new CheckCommand();
new ModCommand();
// Punishment Commands:
new PunishmentCommand("ban", Punishment.PunishmentType.Ban);
new PunishmentCommand("mute", Punishment.PunishmentType.Mute);
new PunishmentCommand("noschemreceiving", Punishment.PunishmentType.NoSchemReceiving);
new PunishmentCommand("noschemsharing", Punishment.PunishmentType.NoSchemSharing);
new PunishmentCommand("noschemsubmitting", Punishment.PunishmentType.NoSchemSubmitting);
new PunishmentCommand("nodev", Punishment.PunishmentType.NoDevServer);
new PunishmentCommand("nofight", Punishment.PunishmentType.NoFightServer);
new PunishmentCommand("noteamserver", Punishment.PunishmentType.NoTeamServer);
new PunishmentCommand("note", Punishment.PunishmentType.Note);
if(!config.isEventmode()){
new BauCommand(helpCommand);
new WebpasswordCommand();
new FightCommand();
new ChallengeCommand();
new HistoricCommand();
new ReplayCommand();
new TutorialCommand();
new Broadcaster();
}else{
new EventModeListener();
}
for(PacketHandler handler : new PacketHandler[] {
new EloPlayerHandler(), new EloSchemHandler(), new ExecuteCommandHandler(), new FightInfoHandler(),
new ImALobbyHandler(), new InventoryCallbackHandler(), new PrepareSchemHandler()
})
handler.register();
new EventStarter();
new SessionManager();
tablistManager = new TablistManager();
new SettingsChangedListener();
schedule(() -> {
SteamwarUser.clear();
UserElo.clear();
Team.clear();
}).repeat(1, TimeUnit.HOURS).schedule();
DiscordConfig discordConfig = DiscordConfig.load();
if (discordConfig != null) {
try {
new DiscordBot(discordConfig);
} catch (Throwable e) {
logger.log(Level.SEVERE, "Could not initialize discord bot", e);
}
}
}
@Subscribe
@Override
public void onProxyShutdown(ProxyShutdownEvent event) {
try {
DiscordBot.withBot(bot -> bot.getJda().shutdownNow());
} catch (Throwable e) {
logger.log(Level.SEVERE, "Could not shutdown discord bot", e);
}
if(tablistManager != null)
tablistManager.disable();
errorLogger.unregister();
Statement.closeAll();
try {
DriverManager.deregisterDriver(sqlDriver);
} catch (SQLException e) {
throw new SecurityException(e);
}
}
private void initStaticServers() {
for (Map.Entry<String, Config.Server> entry : config.getServers().entrySet()) {
Config.Server server = entry.getValue();
List<String> cmds = server.getCommands();
String cmd = cmds.remove(0);
if(server.getSpectatePort() != 0)
EventStarter.addSpectateServer(server.getSpectatePort(), cmd);
new ServerSwitchCommand(cmd, entry.getKey(), cmds.toArray(new String[0]));
}
}
}
@@ -0,0 +1,46 @@
/*
This file is a part of the SteamWar software.
Copyright (C) 2020 SteamWar.de-Serverteam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.velocitycore.commands;
import de.steamwar.velocitycore.discord.DiscordBot;
import de.steamwar.command.SWCommand;
import de.steamwar.messages.Chatter;
import de.steamwar.sql.UserPerm;
public class AlertCommand extends SWCommand {
public AlertCommand() {
super("alert", UserPerm.MODERATION, "broadcast", "bbc");
}
@Register(description = "USAGE_ALERT")
public void broadcast(Chatter sender, @OptionalValue("") @StaticValue("-discord") String sendToDiscord, String... message) {
if (message.length == 0) {
sender.system("USAGE_ALERT");
return;
}
String s = String.join(" ", message);
Chatter.broadcast().system("ALERT", s.replace('&', '§'));
if ("-discord".equals(sendToDiscord))
DiscordBot.withBot(bot -> bot.getAnnouncementChannel().send(s));
}
}
@@ -0,0 +1,72 @@
/*
This file is a part of the SteamWar software.
Copyright (C) 2020 SteamWar.de-Serverteam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.velocitycore.commands;
import com.velocitypowered.api.proxy.server.RegisteredServer;
import de.steamwar.persistent.Servertype;
import de.steamwar.persistent.Subserver;
import de.steamwar.velocitycore.VelocityCore;
import de.steamwar.command.PreviousArguments;
import de.steamwar.command.SWCommand;
import de.steamwar.command.TypeMapper;
import de.steamwar.messages.Chatter;
import de.steamwar.messages.PlayerChatter;
import java.util.Collection;
import java.util.List;
public class ArenaCommand extends SWCommand {
public ArenaCommand() {
super("arena");
}
@Register
public void arenaJoin(PlayerChatter sender, Subserver server) {
TpCommand.teleport(sender, server.getRegisteredServer());
}
@ClassMapper(value = Subserver.class, local = true)
public TypeMapper<Subserver> serverInfoTypeMapper() {
return new TypeMapper<>() {
@Override
public Subserver map(Chatter sender, PreviousArguments previousArguments, String s) {
return Subserver.getSubserver(VelocityCore.getProxy().getServer(s).map(RegisteredServer::getServerInfo).orElse(null));
}
@Override
public boolean validate(Chatter sender, Subserver value, MessageSender messageSender) {
if (value == null || value.getType() != Servertype.ARENA) {
sender.system("ARENA_NOT_FOUND");
return false;
}
return true;
}
@Override
public Collection<String> tabCompletes(Chatter sender, PreviousArguments previousArguments, String s) {
List<Subserver> subserverList = Subserver.getServerList();
synchronized (subserverList) {
return subserverList.stream().filter(subserver -> subserver.getType() == Servertype.ARENA).map(subserver -> subserver.getServer().getName()).toList();
}
}
};
}
}
@@ -0,0 +1,251 @@
/*
This file is a part of the SteamWar software.
Copyright (C) 2020 SteamWar.de-Serverteam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.velocitycore.commands;
import de.steamwar.velocitycore.*;
import de.steamwar.velocitycore.inventory.SWInventory;
import de.steamwar.velocitycore.inventory.SWItem;
import de.steamwar.velocitycore.network.NetworkSender;
import de.steamwar.velocitycore.util.BauLock;
import de.steamwar.velocitycore.util.BauLockState;
import de.steamwar.command.PreviousArguments;
import de.steamwar.command.SWCommand;
import de.steamwar.command.TypeMapper;
import de.steamwar.command.TypeValidator;
import de.steamwar.messages.Chatter;
import de.steamwar.messages.Message;
import de.steamwar.messages.PlayerChatter;
import de.steamwar.network.packets.server.BaumemberUpdatePacket;
import de.steamwar.persistent.Bauserver;
import de.steamwar.sql.BauweltMember;
import de.steamwar.sql.SteamwarUser;
import java.util.Collection;
import java.util.function.Consumer;
public class BauCommand extends SWCommand {
private final HelpCommand command;
public BauCommand(HelpCommand command) {
super("bau", "b", "build", "gs");
this.command = command;
}
@Register(noTabComplete = true)
public void genericHelp(Chatter sender, String... args) {
this.command.sendBauHelp(sender);
}
@Register
public void toBau(PlayerChatter sender, @OptionalValue(value = "", onlyUINIG = true) ServerVersion version) {
new ServerStarter().build(version, sender.user().getUUID()).send(sender.getPlayer()).start();
}
@Register(value = "addmember", description = "BAU_ADDMEMBER_USAGE")
public void addmember(Chatter sender, @Validator("addMemberTarget") SteamwarUser target) {
BauweltMember.addMember(sender.user().getUUID(), target.getUUID());
sender.system("BAU_ADDMEMBER_ADDED");
Chatter.of(target.getUUID()).system("BAU_ADDMEMBER_ADDED_TARGET", sender);
}
@Validator(value = "addMemberTarget", local = true)
public TypeValidator<SteamwarUser> addMemberTargetValidator() {
return (sender, value, messageSender) -> {
if (value == null) {
messageSender.send("UNKNOWN_PLAYER");
return false;
}
if (sender.user().getUUID().equals(value.getUUID())) {
messageSender.send("BAU_ADDMEMBER_SELFADD");
return false;
}
if (BauweltMember.getBauMember(sender.user().getId(), value.getId()) != null) {
messageSender.send("BAU_ADDMEMBER_ISADDED");
return false;
}
return true;
};
}
@Register(value = "tp", description = "BAU_TP_USAGE")
@Register("teleport")
public void teleport(PlayerChatter sender, @Validator("teleportTarget") SteamwarUser worldOwner, @OptionalValue(value = "", onlyUINIG = true) ServerVersion version) {
new ServerStarter().build(version, worldOwner.getUUID()).send(sender.getPlayer()).start();
}
@Validator(value = "teleportTarget", local = true)
public TypeValidator<SteamwarUser> teleportTargetValidator() {
return (sender, owner, messageSender) -> {
if (owner == null) {
messageSender.send("UNKNOWN_PLAYER");
return false;
}
if (sender.user().getId() != owner.getId() && BauweltMember.getBauMember(owner.getId(), sender.user().getId()) == null) {
SubserverSystem.sendDeniedMessage(sender, owner.getUUID());
messageSender.send("BAU_TP_NOALLOWED");
return false;
}
if (BauLock.isLocked(owner, sender.user())) {
messageSender.send("BAU_LOCKED_NOALLOWED");
Chatter.of(owner.getUUID()).system("BAU_LOCK_BLOCKED", sender);
return false;
}
return true;
};
}
@Register("info")
public void info(PlayerChatter sender) {
sender.getPlayer().spoofChatInput("/bauinfo");
}
@Register("setspectator")
public void setSpectator(Chatter sender, @Mapper("addedUsers") @AllowNull @OptionalValue("") SteamwarUser user) {
setPerms(sender, user, "setspectator", "BAU_MEMBER_SET_SPECTATOR", member -> {
member.setBuild(false);
member.setSupervisor(false);
});
}
@Register("setbuild")
public void setBuild(Chatter sender, @Mapper("addedUsers") @AllowNull @OptionalValue("") SteamwarUser user) {
setPerms(sender, user, "setbuild", "BAU_MEMBER_SET_BUILDER", member -> {
member.setBuild(true);
member.setSupervisor(false);
});
}
@Register("setsupervisor")
public void setSupervisor(Chatter sender, @Mapper("addedUsers") @AllowNull @OptionalValue("") SteamwarUser user) {
setPerms(sender, user, "setsupervisor", "BAU_MEMBER_SET_SUPERVISOR", member -> {
member.setBuild(true);
member.setSupervisor(true);
});
}
private void setPerms(Chatter owner, SteamwarUser user, String name, String permName, Consumer<BauweltMember> setter) {
if (user == null) {
owner.system("BAU_MEMBER_SET_USAGE", name);
return;
}
withMember(owner, user, target -> {
setter.accept(target);
Bauserver bauserver = Bauserver.get(owner.user().getUUID());
if(bauserver != null)
bauserver.getRegisteredServer().getPlayersConnected().stream().findAny().ifPresent(player -> NetworkSender.send(player, new BaumemberUpdatePacket()));
Chatter.of(user.getUUID()).system("BAU_MEMBER_SET_TARGET", owner, new Message(permName));
owner.system("BAU_MEMBER_SET", new Message(permName));
});
}
@Register(value = "delmember", description = "BAU_DELMEMBER_USAGE")
public void delmember(Chatter owner, @Mapper("addedUsers") SteamwarUser user) {
withMember(owner, user, target -> {
target.remove();
Chatter member = Chatter.of(user.getUUID());
member.system("BAU_DELMEMBER_DELETED_TARGET", owner);
member.withPlayer(player -> {
Bauserver bauserver = Bauserver.get(owner.user().getUUID());
if (bauserver != null && bauserver.getRegisteredServer().getPlayersConnected().contains(player))
player.createConnectionRequest(VelocityCore.get().getConfig().lobbyserver()).fireAndForget();
});
owner.system("BAU_DELMEMBER_DELETED");
});
}
@Mapper(value = "addedUsers", local = true)
public TypeMapper<SteamwarUser> addedUsers() {
return new TypeMapper<SteamwarUser>() {
@Override
public SteamwarUser map(Chatter sender, PreviousArguments previousArguments, String s) {
return SteamwarUser.get(s);
}
@Override
public Collection<String> tabCompletes(Chatter sender, PreviousArguments previousArguments, String s) {
return BauweltMember.getMembers(sender.user().getId()).stream()
.map(bauweltMember -> SteamwarUser.get(bauweltMember.getMemberID()).getUserName())
.toList();
}
};
}
@Register("resetall")
@Register("delete")
public void delete(PlayerChatter sender, @OptionalValue(value = "", onlyUINIG = true) ServerVersion version) {
SWInventory inventory = new SWInventory(sender, 9, new Message("BAU_DELETE_GUI_NAME"));
inventory.addItem(0, new SWItem(new Message("BAU_DELETE_GUI_DELETE"), 10), click -> {
String world = version.getWorldFolder(ServerStarter.WORLDS_BASE_PATH) + (version != ServerVersion.SPIGOT_12 ? sender.user().getId() : sender.user().getUUID().toString());
VelocityCore.schedule(() -> {
Bauserver subserver = Bauserver.get(sender.user().getUUID());
if(subserver != null)
subserver.stop();
SubserverSystem.deleteFolder(VelocityCore.local, world);
sender.system("BAU_DELETE_DELETED");
}).schedule();
inventory.close();
});
inventory.addItem(8, new SWItem(new Message("BAU_DELETE_GUI_CANCEL"), 1), click -> inventory.close());
inventory.open();
}
@Register("test")
@Register("testarena")
public void testarena(PlayerChatter sender, @Mapper("nonHistoricArenaMode") @OptionalValue("") @AllowNull ArenaMode arenaMode, @Mapper("arenaMap") @OptionalValue("") @AllowNull String map) {
FightCommand.createArena(sender, "/bau testarena ", false, arenaMode, map, false, (chatter, mode, m) ->
VelocityCore.schedule(() -> new ServerStarter().test(mode, m, sender.getPlayer()).start()).schedule()
);
}
@Register(value = "lock", description = "BAU_LOCKED_OPTIONS")
public void lock(Chatter sender, BauLockState bauLockState) {
BauLock.setLocked(sender, bauLockState);
}
@Register("unlock")
public void unlock(Chatter sender) {
BauLock.setLocked(sender, BauLockState.OPEN);
}
private static void withMember(Chatter owner, SteamwarUser member, Consumer<BauweltMember> function) {
if (member == null) {
owner.system("UNKNOWN_PLAYER");
return;
}
BauweltMember target = BauweltMember.getBauMember(owner.user().getId(), member.getId());
if (target == null) {
owner.system("BAU_MEMBER_NOMEMBER");
return;
}
function.accept(target);
}
}
@@ -0,0 +1,39 @@
/*
This file is a part of the SteamWar software.
Copyright (C) 2020 SteamWar.de-Serverteam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.velocitycore.commands;
import de.steamwar.sql.SWException;
import de.steamwar.command.SWCommand;
import de.steamwar.messages.Chatter;
public class BugCommand extends SWCommand {
public BugCommand() {
super("bug");
}
@Register
public void bugMessage(Chatter sender, String... message) {
int id = SWException.logGetId(
String.join(" ", message),
sender.withPlayerOrOffline(player -> player.getCurrentServer().map(connection -> connection.getServerInfo().getName()).orElse("offline"), () -> "offline") + " " + sender.user().getUserName() + " " + sender.user().getId()
);
sender.system("BUG_MESSAGE", id);
}
}
@@ -0,0 +1,167 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2024 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.velocitycore.ArenaMode;
import de.steamwar.velocitycore.ServerStarter;
import de.steamwar.velocitycore.ServerVersion;
import de.steamwar.velocitycore.VelocityCore;
import de.steamwar.command.PreviousArguments;
import de.steamwar.command.SWCommand;
import de.steamwar.command.TypeMapper;
import de.steamwar.messages.Chatter;
import de.steamwar.messages.PlayerChatter;
import de.steamwar.sql.UserPerm;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
public class BuilderCloudCommand extends SWCommand {
public BuilderCloudCommand() {
super("buildercloud", UserPerm.BUILD, "builder");
}
@Register(value = "create", description = "BUILDERCLOUD_CREATE_USAGE")
public void create(Chatter sender, @ErrorMessage("BUILDERCLOUD_VERSION") ServerVersion version, @Mapper("map") String map, @OptionalValue("") @Mapper("generator") @AllowNull File generator) {
mapFile(version, map).mkdir();
sender.withPlayer(p -> new ServerStarter().builder(version, map, generator).send(p).start());
}
@Register(description = "BUILDERCLOUD_USAGE")
public void start(PlayerChatter sender, @ErrorMessage("BUILDERCLOUD_VERSION") ServerVersion version, @Mapper("map") String map) {
if(!mapFile(version, map).exists()) {
sender.system("BUILDERCLOUD_UNKNOWN_MAP");
return;
}
new ServerStarter().builder(version, map, null).send(sender.getPlayer()).start();
}
@Register(value = "rename", description = "BUILDERCLOUD_RENAME_USAGE")
public void rename(Chatter sender, @ErrorMessage("BUILDERCLOUD_VERSION") ServerVersion version, @Mapper("map") String oldName, String newName) {
File oldMap = mapFile(version, oldName);
if(!oldMap.exists()) {
sender.system("BUILDERCLOUD_UNKNOWN_MAP");
return;
}
File newMap = mapFile(version, newName);
if(newMap.exists()) {
sender.system("BUILDERCLOUD_EXISTING_MAP");
return;
}
try {
Files.move(oldMap.toPath(), newMap.toPath());
} catch (IOException e) {
throw new SecurityException(e);
}
sender.system("BUILDERCLOUD_RENAMED");
}
@Register(value = "deploy", description = "BUILDERCLOUD_DEPLOY_USAGE")
public void deploy(Chatter sender, @Mapper("nonHistoricArenaMode") ArenaMode arenaMode, @ErrorMessage("BUILDERCLOUD_VERSION") ServerVersion version, @Mapper("map") String map) {
if(!mapFile(version, map).exists()) {
sender.system("BUILDERCLOUD_UNKNOWN_MAP");
return;
}
VelocityCore.schedule(() -> {
VelocityCore.local.execute("/binarys/deployarena.py", arenaMode.getConfig(), Integer.toString(version.getVersionSuffix()), map);
ArenaMode.init();
sender.system("BUILDERCLOUD_DEPLOY_FINISHED");
}).schedule();
}
@Cached(global = true)
@Mapper(value = "map", local = true)
private TypeMapper<String> mapTypeMapper() {
return new TypeMapper<String>() {
@Override
public String map(Chatter sender, PreviousArguments previousArguments, String s) {
return s;
}
@Override
public Collection<String> tabCompletes(Chatter sender, PreviousArguments previousArguments, String s) {
File folder = getWorldFolder(previousArguments, 1);
String[] files;
if(folder == null || (files = folder.list()) == null)
return Collections.emptyList();
return Arrays.stream(files).filter(file -> new File(folder, file).isDirectory()).filter(file -> s.startsWith(".") || !file.startsWith(".")).toList();
}
};
}
@Cached(global = true)
@Mapper(value = "generator", local = true)
private TypeMapper<File> generatorTypeMapper() {
return new TypeMapper<File>() {
@Override
public File map(Chatter sender, PreviousArguments previousArguments, String s) {
if(s.isEmpty())
return null;
File folder = getWorldFolder(previousArguments, 2);
if(folder == null)
throw new SecurityException();
File generator = new File(folder, s + ".dat");
if(!generator.exists() || !generator.isFile())
throw new SecurityException();
return generator;
}
@Override
public Collection<String> tabCompletes(Chatter sender, PreviousArguments previousArguments, String s) {
File folder = getWorldFolder(previousArguments, 2);
String[] files;
if(folder == null || (files = folder.list()) == null)
return Collections.emptyList();
return Arrays.stream(files).filter(file -> new File(folder, file).isFile()).filter(file -> file.endsWith(".dat")).map(file -> file.substring(0, file.length() - 4)).toList();
}
};
}
private File mapFile(ServerVersion version, String map) {
return new File(version.getWorldFolder(ServerStarter.BUILDER_BASE_PATH), map);
}
private File getWorldFolder(PreviousArguments previousArguments, int offset) {
ServerVersion v = ServerVersion.get(previousArguments.userArgs[previousArguments.userArgs.length - offset]);
if(v == null)
return null;
return new File(v.getWorldFolder(ServerStarter.BUILDER_BASE_PATH));
}
}
@@ -0,0 +1,99 @@
/*
This file is a part of the SteamWar software.
Copyright (C) 2020 SteamWar.de-Serverteam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.velocitycore.commands;
import com.velocitypowered.api.proxy.Player;
import de.steamwar.velocitycore.ArenaMode;
import de.steamwar.velocitycore.ServerStarter;
import de.steamwar.persistent.Servertype;
import de.steamwar.persistent.Subserver;
import de.steamwar.command.SWCommand;
import de.steamwar.command.TypeValidator;
import de.steamwar.messages.Chatter;
import de.steamwar.messages.Message;
import de.steamwar.messages.PlayerChatter;
import de.steamwar.sql.IgnoreSystem;
import net.kyori.adventure.text.event.ClickEvent;
import java.util.LinkedList;
import static de.steamwar.persistent.Storage.challenges;
public class ChallengeCommand extends SWCommand {
public ChallengeCommand() {
super("challenge");
}
@Register(description = "CHALLENGE_USAGE")
public void challenge(@Validator("arenaPlayer") PlayerChatter sender, @Validator("target") Player target, @Mapper("nonHistoricArenaMode") @OptionalValue("") @AllowNull ArenaMode arenaMode, @Mapper("arenaMap") @OptionalValue("") @AllowNull String map) {
FightCommand.createArena(sender, "/challenge " + target.getUsername() + " ", false, arenaMode, map, false, (chatter, mode, m) -> {
Player p = sender.getPlayer();
if(challenges.containsKey(target) && challenges.get(target).contains(p)){
challenges.remove(target);
challenges.remove(p);
new ServerStarter().arena(mode, map).blueLeader(sender.getPlayer()).redLeader(target).callback(
arena -> Chatter.broadcast().system("CHALLENGE_BROADCAST", new Message("CHALLENGE_BROADCAST_HOVER"), ClickEvent.runCommand("/arena " + arena.getServer().getName()), mode.getGameName(), p, target)
).start();
}else{
if(!challenges.containsKey(p)){
challenges.put(p, new LinkedList<>());
}
challenges.get(p).add(target);
sender.system("CHALLENGE_CHALLENGED", target, mode.getGameName());
Chatter.of(target).system("CHALLENGE_CHALLENGED_TARGET", p, mode.getGameName(), mode.getMaps().size() != 1 ? new Message("CHALLENGE_CHALLENGED_MAP", m) : "");
Chatter.of(target).system("CHALLENGE_ACCEPT", new Message("CHALLENGE_ACCEPT_HOVER"), ClickEvent.runCommand("/challenge " + p.getUsername() + " " + mode.getChatName() + " " + m));
}
});
}
@Validator(value = "target", local = true)
public TypeValidator<Player> targetValidator() {
return (sender, value, messageSender) -> {
if (value == null) {
messageSender.send("CHALLENGE_OFFLINE");
return false;
}
if (sender == value) {
messageSender.send("CHALLENGE_SELF");
return false;
}
if (IgnoreSystem.isIgnored(value.getUniqueId(), sender.user().getUUID())) {
messageSender.send("CHALLENGE_IGNORED");
return false;
}
Subserver subserver = Subserver.getSubserver(value);
if (subserver != null && subserver.getType() == Servertype.ARENA) {
messageSender.send("CHALLENGE_INARENA");
return false;
}
return true;
};
}
public static void remove(Player player){
challenges.remove(player);
}
}
@@ -0,0 +1,296 @@
/*
This file is a part of the SteamWar software.
Copyright (C) 2020 SteamWar.de-Serverteam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.velocitycore.commands;
import com.velocitypowered.api.proxy.Player;
import de.steamwar.command.SWCommand;
import de.steamwar.messages.Chatter;
import de.steamwar.messages.Message;
import de.steamwar.messages.PlayerChatter;
import de.steamwar.persistent.Bauserver;
import de.steamwar.sql.*;
import de.steamwar.velocitycore.ArenaMode;
import de.steamwar.velocitycore.ServerStarter;
import de.steamwar.velocitycore.VelocityCore;
import de.steamwar.velocitycore.discord.DiscordBot;
import de.steamwar.velocitycore.discord.util.DiscordAlert;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.event.ClickEvent;
import net.kyori.adventure.text.format.NamedTextColor;
import java.awt.*;
import java.sql.Timestamp;
import java.time.Instant;
import java.util.List;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
public class CheckCommand extends SWCommand {
private static final Map<SchematicType, SchematicType> fightTypes = new HashMap<>();
private static final Map<SchematicType, List<String>> checkQuestions = new HashMap<>();
private static final Map<UUID, CheckSession> currentCheckers = new HashMap<>();
private static final Map<Integer, CheckSession> currentSchems = new HashMap<>();
public static void setCheckQuestions(SchematicType checkType, List<String> checkQuestions) {
CheckCommand.checkQuestions.put(checkType, checkQuestions);
}
public static void addFightType(SchematicType checkType, SchematicType fightType) {
fightTypes.put(checkType, fightType);
}
public static boolean isChecking(Player player){
return currentCheckers.containsKey(player.getUniqueId());
}
public static SchematicNode getCheckingSchem(Player player) {
return currentCheckers.get(player.getUniqueId()).schematic;
}
public static Message getWaitTime(SchematicNode schematic) {
long waitedMillis = Timestamp.from(Instant.now()).getTime() - schematic.getLastUpdate().getTime();
String ce = waitedMillis > 86400000 ? "c" : "e";
String color = waitedMillis > 14400000 ? ce : "a";
long hours = waitedMillis / 3600000;
long minutes = (waitedMillis - hours * 3600000) / 60000;
return new Message("CHECK_LIST_WAIT", color, hours, (minutes < 10) ? "0" + minutes : minutes);
}
public CheckCommand() {
super("check", UserPerm.CHECK);
VelocityCore.schedule(() -> sendReminder(Chatter.serverteam())).repeat(10, TimeUnit.MINUTES).schedule();
}
public static void sendReminder(Chatter chatter) {
List<SchematicNode> schematics = getSchemsToCheck();
if(schematics.size() == currentCheckers.size())
return;
chatter.system("CHECK_REMINDER", new Message("CHECK_REMINDER_HOVER"), ClickEvent.runCommand("/check list"), schematics.size() - currentCheckers.size());
}
@Register(value = "list", description = "CHECK_HELP_LIST")
public void list(Chatter sender) {
List<SchematicNode> schematicList = getSchemsToCheck();
sender.system("CHECK_LIST_HEADER", schematicList.size());
for (SchematicNode schematic : schematicList) {
CheckSession current = currentSchems.get(schematic.getId());
if (current == null) {
sender.prefixless("CHECK_LIST_TO_CHECK",
new Message("CHECK_LIST_TO_CHECK_HOVER"),
ClickEvent.runCommand("/check schematic " + schematic.getId()),
getWaitTime(schematic),
schematic.getSchemtype().getKuerzel(), SteamwarUser.get(schematic.getOwner()).getUserName(), schematic.getName());
} else {
sender.prefixless("CHECK_LIST_CHECKING",
new Message("CHECK_LIST_CHECKING_HOVER"),
ClickEvent.runCommand("/join " + current.checker.user().getUserName()),
getWaitTime(schematic),
schematic.getSchemtype().getKuerzel(), SteamwarUser.get(schematic.getOwner()).getUserName(), schematic.getName(), current.checker.user().getUserName());
}
}
}
@Register(value = "schematic", noTabComplete = true)
public void schematic(PlayerChatter sender, String schemID) {
if(isChecking(sender.getPlayer())){
sender.system("CHECK_SCHEMATIC_ALREADY_CHECKING");
return;
}
SchematicNode schem = SchematicNode.getSchematicNode(Integer.parseInt(schemID));
if(!schem.getSchemtype().check()){
VelocityCore.getLogger().log(Level.SEVERE, () -> sender.user().getUserName() + " tried to check an uncheckable schematic!");
return;
}else if(schem.getOwner() == sender.user().getId()) {
sender.system("CHECK_SCHEMATIC_OWN");
return;
}
int playerTeam = sender.user().hasPerm(UserPerm.MODERATION) ? 0 : sender.user().getTeam();
if (playerTeam != 0 && SteamwarUser.get(schem.getOwner()).getTeam() == playerTeam) {
sender.system("CHECK_SCHEMATIC_OWN_TEAM");
return;
}
new CheckSession(sender, schem);
}
@Register(value = "cancel", description = "CHECK_HELP_CANCEL")
@Register("abort")
public void abortCommand(PlayerChatter sender) {
abort(sender.getPlayer());
}
public static void abort(Player player) {
if(notChecking(player))
return;
Chatter.of(player).system("CHECK_ABORT");
currentCheckers.get(player.getUniqueId()).abort();
}
@Register(value = "next", description = "CHECK_HELP_NEXT")
public void next(PlayerChatter sender) {
if(notChecking(sender.getPlayer()))
return;
currentCheckers.get(sender.getPlayer().getUniqueId()).next();
}
@Register(value = "accept")
public void accept(PlayerChatter sender) {
next(sender);
}
@Register(value = "decline", description = "CHECK_HELP_DECLINE")
public void decline(PlayerChatter sender, String... message) {
if(notChecking(sender.getPlayer()))
return;
currentCheckers.get(sender.getPlayer().getUniqueId()).decline(String.join(" ", message));
}
public static List<SchematicNode> getSchemsToCheck(){
List<SchematicNode> schematicList = new ArrayList<>();
for (SchematicType type : SchematicType.values()) {
if (type.check())
schematicList.addAll(SchematicNode.getAllSchematicsOfType(type));
}
return schematicList;
}
public static String getChecker(SchematicNode schematic) {
if (currentSchems.get(schematic.getId()) == null) return null;
return currentSchems.get(schematic.getId()).checker.user().getUserName();
}
private static boolean notChecking(Player player){
if(!isChecking(player)){
Chatter.of(player).system("CHECK_NOT_CHECKING");
return true;
}
return false;
}
private static class CheckSession{
private final PlayerChatter checker;
private final SchematicNode schematic;
private final Timestamp startTime;
private final ListIterator<String> checkList;
private CheckSession(PlayerChatter checker, SchematicNode schematic){
this.checker = checker;
this.schematic = schematic;
this.startTime = Timestamp.from(Instant.now());
this.checkList = checkQuestions.get(schematic.getSchemtype()).listIterator();
VelocityCore.schedule(() -> {
ArenaMode mode = ArenaMode.getBySchemType(fightTypes.get(schematic.getSchemtype()));
if(!new ServerStarter().test(mode, mode.getRandomMap(), checker.getPlayer()).check(schematic.getId()).start()) {
remove();
return;
}
currentCheckers.put(checker.user().getUUID(), this);
currentSchems.put(schematic.getId(), this);
for(CheckedSchematic previous : CheckedSchematic.previousChecks(schematic))
checker.prefixless("CHECK_SCHEMATIC_PREVIOUS", previous.getEndTime(), SteamwarUser.get(previous.getValidator()).getUserName(), previous.getDeclineReason());
next();
}).schedule();
}
private void next() {
if(!checkList.hasNext()){
accept();
return;
}
checker.prefixless("PLAIN_STRING", checkList.next());
checker.sendMessage(Component
.text(checker.parseToPlain(checkList.hasNext() ? "CHECK_NEXT" : "CHECK_ACCEPT"))
.color(NamedTextColor.GREEN)
.clickEvent(ClickEvent.runCommand("/check next"))
.append(Component
.text(" " + checker.parseToPlain("CHECK_DECLINE"))
.color(NamedTextColor.RED)
.clickEvent(ClickEvent.suggestCommand("/check decline "))));
}
private void accept(){
if(concludeCheckSession("freigegeben", fightTypes.get(schematic.getSchemtype()))) {
Chatter owner = Chatter.of(SteamwarUser.get(schematic.getOwner()).getUUID());
owner.withPlayerOrOffline(
player -> owner.system("CHECK_ACCEPTED", schematic.getSchemtype().name(), schematic.getName()),
() -> DiscordAlert.send(owner, Color.GREEN, new Message("DC_TITLE_SCHEMINFO"), new Message("DC_SCHEM_ACCEPT", schematic.getName()), true)
);
notifyTeam(new Message("CHECK_ACCEPTED_TEAM", schematic.getName(), owner.user().getUserName()));
}
}
private void decline(String reason){
if(concludeCheckSession(reason, SchematicType.Normal)) {
Chatter owner = Chatter.of(SteamwarUser.get(schematic.getOwner()).getUUID());
owner.withPlayerOrOffline(
player -> owner.system("CHECK_DECLINED", schematic.getSchemtype().name(), schematic.getName(), reason),
() -> DiscordAlert.send(owner, Color.RED, new Message("DC_TITLE_SCHEMINFO"), new Message("DC_SCHEM_DECLINE", schematic.getName(), reason), false)
);
notifyTeam(new Message("CHECK_DECLINED_TEAM", schematic.getName(), owner.user().getUserName(), reason));
}
}
private void notifyTeam(Message message) {
Chatter.serverteam().system(message);
DiscordBot.withBot(bot -> bot.getChecklistChannel().system(message));
}
private void abort(){
concludeCheckSession("Prüfvorgang abgebrochen", null);
}
private boolean concludeCheckSession(String reason, SchematicType type) {
if(SchematicNode.getSchematicNode(schematic.getId()) == null) // Schematic was deleted
return false;
CheckedSchematic.create(schematic, checker.user().getId(), startTime, Timestamp.from(Instant.now()), reason);
if(type != null)
schematic.setSchemtype(type);
remove();
VelocityCore.schedule(() -> {
Bauserver subserver = Bauserver.get(checker.user().getUUID());
if(subserver != null)
subserver.stop();
}).schedule();
return true;
}
private void remove() {
currentCheckers.remove(checker.user().getUUID());
currentSchems.remove(schematic.getId());
}
}
}
@@ -0,0 +1,127 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2020 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.velocitycore.commands;
import com.velocitypowered.api.proxy.server.RegisteredServer;
import com.velocitypowered.api.proxy.server.ServerInfo;
import de.steamwar.messages.Message;
import de.steamwar.velocitycore.VelocityCore;
import de.steamwar.command.SWCommand;
import de.steamwar.command.SWCommandUtils;
import de.steamwar.command.TypeMapper;
import de.steamwar.command.TypeValidator;
import de.steamwar.messages.PlayerChatter;
import de.steamwar.sql.Punishment;
import de.steamwar.sql.SteamwarUser;
import java.io.File;
import java.net.InetSocketAddress;
import java.util.HashMap;
import java.util.Map;
public class DevCommand extends SWCommand {
private final File devServerDir = new File("/configs/DevServer");
private final Map<String, RegisteredServer> devServers = new HashMap<>();
public DevCommand() {
super("dev");
}
@Register
public void simpleCommand(@Validator PlayerChatter sender) {
updateDevServers();
if (devServers.isEmpty()) {
sender.system("DEV_NO_SERVER");
return;
} else if (devServers.size() == 1) {
devServers.values().stream().findAny().ifPresent(server -> sender.getPlayer().createConnectionRequest(server).fireAndForget());
return;
}
RegisteredServer server = devServers.get(sender.getPlayer().getUsername().toLowerCase());
if (server == null) {
sender.system("DEV_UNKNOWN_SERVER");
return;
}
sender.getPlayer().createConnectionRequest(server).fireAndForget();
}
@Register
public void selectedCommand(@Validator PlayerChatter sender, @Mapper("dev") String name) {
updateDevServers();
RegisteredServer server = devServers.get(name.toLowerCase());
if (server == null) {
sender.system("DEV_NO_SERVER");
return;
}
sender.getPlayer().createConnectionRequest(server).fireAndForget();
}
@ClassValidator(value = PlayerChatter.class, local = true)
public TypeValidator<PlayerChatter> punishmentGuardChecker() {
return (sender, value, messageSender) -> {
if (sender.user().isPunished(Punishment.PunishmentType.NoDevServer)) {
Message message = PunishmentCommand.punishmentMessage(sender.user(), Punishment.PunishmentType.NoDevServer);
messageSender.send(message.format(), message.params());
return false;
}
return true;
};
}
@Mapper(value = "dev", local = true)
public TypeMapper<String> devServers() {
return SWCommandUtils.createMapper(s -> s, s -> {
updateDevServers();
return devServers.keySet();
});
}
private void updateDevServers() {
String[] serverFiles = devServerDir.list();
Map<String, Integer> devServerFiles = new HashMap<>();
if (serverFiles != null) {
for (String serverFile : serverFiles) {
String[] server = serverFile.split("\\.");
devServerFiles.put(server[0], Integer.parseInt(server[1]));
}
}
devServers.entrySet().removeIf(entry -> {
if (!devServerFiles.containsKey(entry.getKey())) {
VelocityCore.getProxy().unregisterServer(entry.getValue().getServerInfo());
return true;
}
return false;
});
devServerFiles.forEach((key, value) -> {
if (devServers.containsKey(key))
return;
SteamwarUser user = SteamwarUser.get(key);
devServers.put(user.getUserName().toLowerCase(), VelocityCore.getProxy().registerServer(new ServerInfo("Dev " + user.getUserName(), new InetSocketAddress("127.0.0.1", value))));
});
}
}
@@ -0,0 +1,117 @@
/*
This file is a part of the SteamWar software.
Copyright (C) 2020 SteamWar.de-Serverteam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.velocitycore.commands;
import de.steamwar.velocitycore.EventStarter;
import de.steamwar.persistent.Subserver;
import de.steamwar.velocitycore.SubserverSystem;
import de.steamwar.command.SWCommand;
import de.steamwar.command.TypeValidator;
import de.steamwar.messages.Chatter;
import de.steamwar.messages.PlayerChatter;
import de.steamwar.sql.*;
import java.time.Instant;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
public class EventCommand extends SWCommand {
public EventCommand() {
super("event");
}
@Validator("noEvent")
public TypeValidator<Chatter> noEventValidator() {
return (sender, value, messageSender) -> Event.get() == null;
}
@Register
public void noCurrentEvent(@Validator("noEvent") Chatter sender) {
sender.system("EVENT_NO_CURRENT");
List<Event> coming = Event.getComing();
if(coming.isEmpty())
return;
sender.system("EVENT_COMING");
Instant now = Instant.now();
DateTimeFormatter format = DateTimeFormatter.ofPattern(sender.parseToPlain("EVENT_DATE_FORMAT"));
for(Event e : coming){
sender.prefixless("EVENT_COMING_EVENT", e.getStart().toLocalDateTime().format(format), e.getEnd().toLocalDateTime().format(format), e.getEventName());
if(now.isBefore(e.getDeadline().toInstant()))
sender.prefixless("EVENT_COMING_DEADLINE", e.getDeadline());
SchematicType schemType = e.getSchematicType();
if (schemType != null && schemType.getDeadline() != null && now.isBefore(schemType.getDeadline().toInstant()))
sender.prefixless("EVENT_COMING_SCHEM_DEADLINE", schemType.getDeadline());
Set<Team> teams = TeamTeilnahme.getTeams(e.getEventID());
if(!teams.isEmpty())
sender.prefixless("EVENT_COMING_TEAMS", teams.stream().map(team -> sender.parseToLegacy("EVENT_COMING_TEAM", team.getTeamColor(), team.getTeamKuerzel())).collect(Collectors.joining(" ")));
}
}
@Register
public void eventOverview(@Validator(value = "noEvent", invert = true) Chatter sender) {
sender.system("EVENT_USAGE");
Event currentEvent = Event.get();
sender.system("EVENT_CURRENT_EVENT", currentEvent.getEventName());
DateTimeFormatter format = DateTimeFormatter.ofPattern(sender.parseToPlain("EVENT_TIME_FORMAT"));
for(EventFight fight : EventFight.getEvent(currentEvent.getEventID())){
Team blue = Team.get(fight.getTeamBlue());
Team red = Team.get(fight.getTeamRed());
StringBuilder fline = new StringBuilder(sender.parseToLegacy("EVENT_CURRENT_FIGHT", fight.getStartTime().toLocalDateTime().format(format), blue.getTeamColor(), blue.getTeamKuerzel(), red.getTeamColor(), red.getTeamKuerzel()));
if(fight.hasFinished()){
switch(fight.getErgebnis()){
case 1:
fline.append(sender.parseToLegacy("EVENT_CURRENT_FIGHT_WIN", blue.getTeamColor(), blue.getTeamKuerzel()));
break;
case 2:
fline.append(sender.parseToLegacy("EVENT_CURRENT_FIGHT_WIN", red.getTeamColor(), red.getTeamKuerzel()));
break;
default:
fline.append(sender.parseToLegacy("EVENT_CURRENT_FIGHT_DRAW"));
}
}
sender.prefixless("PLAIN_STRING", fline.toString());
}
}
@Register
public void eventWithTeam(@Validator(value = "noEvent", invert = true) PlayerChatter sender, @ErrorMessage("EVENT_NO_TEAM") Team team) {
Subserver eventArena = EventStarter.getEventServer().get(team.getTeamId());
if(eventArena == null || !Subserver.getServerList().contains(eventArena)){
sender.system("EVENT_NO_FIGHT_TEAM");
return;
}
if (!PunishmentCommand.isPunishedWithMessage(sender, Punishment.PunishmentType.NoFightServer)) {
SubserverSystem.sendPlayer(eventArena, sender.getPlayer());
}
}
}
@@ -0,0 +1,66 @@
/*
This file is a part of the SteamWar software.
Copyright (C) 2020 SteamWar.de-Serverteam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.velocitycore.commands;
import de.steamwar.command.SWCommand;
import de.steamwar.messages.Chatter;
import de.steamwar.sql.Event;
import de.steamwar.sql.EventFight;
import de.steamwar.sql.Team;
import de.steamwar.sql.UserPerm;
import java.sql.Timestamp;
import java.util.Date;
import java.util.List;
import java.util.ListIterator;
public class EventRescheduleCommand extends SWCommand {
public EventRescheduleCommand() {
super("eventreschedule", UserPerm.MODERATION);
}
@Register
public void reschedule(Chatter sender, Team teamBlue, Team teamRed) {
Event event = Event.get();
if(event == null){
sender.system("EVENTRESCHEDULE_UNKNOWN_TEAM");
return;
}
List<EventFight> fights = EventFight.getEvent(event.getEventID());
ListIterator<EventFight> it = fights.listIterator(fights.size());
Timestamp now = Timestamp.from(new Date().toInstant());
while(it.hasPrevious()){
EventFight fight = it.previous();
if(fight.getStartTime().after(now))
continue;
if(fight.getTeamBlue() == teamBlue.getTeamId() && fight.getTeamRed() == teamRed.getTeamId()){
sender.system("EVENTRESCHEDULE_STARTING");
fight.reschedule();
EventFight.loadAllComingFights();
return;
}
}
sender.system("EVENTRESCHEDULE_NO_FIGHT");
}
}
@@ -0,0 +1,36 @@
/*
This file is a part of the SteamWar software.
Copyright (C) 2020 SteamWar.de-Serverteam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.velocitycore.commands;
import de.steamwar.command.SWCommand;
import de.steamwar.messages.Chatter;
import de.steamwar.sql.EventFight;
import de.steamwar.sql.UserPerm;
public class EventreloadCommand extends SWCommand {
public EventreloadCommand() {
super("eventreload", UserPerm.MODERATION);
}
@Register
public void execute(Chatter sender) {
EventFight.loadAllComingFights();
}
}
@@ -0,0 +1,141 @@
/*
This file is a part of the SteamWar software.
Copyright (C) 2020 SteamWar.de-Serverteam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.velocitycore.commands;
import com.velocitypowered.api.proxy.Player;
import de.steamwar.velocitycore.*;
import de.steamwar.velocitycore.inventory.SWInventory;
import de.steamwar.velocitycore.inventory.SWItem;
import de.steamwar.command.SWCommand;
import de.steamwar.messages.Chatter;
import de.steamwar.messages.Message;
import de.steamwar.messages.PlayerChatter;
import de.steamwar.persistent.Arenaserver;
import de.steamwar.persistent.Servertype;
import de.steamwar.persistent.Subserver;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.event.ClickEvent;
import net.kyori.adventure.text.event.HoverEvent;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.TextDecoration;
public class FightCommand extends SWCommand {
public FightCommand() {
super("fight", "f");
}
private static void getModes(Chatter sender, String precommand, boolean historic){
Component start = Component.empty();
for(ArenaMode mode : ArenaMode.getAllModes()){
if(mode.withoutChatName() || mode.isHistoric() != historic)
continue;
String command = precommand + mode.getChatName();
start = start.append(Component
.text(mode.getChatName() + " ")
.color(NamedTextColor.GRAY)
.decorate(TextDecoration.BOLD)
.hoverEvent(HoverEvent.showText(Component.text(("§e" + command))))
.clickEvent(ClickEvent.runCommand(command)));
}
sender.sendMessage(start);
}
private static boolean alreadyInArena(PlayerChatter sender){
Subserver subserver = Subserver.getSubserver(sender.getPlayer());
if(subserver != null && subserver.getType() == Servertype.ARENA){
sender.system("FIGHT_IN_ARENA");
return true;
}
return false;
}
static void createArena(PlayerChatter sender, String precommand, boolean allowMerging, ArenaMode arenaMode, String map, boolean historic, FightCallback callback) {
if(alreadyInArena(sender))
return;
if (arenaMode == null) {
getModes(sender, precommand, historic);
return;
}
if (map == null)
map = arenaMode.getRandomMap();
if (!allowMerging) {
callback.run(sender, arenaMode, map);
} else {
suggestMerging(sender, arenaMode, map, callback);
}
}
private static void suggestMerging(PlayerChatter sender, ArenaMode mode, String map, FightCallback declineMerge) {
Arenaserver mergable = null;
synchronized (Subserver.getServerList()) {
for (Subserver subserver : Subserver.getServerList()) {
if(subserver instanceof Arenaserver arenaserver &&
(mode.getInternalName().equals(arenaserver.getMode()) && arenaserver.isAllowMerge() && arenaserver.getRegisteredServer().getPlayersConnected().size() == 1)) {
mergable = arenaserver;
break;
}
}
}
if(mergable == null) {
declineMerge.run(sender, mode, map);
return;
}
SWInventory inventory = new SWInventory(sender, 9, new Message("FIGHT_MERGE_TITLE"));
inventory.addItem(0, new SWItem(new Message("FIGHT_MERGE_DECLINE"), 1), click -> {
inventory.close();
declineMerge.run(sender, mode, map);
});
Arenaserver finalMergable = mergable;
SWItem item = new SWItem(new Message("FIGHT_MERGE_INFO", mode.getGameName(), finalMergable.getMap()), 11);
item.addLore(new Message("FIGHT_MERGE_INFO_LORE_1", finalMergable.getRegisteredServer().getPlayersConnected().toArray(new Player[1])[0].getUsername()));
inventory.addItem(4, item, click -> {});
inventory.addItem(8, new SWItem(new Message("FIGHT_MERGE_ACCEPT"), 10), click -> {
if(Subserver.getServerList().contains(finalMergable)) {
finalMergable.sendPlayer(sender.getPlayer());
} else {
sender.system("FIGHT_MERGE_OFFLINE");
declineMerge.run(sender, mode, map);
}
});
inventory.open();
}
@Register
public void fight(@Validator("arenaPlayer") PlayerChatter sender, @Mapper("nonHistoricArenaMode") @OptionalValue("") @AllowNull ArenaMode arenaMode, @Mapper("arenaMap") @OptionalValue("") @AllowNull String map) {
createArena(sender, "/fight ", true, arenaMode, map, false,
(p, mode, m) -> new ServerStarter().arena(mode, m).blueLeader(p.getPlayer()).callback(
arena -> Chatter.broadcast().system("FIGHT_BROADCAST", new Message("FIGHT_BROADCAST_HOVER"), ClickEvent.runCommand("/arena " + arena.getServer().getName()), mode.getGameName(), p.getPlayer().getUsername())
).start()
);
}
public interface FightCallback {
void run(PlayerChatter player, ArenaMode mode, String map);
}
}
@@ -0,0 +1,284 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2024 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.velocitycore.VelocityCore;
import de.steamwar.command.SWCommand;
import de.steamwar.messages.Chatter;
import de.steamwar.messages.PlayerChatter;
import de.steamwar.sql.SteamwarUser;
import de.steamwar.sql.UserPerm;
import de.steamwar.sql.internal.Statement;
import java.io.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
public class GDPRQuery extends SWCommand {
public GDPRQuery() {
super("gdprquery", UserPerm.ADMINISTRATION);
}
@Register
public void generate(PlayerChatter sender) {
generate(sender, sender.user());
}
@Register
public void generate(Chatter sender, SteamwarUser user) {
VelocityCore.schedule(() -> {
try {
createZip(sender, user);
} catch (IOException e) {
throw new SecurityException("Could not create zip", e);
}
}).schedule();
}
private void createZip(Chatter sender, SteamwarUser user) throws IOException {
sender.system("GDPR_STATUS_WEBSITE");
ZipOutputStream out = new ZipOutputStream(new FileOutputStream(user.getUserName() + ".zip"));
copy(getClass().getClassLoader().getResourceAsStream("GDPRQueryREADME.md"), out, "README.md");
copy(getClass().getClassLoader().getResourceAsStream("GDPRQueryREADME.md"), out, "README.txt");
sender.system("GDPR_STATUS_WORLD");
copyBauwelt(user, out, "/home/minecraft/userworlds/" + user.getUUID().toString(), "BuildWorld12");
copyBauwelt(user, out, "/home/minecraft/userworlds15/" + user.getId(), "BuildWorld15");
sender.system("GDPR_STATUS_INVENTORIES");
copyPlayerdata(user, out, "/home/minecraft/userworlds", "BuildInventories12");
copyPlayerdata(user, out, "/home/minecraft/userworlds15", "BuildInventories15");
sender.system("GDPR_STATUS_DATABASE");
sqlCSV(user, out, bannedIPs, "BannedIPs.csv");
sqlCSV(user, out, bauweltMember, "BuildMember.csv");
sqlCSV(user, out, bauweltMembers, "BuildMembers.csv");
sqlCSV(user, out, checkedSchems, "SchematicChecksessions.csv");
sqlCSV(user, out, userElo, "UserElo.csv");
sqlCSV(user, out, fights, "Fights.csv");
sqlCSV(user, out, ignoredPlayers, "IgnoredPlayers.csv");
sqlCSV(user, out, ignoringPlayers, "IgnoringPlayers.csv");
sqlCSV(user, out, schematicMember, "SchematicMember.csv");
sqlCSV(user, out, schematicMembers, "SchematicMembers.csv");
sqlCSV(user, out, pollAnswers, "PollAnswers.csv");
sqlCSV(user, out, punishments, "Punishments.csv");
sqlCSV(user, out, sessions, "Sessions.csv");
sqlCSV(user, out, userData, "UserData.csv");
sqlCSV(user, out, personalKits, "PersonalKits.csv");
sqlCSV(user, out, schematics, "Schematics.csv");
personalKits(user, out);
schematics(user, out);
userConfig(user, out);
sender.system("GDPR_STATUS_LOGS");
copyLogs(user, out, new File("/logs"), "logs");
out.close();
sender.system("GDPR_STATUS_FINISHED");
}
private static final Statement bannedIPs = new Statement("SELECT Timestamp, IP FROM BannedUserIPs WHERE UserID = ?");
private static final Statement bauweltMember = new Statement("SELECT BauweltID AS Bauwelt, WorldEdit, World FROM BauweltMember WHERE MemberID = ?");
private static final Statement bauweltMembers = new Statement("SELECT u.UserName AS 'User', m.WorldEdit AS WorldEdit, m.World AS World FROM BauweltMember m INNER JOIN UserData u ON m.MemberID = u.id WHERE m.BauweltID = ?");
private static final Statement checkedSchems = new Statement("SELECT NodeName AS Schematic, StartTime, EndTime, DeclineReason AS Result FROM CheckedSchematic WHERE NodeOwner = ? ORDER BY StartTime ASC");
private static final Statement userElo = new Statement("SELECT GameMode, Elo, Season FROM Elo WHERE UserID = ?");
private static final Statement fights = new Statement("SELECT p.Team AS Team, p.Kit AS Kit, p.Kills AS Kills, p.IsOut AS Died, f.GameMode AS GameMode, f.Server AS Server, f.StartTime AS StartTime, f.Duration AS Duration, (f.BlueLeader = p.UserID) AS IsBlueLeader, (f.RedLeader = p.UserID) AS IsRedLeader, f.Win AS Winner, f.WinCondition AS WinCondition FROM Fight f INNER JOIN FightPlayer p ON f.FightID = p.FightID WHERE p.UserID = ? ORDER BY StartTime ASC");
private static final Statement ignoredPlayers = new Statement("SELECT u.UserName AS IgnoredPlayer FROM IgnoredPlayers i INNER JOIN UserData u ON i.Ignored = u.id WHERE Ignorer = ?");
private static final Statement ignoringPlayers = new Statement("SELECT Ignorer AS IgnoringPlayers FROM IgnoredPlayers WHERE Ignored = ?");
private static final Statement schematicMember = new Statement("SELECT s.NodeName AS SchematicName, u.UserName AS SchematicOwner FROM NodeMember m INNER JOIN SchematicNode s ON m.NodeId = s.NodeId INNER JOIN UserData u ON s.NodeOwner = u.id WHERE m.UserId = ?");
private static final Statement schematicMembers = new Statement("SELECT s.NodeName AS SchematicName, u.UserName AS Member FROM NodeMember m INNER JOIN SchematicNode s ON m.NodeId = s.NodeId INNER JOIN UserData u ON m.UserId = u.id WHERE s.NodeOwner = ?");
private static final Statement pollAnswers = new Statement("SELECT Question, Answer FROM PollAnswer WHERE UserID = ?");
private static final Statement punishments = new Statement("SELECT Type, StartTime, EndTime, Perma, Reason FROM Punishments WHERE UserId = ?");
private static final Statement sessions = new Statement("SELECT StartTime, EndTime FROM Session WHERE UserID = ?");
private static final Statement userData = new Statement("SELECT * FROM UserData WHERE id = ?");
private static final Statement personalKits = new Statement("SELECT GameMode, Name, InUse FROM PersonalKit WHERE UserID = ?");
private static final Statement personalKitData = new Statement("SELECT GameMode, Name, Inventory, Armor FROM PersonalKit WHERE UserID = ?");
private static final Statement schematics = new Statement("SELECT NodeName AS SchematicName, ParentNode, LastUpdate, NodeItem, NodeType, NodeRank FROM SchematicNode WHERE NodeOwner = ?");
private static final Statement schematicData = new Statement("SELECT NodeName, ParentNode, NodeFormat, NodeData FROM SchematicNode WHERE NodeOwner = ?");
private static final Statement userConfig = new Statement("SELECT * FROM UserConfig WHERE User = ?");
private void sqlCSV(SteamwarUser user, ZipOutputStream out, Statement statement, String path) throws IOException {
write(stream -> statement.select(rs -> {
try {
OutputStreamWriter writer = new OutputStreamWriter(stream);
int columns = rs.getMetaData().getColumnCount();
for(int i = 1; i <= columns; i++) {
writer.write(rs.getMetaData().getColumnLabel(i));
writer.write(";");
}
writer.write("\n");
while(rs.next()) {
for(int i = 1; i <= columns; i++) {
try {
writer.write(rs.getString(i));
} catch (NullPointerException e) {
// ignored
}
writer.write(";");
}
writer.write("\n");
}
writer.flush();
} catch (IOException e) {
throw new SecurityException("Could not write file", e);
}
return null;
}, user.getId()), out, path);
}
private void personalKits(SteamwarUser user, ZipOutputStream out) {
personalKitData.select(rs -> {
while(rs.next()) {
try {
String path = "PersonalKit/" + rs.getString("GameMode") + "/" + rs.getString("Name");
try(InputStream data = rs.getBinaryStream("Inventory")) {
copy(data, out, path + ".Inventory.yml");
}
try(InputStream data = rs.getBinaryStream("Armor")) {
copy(data, out, path + ".Armor.yml");
}
} catch (IOException e) {
throw new SecurityException("Could not export PersonalKits", e);
}
}
return null;
}, user.getId());
}
private void schematics(SteamwarUser user, ZipOutputStream out) {
schematicData.select(rs -> {
while(rs.next()) {
String name = (rs.getString("ParentNode") != null ? rs.getString("ParentNode") : "") + ":" + rs.getString("NodeName");
boolean format = rs.getBoolean("NodeFormat");
try(InputStream data = rs.getBinaryStream("NodeData")) {
copy(data, out, "Schematics/" + name + (format ? ".schem" : ".schematic"));
} catch (IOException e) {
throw new SecurityException("Could not export Schematic", e);
}
}
return null;
}, user.getId());
}
private void userConfig(SteamwarUser user, ZipOutputStream out) {
userConfig.select(rs -> {
while(rs.next()) {
String name = rs.getString("Config");
try(InputStream data = rs.getBinaryStream("Value")) {
copy(data, out, name + ".yapion");
} catch (IOException e) {
throw new SecurityException("Could not export UserConfig", e);
}
}
return null;
}, user.getId());
}
private void copyLogs(SteamwarUser user, ZipOutputStream out, File log, String outFile) throws IOException {
if (log.isDirectory()) {
for(File logfile : log.listFiles()) {
copyLogs(user, out, logfile, outFile + "/" + logfile.getName().replace(".gz", ""));
}
} else {
Process reader = new ProcessBuilder("zgrep", "^.*" + user.getUserName() + "\\( issued server command:\\| moved too quickly!\\| executed command:\\| lost connection:\\||\\\\|\\[\\|\\]\\).*$", log.getPath()).start();
copy(reader.getInputStream(), out, outFile);
}
}
private void copyBauwelt(SteamwarUser user, ZipOutputStream out, String inDir, String outDir) throws IOException {
File world = new File(inDir);
if(!world.exists())
return;
copy(new File(world, "level.dat"), out, outDir + "/level.dat");
File region = new File(world, "region");
for(File regionfile : region.listFiles()) {
copy(regionfile, out, outDir + "/region/" + regionfile.getName());
}
File poi = new File(world, "poi");
if(poi.exists()) {
for(File regionfile : poi.listFiles()) {
copy(regionfile, out, outDir + "/poi/" + regionfile.getName());
}
}
File playerdata = new File(world, "playerdata/" + user.getUUID().toString() + ".dat");
if(playerdata.exists())
copy(playerdata, out, outDir + "/playerdata/" + user.getUUID().toString() + ".dat");
}
private void copyPlayerdata(SteamwarUser user, ZipOutputStream out, String inDir, String outDir) throws IOException {
File worlds = new File(inDir);
String path = "playerdata/" + user.getUUID().toString() + ".dat";
int i = 0;
for(File world : worlds.listFiles()) {
File playerdata = new File(world, path);
if(!playerdata.exists())
continue;
copy(playerdata, out, outDir + "/" + (i++) + "/" + user.getUUID().toString() + ".dat");
}
}
private void copy(File file, ZipOutputStream out, String path) throws IOException {
try(FileInputStream in = new FileInputStream(file)) {
copy(in, out, path);
}
}
private void copy(InputStream in, ZipOutputStream out, String path) throws IOException {
boolean initialized = false;
int bytes;
for(byte[] buf = new byte[8192]; (bytes = in.read(buf)) > 0; ) {
if(!initialized) {
ZipEntry entry = new ZipEntry(path);
out.putNextEntry(entry);
initialized = true;
}
out.write(buf, 0, bytes);
}
if(initialized) {
out.closeEntry();
}
}
private void write(Writer writer, ZipOutputStream out, String path) throws IOException {
ZipEntry entry = new ZipEntry(path);
out.putNextEntry(entry);
writer.accept(out);
out.closeEntry();
}
private interface Writer {
void accept(OutputStream stream) throws IOException;
}
}
@@ -0,0 +1,117 @@
/*
This file is a part of the SteamWar software.
Copyright (C) 2020 SteamWar.de-Serverteam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.velocitycore.commands;
import de.steamwar.command.SWCommand;
import de.steamwar.messages.Chatter;
import de.steamwar.messages.Message;
import net.kyori.adventure.text.event.ClickEvent;
import java.util.function.Function;
public class HelpCommand extends SWCommand {
public HelpCommand() {
super("help", "?");
}
@Register
public void genericCommand(Chatter sender) {
printPage(sender, ClickEvent::runCommand,
"HELP_LOBBY", "/l",
"HELP_BAU", "/build",
"HELP_BAUSERVER", "/help build",
"HELP_FIGHT", "/fight",
"HELP_CHALLENGE", "/challenge",
"HELP_HISTORIC", "/historic",
"HELP_TEAM", "/team",
"HELP_JOIN", "/join",
"HELP_LOCAL", "/local");
}
@Register({"build","world"})
public void buildWorld(Chatter sender) {
printPage(sender, "HELP_BAU_GROUP_WORLD_TITLE", "HELP_TNT", "HELP_FIRE", "HELP_FREEZE", "HELP_TPSLIMIT", "HELP_PROTECT", "HELP_RESET");
}
@Register({"build","player"})
public void buildPlayer(Chatter sender) {
printPage(sender, "HELP_BAU_GROUP_PLAYER_TITLE", "HELP_SPEED", "HELP_NV", "HELP_DEBUGSTICK", "HELP_TRACE", "HELP_LOADER");
}
@Register({"build","worldedit"})
@Register({"build","we"})
@Register({"build","world-edit"})
@Register({"build","edit"})
public void buildWorldedit(Chatter sender) {
printPage(sender, "HELP_BAU_GROUP_WE_TITLE", "HELP_WE_POS1", "HELP_WE_POS2", "HELP_WE_COPY", "HELP_WE_PASTE", "HELP_WE_FLOPY", "HELP_WE_FLOPYP", "HELP_WE_ROTATE_90", "HELP_WE_ROTATE_180", "HELP_WE_ROTATE_N90");
}
@Register({"build","other"})
public void buildOther(Chatter sender) {
printPage(sender, "HELP_BAU_GROUP_OTHER_TITLE", "HELP_TESTBLOCK", "HELP_SKULL", "HELP_BAUINFO");
sender.prefixless("HELP_SCHEMSUBMIT", new Message("HELP_SCHEMSUBMIT_HOVER"), ClickEvent.openUrl("https://www.youtube.com/watch?v=9QrQ3UBWveE"));
}
@Register("build")
public void sendBauHelpGroup(Chatter sender) {
printPage(sender, ClickEvent::runCommand,
"HELP_BAU_GROUP_ADMIN", "/help build admin",
"HELP_BAU_GROUP_WORLD", "/help build world",
"HELP_BAU_GROUP_PLAYER", "/help build player",
"HELP_BAU_GROUP_WE", "/help build we",
"HELP_BAU_GROUP_OTHER", "/help build other");
}
@Register("buildserver")
@Register({"build","admin"})
@Register({"build","owner"})
@Register({"build","bauwelt"})
public void sendBauHelp(Chatter sender) {
printPage(sender, ClickEvent::suggestCommand,
"HELP_BAU_TP", "/build tp ",
"HELP_BAU_ADDMEMBER", "/build addmember ",
"HELP_BAU_DELMEMBER", "/build delmember ",
"HELP_BAU_SET_SPECTATOR", "/build setSpectator ",
"HELP_BAU_SET_BUILDER", "/build setBuilder ",
"HELP_BAU_SET_SUPERVISOR", "/build setSupervisor ",
"HELP_BAU_DELETE", "/build delete ",
"HELP_BAU_TESTARENA", "/build testarena ",
"HELP_BAU_LOCK", "/build lock ",
"HELP_BAU_UNLOCK", "/build unlock");
}
private static void printPage(Chatter sender, Function<String, ClickEvent> action, String... args) {
for(int i = 0; i < args.length; i += 2) {
String message = args[i];
String hoverMessage = message + "_HOVER";
String command = args[i+1];
sender.system(message, new Message(hoverMessage), action.apply(command));
}
}
private static void printPage(Chatter sender, String title, String... messages) {
sender.system(title);
for (String message : messages) {
sender.prefixless(message);
}
}
}
@@ -0,0 +1,41 @@
/*
This file is a part of the SteamWar software.
Copyright (C) 2020 SteamWar.de-Serverteam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.velocitycore.commands;
import de.steamwar.command.SWCommand;
import de.steamwar.messages.Chatter;
import de.steamwar.messages.Message;
import de.steamwar.messages.PlayerChatter;
import de.steamwar.velocitycore.ArenaMode;
import de.steamwar.velocitycore.ServerStarter;
import net.kyori.adventure.text.event.ClickEvent;
public class HistoricCommand extends SWCommand {
public HistoricCommand() {
super("historic");
}
@Register
public void historic(@Validator("arenaPlayer") PlayerChatter player, @Mapper("historicArenaMode") @OptionalValue("") @AllowNull ArenaMode arenaMode, @Mapper("arenaMap") @OptionalValue("") @AllowNull String map) {
FightCommand.createArena(player, "/historic ", true, arenaMode, map, true, (p, mode, m) -> new ServerStarter().arena(mode, m).blueLeader(p.getPlayer()).callback(
arena -> Chatter.broadcast().system("HISTORIC_BROADCAST", new Message("HISTORIC_BROADCAST_HOVER"), ClickEvent.runCommand("/arena " + arena.getServer().getName()), mode.getGameName(), p.getPlayer())
).start());
}
}
@@ -0,0 +1,49 @@
/*
This file is a part of the SteamWar software.
Copyright (C) 2020 SteamWar.de-Serverteam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.velocitycore.commands;
import de.steamwar.command.SWCommand;
import de.steamwar.messages.Chatter;
import de.steamwar.sql.IgnoreSystem;
import de.steamwar.sql.SteamwarUser;
public class IgnoreCommand extends SWCommand {
public IgnoreCommand() {
super("ignore");
}
@Register(description = "USAGE_IGNORE")
public void genericCommand(Chatter player, @ErrorMessage("UNKNOWN_PLAYER") SteamwarUser target) {
SteamwarUser user = player.user();
if(target.equals(user)){
player.system("IGNORE_YOURSELF");
return;
}
if(IgnoreSystem.isIgnored(user, target)){
player.system("IGNORE_ALREADY");
return;
}
IgnoreSystem.ignore(user, target);
player.system("IGNORE_MESSAGE", target);
}
}
@@ -0,0 +1,38 @@
/*
This file is a part of the SteamWar software.
Copyright (C) 2020 SteamWar.de-Serverteam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.velocitycore.commands;
import de.steamwar.command.SWCommand;
import de.steamwar.messages.Chatter;
import de.steamwar.messages.PlayerChatter;
import de.steamwar.sql.UserPerm;
import net.kyori.adventure.text.event.ClickEvent;
public class JoinmeCommand extends SWCommand {
public JoinmeCommand() {
super("joinme", UserPerm.TEAM);
}
@Register
public void genericCommand(PlayerChatter sender) {
Chatter.broadcast().system("JOINME_BROADCAST", "JOINME_BROADCAST_HOVER", ClickEvent.runCommand("/join " + sender.getPlayer().getUsername()), sender, sender.getPlayer().getCurrentServer().orElseThrow().getServerInfo().getName());
}
}
@@ -0,0 +1,42 @@
/*
This file is a part of the SteamWar software.
Copyright (C) 2020 SteamWar.de-Serverteam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.velocitycore.commands;
import com.velocitypowered.api.proxy.Player;
import de.steamwar.command.SWCommand;
import de.steamwar.messages.Chatter;
import de.steamwar.sql.UserPerm;
public class KickCommand extends SWCommand {
public KickCommand() {
super("kick", UserPerm.MODERATION);
}
@Register(description = "KICK_USAGE")
public void genericCommand(Chatter sender, @ErrorMessage("KICK_OFFLINE") Player target, String... message) {
if (message.length == 0) {
Chatter.disconnect(target).system("KICK_NORMAL");
} else {
Chatter.disconnect(target).system("KICK_CUSTOM", String.join(" ", message));
}
sender.system("KICK_CONFIRM", target);
}
}
@@ -0,0 +1,71 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2020 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.velocitycore.commands;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.ServerConnection;
import de.steamwar.persistent.Servertype;
import de.steamwar.persistent.Subserver;
import de.steamwar.velocitycore.VelocityCore;
import de.steamwar.command.SWCommand;
import de.steamwar.messages.Chatter;
import java.util.ArrayList;
import java.util.List;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.stream.Collectors;
public class ListCommand extends SWCommand {
public ListCommand() {
super("list");
}
public static synchronized SortedMap<String, List<Player>> getCustomTablist(){
SortedMap<String, List<Player>> playerMap = new TreeMap<>();
for (Player player : VelocityCore.getProxy().getAllPlayers()) {
ServerConnection pserver = player.getCurrentServer().orElse(null);
if (pserver == null)
continue;
Subserver subserver = Subserver.getSubserver(pserver.getServerInfo());
if (subserver != null && subserver.getType() == Servertype.BAUSERVER) {
playerMap.computeIfAbsent("Bau", s -> new ArrayList<>()).add(player);
} else {
playerMap.computeIfAbsent(pserver.getServerInfo().getName(), s -> new ArrayList<>()).add(player);
}
}
playerMap.forEach((server, players) -> players.sort((player, t1) -> player.getUsername().compareToIgnoreCase(t1.getUsername())));
return playerMap;
}
@Register
public void list(Chatter sender) {
SortedMap<String, List<Player>> playerMap = getCustomTablist();
for (String server : playerMap.keySet()) {
String serverName = server;
if (server.equals("Bau")) {
serverName = sender.parseToLegacy("TABLIST_BAU");
}
sender.prefixless("LIST_COMMAND", serverName, playerMap.get(server).stream().map(Player::getUsername).collect(Collectors.joining(", ")));
}
}
}
@@ -0,0 +1,36 @@
/*
This file is a part of the SteamWar software.
Copyright (C) 2022 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.velocitycore.listeners.ChatListener;
import de.steamwar.command.SWCommand;
import de.steamwar.messages.PlayerChatter;
public class LocalCommand extends SWCommand {
public LocalCommand() {
super("local", "bc", "bauchat");
}
@Register
public void genericCommand(PlayerChatter player, String... message) {
ChatListener.localChat(player, String.join(" ", message));
}
}
@@ -0,0 +1,120 @@
/*
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.velocitycore.commands;
import de.steamwar.velocitycore.inventory.SWInventory;
import de.steamwar.velocitycore.inventory.SWItem;
import de.steamwar.velocitycore.inventory.SWListInv;
import de.steamwar.velocitycore.inventory.SWStreamInv;
import de.steamwar.command.SWCommand;
import de.steamwar.messages.Chatter;
import de.steamwar.messages.Message;
import de.steamwar.messages.PlayerChatter;
import de.steamwar.sql.Mod;
import de.steamwar.sql.UserPerm;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.function.Consumer;
public class ModCommand extends SWCommand {
public ModCommand() {
super("mod", UserPerm.MODERATION, "mods");
}
public static final Map<UUID, Mod.ModType> playerFilterType = new HashMap<>();
@Register
public void mods(PlayerChatter sender) {
playerFilterType.putIfAbsent(sender.getPlayer().getUniqueId(), Mod.ModType.UNKLASSIFIED);
openGui(sender);
}
private void openGui(PlayerChatter player) {
SWStreamInv<Mod> swStreamInv = new SWStreamInv<>(player, new Message("MOD_COMMAND_GUI_TITLE"), (click, mod) -> openTypeGUI(player, "MOD_COMMAND_CLASSICIATION_GUI", type -> {
mod.setModType(type);
SWInventory.close(player);
openGui(player);
}), page -> {
Mod.ModType filtertype = playerFilterType.get(player.user().getUUID());
return Mod.getAllModsFiltered(page,45, filtertype).stream().map(mod -> new SWListInv.SWListEntry<>(new SWItem("NAME_TAG", new Message("PLAIN_STRING", mod.getModName())).addLore(new Message("PLAIN_STRING", mod.getPlatform().name())), mod)).toList();
});
swStreamInv.addItem(52, new SWItem("NAME_TAG", new Message("MOD_TITLE_FILTER")), click -> {
swStreamInv.close();
openTypeGUI(player, "MOD_TITLE_FILTER", type -> playerFilterType.replace(player.user().getUUID(), type));
});
swStreamInv.open();
}
private void openTypeGUI(PlayerChatter player, String title, Consumer<Mod.ModType> function) {
SWInventory inv = new SWInventory(player, 9, new Message(title));
inv.addItem(1, new SWItem(new Message("MOD_UNCLASSIFIED"), 8), click -> function.accept(Mod.ModType.UNKLASSIFIED));
inv.addItem(2, new SWItem(new Message("MOD_ALLOWED"), 2), click -> function.accept(Mod.ModType.GREEN));
inv.addItem(3, new SWItem(new Message("MOD_FORBIDDEN"), 11), click -> function.accept(Mod.ModType.YELLOW));
inv.addItem(4, new SWItem(new Message("MOD_AUTOBAN"), 1), click -> function.accept(Mod.ModType.RED));
inv.addItem(5, new SWItem(new Message("MOD_YT"), 13), click -> function.accept(Mod.ModType.YOUTUBER_ONLY));
inv.addItem(8, new SWItem("ARROW", new Message("MOD_ITEM_BACK")), click -> {
inv.close();
openGui(player);
});
inv.open();
}
@Register(value = {"set"},description = "MOD_COMMAND_SET_USAGE")
public void set(Chatter sender, String modName, Mod.Platform platform, Mod.ModType newModType) {
Mod mod = Mod.get(modName, platform);
if(mod == null) {
sender.system("MOD_COMMAND_NOT_FOUND_IN_DATABASE", modName, platform.name());
return;
}
mod.setModType(newModType);
sender.system("MOD_CHANGED_TYPE", modName, platform.name(), newModType.name());
}
@Register(value = {"get"},description = "MOD_COMMAND_GET_USAGE")
public void get(Chatter sender, String modName, Mod.Platform platform) {
Mod mod = Mod.get(modName, platform);
if(mod == null) {
sender.system("MOD_COMMAND_NOT_FOUND_IN_DATABASE", modName, platform.name());
return;
}
sender.system("MOD_COMMAND_INFO", modName, platform.name(), mod.getModType().name());
}
@Register(value = {"next"})
public void next(Chatter sender) {
Mod mod = Mod.findFirstMod();
if(mod == null) {
sender.system("MOD_NO_MORE_UNCLASSIFIED_MODS");
return;
}
sender.system("MOD_FOUND_NEXT_MOD", mod.getModName(), mod.getPlatform().name());
}
}
@@ -0,0 +1,63 @@
/*
This file is a part of the SteamWar software.
Copyright (C) 2020 SteamWar.de-Serverteam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.velocitycore.commands;
import com.velocitypowered.api.proxy.Player;
import de.steamwar.velocitycore.listeners.ChatListener;
import de.steamwar.command.SWCommand;
import de.steamwar.messages.Chatter;
import de.steamwar.messages.ChatterGroup;
import de.steamwar.messages.PlayerChatter;
import de.steamwar.sql.IgnoreSystem;
import static de.steamwar.persistent.Storage.lastChats;
public class MsgCommand extends SWCommand {
public MsgCommand() {
super("msg", "w", "tell");
}
@Register(description = "MSG_USAGE")
public void genericCommand(PlayerChatter sender, @ErrorMessage(value = "MSG_OFFLINE") Player target, @ErrorMessage(value = "MSG_USAGE", allowEAs = false) String... message) {
msg(sender, target, message);
}
public static void msg(PlayerChatter sender, Player target, String[] args) {
if(target == null) {
sender.system("MSG_OFFLINE");
return;
}
if (IgnoreSystem.isIgnored(target.getUniqueId(), sender.user().getUUID())) {
sender.system("MSG_IGNORED");
return;
}
Chatter receiver = Chatter.of(target);
ChatListener.sendChat(sender, new ChatterGroup(sender, receiver), "CHAT_MSG", receiver, String.join(" ", args));
lastChats.put(sender.getPlayer(), target);
lastChats.put(target, sender.getPlayer());
}
public static void remove(Player player){
lastChats.remove(player);
}
}
@@ -0,0 +1,35 @@
/*
This file is a part of the SteamWar software.
Copyright (C) 2020 SteamWar.de-Serverteam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.velocitycore.commands;
import de.steamwar.command.SWCommand;
import de.steamwar.messages.PlayerChatter;
public class PingCommand extends SWCommand {
public PingCommand() {
super("ping");
}
@Register
public void genericCommand(PlayerChatter sender) {
sender.system("PING_RESPONSE", sender.getPlayer().getPing());
}
}
@@ -0,0 +1,43 @@
/*
This file is a part of the SteamWar software.
Copyright (C) 2020 SteamWar.de-Serverteam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.velocitycore.commands;
import de.steamwar.command.SWCommand;
import de.steamwar.messages.Chatter;
import de.steamwar.sql.SteamwarUser;
import java.text.NumberFormat;
public class PlaytimeCommand extends SWCommand {
public PlaytimeCommand() {
super("playtime");
}
@Register
public void genericCommand(Chatter sender) {
SteamwarUser user = sender.user();
NumberFormat format = NumberFormat.getNumberInstance(user.getLocale());
format.setMaximumFractionDigits(2);
String formattedText = format.format((user.getOnlinetime() / 3600d));
sender.system("HOURS_PLAYED", formattedText);
}
}
@@ -0,0 +1,64 @@
/*
This file is a part of the SteamWar software.
Copyright (C) 2020 SteamWar.de-Serverteam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.velocitycore.commands;
import de.steamwar.velocitycore.listeners.PollSystem;
import de.steamwar.command.SWCommand;
import de.steamwar.command.TypeValidator;
import de.steamwar.messages.Chatter;
import de.steamwar.sql.PollAnswer;
public class PollCommand extends SWCommand {
public PollCommand() {
super("poll");
}
@Register
public void genericCommand(@Validator Chatter sender) {
PollSystem.sendPoll(sender);
}
@Register(noTabComplete = true)
public void answerPoll(@Validator Chatter sender, String answerString) {
int answer;
try {
answer = Integer.parseUnsignedInt(answerString);
if(answer < 1 || answer > PollSystem.answers())
throw new NumberFormatException();
}catch(NumberFormatException e){
sender.system("POLL_NO_ANSWER");
return;
}
PollAnswer pollAnswer = PollAnswer.get(sender.user().getId());
if(pollAnswer.hasAnswered())
sender.system("POLL_ANSWER_REFRESH");
else
sender.system("POLL_ANSWER_NEW");
pollAnswer.setAnswer(answer);
}
@ClassValidator(value = Chatter.class, local = true)
public TypeValidator<Chatter> noPoll() {
return PollSystem.noPoll();
}
}
@@ -0,0 +1,50 @@
/*
This file is a part of the SteamWar software.
Copyright (C) 2020 SteamWar.de-Serverteam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.velocitycore.commands;
import de.steamwar.velocitycore.listeners.PollSystem;
import de.steamwar.command.SWCommand;
import de.steamwar.command.TypeValidator;
import de.steamwar.messages.Chatter;
import de.steamwar.sql.PollAnswer;
import de.steamwar.sql.UserPerm;
import java.util.Map;
public class PollresultCommand extends SWCommand {
public PollresultCommand() {
super("pollresult", UserPerm.MODERATION);
}
@Register
public void genericCommand(@Validator Chatter sender) {
Map<Integer, Integer> voted = PollAnswer.getCurrentResults();
sender.system("POLLRESULT_HEADER", voted.values().stream().reduce(Integer::sum).orElse(0), PollAnswer.getCurrentPoll());
for (Map.Entry<Integer, Integer> e: voted.entrySet()) {
sender.prefixless("POLLRESULT_LIST", PollSystem.getAnswer(e.getKey()), e.getValue());
}
}
@ClassValidator(value = Chatter.class, local = true)
public TypeValidator<Chatter> noPoll() {
return PollSystem.noPoll();
}
}
@@ -0,0 +1,274 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2020 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.velocitycore.commands;
import com.google.gson.JsonParser;
import com.velocitypowered.api.proxy.Player;
import de.steamwar.velocitycore.VelocityCore;
import de.steamwar.velocitycore.listeners.IPSanitizer;
import de.steamwar.command.PreviousArguments;
import de.steamwar.command.SWCommand;
import de.steamwar.command.TypeMapper;
import de.steamwar.messages.Chatter;
import de.steamwar.messages.Message;
import de.steamwar.sql.BannedUserIPs;
import de.steamwar.sql.Punishment;
import de.steamwar.sql.SteamwarUser;
import de.steamwar.sql.UserPerm;
import java.io.IOException;
import java.net.URL;
import java.sql.Timestamp;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.logging.Level;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
public class PunishmentCommand {
private static final String API_URL = "https://api.mojang.com/users/profiles/minecraft/";
public static SteamwarUser getOrCreateOfflinePlayer(String name) {
SteamwarUser user = SteamwarUser.get(name);
if (user != null) {
return user;
}
UUID uuid = getUUIDofOfflinePlayer(name);
if (uuid == null) {
return null;
}
return SteamwarUser.getOrCreate(uuid, name, u -> {}, (o, n) -> {});
}
private static UUID getUUIDofOfflinePlayer(String playerName) {
try {
final URL url = new URL(API_URL + playerName);
String uuid = JsonParser.parseString(new Scanner(url.openConnection().getInputStream()).nextLine()).getAsJsonObject().get("id").getAsString();
return UUID.fromString(uuid.replaceFirst("(\\w{8})(\\w{4})(\\w{4})(\\w{4})(\\w{12})", "$1-$2-$3-$4-$5"));
} catch (NoSuchElementException e) {
// ignore, player does not exist
} catch (IOException e) {
VelocityCore.getLogger().log(Level.SEVERE, "Could not get offline player UUID %s".formatted(playerName), e);
}
return null;
}
public static boolean isPunishedWithMessage(Chatter player, Punishment.PunishmentType punishment) {
SteamwarUser user = player.user();
if (!user.isPunished(punishment)) {
return false;
}
player.system(punishmentMessage(user, punishment));
return true;
}
public static void ban(SteamwarUser user, Timestamp time, String banReason, SteamwarUser punisher, boolean perma) {
Player player = VelocityCore.getProxy().getPlayer(user.getUUID()).orElse(null);
if (player != null) {
String ip = IPSanitizer.getTrueAddress(player).getHostAddress();
Chatter.disconnect(player).system(punishmentMessage(user, Punishment.PunishmentType.Ban));
for (BannedUserIPs banned : BannedUserIPs.get(ip)) {
SteamwarUser bannedUser = SteamwarUser.get(banned.getUserID());
if (bannedUser.isPunished(Punishment.PunishmentType.Ban) && bannedUser.getPunishment(Punishment.PunishmentType.Ban).getEndTime().before(time)) {
bannedUser.punish(Punishment.PunishmentType.Ban, time, banReason, punisher.getId(), perma);
}
}
BannedUserIPs.banIP(user.getId(), ip);
}
}
public static Message punishmentMessage(SteamwarUser user, Punishment.PunishmentType punishment) {
Punishment currentPunishment = user.getPunishment(punishment);
if (currentPunishment.isPerma()) {
return new Message(punishment.getPlayerMessagePerma(), currentPunishment.getReason());
} else {
return new Message(punishment.getPlayerMessageUntil(), currentPunishment.getEndTime(), currentPunishment.getReason());
}
}
public PunishmentCommand(String command, Punishment.PunishmentType punishmentType) {
new PunishCommand(command, punishmentType);
if (punishmentType.getUnpunishmentMessage() == null)
return;
new UnpunishCommand("un" + command, punishmentType);
}
private static final Pattern RELATIVE_PATTERN = Pattern.compile("([1-9]\\d*[hdwmy])+");
public static Timestamp parseTime(Chatter player, String arg) {
if (arg.equalsIgnoreCase("perma")) {
return Punishment.PERMA_TIME;
} else {
if (RELATIVE_PATTERN.matcher(arg).matches()) {
Instant instant = Instant.now();
StringBuilder st = new StringBuilder();
for (int i = 0; i < arg.length(); i++) {
char c = arg.charAt(i);
if (c >= '0' && c <= '9') {
st.append(c);
continue;
}
int amount = Integer.parseInt(st.toString());
st = new StringBuilder();
switch (c) {
case 'h':
instant = instant.plus(amount, ChronoUnit.HOURS);
break;
case 'd':
instant = instant.plus(amount, ChronoUnit.DAYS);
break;
case 'w':
instant = instant.plus(Duration.ofSeconds(amount * ChronoUnit.WEEKS.getDuration().getSeconds()));
break;
case 'm':
instant = instant.plus(Duration.ofSeconds(amount * ChronoUnit.MONTHS.getDuration().getSeconds()));
break;
case 'y':
instant = instant.plus(Duration.ofSeconds(amount * ChronoUnit.YEARS.getDuration().getSeconds()));
break;
}
}
return Timestamp.from(instant);
}
SimpleDateFormat dateFormat = new SimpleDateFormat("dd.MM.yyyy_HH:mm");
try {
Date parsedDate = dateFormat.parse(arg);
return new java.sql.Timestamp(parsedDate.getTime());
} catch (ParseException e) {
dateFormat = new SimpleDateFormat("dd.MM.yyyy");
try {
Date parsedDate = dateFormat.parse(arg.split("_")[0]);
return new java.sql.Timestamp(parsedDate.getTime());
} catch (ParseException exception) {
player.system("INVALID_TIME");
return null;
}
}
}
}
private static class PunishCommand extends SWCommand {
private final String command;
private final Punishment.PunishmentType punishmentType;
private PunishCommand(String command, Punishment.PunishmentType punishmentType) {
super(command, UserPerm.TEAM);
this.command = command;
this.punishmentType = punishmentType;
}
private SteamwarUser unsafeUser(Chatter sender, String arg) {
SteamwarUser target = getOrCreateOfflinePlayer(arg);
if(target == null)
sender.system("UNKNOWN_PLAYER");
return target;
}
@Register
public void genericCommand(Chatter sender, @Mapper("toPunish") String toPunish, String date, @ErrorMessage(allowEAs = false, value = "PUNISHMENT_USAGE_REASON") String... message) {
SteamwarUser punisher = sender.user();
if (punishmentType.isNeedsAdmin() && !punisher.hasPerm(UserPerm.MODERATION))
return;
SteamwarUser target = unsafeUser(sender, toPunish);
if (target == null)
return;
Timestamp banTime = parseTime(sender, date);
if (banTime == null)
return;
boolean isPerma = date.equalsIgnoreCase("perma");
String msg = String.join(" ", message);
target.punish(punishmentType, banTime, msg, punisher.getId(), isPerma);
if(punishmentType == Punishment.PunishmentType.Ban)
ban(target, banTime, msg, punisher, isPerma);
Chatter.serverteam().system(punishmentType.getTeamMessage(), new Message("PREFIX"), target, sender, new Message((isPerma ? "PUNISHMENT_PERMA" : "PUNISHMENT_UNTIL"), banTime), msg);
}
@Register
public void genericError(Chatter sender, String... args) {
sender.system("PUNISHMENT_USAGE", command);
}
@Mapper(value = "toPunish", local = true)
public TypeMapper<String> allUsers() {
return new TypeMapper<String>() {
@Override
public String map(Chatter sender, PreviousArguments previousArguments, String s) {
return s;
}
@Override
public Collection<String> tabCompletes(Chatter sender, PreviousArguments previousArguments, String s) {
List<String> players = VelocityCore.getProxy().getAllPlayers().stream()
.map(Player::getUsername)
.collect(Collectors.toList());
players.add(s);
return players;
}
};
}
}
private static class UnpunishCommand extends SWCommand {
private final String command;
private final Punishment.PunishmentType punishmentType;
private UnpunishCommand(String command, Punishment.PunishmentType punishmentType) {
super(command, UserPerm.TEAM);
this.command = command;
this.punishmentType = punishmentType;
}
@Register
public void genericCommand(Chatter sender, @ErrorMessage("UNKNOWN_PLAYER") SteamwarUser target) {
if (punishmentType.isNeedsAdmin() && !sender.user().hasPerm(UserPerm.MODERATION))
return;
if (!target.isPunished(punishmentType)) {
sender.system(punishmentType.getUsageNotPunished());
return;
}
target.punish(punishmentType, Timestamp.from(new Date().toInstant()), command, sender.user().getId(), false);
if(punishmentType == Punishment.PunishmentType.Ban)
BannedUserIPs.unbanIPs(target.getId());
sender.system(punishmentType.getUnpunishmentMessage(), target.getUserName());
}
@Register
public void genericError(Chatter sender, String... args) {
sender.system("UNPUNISHMENT_USAGE", command);
}
}
}
@@ -0,0 +1,37 @@
/*
This file is a part of the SteamWar software.
Copyright (C) 2020 SteamWar.de-Serverteam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.velocitycore.commands;
import de.steamwar.command.SWCommand;
import de.steamwar.messages.PlayerChatter;
import static de.steamwar.persistent.Storage.lastChats;
public class RCommand extends SWCommand {
public RCommand() {
super("r", "reply");
}
@Register(description = "R_USAGE")
public void genericCommand(PlayerChatter sender, @ErrorMessage(value = "R_USAGE", allowEAs = false) String... message) {
MsgCommand.msg(sender, lastChats.get(sender.getPlayer()), message);
}
}
@@ -0,0 +1,64 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2020 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.velocitycore.commands;
import de.steamwar.velocitycore.ArenaMode;
import de.steamwar.messages.Message;
import de.steamwar.command.SWCommand;
import de.steamwar.messages.Chatter;
import de.steamwar.sql.SteamwarUser;
import de.steamwar.sql.UserElo;
import java.util.Optional;
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);
for(ArenaMode mode : ArenaMode.getAllModes()) {
if (!mode.isRanked())
continue;
Optional<Integer> elo = UserElo.getElo(user.getId(), mode.getSchemType());
Message eloMsg;
if (elo.isPresent()) {
int placement = UserElo.getPlacement(elo.get(), mode.getSchemType());
eloMsg = new Message("RANK_PLACED", placement, elo.get());
} else {
eloMsg = new Message("RANK_UNPLACED");
}
sender.prefixless("RANK_HEADER", mode.getChatName(), eloMsg);
sender.prefixless("RANK_EMBLEM", UserElo.getEmblemProgression(mode.getChatName(), user.getId()));
}
}
}
@@ -0,0 +1,88 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2022 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.velocitycore.ArenaMode;
import de.steamwar.messages.Message;
import de.steamwar.velocitycore.ServerStarter;
import de.steamwar.velocitycore.inventory.SWItem;
import de.steamwar.velocitycore.inventory.SWListInv;
import de.steamwar.velocitycore.inventory.SWStreamInv;
import de.steamwar.command.SWCommand;
import de.steamwar.messages.PlayerChatter;
import de.steamwar.sql.*;
import java.util.ArrayList;
import java.util.List;
public class ReplayCommand extends SWCommand {
public ReplayCommand() {
super("replay");
}
@Register
public void genericCommand(PlayerChatter sender, @OptionalValue("") String optionalMap) {
if (PunishmentCommand.isPunishedWithMessage(sender, Punishment.PunishmentType.NoFightServer))
return;
new SWStreamInv<>(sender, new Message("REPLAY_TITLE"), (click, fight) -> {
ArenaMode mode = ArenaMode.getBySchemType(fight.getSchemType());
ServerStarter starter = new ServerStarter().replay(fight.getFightID()).blueLeader(sender.getPlayer());
String map = mode.getRandomMap();
if (!optionalMap.equals("")) {
String tMap = mode.hasMap(optionalMap);
if (tMap != null) map = tMap;
}
if (sender.user().hasPerm(UserPerm.MODERATION) && click.isShiftClick() && fight.replayExists()) {
starter.test(mode, map, sender.getPlayer()).start();
} else if(!fight.replayAllowed()) {
sender.system("REPLAY_UNAVAILABLE");
} else {
starter.arena(mode, map).start();
}
}, page -> Fight.getPage(page, 45).stream().map(fight -> new SWListInv.SWListEntry<>(getFightItem(fight), fight)).toList()).open();
}
private SWItem getFightItem(Fight fight) {
SchematicType type = fight.getSchemType();
SWItem item = new SWItem(type != null ? type.getMaterial() : "BARRIER", parseLeader(fight.getBlueLeader(), fight.getBluePlayers().size(), fight.getWin() == 1));
List<Message> lore = new ArrayList<>();
lore.add(parseLeader(fight.getRedLeader(), fight.getRedPlayers().size(), fight.getWin() == 2));
lore.add(new Message("REPLAY_TIME", fight.getStartTime()));
lore.add(new Message("SPACER"));
lore.add(new Message("REPLAY_SERVER", fight.getServer()));
if(!fight.replayAllowed())
lore.add(new Message("REPLAY_UNAVAILABLE"));
item.setLore(lore);
if(fight.replayAllowed())
item.setEnchanted(true);
return item;
}
private Message parseLeader(SteamwarUser leader, int players, boolean winner) {
return new Message("REPLAY_" + (players > 1 ? "" : "SOLO_") + (winner ? "WINNER" : "LOSER"), leader.getUserName(), players - 1);
}
}
@@ -0,0 +1,41 @@
/*
This file is a part of the SteamWar software.
Copyright (C) 2020 SteamWar.de-Serverteam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.velocitycore.commands;
import de.steamwar.command.SWCommand;
import de.steamwar.messages.Chatter;
import de.steamwar.messages.Message;
import net.kyori.adventure.text.event.ClickEvent;
import java.util.Arrays;
public class RulesCommand extends SWCommand {
public RulesCommand() {
super("rules", "regeln");
}
@Register
public void genericCommand(Chatter sender) {
sender.system("REGELN_RULES");
for(String ruleset : Arrays.asList("REGELN_AS", "REGELN_MWG", "REGELN_WG", "REGELN_WS", "REGELN_QG", "REGELN_CONDUCT"))
sender.prefixless(ruleset, new Message(ruleset + "_HOVER"), ClickEvent.openUrl(sender.parseToPlain(ruleset + "_URL")));
}
}
@@ -0,0 +1,40 @@
/*
This file is a part of the SteamWar software.
Copyright (C) 2020 SteamWar.de-Serverteam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.velocitycore.commands;
import com.velocitypowered.api.proxy.server.RegisteredServer;
import de.steamwar.velocitycore.VelocityCore;
import de.steamwar.command.SWCommand;
import de.steamwar.messages.PlayerChatter;
public class ServerSwitchCommand extends SWCommand {
private final RegisteredServer server;
public ServerSwitchCommand(String cmd, String name, String... aliases) {
super(cmd, null, aliases);
server = VelocityCore.getProxy().getServer(name).orElseThrow();
}
@Register
public void genericCommand(PlayerChatter sender) {
sender.getPlayer().createConnectionRequest(server).fireAndForget();
}
}
@@ -0,0 +1,37 @@
/*
This file is a part of the SteamWar software.
Copyright (C) 2020 SteamWar.de-Serverteam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.velocitycore.commands;
import de.steamwar.velocitycore.listeners.ChatListener;
import de.steamwar.command.SWCommand;
import de.steamwar.messages.Chatter;
import de.steamwar.sql.UserPerm;
public class ServerTeamchatCommand extends SWCommand {
public ServerTeamchatCommand() {
super("stc", UserPerm.TEAM, "serverteamchat");
}
@Register(description = "STC_USAGE")
public void genericCommand(Chatter sender, @ErrorMessage(value = "STC_USAGE", allowEAs = false) String... message) {
ChatListener.sendChat(sender, Chatter.serverteam(), "CHAT_SERVERTEAM", null, String.join(" ", message));
}
}
@@ -0,0 +1,41 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2020 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.velocitycore.commands;
import de.steamwar.velocitycore.network.NetworkSender;
import de.steamwar.command.SWCommand;
import de.steamwar.messages.Chatter;
import de.steamwar.network.packets.server.LocaleInvalidationPacket;
import java.util.Objects;
public class SetLocaleCommand extends SWCommand {
public SetLocaleCommand() {
super("setlocale", "setlanguage");
}
@Register
public void genericCommand(Chatter sender) {
sender.user().setLocale(Objects.requireNonNull(sender.getLocale()), true);
sender.withPlayer(player -> NetworkSender.send(player, new LocaleInvalidationPacket(sender.user().getId())));
sender.system("LOCK_LOCALE_CHANGED");
}
}
@@ -0,0 +1,57 @@
/*
This file is a part of the SteamWar software.
Copyright (C) 2020 SteamWar.de-Serverteam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.velocitycore.commands;
import de.steamwar.velocitycore.Node;
import de.steamwar.command.SWCommand;
import de.steamwar.messages.Chatter;
import de.steamwar.sql.UserPerm;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map;
public class StatCommand extends SWCommand {
public StatCommand() {
super("stat", UserPerm.ADMINISTRATION, "stats");
}
@Register
public void genericCommand(Chatter sender) {
Map<String, Integer> serverCount = new HashMap<>();
try {
Process process = new ProcessBuilder("ps", "x").start();
new BufferedReader(new InputStreamReader(process.getInputStream())).lines().forEach(s -> {
if (!s.contains("--port"))
return;
serverCount.compute(
s.contains("ssh -L") ? s.substring(s.indexOf("ssh -L") + 6).split(" ")[2] : "sw",
(server, count) -> (count != null ? count : 0) + 1
);
});
} catch (Exception e) {
throw new SecurityException(e.getMessage(), e);
}
Node.forEach(node -> sender.prefixless("STAT_SERVER", node.getName(), node.belowLoadLimit(), serverCount.getOrDefault(node.getName(), 0)));
}
}
@@ -0,0 +1,601 @@
/*
This file is a part of the SteamWar software.
Copyright (C) 2020 SteamWar.de-Serverteam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.velocitycore.commands;
import com.velocitypowered.api.proxy.ConnectionRequestBuilder;
import com.velocitypowered.api.proxy.server.ServerInfo;
import de.steamwar.persistent.Storage;
import de.steamwar.velocitycore.VelocityCore;
import de.steamwar.velocitycore.discord.DiscordBot;
import de.steamwar.velocitycore.inventory.SWItem;
import de.steamwar.velocitycore.inventory.SWListInv;
import de.steamwar.command.PreviousArguments;
import de.steamwar.command.SWCommand;
import de.steamwar.command.TypeMapper;
import de.steamwar.command.TypeValidator;
import de.steamwar.messages.Chatter;
import de.steamwar.messages.Message;
import de.steamwar.messages.PlayerChatter;
import de.steamwar.sql.*;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.event.ClickEvent;
import net.kyori.adventure.text.event.HoverEvent;
import net.kyori.adventure.text.format.NamedTextColor;
import java.net.*;
import java.time.Instant;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static de.steamwar.persistent.Storage.teamInvitations;
public class TeamCommand extends SWCommand {
public TeamCommand() {
super("team");
}
@Register(noTabComplete = true)
public void help(Chatter sender, String... args){
helpMessages(sender, "TEAM_HELP_HEADER", "TEAM_HELP_LIST", "TEAM_HELP_INFO", "TEAM_HELP_TP");
SteamwarUser user = sender.user();
if(user.getTeam() == 0) {
helpMessages(sender, "TEAM_HELP_CREATE", "TEAM_HELP_JOIN");
}else{
helpMessages(sender, "TEAM_HELP_CHAT", "TEAM_HELP_EVENT", "TEAM_HELP_LEAVE");
if(user.isLeader())
helpMessages(sender, "TEAM_HELP_INVITE", "TEAM_HELP_REMOVE", "TEAM_HELP_KUERZEL", "TEAM_HELP_NAME", "TEAM_HELP_COLOR", "TEAM_HELP_LEADER", "TEAM_HELP_STEP_BACK", "TEAM_HELP_SERVER");
}
}
private void helpMessages(Chatter sender, String... messages) {
for(String message : messages)
sender.system(message);
}
@Register(value = "create", description = "TEAM_CREATE_USAGE")
public void create(@Validator("isNotInTeam") Chatter sender, @Length(min = 2, max = 4) @ErrorMessage("TEAM_KUERZEL_LENGTH") String kuerzel, @Length(min = 4, max = 15) @ErrorMessage("TEAM_NAME_LENGTH") String name){
SteamwarUser user = sender.user();
Team team = Team.get(user.getTeam());
if(checkTeamKuerzel(sender, team, kuerzel) || checkTeamName(sender, team, name))
return;
Team.create(kuerzel, name);
user.setTeam(Team.get(kuerzel).getTeamId());
user.setLeader(true);
sender.system("TEAM_CREATE_CREATED", name);
}
@Register("join")
public void join(@Validator("isNotInTeam") Chatter sender, String... args){
SteamwarUser user = sender.user();
if(notDuringEvent(sender))
return;
if(!teamInvitations.containsKey(user.getId())){
sender.system("TEAM_JOIN_NO_INVITE");
return;
}
List<Integer> invs = teamInvitations.get(user.getId());
Integer t = null;
if(invs.size() == 1){
t = invs.get(0);
}else{
if(args.length != 1){
sender.system("TEAM_JOIN_USAGE");
StringBuilder sb = new StringBuilder();
for(int inv : invs){
Team team = Team.get(inv);
sb.append(team.getTeamName()).append(" ");
}
sender.system("TEAM_JOIN_INVITED", sb.toString());
return;
}
for(int inv : invs){
Team team = Team.get(inv);
if(team.getTeamKuerzel().equalsIgnoreCase(args[0]) || team.getTeamName().equalsIgnoreCase(args[0])){
t = inv;
break;
}
}
if(t == null){
sender.system("TEAM_JOIN_NOT_BY_TEAM");
return;
}
}
user.setTeam(t);
teamInvitations.remove(user.getId());
sender.system("TEAM_JOIN_JOINED", Team.get(t).getTeamName());
}
@Register("stepback")
public void stepBack(@Validator("isLeader") Chatter sender) {
SteamwarUser user = sender.user();
Team team = Team.get(user.getTeam());
if(noRemainingLeaders(team, user)){
sender.system("TEAM_OTHER_LEADER_REQUIRED");
return;
}
user.setLeader(false);
sender.system("TEAM_STEP_BACK");
}
@Register("leave")
public void leave(@Validator("isInTeam") Chatter sender) {
SteamwarUser user = sender.user();
Team team = Team.get(user.getTeam());
int teamSize = team.size();
if(teamSize > 1 && user.isLeader() && noRemainingLeaders(team, user)) {
sender.system("TEAM_OTHER_LEADER_REQUIRED");
return;
}
user.setTeam(0);
if(teamSize == 1)
team.disband(user);
sender.system("TEAM_LEAVE_LEFT");
}
private boolean noRemainingLeaders(Team team, SteamwarUser except) {
return SteamwarUser.getTeam(team.getTeamId()).stream().filter(member -> except.getId() != member.getId()).noneMatch(SteamwarUser::isLeader);
}
@Register(value = "invite", description = "TEAM_INVITE_USAGE")
public void invite(@Validator("isLeader") Chatter sender, @ErrorMessage("TEAM_INVITE_NO_PLAYER") SteamwarUser target){
Team team = Team.get(sender.user().getTeam());
if(notDuringEvent(sender))
return;
if(target.getTeam() != 0){
sender.system("TEAM_INVITE_IN_TEAM");
return;
}
List<Integer> invitations = teamInvitations.computeIfAbsent(target.getId(), id -> new ArrayList<>());
if(invitations.contains(team.getTeamId())){
sender.system("TEAM_INVITE_ALREADY_INVITED");
return;
}
invitations.add(team.getTeamId());
sender.system("TEAM_INVITE_INVITED", target.getUserName());
Chatter.of(target).system("TEAM_INVITE_INVITED_TARGET", team.getTeamColor(), team.getTeamName());
}
@Register(value = "remove", description = "TEAM_REMOVE_USAGE")
public void remove(@Validator("isLeader") Chatter sender, @ErrorMessage("TEAM_REMOVE_NOT_PLAYER") @Mapper("memberList") SteamwarUser target){
Team team = Team.get(sender.user().getTeam());
if (target.isLeader()) {
sender.system("TEAM_REMOVE_NOT_LEADER");
return;
}
List<Integer> invitations = teamInvitations.get(target.getId());
if(invitations != null) {
sender.system(invitations.remove((Integer) team.getTeamId()) ? "TEAM_REMOVE_INVITE" : "TEAM_REMOVE_NO_INVITE");
return;
}
if(target.getTeam() != team.getTeamId()) {
sender.system("TEAM_REMOVE_NOT_IN_TEAM");
return;
}
target.setTeam(0);
sender.system("TEAM_REMOVE_REMOVED");
Chatter.of(target).system("TEAM_REMOVE_REMOVED_TARGET");
}
@Mapper(value = "memberList", local = true)
public TypeMapper<SteamwarUser> memberList() {
return new TypeMapper<SteamwarUser>() {
@Override
public SteamwarUser map(Chatter sender, PreviousArguments previousArguments, String s) {
return SteamwarUser.get(s);
}
@Override
public Collection<String> tabCompletes(Chatter sender, PreviousArguments previousArguments, String s) {
return Team.get(sender.user().getTeam()).getMembers().stream()
.map(SteamwarUser::get)
.map(SteamwarUser::getUserName)
.toList();
}
};
}
@Register(value = "changekurzel", description = "TEAM_KUERZEL_USAGE")
public void changekuerzel(@Validator("isLeader") Chatter sender, @Length(min = 2, max = 4) @ErrorMessage("TEAM_KUERZEL_LENGTH") String kuerzel){
Team team = Team.get(sender.user().getTeam());
if(notDuringEvent(sender))
return;
if(checkTeamKuerzel(sender, team, kuerzel))
return;
team.setTeamKuerzel(kuerzel);
sender.system("TEAM_KUERZEL_CHANGED");
}
@Register(value = "changename", description = "TEAM_NAME_USAGE")
public void changename(@Validator("isLeader") Chatter sender, @Length(min = 4, max = 15) @ErrorMessage("TEAM_NAME_LENGTH") String name){
Team team = Team.get(sender.user().getTeam());
if(notDuringEvent(sender))
return;
if(checkTeamName(sender, team, name))
return;
team.setTeamName(name);
sender.system("TEAM_NAME_CHANGED");
}
@Register(value = "promote", description = "TEAM_LEADER_USAGE")
public void promote(@Validator("isLeader") Chatter sender, @ErrorMessage("TEAM_LEADER_NOT_USER") @Mapper("memberList") SteamwarUser target){
if(notDuringEvent(sender))
return;
if (target.getTeam() != sender.user().getTeam()) {
sender.system("TEAM_LEADER_NOT_MEMBER");
return;
}
target.setLeader(true);
sender.system("TEAM_LEADER_PROMOTED", target);
}
@Register("info")
public void info(@Validator("isInTeam") Chatter sender){
info(sender, Team.get(sender.user().getTeam()));
}
@Register(value = "info", description = "TEAM_INFO_USAGE")
public void info(Chatter sender, @ErrorMessage("UNKNOWN_TEAM") Team team){
List<SteamwarUser> users = team.getMembers().stream().map(SteamwarUser::get).toList();
sender.system("TEAM_INFO_TEAM", team.getTeamName(), team.getTeamColor(), team.getTeamKuerzel());
sender.prefixless("TEAM_INFO_LEADER", users.stream().filter(SteamwarUser::isLeader).count(), getMemberList(users, true));
String members = getMemberList(users, false);
if(!members.isEmpty())
sender.prefixless("TEAM_INFO_MEMBER", users.stream().filter(u -> !u.isLeader()).count(), members);
Set<Event> events = TeamTeilnahme.getEvents(team.getTeamId());
if(!events.isEmpty()){
sender.prefixless("TEAM_INFO_EVENTS", events.stream().map(Event::getEventName).collect(Collectors.joining(", ")));
}
}
private String getMemberList(List<SteamwarUser> users, boolean leaders) {
return users.stream()
.filter(user -> user.isLeader() == leaders)
.map(user -> (VelocityCore.getProxy().getPlayer(user.getUUID()).isPresent() ? "§a" : "§e") + user.getUserName())
.collect(Collectors.joining(" "));
}
@Register("list")
public void list(Chatter sender, @Min(intValue = 1) @OptionalValue("1") @ErrorMessage("TEAM_LIST_NOT_PAGE") int page){
final int TEAMS_PER_PAGE = 10;
List<Team> all = Team.getAll();
final int lastPage = ((all.size() - 1) / 10) + 1;
if(page < 1 || page > lastPage){
sender.system("TEAM_LIST_UNKNOWN_PAGE");
return;
}
sender.system("TEAM_LIST_HEADER", page, lastPage);
for(int i = (page-1) * TEAMS_PER_PAGE; i < (page-1) * TEAMS_PER_PAGE + TEAMS_PER_PAGE && i < all.size(); i++){
Team tm = all.get(i);
sender.prefixless("TEAM_LIST_TEAM", new Message("TEAM_LIST_TEAM_HOVER"),
ClickEvent.runCommand("/team info " + tm.getTeamKuerzel()), tm.getTeamColor(), tm.getTeamKuerzel(), tm.getTeamName());
}
Component beforePage = Component
.text("«« ")
.color(page > 1 ? NamedTextColor.YELLOW : NamedTextColor.DARK_GRAY);
if(page > 1)
beforePage = beforePage
.hoverEvent(HoverEvent.showText(sender.parse("TEAM_LIST_PREV")))
.clickEvent(ClickEvent.runCommand("/team list " + (page - 1)));
Component nextPage = sender.parse("TEAM_LIST_PAGE")
.color(page > 1 ? NamedTextColor.YELLOW : NamedTextColor.DARK_GRAY);
if(page < lastPage)
nextPage = nextPage
.hoverEvent(HoverEvent.showText(sender.parse("TEAM_LIST_NEXT")))
.clickEvent(ClickEvent.runCommand("/team list " + (page + 1)));
sender.sendMessage(beforePage.append(nextPage));
}
@Register("event")
public void event(@Validator("isInTeam") Chatter sender) {
Team team = Team.get(sender.user().getTeam());
sender.system("TEAM_EVENT_USAGE");
Set<Event> events = TeamTeilnahme.getEvents(team.getTeamId());
if(!events.isEmpty()){
sender.system("TEAM_EVENT_HEADER");
DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern(sender.parseToPlain("EVENT_DATE_FORMAT"));
for(Event e : events)
sender.prefixless("TEAM_EVENT_EVENT", e.getStart().toLocalDateTime().format(dateFormat), e.getEventName());
}
}
@Register("event")
public void event(@Validator("isLeader") Chatter sender, Event event){
Team team = Team.get(sender.user().getTeam());
if(notDuringEvent(sender))
return;
if(Instant.now().isAfter(event.getDeadline().toInstant())){
sender.system("TEAM_EVENT_OVER");
return;
}
if(TeamTeilnahme.nimmtTeil(team.getTeamId(), event.getEventID())){
TeamTeilnahme.notTeilnehmen(team.getTeamId(), event.getEventID());
sender.system("TEAM_EVENT_LEFT");
}else{
TeamTeilnahme.teilnehmen(team.getTeamId(), event.getEventID());
sender.system("TEAM_EVENT_JOINED", event.getEventName());
sender.prefixless("TEAM_EVENT_HOW_TO_LEAVE");
}
DiscordBot.withBot(bot -> bot.getEventChannel().update());
}
@Register("tp")
public void tp(@Validator("isInTeam") PlayerChatter sender) {
Team team = Team.get(sender.user().getTeam());
tp(sender, team);
}
@Register("tp")
public void tp(PlayerChatter sender, @ErrorMessage("TEAM_TP_NO_TEAM") Team targetTeam) {
if (targetTeam.getAddress() == null || targetTeam.getAddress().isEmpty()) {
sender.system("TEAM_NO_ADDRESS");
return;
}
InetSocketAddress address = new InetSocketAddress(targetTeam.getAddress(), targetTeam.getPort());
ServerInfo serverInfo = Storage.teamServers.computeIfAbsent(targetTeam.getTeamId(), integer -> {
ServerInfo info = new ServerInfo("Team " + targetTeam.getTeamKuerzel(), address);
VelocityCore.getProxy().registerServer(info);
return info;
});
if (!address.equals(serverInfo.getAddress())) {
VelocityCore.getProxy().unregisterServer(Storage.teamServers.remove(targetTeam.getTeamId()));
tp(sender, targetTeam);
return;
}
sender.getPlayer().createConnectionRequest(VelocityCore.getProxy().getServer(serverInfo.getName()).orElseThrow()).connect().whenComplete((result, throwable) -> {
if(result.getStatus() != ConnectionRequestBuilder.Status.SUCCESS || throwable != null)
sender.system("TEAM_OFFLINE");
});
}
@Register(value = "server", description = "TEAM_SERVER_USAGE")
public void server(@Validator("isLeader") Chatter sender, String server, @Min(intValue = 1) @Max(intValue = 65535) @ErrorMessage("TEAM_SERVER_PORT_INVALID") int port){
Team team = Team.get(sender.user().getTeam());
if (PunishmentCommand.isPunishedWithMessage(sender, Punishment.PunishmentType.NoTeamServer)) {
return;
}
try {
if (isLocalhost(InetAddress.getByName(server))) {
sender.system("TEAM_SERVER_ADDRESS_INVALID");
return;
}
} catch (UnknownHostException e) {
sender.system("TEAM_SERVER_ADDRESS_INVALID");
return;
}
team.setAddress(server);
team.setPort(port);
Storage.teamServers.remove(team.getTeamId());
sender.system("TEAM_SERVER_SET");
}
public static boolean isLocalhost(InetAddress addr) {
// Check if the address is a valid special local or loop back
if (addr.isAnyLocalAddress() || addr.isLoopbackAddress())
return true;
// Check if the address is defined on any interface
try {
return NetworkInterface.getByInetAddress(addr) != null;
} catch (SocketException e) {
return false;
}
}
private static final Map<String, Integer> COLOR_CODES = new HashMap<>();
static {
COLOR_CODES.put("4", 1);
COLOR_CODES.put("c", 15);
COLOR_CODES.put("6", 14);
COLOR_CODES.put("e", 11);
COLOR_CODES.put("2", 2);
COLOR_CODES.put("a", 10);
COLOR_CODES.put("b", 12);
COLOR_CODES.put("3", 6);
COLOR_CODES.put("1", 4);
COLOR_CODES.put("9", 6);
COLOR_CODES.put("d", 9);
COLOR_CODES.put("5", 5);
COLOR_CODES.put("f", 15);
COLOR_CODES.put("7", 7);
COLOR_CODES.put("8", 8);
COLOR_CODES.put("0", 16);
}
@Register("color")
public void changeColor(@Validator("isLeader") PlayerChatter sender) {
Team team = Team.get(sender.user().getTeam());
if(notDuringEvent(sender))
return;
SWListInv<String> inv = new SWListInv<>(sender, new Message("TEAM_COLOR_TITLE"), COLOR_CODES.entrySet().stream().map(entry -> new SWListInv.SWListEntry<>(new SWItem(new Message("PLAIN_STRING", "§" + entry.getKey() + team.getTeamKuerzel()), entry.getValue()), entry.getKey())).toList(), (click, element) -> {});
inv.setCallback((click, element) -> {
inv.close();
team.setTeamColor(element);
});
inv.open();
}
@ClassMapper(Event.class)
public TypeMapper<Event> eventTypeMapper() {
return new TypeMapper<Event>() {
@Override
public Event map(Chatter sender, PreviousArguments previousArguments, String s) {
return Event.get(s);
}
@Override
public boolean validate(Chatter sender, Event value, MessageSender messageSender) {
if (value == null) {
sender.system("TEAM_EVENT_NO_EVENT");
return false;
} else {
return true;
}
}
@Override
public Collection<String> tabCompletes(Chatter sender, PreviousArguments previousArguments, String s) {
return Event.getComing().stream().map(Event::getEventName).toList();
}
};
}
@Validator(value = "isNotInTeam", local = true)
public TypeValidator<Chatter> isNotInTeamValidator() {
return (sender, value, messageSender) -> {
if (sender.user().getTeam() != 0) {
messageSender.send("TEAM_IN_TEAM");
return false;
}
return true;
};
}
@Validator(value = "isInTeam", local = true)
public TypeValidator<Chatter> isInTeamValidator() {
return (sender, value, messageSender) -> {
if (sender.user().getTeam() == 0) {
messageSender.send("TEAM_NOT_IN_TEAM");
return false;
}
return true;
};
}
@Validator(value = "isLeader", local = true)
public TypeValidator<Chatter> isLeaderValidator() {
return (sender, value, messageSender) -> {
SteamwarUser user = sender.user();
if (user.getTeam() == 0) {
messageSender.send("TEAM_NOT_IN_TEAM");
return false;
}
if (!user.isLeader()) {
messageSender.send("TEAM_NOT_LEADER");
return false;
}
return true;
};
}
@ClassMapper(Team.class)
@Cached(global = true, cacheDuration = 60)
public TypeMapper<Team> team() {
return new TypeMapper<Team>() {
@Override
public Team map(Chatter sender, PreviousArguments previousArguments, String s) {
return Team.get(s);
}
@Override
public Collection<String> tabCompletes(Chatter sender, PreviousArguments previousArguments, String s) {
return Team.getAll().stream()
.flatMap(team -> Stream.of(team.getTeamName(), team.getTeamKuerzel()))
.toList();
}
};
}
private boolean checkTeamName(Chatter sender, Team team, String arg){
Team t = Team.get(arg);
if(t != null && t.getTeamId() != team.getTeamId()){
sender.system("TEAM_NAME_TAKEN");
return true;
}
return false;
}
private boolean checkTeamKuerzel(Chatter sender, Team team, String arg){
Team t = Team.get(arg);
if(t != null && (team == null || t.getTeamId() != team.getTeamId())){
sender.system("TEAM_KUERZEL_TAKEN");
return true;
}
return false;
}
private boolean notDuringEvent(Chatter sender){
if(Event.get() != null){
sender.system("TEAM_NOT_IN_EVENT");
return true;
}
return false;
}
}
@@ -0,0 +1,45 @@
/*
This file is a part of the SteamWar software.
Copyright (C) 2020 SteamWar.de-Serverteam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.velocitycore.commands;
import de.steamwar.velocitycore.listeners.ChatListener;
import de.steamwar.command.SWCommand;
import de.steamwar.messages.Chatter;
import de.steamwar.messages.ChatterGroup;
import de.steamwar.sql.SteamwarUser;
public class TeamchatCommand extends SWCommand {
public TeamchatCommand() {
super("tc", "teamchat");
}
@Register(description = "TC_USAGE")
public void genericCommand(Chatter sender, @ErrorMessage(value = "TC_USAGE", allowEAs = false) String... args) {
SteamwarUser user = sender.user();
if(user.getTeam() == 0){
sender.system("TC_NO_TEAM");
return;
}
ChatListener.sendChat(sender, new ChatterGroup(Chatter.allStream().filter(p -> p.user().getTeam() == user.getTeam())), "CHAT_TEAM", null, String.join(" ", args));
}
}
@@ -0,0 +1,169 @@
/*
This file is a part of the SteamWar software.
Copyright (C) 2020 SteamWar.de-Serverteam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.velocitycore.commands;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.ServerConnection;
import com.velocitypowered.api.proxy.server.RegisteredServer;
import de.steamwar.velocitycore.*;
import de.steamwar.velocitycore.util.BauLock;
import de.steamwar.command.PreviousArguments;
import de.steamwar.command.SWCommand;
import de.steamwar.command.TypeMapper;
import de.steamwar.messages.Chatter;
import de.steamwar.messages.PlayerChatter;
import de.steamwar.persistent.Bauserver;
import de.steamwar.persistent.Storage;
import de.steamwar.persistent.Subserver;
import de.steamwar.sql.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
public class TpCommand extends SWCommand {
public TpCommand(){
super("join", "tp", "teleport");
}
@Register
public void genericCommand(Chatter sender) {
sender.system(Event.get() == null ? "TP_USAGE" : "TP_USAGE_EVENT");
}
@Register
public void teleportCommand(PlayerChatter sender, @Mapper("to") String to, String... rest) {
RegisteredServer server = getTarget(to);
//Give control of teleport command to server
Player p = sender.getPlayer();
if (server == null || server.getPlayersConnected().contains(p)) {
if (rest.length == 0) {
p.spoofChatInput("/tp " + to);
} else {
p.spoofChatInput("/tp " + to + " " + String.join(" ", rest));
}
return;
}
teleport(sender, server);
}
@Mapper("to")
@Cached(cacheDuration = 10)
public TypeMapper<String> tabCompleter() {
return new TypeMapper<>() {
@Override
public String map(Chatter sender, PreviousArguments previousArguments, String s) {
return s;
}
@Override
public Collection<String> tabCompletes(Chatter sender, PreviousArguments previousArguments, String s) {
List<String> list = new ArrayList<>();
for (Player player : VelocityCore.getProxy().getAllPlayers()) {
list.add(player.getUsername());
}
if (Event.get() != null) {
EventStarter.getEventServer().keySet().forEach(teamId -> {
Team team = Team.get(teamId);
list.add(team.getTeamName());
list.add(team.getTeamKuerzel());
});
}
if (Storage.teamServers.containsValue(sender.getPlayer().getCurrentServer().orElseThrow().getServerInfo())) {
Storage.directTabItems.getOrDefault(sender.getPlayer(), Collections.emptyMap()).forEach((uuid, item) -> list.add(item.getProfile().getName()));
}
return list;
}
};
}
public static void teleport(PlayerChatter sender, RegisteredServer server) {
if(CheckCommand.isChecking(sender.getPlayer())){
sender.system("CHECK_CHECKING");
return;
}
Subserver subserver = Subserver.getSubserver(server.getServerInfo());
if(subserver == null) {
sender.getPlayer().createConnectionRequest(server).fireAndForget();
return;
}
switch (subserver.getType()) {
case ARENA:
if (PunishmentCommand.isPunishedWithMessage(sender, Punishment.PunishmentType.NoFightServer))
return;
break;
case BAUSERVER:
Bauserver bauserver = (Bauserver) subserver;
Player checker = VelocityCore.getProxy().getPlayer(bauserver.getOwner()).orElse(null);
if (checker != null && CheckCommand.isChecking(checker)) {
if (!sender.user().hasPerm(UserPerm.CHECK) && CheckCommand.getCheckingSchem(checker).getOwner() != sender.user().getId()) {
sender.system("JOIN_PLAYER_BLOCK");
return;
}
} else if (BauLock.isLocked(SteamwarUser.get(bauserver.getOwner()), sender.user())) {
sender.system("BAU_LOCKED_NOALLOWED");
Chatter.of(bauserver.getOwner()).system("BAU_LOCK_BLOCKED", sender);
return;
} else if (!bauserver.getOwner().equals(sender.user().getUUID()) && BauweltMember.getBauMember(bauserver.getOwner(), sender.user().getUUID()) == null) {
SubserverSystem.sendDeniedMessage(sender, bauserver.getOwner());
sender.system("JOIN_PLAYER_BLOCK");
return;
}
break;
case BUILDER:
if(!sender.user().hasPerm(UserPerm.BUILD)) {
sender.system("JOIN_PLAYER_BLOCK");
return;
}
break;
}
SubserverSystem.sendPlayer(subserver, sender.getPlayer());
}
private static RegisteredServer getTarget(String arg) {
RegisteredServer server = null;
//Get target player server
Player target = VelocityCore.getProxy().getPlayer(arg).orElse(null);
if(target != null)
server = target.getCurrentServer().map(ServerConnection::getServer).orElse(server);
//Get target team event arena
if(server == null){
Team team = Team.get(arg);
if(team != null){
Subserver eventArena = EventStarter.getEventServer().get(team.getTeamId());
if(eventArena != null && Subserver.getServerList().contains(eventArena))
server = eventArena.getRegisteredServer();
}
}
return server;
}
}
@@ -0,0 +1,163 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2022 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.velocitycore.*;
import de.steamwar.velocitycore.inventory.SWInventory;
import de.steamwar.velocitycore.inventory.SWItem;
import de.steamwar.velocitycore.inventory.SWListInv;
import de.steamwar.velocitycore.inventory.SWStreamInv;
import de.steamwar.command.SWCommand;
import de.steamwar.command.TypeValidator;
import de.steamwar.messages.Chatter;
import de.steamwar.messages.Message;
import de.steamwar.messages.PlayerChatter;
import de.steamwar.persistent.Servertype;
import de.steamwar.persistent.Subserver;
import de.steamwar.sql.SteamwarUser;
import de.steamwar.sql.Tutorial;
import de.steamwar.sql.UserPerm;
import java.io.File;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
public class TutorialCommand extends SWCommand {
public TutorialCommand() {
super("tutorial");
}
@Register
public void genericCommand(PlayerChatter sender) {
openInventory(sender, true, false);
}
@Register("rate")
public void rate(PlayerChatter sender) {
sender.getPlayer().spoofChatInput("/tutorial rate");
}
@Register("rate")
public void rate(PlayerChatter sender, int id) {
Tutorial tutorial = Tutorial.get(id);
if(tutorial == null) {
sender.getPlayer().spoofChatInput("/tutorial rate"); // Catch players manually entering numbers
return;
}
rate(sender, tutorial);
}
@Register(value = "create", description = "TUTORIAL_CREATE_HELP")
public void create(PlayerChatter sender, String material, String... name) {
create(sender, String.join(" ", name), material.toUpperCase());
}
@Register("own")
public void own(PlayerChatter sender) {
openInventory(sender, false, true);
}
@Register("unreleased")
public void unreleased(@Validator("unreleased") PlayerChatter sender) {
openInventory(sender, false, false);
}
@Validator("unreleased")
public TypeValidator<Chatter> unreleasedChecker() {
return (sender, value, messageSender) -> sender.user().hasPerm(UserPerm.TEAM);
}
private void openInventory(PlayerChatter sender, boolean released, boolean own) {
SteamwarUser user = sender.user();
new SWStreamInv<>(
sender,
new Message("TUTORIAL_TITLE"),
(click, tutorial) -> {
if(!released && click.isShiftClick() && user.hasPerm(UserPerm.TEAM) && user.getId() != tutorial.getCreator()) {
tutorial.release();
openInventory(sender, released, own);
return;
} else if(own && click.isShiftClick() && click.isRightClick()) {
tutorial.delete();
SubserverSystem.deleteFolder(VelocityCore.local, world(tutorial).getPath());
openInventory(sender, released, own);
return;
}
new ServerStarter().tutorial(sender.getPlayer(), tutorial).start();
},
page -> (own ? Tutorial.getOwn(user.getId(), page, 45) : Tutorial.getPage(page, 45, released)).stream().map(tutorial -> new SWListInv.SWListEntry<>(getTutorialItem(tutorial, own), tutorial)).toList()
).open();
}
private SWItem getTutorialItem(Tutorial tutorial, boolean personalHighlights) {
SWItem item = new SWItem(tutorial.getItem(), new Message("TUTORIAL_NAME", tutorial.getName()));
item.setHideAttributes(true);
item.addLore(new Message("TUTORIAL_BY", SteamwarUser.get(tutorial.getCreator()).getUserName()));
item.addLore(new Message("TUTORIAL_STARS", String.format("%.1f", tutorial.getStars())));
if (personalHighlights)
item.addLore(new Message("TUTORIAL_DELETE"));
if (personalHighlights && tutorial.isReleased())
item.setEnchanted(true);
return item;
}
private void rate(PlayerChatter sender, Tutorial tutorial) {
int[] rates = new int[]{1, 2, 3, 4, 5};
new SWListInv<>(sender, new Message("TUTORIAL_RATE_TITLE"), Arrays.stream(rates).mapToObj(rate -> new SWListInv.SWListEntry<>(new SWItem("NETHER_STAR", new Message("TUTORIAL_RATE", rate)), rate)).toList(), (click, rate) -> {
tutorial.rate(sender.user().getId(), rate);
SWInventory.close(sender);
}).open();
}
private void create(PlayerChatter sender, String name, String item) {
Subserver subserver = Subserver.getSubserver(sender.getPlayer());
SteamwarUser user = sender.user();
File tempWorld = new File(ServerStarter.TEMP_WORLD_PATH, ServerStarter.serverToWorldName(ServerStarter.bauServerName(user)));
if(subserver == null || !subserver.isStarted() || subserver.getType() != Servertype.BAUSERVER || !tempWorld.exists()) {
sender.system("TUTORIAL_CREATE_MISSING");
return;
}
subserver.execute("save-all");
VelocityCore.schedule(() -> {
Tutorial tutorial = Tutorial.create(user.getId(), name, item);
File tutorialWorld = world(tutorial);
if (tutorialWorld.exists())
SubserverSystem.deleteFolder(VelocityCore.local, tutorialWorld.getPath());
ServerStarter.copyWorld(VelocityCore.local, tempWorld.getPath(), tutorialWorld.getPath());
sender.system("TUTORIAL_CREATED");
}).delay(1, TimeUnit.SECONDS).schedule();
}
private File world(Tutorial tutorial) {
return new File(ServerStarter.TUTORIAL_PATH, String.valueOf(tutorial.getTutorialId()));
}
}
@@ -0,0 +1,79 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2020 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.velocitycore.commands;
import de.steamwar.velocitycore.ArenaMode;
import de.steamwar.command.PreviousArguments;
import de.steamwar.command.SWCommandUtils;
import de.steamwar.command.TypeMapper;
import de.steamwar.command.TypeValidator;
import de.steamwar.messages.Chatter;
import de.steamwar.messages.PlayerChatter;
import de.steamwar.sql.Punishment;
import lombok.experimental.UtilityClass;
import java.util.Collection;
@UtilityClass
public class TypeMappers {
public static void init() {
SWCommandUtils.addValidator("arenaPlayer", arenaPlayer());
SWCommandUtils.addMapper("nonHistoricArenaMode", arenaModeTypeMapper(false));
SWCommandUtils.addMapper("historicArenaMode", arenaModeTypeMapper(true));
SWCommandUtils.addMapper("arenaMap", arenaMapTypeMapper());
}
private static TypeValidator<PlayerChatter> arenaPlayer() {
return (sender, player, messageSender) -> !PunishmentCommand.isPunishedWithMessage(player, Punishment.PunishmentType.NoFightServer);
}
private static TypeMapper<ArenaMode> arenaModeTypeMapper(boolean historic) {
return new TypeMapper<>() {
@Override
public ArenaMode map(Chatter sender, PreviousArguments previousArguments, String s) {
return ArenaMode.getByChat(s);
}
@Override
public Collection<String> tabCompletes(Chatter sender, PreviousArguments previousArguments, String s) {
return ArenaMode.getAllChatNames(historic);
}
};
}
private static TypeMapper<String> arenaMapTypeMapper() {
return new TypeMapper<>() {
@Override
public String map(Chatter sender, PreviousArguments previousArguments, String s) {
if (previousArguments.userArgs.length == 0) return null;
return ArenaMode.getByChat(previousArguments.userArgs[previousArguments.userArgs.length - 1]).convertToRealMapName(s);
}
@Override
public Collection<String> tabCompletes(Chatter sender, PreviousArguments previousArguments, String s) {
if (previousArguments.userArgs.length == 0) return null;
ArenaMode arenaMode = ArenaMode.getByChat(previousArguments.userArgs[previousArguments.userArgs.length - 1]);
if (arenaMode == null) return null;
return arenaMode.getMaps();
}
};
}
}
@@ -0,0 +1,45 @@
/*
This file is a part of the SteamWar software.
Copyright (C) 2020 SteamWar.de-Serverteam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.velocitycore.commands;
import de.steamwar.command.SWCommand;
import de.steamwar.messages.Chatter;
import de.steamwar.sql.IgnoreSystem;
import de.steamwar.sql.SteamwarUser;
public class UnIgnoreCommand extends SWCommand {
public UnIgnoreCommand() {
super("unignore");
}
@Register(description = "UNIGNORE_USAGE")
public void genericCommand(Chatter sender, @ErrorMessage("UNIGNORE_NOT_PLAYER") SteamwarUser target) {
SteamwarUser user = sender.user();
if(!IgnoreSystem.isIgnored(user, target)){
sender.system("UNIGNORE_NOT_IGNORED");
return;
}
IgnoreSystem.unIgnore(user, target);
sender.system("UNIGNORE_UNIGNORED", target.getUserName());
}
}
@@ -0,0 +1,60 @@
/*
This file is a part of the SteamWar software.
Copyright (C) 2020 SteamWar.de-Serverteam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.velocitycore.commands;
import de.steamwar.velocitycore.VelocityCore;
import de.steamwar.velocitycore.discord.util.AuthManager;
import de.steamwar.command.SWCommand;
import de.steamwar.messages.Chatter;
import net.dv8tion.jda.api.entities.User;
import java.util.Base64;
import java.util.logging.Level;
public class VerifyCommand extends SWCommand {
public VerifyCommand() {
super("verify");
}
@Register(description = "VERIFY_USAGE")
public void genericCommand(Chatter sender, String code) {
byte[] bytes;
try {
bytes = Base64.getDecoder().decode(code);
} catch (IllegalArgumentException e) {
sender.system("VERIFY_INVALID");
return;
}
if(bytes.length != 16) {
sender.system("VERIFY_INVALID");
return;
}
User user = AuthManager.connectAuth(sender.user(), code);
if(user != null) {
VelocityCore.getLogger().log(Level.INFO,"%s Verified with Discorduser: %s".formatted(sender.user().getUserName(), user.getIdLong()));
sender.system("VERIFY_SUCCESS", user.getAsTag());
} else {
sender.system("VERIFY_INVALID");
}
}
}
@@ -0,0 +1,72 @@
/*
This file is a part of the SteamWar software.
Copyright (C) 2020 SteamWar.de-Serverteam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.velocitycore.commands;
import de.steamwar.command.SWCommand;
import de.steamwar.messages.Chatter;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class WebpasswordCommand extends SWCommand {
public WebpasswordCommand() {
super("webpassword", "webpw", "web");
}
@Register(description = "WEB_USAGE")
public void genericCommand(Chatter sender, String password) {
if(password.length() < 8) {
sender.system("WEB_PASSWORD_LENGTH");
return;
}
ProcessBuilder pb = new ProcessBuilder("php", "/var/www/register.php", sender.user().getUserName(), password);
pb.redirectErrorStream(true);
try {
Process regProcess = pb.start();
BufferedReader reader = new BufferedReader(new InputStreamReader(regProcess.getInputStream()));
String errorLine;
if((errorLine = reader.readLine()) != null) {
if ("updated".equals(errorLine)) {
sender.system("WEB_UPDATED");
return;
} else {
throw new SecurityException("Could not create webaccount " + errorLine);
}
}
sender.system("WEB_CREATED");
} catch (IOException e) {
throw new SecurityException("Could not create webaccount", e);
}
}
public static void changeUsername(String oldUsername, String newUsername){
ProcessBuilder pb = new ProcessBuilder("php", "/var/www/changename.php", oldUsername, newUsername);
try {
pb.start();
} catch (IOException e) {
throw new SecurityException("Could not change username", e);
}
}
}
@@ -0,0 +1,176 @@
/*
This file is a part of the SteamWar software.
Copyright (C) 2020 SteamWar.de-Serverteam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.velocitycore.commands;
import com.velocitypowered.api.proxy.Player;
import de.steamwar.persistent.Storage;
import de.steamwar.velocitycore.VelocityCore;
import de.steamwar.velocitycore.mods.ModUtils;
import de.steamwar.command.PreviousArguments;
import de.steamwar.command.SWCommand;
import de.steamwar.command.TypeMapper;
import de.steamwar.messages.Chatter;
import de.steamwar.messages.Message;
import de.steamwar.sql.*;
import lombok.AllArgsConstructor;
import lombok.Getter;
import net.kyori.adventure.text.event.ClickEvent;
import java.sql.Timestamp;
import java.text.DecimalFormat;
import java.time.Instant;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class WhoisCommand extends SWCommand {
public WhoisCommand() {
super("whois");
}
@Register(description = "WHOIS_USAGE")
public void whois(Chatter sender, long id, WhoisParameterTypes... parameters) {
if(!sender.user().hasPerm(UserPerm.ADMINISTRATION)) {
sender.system("UNKNOWN_PLAYER");
return;
}
// SW ID or Discord ID
whois(sender, id < Integer.MAX_VALUE ? SteamwarUser.get((int) id) : SteamwarUser.get(id), parameters);
}
@Register(description = "WHOIS_USAGE")
public void whois(Chatter sender, SteamwarUser user, WhoisParameterTypes... parameters) {
EnumSet<WhoisParameterTypes> parameterTypes = parameters.length == 0 ? EnumSet.noneOf(WhoisParameterTypes.class) : EnumSet.copyOf(Arrays.asList(parameters));
Team team = Team.get(user.getTeam());
sender.system("WHOIS_USERNAME", user.getUserName());
sender.system("WHOIS_PREFIX", user.prefix().getColorCode() + user.prefix().getChatPrefix());
sender.system("WHOIS_TEAM", new Message("WHOIS_TEAM_HOVER", team.getTeamName()), ClickEvent.runCommand("/team info " + team.getTeamKuerzel()), team.getTeamColor(), team.getTeamKuerzel(), team.getTeamName());
if (!sender.user().hasPerm(UserPerm.TEAM))
return;
if (sender.user().hasPerm(UserPerm.MODERATION)) {
sender.system("WHOIS_ID", user.getId());
sender.system("WHOIS_UUID", new Message("WHOIS_UUID_HOVER"), ClickEvent.copyToClipboard(user.getUUID().toString()), user.getUUID().toString());
if (user.getDiscordId() != null)
sender.system("WHOIS_DISCORD_ID", user.getDiscordId());
sender.system("WHOIS_PERMS", user.perms().stream().map(Enum::name).collect(Collectors.joining(", ")));
}
Player target = VelocityCore.getProxy().getPlayer(user.getUUID()).orElse(null);
Timestamp firstJoin = user.getFirstjoin();
double onlineTime = user.getOnlinetime();
if(firstJoin == null && target != null) {
firstJoin = Storage.sessions.get(target);
}
if(firstJoin != null)
sender.system("WHOIS_JOINED_FIRST", firstJoin.toString());
sender.system("WHOIS_HOURS_PLAYED", new DecimalFormat("###.##").format(onlineTime / 3600d));
if(target != null) {
sender.system("WHOIS_CURRENT_PLAYED", new DecimalFormat("####.##").format((Instant.now().getEpochSecond() - Storage.sessions.get(target).toInstant().getEpochSecond()) / 60d));
sender.system("WHOIS_CURRENT_SERVER", target.getCurrentServer().orElseThrow().getServerInfo().getName());
sender.system("WHOIS_CURRENT_PROTOCOL", target.getProtocolVersion().getMostRecentSupportedVersion());
List<Mod> mods = ModUtils.getPlayerModMap().get(user.getUUID());
if(mods == null)
mods = Collections.emptyList();
sender.system("WHOIS_PLATFORM", mods.isEmpty() ? "Vanilla" : mods.get(0).getPlatform().toString());
if (parameterTypes.contains(WhoisParameterTypes.MOD)) {
if (!mods.isEmpty()) {
sender.system("WHOIS_ACTIVE_MODS", mods.size(), mods.stream().map(mod -> "§" + mod.getModType().getColorCode() + mod.getModName()).collect(Collectors.joining("§8, ")));
} else {
sender.system("WHOIS_NO_ACTIVE_MODS");
}
}
}
sender.system("WHOIS_PUNISHMENTS");
List<Punishment> punishmentList = Punishment.getAllPunishmentsOfPlayer(user.getId());
Set<Punishment.PunishmentType> found = new HashSet<>();
boolean isPunished = false;
boolean all = parameterTypes.contains(WhoisParameterTypes.ALL);
for (Punishment punishment : punishmentList) {
if (!all && !punishment.getType().isMulti() && !found.add(punishment.getType())) {
continue;
}
if (!all && !punishment.isCurrent()) {
continue;
}
sender.prefixless("WHOIS_PUNISHMENT", SteamwarUser.get(punishment.getPunisher()), punishment.getType().name(), duration(sender, punishment.getStartTime(), false), duration(sender, punishment.getEndTime(), punishment.isPerma()), punishment.getReason());
isPunished = true;
}
if (!isPunished) {
sender.system(all ? "WHOIS_NO_ALL_PUNISHMENT" : "WHOIS_NO_PUNISHMENT");
}
}
private Message duration(Chatter sender, Timestamp timestamp, boolean perma) {
if(perma)
return new Message("PUNISHMENT_PERMA");
else
return new Message("PLAIN_STRING", timestamp.toLocalDateTime().format(DateTimeFormatter.ofPattern(sender.parseToPlain("TIMEFORMAT"))));
}
@ClassMapper(value = WhoisParameterTypes.class, local = true)
public TypeMapper<WhoisParameterTypes> argumentTypeMapper() {
WhoisParameterTypes[] values = WhoisParameterTypes.values();
return new TypeMapper<WhoisParameterTypes>() {
@Override
public WhoisParameterTypes map(Chatter sender, PreviousArguments previousArguments, String s) {
SteamwarUser user = sender.user();
return Stream.of(values)
.filter(p -> user.hasPerm(p.perm))
.filter(p -> p.getTabCompletes().contains(s))
.findFirst()
.orElse(null);
}
@Override
public Collection<String> tabCompletes(Chatter sender, PreviousArguments previousArguments, String s) {
SteamwarUser user = sender.user();
return Stream.of(values)
.filter(p -> user.hasPerm(p.perm))
.flatMap(p -> p.getTabCompletes().stream())
.collect(Collectors.toList());
}
};
}
@AllArgsConstructor
public enum WhoisParameterTypes {
ALL(Arrays.asList("-a", "-all"), UserPerm.TEAM),
MOD(Arrays.asList("-m", "-mod", "-mods"), UserPerm.MODERATION);
@Getter
private final List<String> tabCompletes;
private final UserPerm perm;
}
}
@@ -0,0 +1,218 @@
/*
This file is a part of the SteamWar software.
Copyright (C) 2020 SteamWar.de-Serverteam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.velocitycore.discord;
import de.steamwar.command.SWCommand;
import de.steamwar.messages.Chatter;
import de.steamwar.sql.Event;
import de.steamwar.velocitycore.VelocityCore;
import de.steamwar.velocitycore.discord.channels.*;
import de.steamwar.velocitycore.discord.listeners.ChannelListener;
import de.steamwar.velocitycore.discord.listeners.DiscordSchemUpload;
import de.steamwar.velocitycore.discord.listeners.DiscordTeamEvent;
import de.steamwar.velocitycore.discord.listeners.DiscordTicketHandler;
import de.steamwar.velocitycore.discord.util.AuthManager;
import lombok.Getter;
import net.dv8tion.jda.api.*;
import net.dv8tion.jda.api.entities.*;
import net.dv8tion.jda.api.events.message.guild.GuildMessageReceivedEvent;
import net.dv8tion.jda.api.exceptions.ErrorResponseException;
import net.dv8tion.jda.api.interactions.commands.Command;
import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.build.CommandData;
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
import net.dv8tion.jda.api.interactions.components.ActionRow;
import net.dv8tion.jda.api.interactions.components.Button;
import net.dv8tion.jda.api.requests.restaction.CommandListUpdateAction;
import net.dv8tion.jda.api.utils.MemberCachePolicy;
import javax.security.auth.login.LoginException;
import java.awt.*;
import java.util.List;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
public class DiscordBot {
public static final String ARGUMENT_NAME = "arguments";
@Getter
private static DiscordBot instance;
@Getter
private static final Map<String, SWCommand> commands = new HashMap<>();
public static void withBot(Consumer<DiscordBot> consumer) {
if(instance != null)
consumer.accept(instance);
}
public static Guild getGuild() {
return instance.jda.getGuildById(instance.config.getGuild());
}
@Getter
private DiscordChannel announcementChannel;
@Getter
private DiscordChatRoom ingameChat;
@Getter
private DiscordChatRoom serverTeamChat;
@Getter
private StaticMessageChannel eventChannel;
@Getter
private ChecklistChannel checklistChannel;
@Getter
private final DiscordConfig config;
@Getter
private final JDA jda;
public DiscordBot(DiscordConfig config) {
this.config = config;
try {
jda = JDABuilder
.createDefault(config.getToken())
.setStatus(OnlineStatus.ONLINE)
.setMemberCachePolicy(MemberCachePolicy.ONLINE)
.build();
} catch (LoginException e) {
throw new SecurityException("Could not login", e);
}
instance = this;
VelocityCore.schedule(this::asyncInit).schedule();
}
private void asyncInit() {
try {
jda.awaitReady();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
activity();
new StaticMessageChannel(config.channel("roles"), () -> new MessageBuilder()
.setContent("**Rollenvergabe**\nKlicke um eine Rolle zu bekommen:")
.setActionRows(ActionRow.of(config.getRoles().values().stream().map(DiscordConfig.DiscordRole::toButton).toArray(Button[]::new))), event -> InteractionReply.reply(event, reply -> {
Member member = event.getMember();
Guild guild = event.getGuild();
Role role = guild.getRoleById(event.getComponentId());
if (member.getRoles().stream().anyMatch(role::equals)) {
guild.removeRoleFromMember(member, role).queue();
reply.system("DC_ROLE_REMOVED", role.getAsMention());
} else {
guild.addRoleToMember(member, role).queue();
reply.system("DC_ROLE_ADDED", role.getAsMention());
}
}));
new StaticMessageChannel(config.channel("rules"), () -> new MessageBuilder()
.setEmbeds(new EmbedBuilder()
.setDescription(String.join("\n", config.getRules()))
.setColor(Color.GRAY)
.setAuthor("SteamWar", "https://steamwar.de")
.setTitle("Regeln und Infos")
.build())
.setActionRows(
ActionRow.of(Button.link("https://steamwar.de", "Website"), Button.link("https://steamwar.de/youtube", "YouTube")),
ActionRow.of(Button.primary("auth", Emoji.fromUnicode("U+2705")).withLabel("Minecraft verknüpfen"))
), event -> {
if(event.getComponentId().equals("auth"))
event.reply("Gebe innerhalb der nächsten 10 Minuten ``/verify " + AuthManager.createDiscordAuthToken(event.getUser()) + "`` auf dem Minecraft Server ein").setEphemeral(true).queue();
});
new StaticMessageChannel(config.channel("ticket"), () -> new MessageBuilder()
.setEmbeds(new EmbedBuilder()
.setDescription("Hier kannst du Tickets öffnen, welche nur von dir und Teammitgliedern eingesehen werden können.")
.setTitle("SteamWar Tickets")
.setColor(Color.RED)
.build())
.setActionRows(ActionRow.of(Arrays.stream(DiscordTicketType.values()).map(DiscordTicketType::toButton).toArray(Button[]::new))), DiscordTicketHandler::openTicket);
eventChannel = new StaticMessageChannel(config.channel("events"), EventChannel::get);
checklistChannel = new ChecklistChannel(config.channel("checklist"));
announcementChannel = new DiscordChannel(config.channel("announcement")) {
@Override
public void received(GuildMessageReceivedEvent event) {
Chatter.broadcast().system("ALERT", event.getMessage().getContentDisplay());
}
};
ingameChat = new DiscordChatRoom(config.channel("ingame"), "CHAT_DISCORD_GLOBAL", Chatter::broadcast);
serverTeamChat = new DiscordChatRoom(config.channel("serverteam"), "CHAT_SERVERTEAM", Chatter::serverteam);
VelocityCore.schedule(() -> {
try {
activity();
eventChannel.update();
checklistChannel.update();
} catch (ErrorResponseException e) {
//ignored
}
}).repeat(30, TimeUnit.SECONDS).schedule();
jda.addEventListener(
new DiscordTicketHandler(),
new DiscordTeamEvent(),
new ChannelListener(),
new DiscordSchemUpload()
);
commandSetup(jda.retrieveCommands().complete(), jda.updateCommands());
}
private final OptionData commandArgument = new OptionData(OptionType.STRING, ARGUMENT_NAME, "Command arguments", false);
private void commandSetup(List<Command> existing, CommandListUpdateAction updateCommands) {
Set<String> correctCommands = new HashSet<>();
for(Command command : existing) {
if(!commands.containsKey(command.getName())) {
command.delete().complete();
continue;
}
List<Command.Option> options = command.getOptions();
if(options.size() != 1 || options.get(0).getType() != OptionType.STRING)
command.editCommand().clearOptions().addOptions(commandArgument).complete();
correctCommands.add(command.getName());
}
updateCommands
.addCommands(commands
.keySet().stream()
.filter(command -> !correctCommands.contains(command))
.filter(command -> command.matches("^[\\w-]+$"))
.map(command -> new CommandData(command, command).addOptions(commandArgument))
.toArray(CommandData[]::new))
.queue();
}
private boolean activityToggle = false;
private void activity() {
if(activityToggle) {
Event event = Event.get();
jda.getPresence().setActivity(event != null ? Activity.competing("dem Event " + event.getEventName()) : Activity.playing("auf SteamWar.de"));
} else {
int count = VelocityCore.getProxy().getPlayerCount();
jda.getPresence().setActivity(Activity.playing(count == 1 ? "mit 1 Spieler" : ("mit " + count + " Spielern")));
}
activityToggle = !activityToggle;
}
}
@@ -0,0 +1,70 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2024 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.discord;
import de.steamwar.velocitycore.Config;
import de.steamwar.velocitycore.VelocityCore;
import lombok.Getter;
import lombok.NoArgsConstructor;
import net.dv8tion.jda.api.entities.Emoji;
import net.dv8tion.jda.api.interactions.components.Button;
import net.dv8tion.jda.api.interactions.components.ButtonStyle;
import java.io.File;
import java.util.List;
import java.util.Map;
@Getter
public class DiscordConfig {
public static DiscordConfig load() {
File file = new File(System.getProperty("user.home"), "discord.yml");
if(!file.exists() || VelocityCore.get().getConfig().isEventmode())
return null;
return Config.load(DiscordConfig.class, file, description -> description.addPropertyParameters("roles", String.class, DiscordRole.class));
}
public String channel(String type) {
return channels.get(type);
}
private String token;
private String guild;
private Map<String, String> channels;
private Map<String, String> ranks;
private Map<String, DiscordRole> roles;
private List<String> rules;
private String ticketcategory;
@NoArgsConstructor
public static class DiscordRole {
private String emoji;
private String label;
private String roleId;
public Button toButton() {
return Button.of(ButtonStyle.SECONDARY, roleId, label, Emoji.fromUnicode(emoji));
}
}
}
@@ -0,0 +1,46 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2024 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.discord;
import lombok.AllArgsConstructor;
import net.dv8tion.jda.api.entities.Emoji;
import net.dv8tion.jda.api.interactions.components.Button;
import net.dv8tion.jda.api.interactions.components.ButtonStyle;
@AllArgsConstructor
public enum DiscordTicketType {
REPORT("U+1F46E", "Spieler melden", ButtonStyle.DANGER),
IDEA("U+1F4A1", "Feature vorschlagen", ButtonStyle.SUCCESS),
BUG("U+1F41B", "Bug melden", ButtonStyle.SECONDARY),
QUESTION("U+2753", "Fragen", ButtonStyle.PRIMARY),
APPEAL("U+1F528", "Entbannungsantrag", ButtonStyle.SECONDARY);
private final String emoji;
private final String label;
private final ButtonStyle style;
public Button toButton() {
return Button.of(style, name().toLowerCase(), Emoji.fromUnicode(emoji)).withLabel(label);
}
public String introduction() {
return "DC_TICKETINTRO_" + name();
}
}
@@ -0,0 +1,48 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2020 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.velocitycore.discord.channels;
import de.steamwar.sql.SchematicNode;
import de.steamwar.sql.SteamwarUser;
import de.steamwar.velocitycore.commands.CheckCommand;
import java.util.ArrayList;
import java.util.List;
public class ChecklistChannel extends DiscordChannel {
private final List<Integer> lastSchematics = new ArrayList<>();
public ChecklistChannel(String channel) {
super(channel);
}
public void update() {
List<SchematicNode> schems = CheckCommand.getSchemsToCheck();
lastSchematics.removeIf(id -> schems.stream().noneMatch(schem -> schem.getId() == id));
schems.removeIf(schem -> lastSchematics.contains(schem.getId()));
for(SchematicNode schem : schems) {
system("CHECK_LIST_TO_CHECK", CheckCommand.getWaitTime(schem), schem.getSchemtype().getKuerzel(), SteamwarUser.get(schem.getOwner()).getUserName(), schem.getName());
lastSchematics.add(schem.getId());
}
}
}
@@ -0,0 +1,99 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2024 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.discord.channels;
import de.steamwar.velocitycore.discord.DiscordBot;
import de.steamwar.velocitycore.discord.listeners.ChannelListener;
import de.steamwar.messages.Chatter;
import de.steamwar.messages.Message;
import de.steamwar.sql.SteamwarUser;
import lombok.AllArgsConstructor;
import lombok.Getter;
import net.dv8tion.jda.api.MessageBuilder;
import net.dv8tion.jda.api.entities.MessageChannel;
import net.dv8tion.jda.api.entities.User;
import net.dv8tion.jda.api.events.interaction.GenericComponentInteractionCreateEvent;
import net.dv8tion.jda.api.events.message.guild.GuildMessageReceivedEvent;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
@AllArgsConstructor
public class DiscordChannel extends Chatter.PlayerlessChatter {
public static SteamwarUser userOrPublic(User dcUser) {
SteamwarUser user = SteamwarUser.get(dcUser.getIdLong());
return user != null ? user : SteamwarUser.get(0);
}
private final SteamwarUser user;
@Getter
private final MessageChannel channel;
public DiscordChannel(User user) {
this(userOrPublic(user), user.openPrivateChannel().complete());
}
public DiscordChannel(String channel) {
this(SteamwarUser.get(-1), DiscordBot.getGuild().getTextChannelById(channel));
ChannelListener.getChannels().put(this.channel, this);
}
public void send(String message) {
send(new MessageBuilder()
.append(message
.replace("&", "")
.replace("@everyone", "`@everyone`")
.replace("@here", "`@here`")
.replaceAll("<[@#]!?\\d+>", "`$0`")));
}
public void send(MessageBuilder builder) {
channel.sendMessage(builder.build()).queue();
}
public void received(GuildMessageReceivedEvent event) {
event.getMessage().delete().queue();
}
public void received(GenericComponentInteractionCreateEvent event) {
//ignored
}
@Override
public SteamwarUser user() {
return user;
}
@Override
public boolean chatShown() {
return true;
}
@Override
public Component parse(boolean prefixed, Message message) {
return super.parse(false, message);
}
@Override
public void sendMessage(Component msg) {
send(PlainTextComponentSerializer.plainText().serialize(msg));
}
}
@@ -0,0 +1,52 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2024 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.discord.channels;
import de.steamwar.velocitycore.listeners.ChatListener;
import de.steamwar.messages.Chatter;
import de.steamwar.messages.ChatterGroup;
import de.steamwar.sql.Punishment;
import de.steamwar.sql.SteamwarUser;
import net.dv8tion.jda.api.events.message.guild.GuildMessageReceivedEvent;
import java.util.function.Supplier;
public class DiscordChatRoom extends DiscordChannel {
private final String format;
private final Supplier<ChatterGroup> target;
public DiscordChatRoom(String channel, String format, Supplier<ChatterGroup> target) {
super(channel);
this.format = format;
this.target = target;
}
@Override
public void received(GuildMessageReceivedEvent event) {
SteamwarUser user = SteamwarUser.get(event.getAuthor().getIdLong());
if (user == null || event.getMessage().getContentRaw().length() > 250 || user.isPunished(Punishment.PunishmentType.Ban) || user.isPunished(Punishment.PunishmentType.Mute)) {
event.getMessage().delete().queue();
return;
}
ChatListener.sendChat(Chatter.of(user), target.get(), format, null, event.getMessage().getContentDisplay().replace('§', '&').replace('\n', ' '));
}
}
@@ -0,0 +1,119 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2020 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.velocitycore.discord.channels;
import de.steamwar.sql.Event;
import de.steamwar.sql.EventFight;
import de.steamwar.sql.Team;
import de.steamwar.sql.TeamTeilnahme;
import lombok.experimental.UtilityClass;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.MessageBuilder;
import net.dv8tion.jda.api.entities.Emoji;
import net.dv8tion.jda.api.interactions.components.ActionRow;
import net.dv8tion.jda.api.interactions.components.selections.SelectionMenu;
import java.awt.*;
import java.sql.Timestamp;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.stream.Collectors;
@UtilityClass
public class EventChannel {
public MessageBuilder get() {
if (Event.get() == null)
return updateComing();
return updateCurrent();
}
private MessageBuilder updateComing() {
EmbedBuilder embedBuilder = new EmbedBuilder()
.setColor(Color.GRAY)
.setTitle("Zukünftige Events")
.setAuthor("SteamWar", "https://www.steamwar.de");
SelectionMenu.Builder menuBuilder = SelectionMenu.create("eventName")
.setPlaceholder("Wähle ein Event aus!")
.setMinValues(1)
.setMaxValues(1);
Timestamp now = Timestamp.from(Instant.now());
List<Event> events = Event.getComing();
events.forEach(event -> {
StringBuilder st = new StringBuilder();
if (event.getDeadline().after(now)) {
st.append("Deadline: <t:").append(event.getDeadline().getTime() / 1000).append(":R>\n");
}
st.append("Start: <t:").append(event.getStart().getTime() / 1000).append(":R>");
String teilname = TeamTeilnahme.getTeams(event.getEventID()).stream().map(Team::getTeamKuerzel).collect(Collectors.joining(", "));
if (!teilname.isEmpty()) {
st.append("\nAngemeldete Teams: ").append(teilname);
}
embedBuilder.addField(event.getEventName(), st.toString(), false);
if(event.getDeadline().after(Timestamp.from(Instant.now()))) {
menuBuilder.addOption(event.getEventName(), event.getEventID() + "", "An " + event.getEventName() + " teilnehmen", Emoji.fromUnicode("U+1F4DD"));
}
});
MessageBuilder messageBuilder = new MessageBuilder()
.setEmbeds(embedBuilder.build());
if(events.stream().anyMatch(event -> event.getDeadline().after(Timestamp.from(Instant.now())))) {
messageBuilder.setActionRows(ActionRow.of(menuBuilder.build()));
}
return messageBuilder;
}
private MessageBuilder updateCurrent() {
Event event = Event.get();
EmbedBuilder embedBuilder = new EmbedBuilder()
.setColor(Color.GRAY)
.setTitle("Event: " + event.getEventName())
.setAuthor("SteamWar", "https://www.steamwar.de");
Instant now = Instant.now();
EventFight.getEvent(event.getEventID()).forEach(eventFight -> {
Team teamBlue = Team.get(eventFight.getTeamBlue());
Team teamRed = Team.get(eventFight.getTeamRed());
StringBuilder st = new StringBuilder();
st.append("Fightbeginn: <t:").append(eventFight.getStartTime().getTime() / 1000).append(":R>");
if(now.isAfter(eventFight.getStartTime().toInstant().plus(35, ChronoUnit.MINUTES))) {
st.append("\n");
if (eventFight.getErgebnis() == 1) {
st.append("Sieg ").append(teamBlue.getTeamKuerzel());
} else if (eventFight.getErgebnis() == 2) {
st.append("Sieg ").append(teamRed.getTeamKuerzel());
} else {
st.append("Unentschieden");
}
}
embedBuilder.addField(teamBlue.getTeamKuerzel() + " vs. " + teamRed.getTeamKuerzel(), st.toString(), true);
});
return StaticMessageChannel.toMessageBuilder(embedBuilder);
}
}
@@ -0,0 +1,65 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2024 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.discord.channels;
import net.dv8tion.jda.api.interactions.Interaction;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
public class InteractionReply extends DiscordChannel {
public static void reply(Interaction interaction, Consumer<InteractionReply> consumer) {
InteractionReply reply = new InteractionReply(interaction);
consumer.accept(reply);
reply.submit();
}
private final Interaction interaction;
private boolean replied = false;
private final List<String> messages = new ArrayList<>();
private InteractionReply(Interaction interaction) {
super(interaction.getUser());
this.interaction = interaction;
}
@Override
public void sendMessage(Component msg) {
if(replied) {
super.sendMessage(msg);
return;
}
messages.add(PlainTextComponentSerializer.plainText().serialize(msg));
}
public void submit() {
(messages.isEmpty() ?
interaction.deferReply() :
interaction.reply(String.join("\n", messages))
).setEphemeral(true).queue();
replied = true;
}
}
@@ -0,0 +1,69 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2024 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.discord.channels;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.MessageBuilder;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.events.interaction.GenericComponentInteractionCreateEvent;
import java.util.function.Consumer;
import java.util.function.Supplier;
public class StaticMessageChannel extends DiscordChannel {
public static MessageBuilder toMessageBuilder(EmbedBuilder embedBuilder) {
MessageBuilder messageBuilder = new MessageBuilder();
messageBuilder.setEmbeds(embedBuilder.build());
return messageBuilder;
}
private Message message;
private final Supplier<MessageBuilder> supplier;
private final Consumer<GenericComponentInteractionCreateEvent> interaction;
public StaticMessageChannel(String channel, Supplier<MessageBuilder> supplier) {
this(channel, supplier, event -> {});
}
public StaticMessageChannel(String channel, Supplier<MessageBuilder> supplier, Consumer<GenericComponentInteractionCreateEvent> interaction) {
super(channel);
this.supplier = supplier;
this.interaction = interaction;
if(getChannel().hasLatestMessage())
message = getChannel().getIterableHistory().complete().stream().filter(m -> m.getAuthor().isBot()).findFirst().orElse(null);
update();
}
public void update() {
if (message == null) {
getChannel().sendMessage(supplier.get().build()).queue(m -> message = m);
} else {
message.editMessage(supplier.get().build()).queue();
}
}
@Override
public void received(GenericComponentInteractionCreateEvent event) {
interaction.accept(event);
}
}
@@ -0,0 +1,91 @@
/*
* This file is a part of the SteamWar software.
* <p>
* Copyright (C) 2021 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.
* <p>
* 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.
* <p>
* 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.discord.listeners;
import de.steamwar.command.SWCommand;
import de.steamwar.sql.UserPerm;
import de.steamwar.velocitycore.VelocityCore;
import de.steamwar.velocitycore.discord.DiscordBot;
import de.steamwar.velocitycore.discord.channels.DiscordChannel;
import de.steamwar.velocitycore.discord.channels.InteractionReply;
import lombok.Getter;
import net.dv8tion.jda.api.entities.ChannelType;
import net.dv8tion.jda.api.entities.MessageChannel;
import net.dv8tion.jda.api.events.interaction.GenericComponentInteractionCreateEvent;
import net.dv8tion.jda.api.events.interaction.SlashCommandEvent;
import net.dv8tion.jda.api.events.message.guild.GuildMessageReceivedEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import net.dv8tion.jda.api.interactions.InteractionType;
import net.dv8tion.jda.api.interactions.commands.OptionMapping;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
public class ChannelListener extends ListenerAdapter {
@Getter
private static final Map<MessageChannel, DiscordChannel> channels = new HashMap<>();
@Override
public void onGuildMessageReceived(@NotNull GuildMessageReceivedEvent event) {
if(event.getAuthor().isBot())
return;
DiscordChannel channel = channels.get(event.getChannel());
if(channel != null)
channel.received(event);
}
@Override
public void onGenericComponentInteractionCreate(@NotNull GenericComponentInteractionCreateEvent event) {
if(event.getType() != InteractionType.COMPONENT)
return;
DiscordChannel channel = channels.get(event.getChannel());
if(channel != null) {
channel.received(event);
return;
}
if(event.getChannelType() == ChannelType.PRIVATE && event.getComponentId().equals("tada"))
event.reply(":tada:").queue();
}
@Override
public void onSlashCommand(@NotNull SlashCommandEvent event) {
InteractionReply.reply(event, sender -> {
if(sender.user().getDiscordId() == null)
return;
OptionMapping option = event.getOption(DiscordBot.ARGUMENT_NAME);
String args = "";
if(option != null)
args = option.getAsString();
VelocityCore.getLogger().log(Level.INFO, "%s -> executed Discord command /%s %s".formatted(sender.user().getUserName(), event.getName(), args));
SWCommand command = DiscordBot.getCommands().get(event.getName());
UserPerm permission = command.getPermission();
if(permission != null && !sender.user().perms().contains(permission))
return;
command.execute(sender, args.split(" "));
});
}
}
@@ -0,0 +1,90 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2024 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.discord.listeners;
import de.steamwar.velocitycore.VelocityCore;
import de.steamwar.velocitycore.discord.channels.DiscordChannel;
import de.steamwar.sql.NodeData;
import de.steamwar.sql.Punishment;
import de.steamwar.sql.SchematicNode;
import de.steamwar.sql.SteamwarUser;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.events.message.priv.PrivateMessageReceivedEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.logging.Level;
public class DiscordSchemUpload extends ListenerAdapter {
private static final List<String> SCHEM_FILE_ENDINGS = Arrays.asList(".schem", ".schematic");
@Override
public void onPrivateMessageReceived(PrivateMessageReceivedEvent event) {
Message message = event.getMessage();
if(message.getAttachments().isEmpty())
return;
DiscordChannel sender = new DiscordChannel(event.getAuthor());
SteamwarUser user = sender.user();
if(user.getId() == 0) {
sender.system("DC_UNLINKED");
return;
}
if(user.isPunished(Punishment.PunishmentType.NoSchemReceiving)) {
sender.system("DC_SCHEMUPLOAD_NOPERM");
return;
}
for (Message.Attachment attachment : message.getAttachments()) {
String fileName = attachment.getFileName();
int dot = fileName.lastIndexOf('.');
if(dot == -1 || !SCHEM_FILE_ENDINGS.contains(fileName.substring(dot).toLowerCase())) {
sender.system("DC_SCHEMUPLOAD_IGNORED", attachment.getFileName());
continue;
}
String name = fileName.substring(0, dot);
if(SchematicNode.invalidSchemName(new String[] {name})) {
sender.system("DC_SCHEMUPLOAD_INVCHAR", name);
continue;
}
SchematicNode node = SchematicNode.getSchematicNode(user.getId(), name, (Integer) null);
if(node == null)
node = SchematicNode.createSchematic(user.getId(), name, null);
try (InputStream in = attachment.retrieveInputStream().get()) {
NodeData.get(node).saveFromStream(in, fileName.substring(dot).equalsIgnoreCase(".schem"));
sender.system("DC_SCHEMUPLOAD_SUCCESS", name);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} catch (ExecutionException | IOException e) {
VelocityCore.getLogger().log(Level.SEVERE, "Could not upload schem \"%s\" for user \"%s\"".formatted(name, user.getUserName()), e);
sender.system("DC_SCHEMUPLOAD_ERROR", name);
}
}
}
}
@@ -0,0 +1,67 @@
/*
This file is a part of the SteamWar software.
Copyright (C) 2020 SteamWar.de-Serverteam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.velocitycore.discord.listeners;
import de.steamwar.velocitycore.VelocityCore;
import de.steamwar.velocitycore.discord.DiscordBot;
import de.steamwar.velocitycore.discord.channels.InteractionReply;
import de.steamwar.sql.Event;
import net.dv8tion.jda.api.events.interaction.SelectionMenuEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import org.jetbrains.annotations.NotNull;
public class DiscordTeamEvent extends ListenerAdapter {
private final String eventsChannel = DiscordBot.getInstance().getConfig().channel("events");
@Override
public void onSelectionMenu(@NotNull SelectionMenuEvent event) {
if(!event.getChannel().getId().equals(eventsChannel))
return;
if(event.getSelectedOptions().isEmpty()) {
event.deferReply(true).queue();
return;
}
InteractionReply.reply(event, reply -> {
int eventId;
try {
eventId = Integer.parseInt(event.getSelectedOptions().get(0).getValue());
} catch (NumberFormatException e) {
reply.system("UNKNOWN_EVENT");
return;
}
if(reply.user().getId() == 0) {
reply.system("DC_UNLINKED");
return;
}
Event tournament = Event.get(eventId);
if(tournament == null){
reply.system("UNKNOWN_EVENT");
return;
}
VelocityCore.get().getTeamCommand().event(reply, tournament);
});
}
}
@@ -0,0 +1,165 @@
/*
This file is a part of the SteamWar software.
Copyright (C) 2020 SteamWar.de-Serverteam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.velocitycore.discord.listeners;
import de.steamwar.messages.Chatter;
import de.steamwar.messages.ChatterGroup;
import de.steamwar.messages.Message;
import de.steamwar.sql.SteamwarUser;
import de.steamwar.sql.UserPerm;
import de.steamwar.velocitycore.discord.DiscordBot;
import de.steamwar.velocitycore.discord.DiscordTicketType;
import de.steamwar.velocitycore.discord.channels.DiscordChannel;
import de.steamwar.velocitycore.discord.channels.InteractionReply;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.MessageBuilder;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.Emoji;
import net.dv8tion.jda.api.entities.MessageChannel;
import net.dv8tion.jda.api.entities.TextChannel;
import net.dv8tion.jda.api.entities.User;
import net.dv8tion.jda.api.events.interaction.GenericComponentInteractionCreateEvent;
import net.dv8tion.jda.api.events.message.guild.GuildMessageReceivedEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import net.dv8tion.jda.api.interactions.components.ActionRow;
import net.dv8tion.jda.api.interactions.components.Button;
import net.kyori.adventure.text.event.ClickEvent;
import org.jetbrains.annotations.NotNull;
import java.awt.*;
import java.time.Instant;
import java.util.LinkedList;
import java.util.stream.Collectors;
public class DiscordTicketHandler extends ListenerAdapter {
private static final String TICKET_CATEGORY = DiscordBot.getInstance().getConfig().getTicketcategory();
private static final String TICKET_LOG = DiscordBot.getInstance().getConfig().channel("ticketlog");
private static final String TICKET_CHANNEL = DiscordBot.getInstance().getConfig().channel("ticket");
public static void openTicket(GenericComponentInteractionCreateEvent event) {
DiscordTicketType ticketType = DiscordTicketType.valueOf(event.getComponentId().toUpperCase());
SteamwarUser user = SteamwarUser.get(event.getUser().getIdLong());
TextChannel ticketChannel = event.getGuild().getCategoryById(TICKET_CATEGORY).createTextChannel((user == null ? event.getUser().getName() : user.getUserName()) + "-" + event.getComponentId() + "-" + System.currentTimeMillis() % 1000).complete();
ticketChannel.createPermissionOverride(event.getMember()).setAllow(
Permission.VIEW_CHANNEL,
Permission.MESSAGE_WRITE,
Permission.MESSAGE_ATTACH_FILES,
Permission.MESSAGE_ADD_REACTION,
Permission.MESSAGE_READ,
Permission.MESSAGE_EMBED_LINKS,
Permission.MESSAGE_HISTORY).complete();
ticketChannel.getManager().setTopic(event.getUser().getId()).complete();
DiscordChannel channel = new DiscordChannel(DiscordChannel.userOrPublic(event.getUser()), ticketChannel);
channel.send(new MessageBuilder()
.setEmbeds(new EmbedBuilder()
.setTitle(channel.parseToPlain("DC_TICKET_TITLE"))
.setDescription(channel.parseToPlain(ticketType.introduction()))
.setColor(Color.GREEN)
.build())
.setActionRows(ActionRow.of(Button.danger("close-" + ticketChannel.getName(), channel.parseToPlain("DC_TICKET_CLOSE")).withEmoji(Emoji.fromUnicode("U+26A0")))));
InteractionReply.reply(event, reply -> reply.system("DC_TICKET_CREATED", ticketChannel.getAsMention()));
Chatter.serverteam().prefixless("DISCORD_TICKET_NEW", ticketChannel.getName());
}
@Override
public void onGenericComponentInteractionCreate(@NotNull GenericComponentInteractionCreateEvent event) {
MessageChannel messageChannel = event.getChannel();
if(messageChannel instanceof TextChannel channel && channel.getParent() != null && channel.getParent().getId().equals(TICKET_CATEGORY) && event.getComponentId().startsWith("close-")) {
LinkedList<StringBuilder> messages = channel.getIterableHistory().complete().stream()
.filter(message -> !message.getAuthor().isSystem() && !message.getAuthor().isBot())
.map(message -> {
StringBuilder stringBuilder = new StringBuilder()
.append("<t:").append(message.getTimeCreated().toInstant().getEpochSecond()).append("> ")
.append("**").append(message.getAuthor().getName()).append("**: ")
.append(message.getContentRaw())
.append("\n");
if(!message.getAttachments().isEmpty()) {
message.getAttachments().forEach(attachment -> stringBuilder.append(attachment.getUrl()));
stringBuilder.append("\n");
}
return stringBuilder;
})
.collect(Collectors.toCollection(LinkedList::new));
messages.addFirst(new StringBuilder().append("<t:").append(Instant.now().getEpochSecond()).append("> **").append(event.getUser().getName()).append("**: Ticket closed"));
LinkedList<StringBuilder> messageBuilders = new LinkedList<>();
messageBuilders.add(new StringBuilder());
messages.descendingIterator()
.forEachRemaining(stringBuilder -> {
if(stringBuilder.length() >= 4096) {
messageBuilders.getLast().append(stringBuilder.substring(0, 4090));
messageBuilders.add(new StringBuilder(stringBuilder.substring(4090, stringBuilder.length() - 1)));
} else if (stringBuilder.length() + messageBuilders.getLast().length() >= 4096) {
messageBuilders.add(new StringBuilder(stringBuilder.toString()));
} else {
messageBuilders.getLast().append(stringBuilder);
}
});
EmbedBuilder embedBuilder = new EmbedBuilder()
.setColor(Color.GREEN)
.setTimestamp(Instant.now())
.setTitle(event.getTextChannel().getName());
if(channel.getTopic() != null && !channel.getTopic().isEmpty()) {
User user = event.getJDA().retrieveUserById(channel.getTopic()).complete();
embedBuilder.setAuthor(user.getName(), null, user.getAvatarUrl());
}
TextChannel logChannel = event.getGuild().getTextChannelById(TICKET_LOG);
messageBuilders.forEach(stringBuilder -> logChannel.sendMessage(new MessageBuilder().setEmbeds(embedBuilder.setDescription(stringBuilder.toString()).build()).build()).queue());
Chatter.serverteam().prefixless("DISCORD_TICKET_CLOSED", channel.getName());
channel.delete().reason("Closed").queue();
}
}
@Override
public void onGuildMessageReceived(@NotNull GuildMessageReceivedEvent event) {
TextChannel channel = event.getChannel();
if(
channel.getParent() != null &&
channel.getParent().getId().equals(TICKET_CATEGORY) &&
!channel.getId().equals(TICKET_CHANNEL) &&
!channel.getId().equals(TICKET_LOG)
) {
if(event.getAuthor().isBot())
return;
ChatterGroup receivers = new ChatterGroup(Chatter.allStream().filter(player -> player.user().hasPerm(UserPerm.TICKET_LOG)));
try {
SteamwarUser user = SteamwarUser.get(Long.parseLong(channel.getTopic()));
if(user != null && !user.perms().contains(UserPerm.TEAM))
receivers = new ChatterGroup(receivers, Chatter.of(user));
} catch(NumberFormatException e) {
//ignored
}
receivers.prefixless("DISCORD_TICKET_MESSAGE", new Message("DISCORD_TICKET_HOVER"), ClickEvent.openUrl(event.getMessage().getJumpUrl()), event.getChannel().getName(), event.getAuthor().getName(), event.getMessage().getContentRaw());
}
}
}
@@ -0,0 +1,72 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2024 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.discord.util;
import de.steamwar.velocitycore.VelocityCore;
import de.steamwar.velocitycore.discord.channels.DiscordChannel;
import de.steamwar.sql.SteamwarUser;
import lombok.experimental.UtilityClass;
import net.dv8tion.jda.api.MessageBuilder;
import net.dv8tion.jda.api.entities.Emoji;
import net.dv8tion.jda.api.entities.User;
import net.dv8tion.jda.api.interactions.components.ActionRow;
import net.dv8tion.jda.api.interactions.components.Button;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
@UtilityClass
public class AuthManager {
private final Map<String, User> TOKENS = new HashMap<>();
private final Random rand = new Random();
public String createDiscordAuthToken(User user) {
TOKENS.values().removeIf(user::equals);
byte[] randBytes = new byte[16];
rand.nextBytes(randBytes);
String code = Base64.getEncoder().encodeToString(randBytes);
TOKENS.put(code, user);
VelocityCore.getLogger().log(Level.INFO, "Created Discord Auth-Token: %s for: %s".formatted(code, user.getAsTag()));
VelocityCore.schedule(() -> TOKENS.remove(code)).delay(10, TimeUnit.MINUTES).schedule();
return code;
}
public User connectAuth(SteamwarUser user, String code) {
User dcUser = TOKENS.remove(code);
if(dcUser == null)
return null;
user.setDiscordId(dcUser.getIdLong());
DiscordChannel channel = new DiscordChannel(dcUser);
channel.send(new MessageBuilder()
.setContent(channel.parseToPlain("DC_AUTH_SUCCESS", user))
.setActionRows(ActionRow.of(Button.success("tada", Emoji.fromUnicode("U+1F389")))));
return dcUser;
}
}
@@ -0,0 +1,64 @@
/*
This file is a part of the SteamWar software.
Copyright (C) 2020 SteamWar.de-Serverteam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.velocitycore.discord.util;
import de.steamwar.messages.Chatter;
import de.steamwar.messages.Message;
import de.steamwar.velocitycore.discord.DiscordBot;
import de.steamwar.velocitycore.discord.channels.DiscordChannel;
import lombok.experimental.UtilityClass;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.MessageBuilder;
import net.dv8tion.jda.api.entities.Emoji;
import net.dv8tion.jda.api.entities.User;
import net.dv8tion.jda.api.interactions.components.ActionRow;
import net.dv8tion.jda.api.interactions.components.Button;
import java.awt.*;
import java.time.Instant;
@UtilityClass
public class DiscordAlert {
public static void send(Chatter player, Color color, Message title, Message description, boolean success) {
DiscordBot.withBot(bot -> {
Long discordId = player.user().getDiscordId();
if(discordId == null)
return;
User user = DiscordBot.getInstance().getJda().getUserById(discordId);
if(user == null)
return;
MessageBuilder builder = new MessageBuilder()
.setEmbeds(new EmbedBuilder()
.setAuthor("SteamWar", "https://steamwar.de", "https://cdn.discordapp.com/app-icons/869606970099904562/60c884000407c02671d91d8e7182b8a1.png")
.setColor(color)
.setTitle(player.parseToPlain(title))
.setDescription(player.parseToPlain(description))
.setTimestamp(Instant.now())
.build());
if(success)
builder.setActionRows(ActionRow.of(Button.success("tada", Emoji.fromUnicode("U+1F389"))));
new DiscordChannel(user).send(builder);
});
}
}
@@ -0,0 +1,59 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2020 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.velocitycore.discord.util;
import de.steamwar.velocitycore.discord.DiscordBot;
import de.steamwar.sql.SteamwarUser;
import de.steamwar.sql.UserPerm;
import lombok.experimental.UtilityClass;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.exceptions.ErrorResponseException;
import java.util.*;
import java.util.stream.Collectors;
@UtilityClass
public class DiscordRanks {
private final Map<UserPerm.Prefix, String> prefixToPermName = UserPerm.prefixes.entrySet().stream().collect(Collectors.toMap(Map.Entry::getValue, entry -> entry.getKey().name().toLowerCase()));
public void update(SteamwarUser user) {
if (user.getDiscordId() == null)
return;
Set<String> swRoles = new HashSet<>(DiscordBot.getInstance().getConfig().getRanks().values());
Guild guild = DiscordBot.getGuild();
guild.retrieveMemberById(user.getDiscordId()).queue(member -> {
String prefixRole = DiscordBot.getInstance().getConfig().getRanks().get(prefixToPermName.get(user.prefix()));
member.getRoles().stream()
.filter(role -> swRoles.contains(role.getId()))
.filter(role -> !role.getId().equals(prefixRole))
.forEach(role -> guild.removeRoleFromMember(member, role).queue());
if (prefixRole != null && member.getRoles().stream().noneMatch(role -> role.getId().equals(prefixRole)))
guild.addRoleToMember(member, guild.getRoleById(prefixRole)).queue();
}, e -> {
if(e instanceof ErrorResponseException err && err.getErrorCode() == 10007)
user.setDiscordId(null);
});
}
}
@@ -0,0 +1,61 @@
/*
This file is a part of the SteamWar software.
Copyright (C) 2020 SteamWar.de-Serverteam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.velocitycore.inventory;
public interface InvCallback {
void clicked(ClickType click);
enum ClickType {
LEFT,
SHIFT_LEFT,
RIGHT,
SHIFT_RIGHT,
WINDOW_BORDER_LEFT,
WINDOW_BORDER_RIGHT,
MIDDLE,
NUMBER_KEY,
DOUBLE_CLICK,
DROP,
CONTROL_DROP,
CREATIVE,
UNKNOWN;
public boolean isKeyboardClick() {
return this == NUMBER_KEY || this == DROP || this == CONTROL_DROP;
}
public boolean isCreativeAction() {
return this == MIDDLE || this == CREATIVE;
}
public boolean isRightClick() {
return this == RIGHT || this == SHIFT_RIGHT;
}
public boolean isLeftClick() {
return this == LEFT || this == SHIFT_LEFT || this == DOUBLE_CLICK || this == CREATIVE;
}
public boolean isShiftClick() {
return this == SHIFT_LEFT || this == SHIFT_RIGHT || this == CONTROL_DROP;
}
}
}
@@ -0,0 +1,105 @@
/*
This file is a part of the SteamWar software.
Copyright (C) 2020 SteamWar.de-Serverteam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.velocitycore.inventory;
import de.steamwar.messages.Message;
import de.steamwar.velocitycore.network.NetworkSender;
import de.steamwar.velocitycore.network.handlers.InventoryCallbackHandler;
import de.steamwar.messages.Chatter;
import de.steamwar.messages.PlayerChatter;
import de.steamwar.network.packets.server.CloseInventoryPacket;
import de.steamwar.network.packets.server.InventoryPacket;
import lombok.Getter;
import lombok.Setter;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.BiFunction;
public class SWInventory {
private final Map<Integer, SWItem> itemMap = new HashMap<>();
private final Chatter player;
private final int size;
private final Message title;
@Setter
private InvCallback close;
@Getter
@Setter
private boolean next = false;
private final AtomicBoolean processingClick = new AtomicBoolean();
public SWInventory(PlayerChatter player, int size, Message title) {
InventoryCallbackHandler.inventoryHashMap.put(player.user().getId(), this);
this.player = player;
this.size = size;
this.title = title;
}
public void addItem(int pos, SWItem item, InvCallback callback) {
item.setCallback(callback);
addItem(pos, item);
}
public void addItem(int pos, SWItem item) {
itemMap.put(pos, item);
}
public void setCallback(int pos, InvCallback callback) {
itemMap.get(pos).setCallback(callback);
}
public void handleCallback(InvCallback.ClickType type, int pos) {
if(processingClick.compareAndSet(false, true)) {
itemMap.get(pos).getCallback().clicked(type);
processingClick.set(false);
}
}
public void handleClose() {
if(processingClick.compareAndSet(false, true)) {
InventoryCallbackHandler.inventoryHashMap.remove(player.user().getId(), this);
if(close != null)
close.clicked(null);
processingClick.set(false);
}
}
public void open() {
InventoryPacket inv = new InventoryPacket(player.parseToLegacy(title), player.user().getId(), size, map(itemMap, (integer, swItem) -> swItem.writeToString(player, integer).toString()));
player.withPlayer(p -> NetworkSender.send(p, inv));
}
private static <T, K, J> Map<T, J> map(Map<T, K> map, BiFunction<T, K, J> function) {
Map<T, J> result = new HashMap<>();
map.forEach((key, value) -> result.put(key, function.apply(key, value)));
return result;
}
public void close() {
close(player);
}
public static void close(Chatter player) {
player.withPlayer(p -> NetworkSender.send(p, new CloseInventoryPacket(player.user().getId())));
}
}
@@ -0,0 +1,90 @@
/*
This file is a part of the SteamWar software.
Copyright (C) 2020 SteamWar.de-Serverteam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.velocitycore.inventory;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import de.steamwar.messages.Message;
import de.steamwar.messages.Chatter;
import lombok.Getter;
import lombok.Setter;
import java.util.ArrayList;
import java.util.List;
@Setter
public class SWItem {
@Getter
private String material = "DYE";
private Message title;
private String skullOwner;
private boolean enchanted;
private boolean hideAttributes;
private List<Message> lore = new ArrayList<>();
@Getter
private InvCallback callback;
private int color = 0;
public SWItem(String material, Message title) {
this.material = material.toUpperCase();
this.title = title;
}
public SWItem(Message title, int color) {
this.title = title;
this.color = color;
}
public static SWItem getSkull(String skullOwner) {
SWItem item = new SWItem("SKULL", new Message("PLAIN_STRING", skullOwner));
item.setSkullOwner(skullOwner);
return item;
}
public SWItem addLore(Message lore) {
this.lore.add(lore);
return this;
}
public JsonObject writeToString(Chatter player, int position) {
JsonObject object = new JsonObject();
object.addProperty("material", material);
object.addProperty("position", position);
object.addProperty("title", player.parseToLegacy(title));
if(skullOwner != null)
object.addProperty("skullOwner", skullOwner);
if(enchanted)
object.addProperty("enchanted", true);
if(hideAttributes)
object.addProperty("hideAttributes", true);
if(color != 0)
object.addProperty("color", color);
if(lore != null) {
JsonArray array = new JsonArray();
for (Message lores : lore) {
array.add(player.parseToLegacy(lores));
}
object.add("lore", array);
}
return object;
}
}
@@ -0,0 +1,88 @@
/*
This file is a part of the SteamWar software.
Copyright (C) 2020 SteamWar.de-Serverteam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.velocitycore.inventory;
import de.steamwar.messages.Message;
import de.steamwar.messages.PlayerChatter;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
public class SWListInv<T> extends SWInventory {
@Setter
private ListCallback<T> callback;
private final List<SWListEntry<T>> elements;
private int page;
public SWListInv(PlayerChatter p, Message t, List<SWListEntry<T>> l, ListCallback<T> c){
super(p, (l.size()>45) ? 54 : (l.size() + 9-l.size()%9), t);
callback = c;
elements = l;
page = 0;
}
@Override
public void open(){
if(elements.size() > 54){
if(page != 0)
addItem(45, new SWItem(new Message("INV_PAGE_BACK", "e"), 10), (InvCallback.ClickType click) -> {
page--;
open();
});
else
addItem(45, new SWItem(new Message("INV_PAGE_BACK", "7"), 8), (InvCallback.ClickType click) -> {});
if(page < elements.size()/45)
addItem(53, new SWItem(new Message("INV_PAGE_NEXT", "e"), 10), (InvCallback.ClickType click) -> {
page++;
open();
});
else
addItem(53, new SWItem(new Message("INV_PAGE_NEXT", "7"), 8), (InvCallback.ClickType click) -> {});
}
int ipageLimit = elements.size() - page*45;
if(ipageLimit > 45 && elements.size() > 54){
ipageLimit = 45;
}
int i = page*45;
for(int ipage=0; ipage < ipageLimit; ipage++ ){
SWItem e = elements.get(i).getItem();
final int pos = i;
addItem(ipage, e);
setCallback(ipage, (InvCallback.ClickType click) -> callback.clicked(click, elements.get(pos).getObject()));
i++;
}
super.open();
}
public interface ListCallback<T>{
void clicked(InvCallback.ClickType click, T element);
}
@Getter
@AllArgsConstructor
public static class SWListEntry<T> {
final SWItem item;
final T object;
}
}
@@ -0,0 +1,71 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2024 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.inventory;
import de.steamwar.messages.Message;
import de.steamwar.messages.PlayerChatter;
import java.util.List;
import java.util.function.Function;
public class SWStreamInv<T> extends SWInventory {
private final SWListInv.ListCallback<T> callback;
private final Function<Integer, List<SWListInv.SWListEntry<T>>> constructor;
private int page;
public SWStreamInv(PlayerChatter chatter, Message title, SWListInv.ListCallback<T> callback, Function<Integer, List<SWListInv.SWListEntry<T>>> constructor) {
super(chatter, 54, title);
this.callback = callback;
this.constructor = constructor;
page = 0;
}
@Override
public void open(){
List<SWListInv.SWListEntry<T>> entries = constructor.apply(page);
if(page != 0)
addItem(45, new SWItem(new Message("INV_PAGE_BACK", "e"), 10), (InvCallback.ClickType click) -> {
page--;
open();
});
else
addItem(45, new SWItem(new Message("INV_PAGE_BACK", "7"), 8), (InvCallback.ClickType click) -> {});
if(entries.size() == 45)
addItem(53, new SWItem(new Message("INV_PAGE_NEXT", "e"), 10), (InvCallback.ClickType click) -> {
page++;
open();
});
else
addItem(53, new SWItem(new Message("INV_PAGE_NEXT", "7"), 8), (InvCallback.ClickType click) -> {});
for(int i = 0; i < entries.size(); i++) {
SWListInv.SWListEntry<T> item = entries.get(i);
addItem(i, item.getItem());
setCallback(i, (InvCallback.ClickType click) -> {
close();
callback.clicked(click, item.getObject());
});
}
super.open();
}
}
@@ -0,0 +1,85 @@
/*
This file is a part of the SteamWar software.
Copyright (C) 2020 SteamWar.de-Serverteam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.velocitycore.listeners;
import com.velocitypowered.api.event.Subscribe;
import com.velocitypowered.api.event.connection.LoginEvent;
import com.velocitypowered.api.proxy.Player;
import de.steamwar.messages.Chatter;
import de.steamwar.messages.Message;
import de.steamwar.sql.BannedUserIPs;
import de.steamwar.sql.Punishment;
import de.steamwar.sql.SteamwarUser;
import de.steamwar.velocitycore.commands.PunishmentCommand;
import net.kyori.adventure.text.event.ClickEvent;
import java.sql.Timestamp;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
public class BanListener extends BasicListener {
@Subscribe
public void onLogin(LoginEvent event) {
Player player = event.getPlayer();
SteamwarUser user = SteamwarUser.get(player.getUniqueId());
String ip = IPSanitizer.getTrueAddress(player).getHostAddress();
if (user.isPunished(Punishment.PunishmentType.Ban)) {
BannedUserIPs.banIP(user.getId(), ip);
Chatter.of(event).system(PunishmentCommand.punishmentMessage(user, Punishment.PunishmentType.Ban));
return;
}
List<BannedUserIPs> ips = BannedUserIPs.get(ip);
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;
}
if(ban.getEndTime().after(highestBan))
highestBan = ban.getEndTime();
}
}
ClickEvent clickEvent = ClickEvent.runCommand("/ban " + user.getUserName() + " "
+ (perma ? "perma" : highestBan.toLocalDateTime().format(DateTimeFormatter.ofPattern("dd.MM.yyyy_HH:mm")))
+ " Ban Evasion - Bannumgehung");
Chatter.serverteam().system(
"BAN_AVOIDING_ALERT",
new Message("BAN_AVOIDING_BAN_HOVER"),
clickEvent,
user.getUserName(),
(Function<Chatter, String>) ((Chatter sender) -> ips.stream().map(banned -> {
SteamwarUser bannedUser = SteamwarUser.get(banned.getUserID());
return sender.parseToLegacy("BAN_AVOIDING_LIST", bannedUser.getUserName(),
banned.getTimestamp().toLocalDateTime().format(DateTimeFormatter.ofPattern(sender.parseToPlain("TIMEFORMAT"))));
}).collect(Collectors.joining(" ")))
);
}
}
}
@@ -0,0 +1,29 @@
/*
This file is a part of the SteamWar software.
Copyright (C) 2020 SteamWar.de-Serverteam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.velocitycore.listeners;
import de.steamwar.velocitycore.VelocityCore;
public abstract class BasicListener {
protected BasicListener() {
VelocityCore.getProxy().getEventManager().register(VelocityCore.get(), this);
}
}
@@ -0,0 +1,265 @@
/*
This file is a part of the SteamWar software.
Copyright (C) 2022 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.listeners;
import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.event.PostOrder;
import com.velocitypowered.api.event.Subscribe;
import com.velocitypowered.api.event.command.CommandExecuteEvent;
import com.velocitypowered.api.event.player.PlayerChatEvent;
import com.velocitypowered.api.event.player.TabCompleteEvent;
import com.velocitypowered.api.proxy.ConsoleCommandSource;
import com.velocitypowered.api.proxy.Player;
import de.steamwar.messages.Chatter;
import de.steamwar.messages.ChatterGroup;
import de.steamwar.messages.Message;
import de.steamwar.messages.PlayerChatter;
import de.steamwar.network.packets.server.PingPacket;
import de.steamwar.persistent.Servertype;
import de.steamwar.persistent.Subserver;
import de.steamwar.sql.*;
import de.steamwar.velocitycore.ArenaMode;
import de.steamwar.velocitycore.VelocityCore;
import de.steamwar.velocitycore.commands.PunishmentCommand;
import de.steamwar.velocitycore.discord.DiscordBot;
import de.steamwar.velocitycore.network.NetworkSender;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
public class ChatListener extends BasicListener {
private static final Logger cmdLogger = Logger.getLogger("Command logger");
private static final List<String> rankedModes = ArenaMode.getAllModes().stream().filter(ArenaMode::isRanked).map(ArenaMode::getSchemType).toList();
@Subscribe(order = PostOrder.FIRST)
public void fixCommands(CommandExecuteEvent e) {
String command = e.getCommand();
if(command.startsWith("7")) {
command = "/" + command.substring(1);
CommandExecuteEvent.CommandResult result = e.getResult();
if(result.isForwardToServer())
result = CommandExecuteEvent.CommandResult.forwardToServer(command);
else if(result.isAllowed())
result = CommandExecuteEvent.CommandResult.command(command);
e.setResult(result);
}
}
@Subscribe(order = PostOrder.LAST)
public void logCommands(CommandExecuteEvent e) {
String command = e.getCommand();
int space = command.indexOf(' ');
if(VelocityCore.getProxy().getCommandManager().hasCommand(space != -1 ? command.substring(0, space) : command)) {
CommandSource source = e.getCommandSource();
String name;
if(source instanceof Player player)
name = player.getUsername();
else if(source instanceof ConsoleCommandSource)
name = "«CONSOLE»";
else
name = source.toString();
cmdLogger.log(Level.INFO, "%s -> executed command /%s".formatted(name, command));
} else if (e.getCommandSource() instanceof Player player) {
player.spoofChatInput("/" + command);
e.setResult(CommandExecuteEvent.CommandResult.denied());
}
}
@Subscribe
public void onChatEvent(PlayerChatEvent e) {
Player player = e.getPlayer();
String message = e.getMessage();
e.setResult(PlayerChatEvent.ChatResult.denied());
if (message.contains("jndi:ldap")) {
SteamwarUser user = SteamwarUser.get(player.getUniqueId());
PunishmentCommand.ban(user, Punishment.PERMA_TIME, "Versuchte Exploit-Ausnutzung", SteamwarUser.get(-1), true);
VelocityCore.getLogger().log(Level.SEVERE, "%s %s wurde automatisch wegen jndi:ldap gebannt.".formatted(user.getUserName(), user.getId()));
return;
}
if (isMistypedCommand(player, message))
return;
Subserver subserver = Subserver.getSubserver(player);
if(subserver != null && subserver.getType() == Servertype.ARENA && subserver.getServer() == player.getCurrentServer().orElseThrow().getServerInfo()) {
localChat(Chatter.of(player), message);
} else if (message.startsWith("+")) {
localChat(Chatter.of(player), message.substring(1));
} else {
sendChat(Chatter.of(player), Chatter.globalChat(), "CHAT_GLOBAL", null, message);
}
}
private static boolean isMistypedCommand(Player player, String message) {
String command = message.substring(1);
boolean isCommand = message.startsWith("7") && command.matches("^[7/]?[A-Za-z]+");
if(isCommand && Boolean.FALSE.equals(VelocityCore.getProxy().getCommandManager().executeAsync(player, command).join())) {
if(command.startsWith("7"))
command = "/" + command.substring(1);
message = "/" + command;
if(filteredCommand(Chatter.of(player), message))
return true;
player.spoofChatInput(message);
}
return isCommand;
}
public static void sendChat(Chatter sender, ChatterGroup receivers, String format, Chatter msgReceiver, String message) {
SteamwarUser user = sender.user();
final String coloredMessage = user.hasPerm(UserPerm.COLOR_CHAT) ? message.replace('&', '§') : message;
if(chatFilter(sender, coloredMessage))
return;
boolean noReceiver = true;
for(Chatter player : receivers.getChatters()) {
if(player.chatShown()) {
chatToReciever(player, msgReceiver, user, format, coloredMessage);
if(sender.user().getId() != player.user().getId())
noReceiver = false;
}
}
if(format.equals("CHAT_GLOBAL")) {
DiscordBot.withBot(bot -> chatToReciever(bot.getIngameChat(), msgReceiver, user, format, coloredMessage));
} else if (format.equals("CHAT_SERVERTEAM")) {
DiscordBot.withBot(bot -> chatToReciever(bot.getServerTeamChat(), msgReceiver, user, format, coloredMessage));
} else if (noReceiver) {
sender.system("CHAT_NO_RECEIVER");
}
}
public static void localChat(PlayerChatter sender, String message) {
if(message.isEmpty()){
sender.system("CHAT_BC_USAGE");
return;
}
if(ChatListener.filteredCommand(sender, message))
return;
if(!message.startsWith("/") && chatFilter(sender, message))
return;
sender.getPlayer().spoofChatInput(message);
}
private static boolean chatFilter(Chatter sender, String message) {
if(!sender.chatShown()) {
sender.system("CHAT_RECEIVE");
return true;
}
if(message.replace("§[a-f0-9klmno]", "").trim().isEmpty()) {
sender.system("CHAT_EMPTY");
return true;
}
SteamwarUser user = sender.user();
if(!user.hasPerm(UserPerm.TEAM) && (message.contains("http:") || message.contains("https:") || message.contains("www."))){
sender.system("CHAT_NO_LINKS");
return true;
}
if (PunishmentCommand.isPunishedWithMessage(sender, Punishment.PunishmentType.Mute))
return true;
if (message.contains("LIXFEL"))
specialAlert(sender, "Lixfel", "CHAT_LIXFEL_", 3, 6, 11, 12, 15);
if (message.contains("YOYONOW"))
specialAlert(sender, "YoyoNow", "CHAT_YOYONOW_", 3, 6, 11, 12);
if (message.contains("CHAOSCAOT"))
specialAlert(sender, "Chaoscaot", "CHAT_CHAOSCAOT_", 3, 6, 11, 12, 15, 17);
return false;
}
private static void chatToReciever(Chatter receiver, Chatter msgReceiver, SteamwarUser sender, String format, String message) {
UserPerm.Prefix prefix = sender.prefix();
String chatColorCode = sender.hasPerm(UserPerm.TEAM) ? "§f" : "§7";
receiver.prefixless(format,
sender,
msgReceiver == null ? receiver : msgReceiver,
highlightMentions(message, chatColorCode, receiver),
sender.getTeam() == 0 ? "" : "§" + Team.get(sender.getTeam()).getTeamColor() + Team.get(sender.getTeam()).getTeamKuerzel() + " ",
UserElo.getEmblem(sender, rankedModes),
prefix.getColorCode(),
prefix.getChatPrefix().length() == 0 ? "§f" : prefix.getChatPrefix() + " ",
chatColorCode);
}
private static boolean filteredCommand(Chatter sender, String message) {
String command = message.split(" ", 2)[0];
if(command.startsWith("/") && command.contains(":")) {
sender.system("UNKNOWN_COMMAND");
return true;
}
return false;
}
private static void specialAlert(Chatter sender, String name, String baseMessage, int... delay) {
sender.system("CHAT_LIXFEL_ACTION_BAR");
for(int i = 0; i < delay.length; i++) {
int finalI = i;
VelocityCore.schedule( () -> sender.prefixless("CHAT_MSG", name, sender.user(), new Message(baseMessage + (finalI+1)))).delay(delay[i], TimeUnit.SECONDS).schedule();
}
}
private static String highlightMentions(String message, String returnColor, Chatter player) {
if(!message.contains("@"))
return message;
String mark = "@" + player.user().getUserName();
return Arrays.stream(message.split(" ")).map(cur -> {
if(cur.equalsIgnoreCase(mark) && player.getPlayer() != null) {
NetworkSender.send(player.getPlayer(), new PingPacket(player.user().getId()));
return "§e" + cur + returnColor;
}
return cur;
}).collect(Collectors.joining(" "));
}
@Subscribe
public void onTabCompleteResponseEvent(TabCompleteEvent e){
List<String> suggestions = e.getSuggestions();
int i = 0;
while (i < suggestions.size()) {
String suggestion = suggestions.get(i);
if(suggestion.startsWith("/") && suggestion.contains(":"))
suggestions.remove(i);
else
i++;
}
}
}
@@ -0,0 +1,74 @@
/*
This file is a part of the SteamWar software.
Copyright (C) 2020 SteamWar.de-Serverteam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.velocitycore.listeners;
import com.velocitypowered.api.event.Subscribe;
import com.velocitypowered.api.event.connection.DisconnectEvent;
import com.velocitypowered.api.event.connection.PostLoginEvent;
import com.velocitypowered.api.event.player.ServerConnectedEvent;
import com.velocitypowered.api.proxy.Player;
import de.steamwar.persistent.Bauserver;
import de.steamwar.persistent.Servertype;
import de.steamwar.persistent.Subserver;
import de.steamwar.velocitycore.commands.CheckCommand;
import de.steamwar.messages.Chatter;
import de.steamwar.sql.SchematicNode;
import de.steamwar.sql.SchematicType;
import java.util.ArrayList;
import java.util.List;
public class CheckListener extends BasicListener {
@Subscribe
public void onPlayerJoin(PostLoginEvent e){
Chatter sender = Chatter.of(e.getPlayer());
List<SchematicNode> uncheckedSchematics = new ArrayList<>();
for(SchematicType type : SchematicType.values()){
if(type.check())
uncheckedSchematics.addAll(SchematicNode.getAllSchematicsOfType(sender.user().getId(), type.toDB()));
}
if(!uncheckedSchematics.isEmpty())
sender.system("CHECK_UNCHECKED", uncheckedSchematics.size());
}
@Subscribe
public void onServerSwitch(ServerConnectedEvent e){
Player player = e.getPlayer();
if(CheckCommand.isChecking(player)){
Subserver server = Subserver.getSubserver(e.getServer().getServerInfo());
if(
server == null ||
server.getType() != Servertype.BAUSERVER ||
((Bauserver)server).getOwner() != player.getUniqueId()
)
CheckCommand.abort(player);
}
}
@Subscribe
public void onPlayerDisconnect(DisconnectEvent e){
Player player = e.getPlayer();
if(CheckCommand.isChecking(player))
CheckCommand.abort(player);
}
}
@@ -0,0 +1,100 @@
/*
This file is a part of the SteamWar software.
Copyright (C) 2020 SteamWar.de-Serverteam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.velocitycore.listeners;
import com.velocitypowered.api.event.Subscribe;
import com.velocitypowered.api.event.connection.DisconnectEvent;
import com.velocitypowered.api.event.connection.PostLoginEvent;
import com.velocitypowered.api.event.permission.PermissionsSetupEvent;
import com.velocitypowered.api.permission.Tristate;
import com.velocitypowered.api.proxy.Player;
import de.steamwar.messages.Chatter;
import de.steamwar.messages.Message;
import de.steamwar.persistent.Servertype;
import de.steamwar.persistent.Subserver;
import de.steamwar.sql.SteamwarUser;
import de.steamwar.sql.UserPerm;
import de.steamwar.velocitycore.commands.*;
import de.steamwar.velocitycore.discord.DiscordBot;
import de.steamwar.velocitycore.discord.util.DiscordRanks;
import de.steamwar.velocitycore.mods.ModUtils;
import net.kyori.adventure.text.event.ClickEvent;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
public class ConnectionListener extends BasicListener {
private static final Set<UUID> newPlayers = new HashSet<>();
public static void newPlayer(UUID player){
newPlayers.add(player);
}
private static final Set<String> TEAM_PERMISSIONS = Set.of("velocity.command.send", "velocity.command.glist");
@Subscribe
public void onPermissionSetup(PermissionsSetupEvent event) {
event.setProvider(subject -> {
if(!(subject instanceof Player player))
return perm -> Tristate.TRUE;
Set<UserPerm> perms = SteamwarUser.getOrCreate(player.getUniqueId(), player.getUsername(), ConnectionListener::newPlayer, WebpasswordCommand::changeUsername).perms();
if(perms.contains(UserPerm.ADMINISTRATION))
return perm -> Tristate.TRUE;
else if(perms.contains(UserPerm.TEAM))
return perm -> Tristate.fromBoolean(TEAM_PERMISSIONS.contains(perm));
else
return perm -> Tristate.FALSE;
});
}
@Subscribe
public void onPostLogin(PostLoginEvent event) {
Player player = event.getPlayer();
SteamwarUser user = SteamwarUser.get(player.getUniqueId());
Chatter chatter = Chatter.of(player);
if(user.hasPerm(UserPerm.CHECK))
CheckCommand.sendReminder(chatter);
for(Subserver subserver : Subserver.getServerList()) {
if(subserver.getType() == Servertype.ARENA) {
chatter.system("JOIN_ARENA", new Message("JOIN_ARENA_HOVER"), ClickEvent.runCommand("/arena " + subserver.getServer().getName()), subserver.getServer().getName());
}
}
if(newPlayers.contains(player.getUniqueId())){
Chatter.broadcast().system("JOIN_FIRST", player);
newPlayers.remove(player.getUniqueId());
}
DiscordBot.withBot(bot -> DiscordRanks.update(user));
}
@Subscribe
public void onDisconnect(DisconnectEvent e){
ChallengeCommand.remove(e.getPlayer());
MsgCommand.remove(e.getPlayer());
ModUtils.getPlayerModMap().remove(e.getPlayer().getUniqueId());
ModCommand.playerFilterType.remove(e.getPlayer().getUniqueId());
}
}
@@ -0,0 +1,49 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2024 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.listeners;
import com.velocitypowered.api.event.Subscribe;
import com.velocitypowered.api.event.connection.PostLoginEvent;
import de.steamwar.messages.Chatter;
import de.steamwar.sql.Event;
import de.steamwar.sql.Referee;
import de.steamwar.sql.TeamTeilnahme;
public class EventModeListener extends BasicListener {
@Subscribe
public void onPostLogin(PostLoginEvent e) {
Chatter sender = Chatter.disconnect(e.getPlayer());
Event event = Event.get();
if(event == null) {
sender.system("EVENTMODE_KICK");
return;
}
if(TeamTeilnahme.nimmtTeil(sender.user().getTeam(), event.getEventID()))
return;
if(Referee.get(event.getEventID()).contains(sender.user().getId()))
return;
sender.system("EVENTMODE_KICK");
}
}
@@ -0,0 +1,52 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2020 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.velocitycore.listeners;
import com.velocitypowered.api.event.Subscribe;
import com.velocitypowered.api.event.connection.PreLoginEvent;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import com.velocitypowered.proxy.connection.client.LoginInboundConnection;
import de.steamwar.persistent.Reflection;
import de.steamwar.velocitycore.VelocityCore;
import de.steamwar.velocitycore.mods.Hostname;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.logging.Level;
public class IPSanitizer extends BasicListener {
private static final Reflection.Field<MinecraftConnection, SocketAddress> remoteAddress = new Reflection.Field<>(MinecraftConnection.class, "remoteAddress");
public static InetAddress getTrueAddress(Player player) {
return ((InetSocketAddress) ((ConnectedPlayer)player).getConnection().getChannel().remoteAddress()).getAddress();
}
private final InetSocketAddress sanitized = new InetSocketAddress("127.127.127.127", 25565);
@Subscribe
public void loginEvent(PreLoginEvent e) {
VelocityCore.getLogger().log(Level.INFO, "%s has logged in with user name %s".formatted(e.getConnection().getRemoteAddress(), e.getUsername()));
remoteAddress.set(Hostname.getInitialInboundConnection((LoginInboundConnection) e.getConnection()).getConnection(), sanitized);
}
}
@@ -0,0 +1,507 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2024 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.listeners;
import com.lunarclient.apollo.ApolloManager;
import com.velocitypowered.api.event.Subscribe;
import com.velocitypowered.api.event.connection.PluginMessageEvent;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.ServerConnection;
import com.velocitypowered.api.proxy.messages.ChannelMessageSource;
import com.velocitypowered.api.proxy.messages.LegacyChannelIdentifier;
import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import de.steamwar.velocitycore.VelocityCore;
import de.steamwar.velocitycore.commands.TeamCommand;
import de.steamwar.velocitycore.mods.*;
import de.steamwar.velocitycore.network.ServerMetaInfo;
import de.steamwar.messages.Chatter;
import de.steamwar.network.packets.NetworkPacket;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import net.kyori.adventure.text.Component;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.*;
import java.util.function.Consumer;
import java.util.logging.Level;
public class PluginMessage extends BasicListener {
public static void send(Player player, String legacyChannel, String channel, byte[] data) {
// 1.12 format change
send(player, player.getProtocolVersion().greaterThan(ProtocolVersion.MINECRAFT_1_12_2) ? channel : legacyChannel, data);
}
public static void send(Player player, String channel, byte[] data) {
//TODO only if player has registered channel
player.sendPluginMessage(channel.indexOf(':') != -1 ? MinecraftChannelIdentifier.from(channel) : new LegacyChannelIdentifier(channel), data);
}
public static byte[] genBufPacket(Consumer<ByteBuf> generator) {
ByteBuf buf = Unpooled.buffer();
generator.accept(buf);
byte[] packet = new byte[buf.readableBytes()];
buf.readBytes(packet);
return packet;
}
public static byte[] genStreamPacket(StreamConsumer generator) {
ByteArrayOutputStream stream = new ByteArrayOutputStream();
DataOutputStream out = new DataOutputStream(stream);
try {
generator.accept(out);
} catch (IOException e) {
throw new SecurityException("Could not create PluginMessage packet", e);
}
return stream.toByteArray();
}
public interface StreamConsumer {
void accept(DataOutputStream out) throws IOException;
}
private static final Parser UNKNOWN = event -> VelocityCore.getLogger().log(Level.WARNING, () -> "Undefined PluginMessage on channel " + event.getIdentifier() + " from " + event.getSource() + " received.\n" + new String(event.getData()) + "\n" + Arrays.toString(event.getData()));
private static final Parser PASS_THROUGH = event -> event.setResult(PluginMessageEvent.ForwardResult.forward());
private static final Parser DROP = event -> {};
private final Lunar lunar = new Lunar();
private final Badlion badlion = new Badlion();
private final Set<String> knownBrands = new HashSet<>();
private final Map<String, Consumer<Player>> channelRegisterHandlers = new HashMap<>();
private final Map<String, Parser> handlers = new HashMap<>();
public PluginMessage() {
LabyMod labyMod = new LabyMod();
WorldDownloader wdl = new WorldDownloader();
knownBrands.addAll(Arrays.asList("vanilla", "badlion", "fabric", "quilt", "forge", "optifine", "Geyser", "labymod", "Feather Fabric"));
for(String channel : Arrays.asList(
"fabric:container/open", "fabric:registry/sync/direct", "fabric:registry/sync",
"fabric-screen-handler-api-v1:open_screen",
FML.CHANNEL, "FML|MP", "FML", "FORGE",
"fml:loginwrapper", "fml:handshake", "fml:play",
"forge:tier_sorting", "forge:split", "forge:login", "forge:handshake",
"labymod3:main", "labymod:neo",
"feather:client/frag",
Alpine.HANDSHAKE, Alpine.PLAY,
"sw:hotkeys",
"floodgate:form", "floodgate:transfer", "floodgate:packet",
"Replay|Restrict", "replaymod:restrict",
"WDL|CONTROL", "wdl:control",
"tpshud:handshake", "tpshud:tps", //https://github.com/mooziii/tpshud-fabric/tree/main
"methane_server:statepacket", //https://modrinth.com/mod/methane
"servux:structures", //https://modrinth.com/mod/servux
"architectury:spawn_entity_packet", //https://modrinth.com/mod/architectury-api
"jei:channel", "jei:cheat_permission", //https://modrinth.com/mod/jei
"owo:local_packet", "owo:sync_screen_handler_properties", //https://modrinth.com/mod/owo-lib
"minecraft:intave", //https://intave.ac seems to be a client side integration of intave with labymod 4
"midnightcontrols:feature", "midnightcontrols:controls_mode", //https://modrinth.com/mod/midnightcontrols
"controlify:vibrate_from_origin", "controlify:vibration", "controlify:vibrate_from_entity",
"carpet:structures", //https://modrinth.com/mod/carpet
"plasmo:voice/v2/installed", "plasmo:voice/v2", //https://modrinth.com/plugin/plasmo-voice (Voice chat)
"inventorysorter:sync_blacklist_packet", //https://github.com/cpw/inventorysorter (needs server side component to work)
"emi:ping", "emi:command", "emi:chess", //https://github.com/emilyploszaj/emi/
"fancymenu:execute_command", "fancymenu:packet_bridge", //https://github.com/Keksuccino/FancyMenu (Custom menus)
"itemswapper:enableshulker", "itemswapper:enablerefill", //https://github.com/tr7zw/ItemSwapper/tree/main (Easier inventory item swapping)
"jade:show_overlay", "jade:receive_data", "jade:server_ping", //https://github.com/Snownee/Jade (Information over block/entity under crosshair)
"bclib:hello_client", "bclib:request_files", "bclib:send_files", "bclib:chunker", //https://github.com/quiqueck/BCLib (Library for additional dimensions)
"roughlyenoughitems:ci_msg", "roughlyenoughitems:request_tags_s2c", "roughlyenoughitems:og_not_enough", //https://github.com/shedaniel/RoughlyEnoughItems (Crafting recipe helper)
"essentialclient:chunkdebug", "essentialclient:clientscript", "essentialclient:gamerule", //https://github.com/senseiwells/EssentialClient (Carpet mod extension)
"couplings:server_config", //https://github.com/ChloeDawn/Couplings (Opens/closes double doors/gates simultaneously)
"yigd:grave_overview_s2c", "yigd:grave_selection_s2c", "yigd:player_selection_s2c", //https://github.com/B1n-ry/Youre-in-grave-danger (Adds new block - graves)
"bagofholding:1/0", //https://github.com/Fuzss/bagofholding (Adds new item - bag)
"betterend:ritual_update", //https://github.com/quiqueck/BetterEnd (Dimension improvement)
"calio:sync_data_object_registry", //https://github.com/apace100/calio (Data serialization library)
"chunk_debug:batched_chunk_info", "chunk_debug:hello", //https://github.com/senseiwells/ChunkDebug (Serverside extension for EssentialClient)
"chunky:border", //https://github.com/pop4959/Chunky (Serverside chunk generator)
"commandblockide:edit_function", "commandblockide:update_function_command", //https://github.com/arm32x/command-block-ide (Command block interface)
"configured:session_data", //https://github.com/MrCrayfish/Configured (Confiuration system)
"corpse:default", //https://github.com/henkelmax/corpse/tree/master (Adds new entity - corpse)
"craftingtweaks:hello", "craftingtweaks:sync_config", //https://github.com/TwelveIterationMods/CraftingTweaks (Additional Crafting UI)
"create:main", //https://github.com/Creators-of-Create/Create (Additional redstone blocks mod)
"dummmmmmy:0", "dummmmmmy:1", "dummmmmmy:2", //https://github.com/MehVahdJukaar/DuMmmMmmy (Adding dummy target item)
"easyanvils:1/0", "easyanvils:1/2", //https://github.com/Fuzss/easyanvils (Anvil handling improvements)
"easymagic:1/0", "easymagic.main:0", //https://github.com/Fuzss/easymagic (Enchantment table handling improvements)
"enhancedvisuals:main0", "enhancedvisuals:main1", "enhancedvisuals:main2", //https://github.com/CreativeMD/EnhancedVisuals (Visual effects)
"fallingtree:configuration-packet", //https://github.com/RakambdaOrg/FallingTree (Serverside tree cutting enhancements)
"forgeconfigscreens:play/0", "forgeconfigscreens:play/1", //https://github.com/Fuzss/forgeconfigscreens (Config GUI)
"fwaystones:void_totem_revive", "fwaystones:sync_player", "fwaystones:waystone_packet", //https://github.com/LordDeatHunter/FabricWaystones (Adds new block - waystone)
"fzzy_config:sync_config_packet", //https://github.com/fzzyhmstrs/fconfig (Synchronizing configuation library)
"graveyard:spawn_entity", //https://github.com/finallion/The-Graveyard-FORGE (Adding graveyard themed blocks, items and mobs)
"immersive_weathering:0", //https://github.com/AstralOrdana/Immersive-Weathering (Adds additional random block transitions)
"kiwi:sync_cosmetic", //github.com/Snownee/Kiwi (General purpose library)
"libgui:screen_messag", "libgui:screen_message_s2c", //https://github.com/cottonmc/libgui (Ingame GUI library)
"libjf-config-network", "libjf-config-network-v0:request", "libjf-config-network-v0:response", //https://git.frohnmeyer-wds.de/JfMods/LibJF (General purpose library)
"libz:set_mouse_position", "libz:sync_config", //https://github.com/Globox1997/LibZ (General purpose library)
"moonlight:0", "moonlight:1", "moonlight:2", "moonlight:3", "moonlight:4", "moonlight:5", //https://github.com/MehVahdJukaar/Moonlight (General purpose library)
"nochatreports:sync", //https://github.com/Aizistral-Studios/No-Chat-Reports (Unsigns chat messages)
"omegaconfig:sync", //https://github.com/Draylar/omega-config (Config library)
"openpartiesandclaims:main", //https://github.com/thexaero/open-parties-and-claims (Chunk loading tool, needs server cooperation)
"owo:config_sync", //https://github.com/wisp-forest/owo-lib (GUI and config library)
"patchouli:open_book", "patchouli:reload_books", //https://github.com/VazkiiMods/Patchouli/ (Ingame user guide books)
"pickupnotifier:1/0", "pickupnotifier:1/1", //https://modrinth.com/mod/pick-up-notifier
"porting_lib:extra_entity_spawn_data", "porting_lib:open_screen", "port_lib:tier_sorting", //https://github.com/Fabricators-of-Create/Porting-Lib (Forge fabric porting library)
"showmeyourskin:config_sync", //https://github.com/enjarai/show-me-your-skin (Configurable armor rendering)
"simplerpc:serverconfig", //https://modrinth.com/mod/simple-discord-rpc (Customizable Discord rich presence)
"skinshuffle:handshake", //https://github.com/IMB11/SkinShuffle (Ingame realtime skin editor)
"toms_storage:data_s2c", //https://github.com/tom5454/Toms-Storage (Additional storage blocks)
"travelersbackpack:sync_backpack", "travelersbackpack:update_config", //https://github.com/Tiviacz1337/Travelers-Backpack (Additional backpack items)
"trinkets:break", "trinkets:sync_inventory", "trinkets:sync_slots", //https://github.com/emilyploszaj/trinkets (Additional armor slots)
"tweed4:sync_config", //https://github.com/Siphalor/tweed-api (Config library)
"umu_backpack:load", "umu_backpack:unload", "umu_config:sync_config", //https://github.com/Zemelua/UMU-Backpack (Additional backpack item)
"visualoverhaul:brewingstand", "visualoverhaul:furnace", "visualoverhaul:record", //https://github.com/TeamMidnightDust/VisualOverhaul (Graphical overhaul for certain blocks)
"walkietalkie:buttonpressedresponse", //https://github.com/Flaton1/walkie-talkie-mod (Simple voice chat walkietalkie addon)
"whereisit:s2c_founditem", "whereisit:found_item_s2c", //https://modrinth.com/mod/where-is-it (needs server side component to work)
"wildfire_gender:hurt", "wildfire_gender:sync", //https://github.com/WildfireRomeo/WildfireFemaleGenderMod (Female player model)
//https://github.com/ZsoltMolnarrr/SpellEngine (Magic library)
"spell_engine:config_sync", "spell_engine:particle_effects", "spell_engine:spell_animation",
"spell_engine:spell_cooldown", "spell_engine:spell_registry_sync",
//https://modrinth.com/mod/shulkerboxtooltip
"shulkerboxtooltip:s2c_handshake", "shulkerboxtooltip:ec_update", "shulkerboxtooltip:ec",
"shulkerboxtooltip:s2",
//https://github.com/QuiltMC/quilt-standard-libraries (Quilt general purpose library)
"qsl:registry_sync/end", "qsl:registry_sync/error_style", "qsl:registry_sync/handshake",
"qsl:registry_sync/mod_protocol", "qsl:registry_sync/registry_apply", "qsl:registry_sync/registry_data",
"qsl:registry_sync/registry_restore", "qsl:registry_sync/registry_start",
"qsl:registry_sync/validate/block_states", "qsl:registry_sync/validate/fluid_states",
"quilt:extended_entity_spawn_packet", "quilt_registry_entry_attachments:sync",
//https://github.com/apace100/origins-fabric (Minecraft player special powers mod)
"origins:badge_list", "origins:confirm_origin", "origins:layer_list", "origins:open_origin_screen",
"origins:origin_list",
//https://github.com/skyecodes/IBE-Editor (Item data editor)
"ibeeditor:network/block_editor_response", "ibeeditor:network/block_inventory_item_editor_response",
"ibeeditor:network/editor_command", "ibeeditor:network/player_inventory_item_editor_response",
"ibeeditor:network/entity_inventory_item_editor_response", "ibeeditor:network/server_notification",
"ibeeditor:network/main_hand_item_editor_response", "ibeeditor:network/entity_editor_response",
//https://github.com/enjarai/do-a-barrel-roll (Elytra flight visuals changes)
"do_a_barrel_roll:config_sync", "do_a_barrel_roll:handshake", "do_a_barrel_roll:player_roll",
"do_a_barrel_roll:server_config_update",
//https://github.com/CreativeMD/CreativeCore (General purpose library)
"creativecore:main0", "creativecore:main1", "creativecore:main2", "creativecore:main3",
"creativecore:main4", "creativecore:main5", "creativecore:main6", "creativecore:main7",
//https://github.com/Ladysnake/Cardinal-Components-API (General purpose library)
"cardinal-components:block_entity_sync", "cardinal-components:chunk_sync",
"cardinal-components:entity_sync", "cardinal-components:level_sync", "cardinal-components:world_sync",
"cardinal-components:scoreboard_sync", "cardinal-components:team_sync",
//https://github.com/RedLime/SpeedRunIGT (Speedrunning overlay)
"speedrunigt:achieve_advancement", "speedrunigt:achieve_criteria", "speedrunigt:condition_custom",
"speedrunigt:condition_data", "speedrunigt:timer_category", "speedrunigt:timer_complete",
"speedrunigt:timer_init", "speedrunigt:timer_start", "speedrunigt:timer_timeline",
"speedrunigt:timer_uncompleted",
//https://github.com/miyo6032/bosses-of-mass-destruction (Adds new entities)
"bosses_of_mass_destruction:change_hitbox", "bosses_of_mass_destruction:charged_ender_pearl_impact",
"bosses_of_mass_destruction:client_vec3d", "bosses_of_mass_destruction:gauntlet_blindness",
"bosses_of_mass_destruction:obsidilith_revive", "bosses_of_mass_destruction:player_velocity",
"bosses_of_mass_destruction:spawn_entity", "bosses_of_mass_destruction:void_blossom_head",
"bosses_of_mass_destruction:void_blossom_place", "bosses_of_mass_destruction:void_blossom_revive",
"bosses_of_mass_destruction:void_blossom_spikes", "bosses_of_mass_destruction:void_lily_pollen",
//https://github.com/AzureDoom/AzureLib (Animation library)
"azurelib:anim_data_sync", "azurelib:anim_trigger_sync", "azurelibarmor:anim_data_sync",
"azurelibarmor:anim_trigger_sync", "azurelib:block_entity_anim_data_sync",
"azurelib:block_entity_anim_trigger_sync", "azurelib:entity_anim_data_sync",
"azurelib:entity_anim_trigger_sync", "azurelib:s2c_send_config_data", "azurelib:spawn_entity",
//https://github.com/apace100/apoli (Entity power library)
"apoli:player_dismount", "apoli:player_mount", "apoli:power_list", "apoli:set_attacker",
"apoli:sync_power", "apoli:sync_status_effect",
//https://essential.gg/
"essential:", "essential:game_rule_hello", "essential:game_rule_permissions",
"essential:game_rules_changed",
//https://github.com/MehVahdJukaar/Supplementaries (Additional blocks)
"supplementaries:0", "supplementaries:1", "supplementaries:2", "supplementaries:3", "supplementaries:4",
"supplementaries:5", "supplementaries:6", "supplementaries:7", "supplementaries:8", "supplementaries:9",
"supplementaries:10", "supplementaries:11", "supplementaries:12", "supplementaries:13",
"supplementaries:14", "supplementaries:24", "supplementaries:25",
//https://www.curseforge.com/minecraft/mc-mods/waila (Information over block/entity under crosshair)
"waila:data", "waila:blacklist", "waila:config", "waila:version", "waila:generate_client_dump",
//https://modrinth.com/plugin/simple-voice-chat (Voice chat)
"voicechat:secret", "voicechat:joined_group", "voicechat:player_states", "voicechat:player_state",
"voicechat:remove_category", "voicechat:remove_group", "voicechat:add_category",
"voicechat:leave_group", "voicechat:create_group", "voicechat:request_secret", "voicechat:set_group",
"voicechat:update_state", "voicechat:add_group",
//https://github.com/mim1q/MineCells (Additional dimensions and gamemodes)
"minecells:obelisk_activation", "minecells:connect", "minecells:crit", "minecells:explosion",
"minecells:elevator_destroyed", "minecells:spawn_rune_particles", "minecells:sync_minecells_data",
//https://modrinth.com/mod/appleskin (Additional food bar information)
"appleskin:exhaustion_sync", "appleskin:saturation_sync",
"appleskin:saturation", "appleskin:exhaustion",
//https://modrinth.com/mod/puzzles-lib (General purpose library)
"puzzleslib:1/0", "puzzleslib:1/1", "puzzleslib:1/2",
"puzzleslib.main:0", "puzzleslib.main:1", "puzzleslib:play/0",
//https://github.com/Fuzss/puzzlesapi (General purpose library extension)
"puzzlesapi:1/1", "puzzlesapi:1/2", "puzzlesapi:1/5", "puzzlesapi:2/1", "puzzlesapi:2/2",
//https://github.com/bernie-g/geckolib
"geckolib:block_entity_anim_trigger_sync", "geckolib:entity_anim_trigger_sync",
"geckolib:block_entity_anim_data_sync", "geckolib:anim_data_sync",
"geckolib:entity_anim_data_sync", "geckolib:anim_trigger_sync",
//https://github.com/Noxcrew/noxesium (MC Championship helper)
"noxesium:server_rules",
"noxesium-v1:reset", "noxesium-v1:change_server_rules", "noxesium-v1:server_info",
"noxesium-v1:mcc_server", "noxesium-v1:mcc_game_state", "noxesium-v1:reset_server_rules",
"noxesium-v1:stop_sound", "noxesium-v1:start_sound", "noxesium-v1:modify_sound",
"noxesium-v2:reset", "noxesium-v2:change_server_rules", "noxesium-v2:server_info",
"noxesium-v2:mcc_server", "noxesium-v2:mcc_game_state", "noxesium-v2:reset_server_rules",
"noxesium-v2:stop_sound", "noxesium-v2:start_sound", "noxesium-v2:modify_sound"
))
channelRegisterHandlers.put(channel, player -> {});
channelRegisterHandlers.put(ApolloManager.PLUGIN_MESSAGE_CHANNEL, lunar::sendRestrictions);
channelRegisterHandlers.put(Feather.CHANNEL, new Feather()::sendRestrictions);
channelRegisterHandlers.put("openboatutils:settings", player -> send(player, "openboatutils:settings", new byte[] { 0, 0 })); //https://github.com/o7Moon/OpenBoatUtils/wiki/Packets (Reset packet)
channelRegisterHandlers.put("itemswapper:disable", player -> send(player, "itemswapper:disable", new byte[]{ 0 })); //https://github.com/tr7zw/ItemSwapper/blob/main/src/main/java/dev/tr7zw/itemswapper/packets/DisableModPayload.java
channelRegisterHandlers.put("xaerominimap:main", player -> player.sendMessage(Component.text("§n§o§m§i§n§i§m§a§p"))); //https://www.curseforge.com/minecraft/mc-mods/xaeros-minimap
channelRegisterHandlers.put("litemoretica:init_easy_place", player -> Chatter.disconnect(player).prefixless("MOD_YELLOW_SING", "litematica")); //https://github.com/Earthcomputer/litemoretica/tree/master
channelRegisterHandlers.put("litemoretica:init_ea", player -> Chatter.disconnect(player).prefixless("MOD_YELLOW_SING", "litematica")); //https://github.com/Earthcomputer/litemoretica/tree/master
channelRegisterHandlers.put("voxelmap:settings", player -> Chatter.disconnect(player).prefixless("MOD_YELLOW_SING", "voxelmap")); //https://modrinth.com/mod/voxelmap-updated undocumented
channelRegisterHandlers.put(Controlify.CHANNEL, new Controlify()::onRegister);
for(String channel : Arrays.asList(
"worldinfo:world_id", // JourneyMap and VoxelMap
"journeymap:version", "journeymap:admin_req", "journeymap:mp_options_req", "journeymap:waypoint",
"journeymap:player_loc", "journeymap:admin_save", "journeymap:teleport_req", "journeymap:common",
"journeymap:perm_req"
))
channelRegisterHandlers.put(channel, player -> Chatter.disconnect(player).prefixless("MOD_YELLOW_SING", "minimap"));
for(String channel : Arrays.asList("bedrockify:cauldron_particles", "bedrockify:eat-particles")) //https://github.com/juancarloscp52/BedrockIfy (Bedrock features on Java, banned for reach-around block placement)
channelRegisterHandlers.put(channel, player -> Chatter.disconnect(player).prefixless("MOD_YELLOW_SING", "bedrockify"));
registerBiDirPassthrough("WECUI", "worldedit:cui", "worldedit:internal", "minecraft:wecui");
registerPassthroughToClient(
"axiom:enable", "axiom:initialize_hotbars",
"axiom:response_chunk_data", "axiom:register_world_properties", "axiom:set_world_property",
"axiom:ack_world_properties", "axiom:restrictions", "axiom:marker_data", "axiom:marker_nbt_response",
"axiom:custom_blocks", "axiom:editor_warning", "axiom:blueprint_manifest", "axiom:response_blueprint"
);
registerBiDirPassthrough("axiom:handle_big_payload", "axiom:set_editor_views");
for(String channel : Arrays.asList(
"axiom:hello", "axiom:set_gamemode", "axiom:set_fly_speed", "axiom:set_world_time",
"axiom:set_world_property", "axiom:set_block", "axiom:set_hotbar_slot", "axiom:switch_active_hotbar",
"axiom:teleport", "axiom:request_chunk_data", "axiom:spawn_entity",
"axiom:manipulate_entity", "axiom:delete_entity", "axiom:marker_nbt_request", "axiom:set_buffer"
)) {
channelRegisterHandlers.put(channel, player -> {});
registerPassthroughToServer(channel);
}
for(String channel : Arrays.asList(
"floodgate:skin",
"watut:nbt", //https://github.com/Corosauce/WATUT
"bclib:hello_server",
"vivecraft:data", //https://github.com/Vivecraft/VivecraftMod https://github.com/jrbudda/Vivecraft_Spigot_Extensions https://github.com/Techjar/Vivecraft_BungeeCord_Extensions (VR support)
"badpackets:channel_sync" //https://github.com/badasintended/badpackets (Forge fabric translation layer)
)) {
channelRegisterHandlers.put(channel, player -> {});
register(channel, false, directional(UNKNOWN, DROP));
}
for(String channel : Arrays.asList(
"UNREGISTER", "minecraft:unregister", // used by carpet and servux
"WDL|REQUEST", "wdl:request",
"minecraft:intave", //undocumented, byte stringlength, clientconfig, byte length, json {"legacySneakHeight":false,"legacyOldRange":false,"legacyOldSlowdown":false}
"waila:entity", "waila:block",
"lambdacontrols:hello",
"midnightcontrols:controls_mode",
"inventorysorter:sync_settings_packet",
"voicechat:request_secret", "voicechat:update_state",
"shulkerboxtooltip:c2s_handshake",
"noxesium:client_information", "noxesium:client_settings",
"polymer:handshake", //https://github.com/Patbox/polymer
"minecells:request_sync_minecells_data",
"puzzlesapi:1/4",
"fancymenu:variable_cmd_sugg", "fancymenu:packet_bridge",
"jade:request_entity", "jade:request_tile",
"tiscm:network/v1", //https://github.com/gnembon/fabric-carpet
"couplings:client_config",
"yigd:config_update_c2s",
"kiwi:set_cosmetic",
"fwaystones:request_player_waystone_update",
"openboatutils:settings", //https://github.com/o7Moon/OpenBoatUtils
"block-event-separator:handshake", //https://github.com/SpaceWalkerRS/block-event-separator (Separating block events)
"oth3r-sit:settings_v1.1" //https://github.com/Oth3r/Sit (Sitting mod)
))
register(channel, false, directional(UNKNOWN, DROP));
register("REGISTER", false, directional(this::serverRegistersChannel, this::clientRegistersChannel));
register("minecraft:register", false, directional(this::serverRegistersChannel, this::clientRegistersChannel));
register("MC|Brand", false, directional(this::steamWarBrand, this::userBrand));
register("minecraft:brand", false, directional(this::steamWarBrand, this::userBrand));
//Needs to be registered cause paper refuses to send PluginMessages on unregistered channels...
register("sw:bridge", true, directional(onlySWSource(async(event -> NetworkPacket.handle(new ServerMetaInfo((ServerConnection) event.getSource()), event.getData()))), UNKNOWN));
registerPassthroughToServer("sw:hotkeys");
register("fabricmodsender:mods", true, directional(UNKNOWN, async(new FabricModSender()::handlePluginMessage)));
register("WDL|INIT", true, directional(UNKNOWN, wdl::handlePluginMessage));
register("wdl:init", true, directional(UNKNOWN, wdl::handlePluginMessage));
register(ApolloManager.PLUGIN_MESSAGE_CHANNEL, true, async(lunar::handlePluginMessage));
register(Alpine.HANDSHAKE, false, directional(UNKNOWN, new Alpine()::handleHandshakeMessage));
register("LMC", true, directional(UNKNOWN, async(labyMod::handlePluginMessage)));
register("labymod3:main", true, directional(UNKNOWN, async(labyMod::handlePluginMessage)));
register("labymod:neo", false, directional(UNKNOWN, DROP)); //undocumented, JSON format "0" byte, packetlängen byte, {"version":"4.1.25"}
register(FML.CHANNEL, true, directional(UNKNOWN, async(new FML()::handlePluginMessage)));
//vanilla does not register any channels (sends only one minecraft:brand vanilla, nothing else (potential spoofed client detection))
//Forge interestingly registers all channels the server registers
//meteor https://github.com/MeteorDevelopment/meteor-client/blob/master/src/main/java/meteordevelopment/meteorclient/systems/modules/misc/ServerSpoof.java https://github.com/MeteorDevelopment/meteor-client/blob/master/src/main/java/meteordevelopment/meteorclient/systems/modules/misc/DiscordPresence.java
//litematica/malilib https://github.com/maruohon/litematica/issues/75 https://github.com/maruohon/malilib/blob/liteloader_1.12.2/src/main/java/malilib/network/message/ConfigLockPacketHandler.java#L65
// Hackclientlike modsuppressor for labymod: https://github.com/Neocraftr/LabyMod-NeoEssentials (Potentially recognizable from NO Addons/NO Mods?) https://github.com/Neocraftr/LabyMod-NeoEssentials/blob/master/src/main/java/de/neocraftr/neoessentials/utils/BytecodeMethods.java
}
@Subscribe
public void onPluginMessage(PluginMessageEvent event) {
event.setResult(PluginMessageEvent.ForwardResult.handled());
try {
handlers.getOrDefault(event.getIdentifier().getId(), UNKNOWN).handle(event);
} catch (Exception e) {
throw new SecurityException("PluginMessage handling exception: " + event + "\n" + Arrays.toString(event.getData()), e);
}
}
private void registerPassthroughToClient(String... channels) {
for(String channel : channels) {
channelRegisterHandlers.put(channel, player -> {});
register(channel, false, directional(PASS_THROUGH, UNKNOWN));
}
}
private void registerPassthroughToServer(String... channels) {
for(String channel : channels)
register(channel, false, directional(UNKNOWN, PASS_THROUGH));
}
private void registerBiDirPassthrough(String... channels) {
for(String channel : channels) {
channelRegisterHandlers.put(channel, player -> {});
register(channel, false, PASS_THROUGH);
}
}
private void register(String channel, boolean clientSideRegister, Parser handler) {
handlers.put(channel, handler);
if(clientSideRegister)
VelocityCore.getProxy().getChannelRegistrar().register(channel.indexOf(':') != -1 ? MinecraftChannelIdentifier.from(channel) : new LegacyChannelIdentifier(channel));
}
private void clientRegistersChannel(PluginMessageEvent event) {
Player player = (Player) event.getSource();
for(String channel : new String(event.getData()).split("\0")) {
channelRegisterHandlers.getOrDefault(channel, p -> VelocityCore.getLogger().log(Level.WARNING, () -> p.getUsername() + " registered unknown channel " + channel)).accept(player);
}
PASS_THROUGH.handle(event);
}
private void serverRegistersChannel(PluginMessageEvent event) {
Player player = (Player) event.getTarget();
List<String> channels = new ArrayList<>(Arrays.asList(new String(event.getData()).split("\0")));
channels.removeIf(channel -> channel.equals("sw:bridge"));
send(player, "REGISTER", "minecraft:register", String.join("\0", channels).getBytes());
}
private void userBrand(PluginMessageEvent event) {
Player player = (Player) event.getSource();
ByteBuf buf = Unpooled.wrappedBuffer(event.getData());
String brand = ProtocolUtils.readString(buf);
boolean lunarclient = brand.startsWith("lunarclient:");
VelocityCore.getLogger().log(knownBrands.contains(brand) || lunarclient ? Level.INFO : Level.WARNING, () -> player.getUsername() + " joins with brand: " + brand);
if(lunarclient)
lunar.sendRestrictions(player);
if(brand.equals("badlion"))
badlion.sendRestrictions(player);
PASS_THROUGH.handle(event);
}
private void steamWarBrand(PluginMessageEvent event) {
Player player = (Player) event.getTarget();
String brand = Chatter.of(player).parseToLegacy("STEAMWAR_BRAND", "Velocity", player.getCurrentServer().map(serverConnection -> serverConnection.getServerInfo().getName()).orElse(""), new String(event.getData(), 1, event.getData().length - 1));
player.sendPluginMessage(event.getIdentifier(), genBufPacket(buf -> ProtocolUtils.writeString(buf, brand)));
}
private Parser directional(Parser fromServer, Parser fromPlayer) {
return event -> {
if(event.getSource() instanceof Player)
fromPlayer.handle(event);
else
fromServer.handle(event);
};
}
private Parser onlySWSource(Parser parser) {
return event -> {
ChannelMessageSource sender = event.getSource();
if(TeamCommand.isLocalhost(sender instanceof Player player ? IPSanitizer.getTrueAddress(player) : ((ServerConnection) sender).getServerInfo().getAddress().getAddress()))
parser.handle(event);
else
UNKNOWN.handle(event);
};
}
private Parser async(Parser parser) {
return event -> VelocityCore.schedule(() -> parser.handle(event)).schedule();
}
private interface Parser {
void handle(PluginMessageEvent event);
}
}
@@ -0,0 +1,91 @@
/*
This file is a part of the SteamWar software.
Copyright (C) 2020 SteamWar.de-Serverteam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.velocitycore.listeners;
import com.velocitypowered.api.event.Subscribe;
import com.velocitypowered.api.event.connection.PostLoginEvent;
import de.steamwar.velocitycore.Config;
import de.steamwar.velocitycore.VelocityCore;
import de.steamwar.command.TypeValidator;
import de.steamwar.messages.Chatter;
import de.steamwar.messages.Message;
import de.steamwar.sql.PollAnswer;
import net.kyori.adventure.text.event.ClickEvent;
public class PollSystem extends BasicListener {
public static void init() {
poll = VelocityCore.get().getConfig().getPoll();
if(poll == null)
return;
if(noCurrentPoll())
return;
PollAnswer.setCurrentPoll(poll.getQuestion());
new PollSystem();
}
private static Config.Poll poll = null;
@Subscribe
public void onPostLogin(PostLoginEvent event){
Chatter player = Chatter.of(event.getPlayer());
PollAnswer answer = PollAnswer.get(player.user().getId());
if(answer.hasAnswered())
return;
sendPoll(player);
}
public static void sendPoll(Chatter player) {
player.system("POLL_HEADER");
player.prefixless("POLL_HEADER2");
player.prefixless("POLL_QUESTION", poll.getQuestion());
for(int i = 1; i <= poll.getAnswers().size(); i++) {
player.prefixless("POLL_ANSWER", new Message("POLL_ANSWER_HOVER", poll.getAnswers().get(i-1)), ClickEvent.runCommand("/poll " + i), poll.getAnswers().get(i-1));
}
}
private static boolean noCurrentPoll(){
return poll == null;
}
public static TypeValidator<Chatter> noPoll() {
return (sender, value, messageSender) -> {
if (PollSystem.noCurrentPoll()) {
messageSender.send("POLL_NO_POLL");
return false;
}
return true;
};
}
public static int answers(){
return poll.getAnswers().size();
}
public static String getAnswer(int i) {
return poll.getAnswers().get(i);
}
}
@@ -0,0 +1,48 @@
/*
This file is a part of the SteamWar software.
Copyright (C) 2020 SteamWar.de-Serverteam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.velocitycore.listeners;
import com.velocitypowered.api.event.Subscribe;
import com.velocitypowered.api.event.connection.DisconnectEvent;
import com.velocitypowered.api.event.connection.PostLoginEvent;
import de.steamwar.velocitycore.VelocityCore;
import de.steamwar.sql.Session;
import de.steamwar.sql.SteamwarUser;
import java.sql.Timestamp;
import java.time.Instant;
import static de.steamwar.persistent.Storage.sessions;
public class SessionManager extends BasicListener {
@Subscribe
public void onPostLogin(PostLoginEvent event){
sessions.put(event.getPlayer(), Timestamp.from(Instant.now()));
}
@Subscribe
public void onDisconnect(DisconnectEvent e){
Timestamp timestamp = sessions.remove(e.getPlayer());
if(timestamp != null) {
VelocityCore.schedule(() -> Session.insertSession(SteamwarUser.get(e.getPlayer().getUniqueId()).getId(), timestamp)).schedule();
}
}
}
@@ -0,0 +1,41 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2020 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.velocitycore.listeners;
import com.velocitypowered.api.event.Subscribe;
import com.velocitypowered.api.event.player.PlayerSettingsChangedEvent;
import com.velocitypowered.api.proxy.Player;
import de.steamwar.velocitycore.VelocityCore;
import de.steamwar.velocitycore.network.NetworkSender;
import de.steamwar.sql.SteamwarUser;
import de.steamwar.network.packets.server.LocaleInvalidationPacket;
public class SettingsChangedListener extends BasicListener {
@Subscribe
public void onSettingsChanged(PlayerSettingsChangedEvent event) {
VelocityCore.schedule(() -> {
Player player = event.getPlayer();
SteamwarUser user = SteamwarUser.get(player.getUniqueId());
user.setLocale(event.getPlayerSettings().getLocale(), false);
NetworkSender.send(player, new LocaleInvalidationPacket(user.getId()));
}).schedule();
}
}
@@ -0,0 +1,71 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2024 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.mods;
import com.velocitypowered.api.event.connection.PluginMessageEvent;
import com.velocitypowered.api.proxy.Player;
import de.steamwar.velocitycore.listeners.PluginMessage;
import org.msgpack.core.MessagePack;
import org.msgpack.core.MessagePacker;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
public class Alpine {
//https://github.com/alpine-client/alpine-client-api
public static final String HANDSHAKE = "ac:handshake";
public static final String PLAY = "ac:play";
private final byte[] magicHandshakeResponse = new byte[] {0x3A, 0x3D}; //https://github.com/alpine-client/alpine-client-api/blob/master/src/main/java/com/alpineclient/plugin/listener/plugin/HandshakeListener.java#L19
private final byte[] modulesPacket;
public Alpine() {
//https://github.com/alpine-client/alpine-client-api/blob/master/MODULES.md
String[] disabledModules = new String[] {
"armor_status", "cannon_playback", "cannon_view", "clear_water", "explosion_boxes", "fullbright",
"hit_color", "inventory_tweaks", "low_hp_tint", "minimap", "perspective_mod", "potion_status",
"reach_display", "schematica", "tnt_timer", "toggle_sneak_sprint"
};
ByteArrayOutputStream stream = new ByteArrayOutputStream();
try (MessagePacker packer = MessagePack.newDefaultPacker(stream)) {
//https://github.com/alpine-client/alpine-client-api/blob/master/src/main/java/com/alpineclient/plugin/network/Packet.java#L103
packer.packInt(0xB0);
//https://github.com/alpine-client/alpine-client-api/blob/master/src/main/java/com/alpineclient/plugin/network/packet/PacketModules.java
packer.packArrayHeader(disabledModules.length);
for (String module : disabledModules) {
packer.packString(module);
packer.packBoolean(false); //https://github.com/alpine-client/alpine-client-api/blob/master/src/main/java/com/alpineclient/plugin/config/impl/GeneralConfig.java
}
} catch (IOException e) {
throw new SecurityException(e);
}
modulesPacket = stream.toByteArray();
}
public void handleHandshakeMessage(PluginMessageEvent event) {
Player player = (Player) event.getSource();
PluginMessage.send(player, HANDSHAKE, magicHandshakeResponse);
PluginMessage.send(player, PLAY, modulesPacket);
}
}
@@ -0,0 +1,55 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2024 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.mods;
import com.google.gson.JsonObject;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier;
public class Badlion {
// https://github.com/BadlionClient/BadlionClientModAPI
private final byte[] packet;
public Badlion() { //TODO check if working or (json) modsDisallowed wrapper necessary
JsonObject disabled = new JsonObject();
disabled.addProperty("disabled", true);
JsonObject json = new JsonObject();
json.add("Clear Glass", disabled);
json.add("ClearWater", disabled);
json.add("FOV Changer", disabled);
json.add("Hitboxes", disabled);
json.add("LevelHead", disabled);
json.add("MiniMap", disabled);
json.add("MLG Cobweb", disabled);
json.add("Replay", disabled); //TODO check if ReplayMod restrictions work
json.add("Schematica", disabled);
json.add("ToggleSneak", disabled);
json.add("ToggleSprint", disabled);
json.add("TNT Time", disabled);
packet = json.toString().getBytes();
}
public void sendRestrictions(Player player) {
player.sendPluginMessage(MinecraftChannelIdentifier.from("badlion:mods"), packet);
}
}
@@ -0,0 +1,53 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2024 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.mods;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import de.steamwar.velocitycore.listeners.PluginMessage;
public class Controlify {
//https://modrinth.com/mod/controlify
//https://github.com/isXander/Controlify/blob/1.20.x/dev/src/main/java/dev/isxander/controlify/server/ServerPolicyPacket.java
//https://github.com/isXander/Controlify/blob/1.20.x/dev/src/main/java/dev/isxander/controlify/server/ServerPolicies.java
public static final String CHANNEL = "controlify:server_policy";
private final byte[][] packets;
public Controlify() {
packets = new byte[][] {
restrict("reachAround"),
restrict("disableFlyDrifting")
};
}
private byte[] restrict(String name) {
return PluginMessage.genBufPacket(buf -> {
ProtocolUtils.writeString(buf, name);
buf.writeBoolean(false);
});
}
public void onRegister(Player player) {
for(byte[] packet : packets)
player.sendPluginMessage(MinecraftChannelIdentifier.from(CHANNEL), packet);
}
}
@@ -0,0 +1,95 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2024 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.mods;
import com.velocitypowered.api.event.Subscribe;
import com.velocitypowered.api.event.connection.PluginMessageEvent;
import com.velocitypowered.api.event.connection.PostLoginEvent;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.messages.LegacyChannelIdentifier;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import de.steamwar.velocitycore.VelocityCore;
import de.steamwar.velocitycore.listeners.BasicListener;
import de.steamwar.messages.Chatter;
import de.steamwar.sql.Mod;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import java.util.*;
import java.util.concurrent.TimeUnit;
public class FML extends BasicListener {
// https://wiki.vg/Minecraft_Forge_Handshake#FML_protocol_.281.7_-_1.12.29
public static final String CHANNEL = "FML|HS";
private final byte[] helloPacket = new byte[]{
/* Packet type: ServerHello */ 0,
/* FML protocol version */ 2,
/* Override dimension (int) */ 0, 0, 0, 0
};
private static final Set<UUID> unlocked = new HashSet<>();
@Subscribe
public void onPostLogin(PostLoginEvent event) {
Player player = event.getPlayer();
synchronized (unlocked) {
if(unlocked.contains(player.getUniqueId())){
unlocked.remove(player.getUniqueId());
return;
}
}
//if(isFML(player, "\0FML\0"))
if(player.getProtocolVersion().lessThan(ProtocolVersion.MINECRAFT_1_13))
player.sendPluginMessage(new LegacyChannelIdentifier(CHANNEL), helloPacket);
}
public void handlePluginMessage(PluginMessageEvent event) {
Player p = (Player) event.getSource();
ByteBuf buf = Unpooled.wrappedBuffer(event.getData());
if (buf.readByte() == /* ModList */ 2) {
int numMods = ProtocolUtils.readVarInt(buf);
List<Mod> mods = new ArrayList<>();
for(int i = 0; i < numMods; i++) {
String name = ProtocolUtils.readString(buf);
ProtocolUtils.readString(buf); // version
mods.add(Mod.getOrCreate(name, Mod.Platform.FORGE));
}
if (ModUtils.handleMods(p, mods)) {
synchronized (unlocked) {
unlocked.add(p.getUniqueId());
}
Chatter.disconnect(p).system("MODS_CHECKED");
VelocityCore.schedule(() -> {
synchronized (unlocked) {
unlocked.remove(p.getUniqueId());
}
}).delay(30, TimeUnit.SECONDS).schedule();
}
}
}
}
@@ -0,0 +1,176 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2024 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.mods;
import com.velocitypowered.api.event.Subscribe;
import com.velocitypowered.api.event.connection.PreLoginEvent;
import com.velocitypowered.api.proxy.LoginPhaseConnection;
import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier;
import com.velocitypowered.proxy.connection.client.LoginInboundConnection;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import de.steamwar.velocitycore.VelocityCore;
import de.steamwar.velocitycore.listeners.BasicListener;
import de.steamwar.velocitycore.listeners.PluginMessage;
import de.steamwar.sql.Mod;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import lombok.AllArgsConstructor;
import net.kyori.adventure.text.Component;
import java.util.*;
import java.util.logging.Level;
public class FML2 extends BasicListener {
// FML2: https://wiki.vg/Minecraft_Forge_Handshake#FML2_protocol_.281.13_-_Current.29
// FML3: https://github.com/adde0109/Ambassador/tree/non-api/src/main/java/org/adde0109/ambassador/forge
// FORGE: https://github.com/MinecraftForge/MinecraftForge/blob/1.20.x/src/main/java/net/minecraftforge/network/NetworkInitialization.java
// FORGE: https://github.com/MinecraftForge/MinecraftForge/blob/1.20.x/src/main/java/net/minecraftforge/network/ForgePacketHandler.java
// FORGE: https://github.com/MinecraftForge/MinecraftForge/blob/1.20.x/src/main/java/net/minecraftforge/network/packets/ModVersions.java
public static boolean isFML(LoginInboundConnection connection, String type) {
return Hostname.getExtraHandshakeData(connection).equals(type);
}
private final byte[] fml2ModListPacket;
private final byte[] fml3ModListPacket;
private final byte[] forgeModListPacket;
public FML2() {
fml2ModListPacket = generateModListPacket(false);
fml3ModListPacket = generateModListPacket(true);
forgeModListPacket = PluginMessage.genBufPacket(buf -> {
buf.writeByte(0); // Login wrapper packet
ProtocolUtils.writeString(buf, "forge:handshake");
ByteBuf packet = Unpooled.buffer();
packet.writeByte(1); // Mod list packet
ProtocolUtils.writeVarInt(packet, 0); // Mod amount
ProtocolUtils.writeVarInt(buf, packet.readableBytes());
buf.writeBytes(packet);
});
}
@Subscribe
public void onLogin(PreLoginEvent event) {
LoginInboundConnection connection = (LoginInboundConnection) event.getConnection();
boolean fml2 = isFML(connection, "\0FML2\0");
boolean fml3 = isFML(connection, "\0FML3\0");
boolean forge = isFML(connection, "\0FORGE");
if(!fml2 && !fml3 && !forge)
return;
FML2LoginHandler handler = new FML2LoginHandler(connection, event.getUniqueId(), forge);
if(forge) {
connection.sendLoginPluginMessage(MinecraftChannelIdentifier.from("forge:login"), forgeModListPacket, handler);
} else {
connection.sendLoginPluginMessage(MinecraftChannelIdentifier.from("fml:loginwrapper"), fml3 ? fml3ModListPacket : fml2ModListPacket, handler);
}
}
private byte[] generateModListPacket(boolean fml3) {
return PluginMessage.genBufPacket(buf -> {
ProtocolUtils.writeString(buf, "fml:handshake");
ByteBuf packet = Unpooled.buffer();
packet.writeByte(1); // Mod list packet
ProtocolUtils.writeVarInt(packet, 0); // Mod amount
if(fml3) {
ProtocolUtils.writeVarInt(packet,1); // Channel amount
ProtocolUtils.writeString(packet, "forge:tier_sorting");
ProtocolUtils.writeString(packet, "1.0");
} else {
ProtocolUtils.writeVarInt(packet, 0); // Channel amount
}
ProtocolUtils.writeVarInt(packet, 0); // Registries amount
if(fml3)
ProtocolUtils.writeVarInt(packet, 0); // DataPacks amount
ProtocolUtils.writeVarInt(buf, packet.readableBytes());
buf.writeBytes(packet);
});
}
@AllArgsConstructor
private static class FML2LoginHandler implements LoginPhaseConnection.MessageConsumer {
private final LoginInboundConnection connection;
private final UUID uuid;
private final boolean forge;
@Override
public String toString() {
return "SteamWar Forge Handler";
}
@Override
public void onMessageResponse(byte[] data) {
if(data == null) {
abort(null, "Not FML2/3 client");
return;
}
ByteBuf buf = Unpooled.wrappedBuffer(data);
if(forge && buf.readByte() != 0) {
abort(data, "Not FORGE login wrapper");
return;
}
if(!ProtocolUtils.readString(buf).equals(forge ? "forge:handshake" : "fml:handshake")) {
abort(data, "Not FML2/3/FORGE handshake response");
return;
}
if(ProtocolUtils.readVarInt(buf) != buf.readableBytes()) {
abort(data, "FML2/3/FORGE packet size mismatch");
return;
}
if(ProtocolUtils.readVarInt(buf) != (forge ? /* Mod Versions */ 1 : /* Mod List Reply */ 2)) {
abort(data, "Not FML2/3/FORGE mod list reply");
return;
}
List<Mod> mods = new ArrayList<>();
int modCount = ProtocolUtils.readVarInt(buf);
for(int i = 0; i < modCount; i++) {
mods.add(Mod.getOrCreate(ProtocolUtils.readString(buf), Mod.Platform.FORGE));
if(forge) {
ProtocolUtils.readString(buf); // Human readable name
ProtocolUtils.readString(buf); // Version
}
}
ModUtils.handleMods(uuid, Locale.getDefault(), connection::disconnect, mods);
}
private void abort(byte[] response, String error) {
connection.disconnect(Component.text(error));
VelocityCore.getLogger().log(Level.SEVERE, () -> error + "\n" + Base64.getEncoder().encodeToString(response));
}
}
}
@@ -0,0 +1,144 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2024 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.mods;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import com.velocitypowered.api.event.Subscribe;
import com.velocitypowered.api.event.connection.DisconnectEvent;
import com.velocitypowered.api.event.connection.PluginMessageEvent;
import com.velocitypowered.api.event.player.ServerConnectedEvent;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import de.steamwar.persistent.Storage;
import de.steamwar.velocitycore.VelocityCore;
import de.steamwar.velocitycore.listeners.BasicListener;
import de.steamwar.sql.Mod;
import de.steamwar.sql.SWException;
import de.steamwar.sql.SteamwarUser;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import java.util.*;
import java.util.concurrent.TimeUnit;
public class FabricModSender extends BasicListener {
private final Set<String> neededFabricMods = new HashSet<>();
private final Set<String> neededQuiltMods = new HashSet<>();
public FabricModSender() {
neededFabricMods.add("java");
neededFabricMods.add("minecraft");
neededFabricMods.add("steamwarmodsender");
neededQuiltMods.addAll(neededFabricMods);
neededFabricMods.add("fabricloader");
neededQuiltMods.add("quilt_loader");
VelocityCore.schedule(() -> {
synchronized (Storage.fabricExpectPluginMessage) {
for (Map.Entry<Player, Long> entry : Storage.fabricExpectPluginMessage.entrySet()) {
if (!Storage.fabricCheckedPlayers.containsKey(entry.getKey())) {
continue;
}
if (System.currentTimeMillis() - entry.getValue() > TimeUnit.SECONDS.toMillis(20)) {
Storage.fabricExpectPluginMessage.remove(entry.getKey());
return;
}
}
}
}).repeat(1, TimeUnit.SECONDS).schedule();
}
public void handlePluginMessage(PluginMessageEvent e){
Player player = (Player) e.getSource();
SteamwarUser user = SteamwarUser.get(player.getUniqueId());
if (!Storage.fabricCheckedPlayers.containsKey(player)) {
synchronized (Storage.fabricExpectPluginMessage) {
if (Storage.fabricExpectPluginMessage.containsKey(player)) {
logMessage(user, "Was not fabric checked but send message nonetheless", Arrays.toString(e.getData()));
return;
}
}
}
Storage.fabricExpectPluginMessage.remove(player);
List<Mod> mods = new ArrayList<>();
ByteBuf buf = Unpooled.wrappedBuffer(e.getData());
String data = ProtocolUtils.readString(buf, 1024*1024);
if(buf.readableBytes() > 0) {
logMessage(user, "Invalid message length", Arrays.toString(e.getData()));
return;
}
JsonArray array = JsonParser.parseString(data).getAsJsonArray();
for(JsonElement mod : array) {
mods.add(Mod.getOrCreate(mod.getAsString(), Mod.Platform.FABRIC));
}
if(!neededModsContained(neededFabricMods, mods) && !neededModsContained(neededQuiltMods, mods)) {
logMessage(user, "Needed mods are not contained", data);
return;
}
if(!ModUtils.handleMods(player,mods))
return;
if (!Storage.fabricCheckedPlayers.containsKey(player)) {
Storage.fabricCheckedPlayers.put(player, data.hashCode());
} else if (Storage.fabricCheckedPlayers.get(player) != data.hashCode()) {
logMessage(user, "Mods changed during runtime", data);
}
}
@Subscribe
public void onServerSwitchEvent(ServerConnectedEvent e) {
if (e.getPreviousServer().isEmpty()) return;
synchronized (Storage.fabricExpectPluginMessage) {
Storage.fabricExpectPluginMessage.put(e.getPlayer(), System.currentTimeMillis());
}
}
@Subscribe
public void onDisconnect(DisconnectEvent e) {
Player player = e.getPlayer();
Storage.fabricCheckedPlayers.remove(player);
synchronized (Storage.fabricExpectPluginMessage) {
Storage.fabricExpectPluginMessage.remove(player);
}
}
private boolean neededModsContained(Set<String> neededMods, List<Mod> mods) {
return mods.stream()
.map(Mod::getModName)
.filter(neededMods::contains)
.count() == neededFabricMods.size();
}
private void logMessage(SteamwarUser user, String reason, String data) {
SWException.log("FabricModSender " + user.getUserName() + ": " + reason, data);
}
}
@@ -0,0 +1,57 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2024 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.mods;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier;
public class Feather {
//https://github.com/Koupah/Feather-Client-API/blob/main/src/club/koupah/feather/packets/FeatherMod.java
//https://archive.org/details/feather-server-api
public static final String CHANNEL = "feather:client";
private final byte[] packet;
public Feather() {
JsonArray array = new JsonArray();
array.add("clearWater");
array.add("coordinates");
array.add("coordinates");
array.add("hitbox");
array.add("hypixel");
array.add("reachDisplay");
array.add("snaplook");
array.add("toggleSprint");
JsonObject mods = new JsonObject();
mods.add("mods", array);
JsonObject obj = new JsonObject();
obj.addProperty("packetType", "DISABLE_MODS");
obj.add("payload", mods);
packet = obj.toString().getBytes();
}
public void sendRestrictions(Player player) {
player.sendPluginMessage(MinecraftChannelIdentifier.from(CHANNEL), packet);
}
}
@@ -0,0 +1,84 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2024 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.mods;
import com.velocitypowered.api.event.Subscribe;
import com.velocitypowered.api.event.connection.ConnectionHandshakeEvent;
import com.velocitypowered.proxy.connection.client.InitialInboundConnection;
import com.velocitypowered.proxy.connection.client.LoginInboundConnection;
import com.velocitypowered.proxy.protocol.packet.HandshakePacket;
import de.steamwar.persistent.Reflection;
import de.steamwar.velocitycore.VelocityCore;
import de.steamwar.velocitycore.listeners.BasicListener;
import java.util.HashSet;
import java.util.Set;
import java.util.logging.Level;
public class Hostname extends BasicListener {
private static final Reflection.Field<LoginInboundConnection, InitialInboundConnection> delegate = new Reflection.Field<>(LoginInboundConnection.class, "delegate");
public static InitialInboundConnection getInitialInboundConnection(LoginInboundConnection loginInboundConnection) {
return delegate.get(loginInboundConnection);
}
private static final Reflection.Field<InitialInboundConnection, HandshakePacket> handshake = new Reflection.Field<>(InitialInboundConnection.class, "handshake");
public static String getExtraHandshakeData(LoginInboundConnection loginInboundConnection) {
HandshakePacket handshakePacket = handshake.get(getInitialInboundConnection(loginInboundConnection));
int i = handshakePacket.getServerAddress().indexOf('\0');
if(i == -1)
return "";
return handshakePacket.getServerAddress().substring(i);
}
private final Set<String> knownHostnames = new HashSet<>();
private final Set<String> knownExtraData = new HashSet<>();
public Hostname() {
knownHostnames.add("steamwar.de");
knownHostnames.add("78.31.71.136");
knownHostnames.add("memewar.de"); // Chaoscaot
knownHostnames.add("dampfkrieg.de"); // Chaoscaot
knownHostnames.add("127.0.0.1"); // Geyser
knownHostnames.add("@mat:matdoes.dev "); //https://github.com/mat-1/matscan
knownHostnames.add("wtf.mynx.lol"); //https://discord.com/invite/serverseeker
knownHostnames.add("masscan");
knownHostnames.add("aaa");
knownExtraData.add("");
knownExtraData.add("\0FML\0");
knownExtraData.add("\0FML2\0");
knownExtraData.add("\0FML3\0");
knownExtraData.add("\0FORGE");
}
@Subscribe
public void onHandshake(ConnectionHandshakeEvent event) {
String hostname = event.getConnection().getVirtualHost().orElseThrow().getHostName().toLowerCase();
if (!knownHostnames.contains(hostname) && !hostname.endsWith(".steamwar.de"))
VelocityCore.getLogger().log(Level.WARNING, () -> event.getConnection().getRemoteAddress() + " connected with unknown hostname " + hostname);
String extraData = getExtraHandshakeData((LoginInboundConnection) event.getConnection());
if (!knownExtraData.contains(extraData))
VelocityCore.getLogger().log(Level.WARNING, () -> event.getConnection().getRemoteAddress() + " connected with unknown extra data " + extraData);
}
}
@@ -0,0 +1,87 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2024 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.mods;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.velocitypowered.api.event.connection.PluginMessageEvent;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import de.steamwar.velocitycore.VelocityCore;
import de.steamwar.velocitycore.listeners.PluginMessage;
import de.steamwar.sql.Mod;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import java.util.LinkedList;
import java.util.List;
import java.util.logging.Level;
public class LabyMod {
// https://docs.labymod.net/pages/server/introduction/
// https://github.com/LabyMod/labymod-server-api
// https://dl.labymod.net/addons.json
private final byte[] gameInfoPacket;
public LabyMod() {
gameInfoPacket = PluginMessage.genBufPacket(buf -> {
ProtocolUtils.writeString(buf, "discord_rpc");
JsonObject json = new JsonObject();
json.addProperty("hasGame", true);
json.addProperty("game_mode", "steamwar.de");
json.addProperty("game_startTime", 0);
json.addProperty("game_endTime", 0);
ProtocolUtils.writeString(buf, json.toString());
});
}
public void handlePluginMessage(PluginMessageEvent event) {
Player player = (Player) event.getSource();
player.sendPluginMessage(event.getIdentifier(), gameInfoPacket);
ByteBuf buf = Unpooled.wrappedBuffer(event.getData());
String purpose = ProtocolUtils.readString(buf);
if(!"INFO".equals(purpose))
return;
JsonObject message = JsonParser.parseString(ProtocolUtils.readString(buf)).getAsJsonObject();
List<Mod> mods = new LinkedList<>();
if(message.has("addons")) {
for(JsonElement element : message.getAsJsonArray("addons")) {
JsonObject addon = element.getAsJsonObject();
mods.add(Mod.getOrCreate(addon.get("name").getAsString(), Mod.Platform.LABYMOD));
}
}
if(message.has("mods")) {
VelocityCore.getLogger().log(Level.WARNING, () -> "LabyMod External Mods for debugging: " + message.getAsJsonArray("mods"));
//for(JsonElement element : message.getAsJsonArray("mods")) {
// JsonObject addon = element.getAsJsonObject();
// //TODO observe: FORGE and FABRIC mods available, do they always and with .jar? (would equal new mod platform)
// //mods.add(Mod.getOrCreate(addon.get("name").getAsString().replace(".jar", ""), Mod.Platform.FORGE));
//}
}
ModUtils.handleMods(player, mods);
}
}
@@ -0,0 +1,130 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2024 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.mods;
import com.lunarclient.apollo.ApolloManager;
import com.lunarclient.apollo.libs.protobuf.Any;
import com.lunarclient.apollo.libs.protobuf.InvalidProtocolBufferException;
import com.lunarclient.apollo.libs.protobuf.Message;
import com.lunarclient.apollo.mods.impl.*;
import com.lunarclient.apollo.module.ApolloModuleManager;
import com.lunarclient.apollo.module.ApolloModuleManagerImpl;
import com.lunarclient.apollo.module.modsetting.ModSettingModule;
import com.lunarclient.apollo.network.NetworkOptions;
import com.lunarclient.apollo.option.Options;
import com.lunarclient.apollo.player.AbstractApolloPlayer;
import com.lunarclient.apollo.player.v1.ModMessage;
import com.lunarclient.apollo.player.v1.PlayerHandshakeMessage;
import com.velocitypowered.api.event.connection.PluginMessageEvent;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier;
import de.steamwar.velocitycore.VelocityCore;
import de.steamwar.sql.Mod;
import lombok.AllArgsConstructor;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.function.Consumer;
import java.util.logging.Level;
public class Lunar {
// https://lunarclient.dev/apollo/introduction
// https://github.com/LunarClient/Apollo
private final ApolloModuleManager manager = new ApolloModuleManagerImpl().addModule(ModSettingModule.class);
public Lunar() { //TODO seems defunct
Options modSettings = manager.getModule(ModSettingModule.class).getOptions();
modSettings.set(ModReplaymod.ENABLED, false); // TODO check if restrictions working
modSettings.set(ModFreelook.ENABLED, false);
modSettings.set(ModHypixelMod.ENABLED, false);
modSettings.set(ModMinimap.ENABLED, false);
modSettings.set(ModNametag.ENABLED, false);
modSettings.set(ModTeamView.ENABLED, false);
modSettings.set(ModTntCountdown.ENABLED, false);
modSettings.set(ModToggleSneak.TOGGLE_SNEAK_CONTAINER, false);
}
public void sendRestrictions(Player player) {
NetworkOptions.sendOptions(manager.getModules(), true, new SWApolloPlayer(player));
}
public void handlePluginMessage(PluginMessageEvent event) {
Player player = (Player) event.getSource();
Any packet;
try {
packet = Any.parseFrom(event.getData());
} catch (InvalidProtocolBufferException e) {
throw new SecurityException(e);
}
handle(PlayerHandshakeMessage.class, packet, handshake -> {
List<Mod> mods = new ArrayList<>();
for(ModMessage mod : handshake.getInstalledModsList()) {
switch(mod.getType()) {
case TYPE_FABRIC_INTERNAL, TYPE_FORGE_INTERNAL:
// Controlled with ModSettings
break;
case TYPE_FABRIC_EXTERNAL:
mods.add(Mod.getOrCreate(mod.getName(), Mod.Platform.FABRIC));
break;
case TYPE_FORGE_EXTERNAL:
mods.add(Mod.getOrCreate(mod.getName(), Mod.Platform.FORGE));
break;
default:
VelocityCore.getLogger().log(Level.INFO, () -> player.getUsername() + " uses Lunar mod with unknown type " + mod);
break;
}
}
ModUtils.handleMods(player, mods);
});
}
private <T extends Message> void handle(Class<T> type, Any packet, Consumer<T> handler) {
try {
handler.accept(packet.unpack(type));
} catch (InvalidProtocolBufferException ignored) { /*ignored*/ }
}
@AllArgsConstructor
private static class SWApolloPlayer extends AbstractApolloPlayer {
private final Player player;
@Override
public void sendPacket(Message message) {
sendPacket(Any.pack(message).toByteArray());
}
@Override
public void sendPacket(byte[] bytes) {
player.sendPluginMessage(MinecraftChannelIdentifier.from(ApolloManager.PLUGIN_MESSAGE_CHANNEL), bytes);
}
@Override public UUID getUniqueId() { return player.getUniqueId(); }
@Override public String getName() { return player.getUsername(); }
@Override public boolean hasPermission(String s) { return player.hasPermission(s); }
@Override public Object getPlayer() { return player; }
}
}
@@ -0,0 +1,92 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2024 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.mods;
import com.velocitypowered.api.proxy.Player;
import de.steamwar.velocitycore.VelocityCore;
import de.steamwar.velocitycore.commands.PunishmentCommand;
import de.steamwar.messages.Chatter;
import de.steamwar.sql.Mod;
import de.steamwar.sql.Mod.ModType;
import de.steamwar.sql.SteamwarUser;
import de.steamwar.sql.UserPerm;
import lombok.Getter;
import lombok.experimental.UtilityClass;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import java.sql.Timestamp;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.function.Consumer;
import java.util.logging.Level;
import java.util.stream.Collectors;
@UtilityClass
public class ModUtils {
@Getter
private static final Map<UUID,List<Mod>> playerModMap = new HashMap<>();
public static boolean handleMods(Player player, List<Mod> mods) {
return handleMods(player.getUniqueId(), Chatter.of(player).getLocale(), player::disconnect, mods);
}
public static boolean handleMods(UUID uuid, Locale locale, Consumer<Component> disconnect, List<Mod> mods){
Chatter sender = Chatter.of(uuid);
SteamwarUser user = sender.user();
playerModMap.put(uuid,new ArrayList<>(mods));
VelocityCore.getLogger().log(Level.INFO, "%s declares mods: %s".formatted(user.getUserName(), mods.stream().map(mod -> mod.getPlatform() + ":" + mod.getModName()).collect(Collectors.joining(" "))));
ModType max = ModType.YELLOW;
Iterator<Mod> it = mods.iterator();
while(it.hasNext()){
Mod mod = it.next();
if(mod.getModType() == ModType.UNKLASSIFIED || mod.getModType() == ModType.GREEN || (mod.getModType() == ModType.YOUTUBER_ONLY && user.hasPerm(UserPerm.RESTRICTED_MODS)))
it.remove();
else if(mod.getModType() == ModType.RED)
max = ModType.RED;
}
if(mods.isEmpty()) {
return true;
}
ModType finalMax = max;
String modList = mods.stream().filter(mod -> finalMax == ModType.YELLOW || mod.getModType() == ModType.RED).map(Mod::getModName).collect(Collectors.joining("\n"));
String message;
if(mods.size() == 1) {
message = sender.parseToLegacy(max == ModType.RED ? "MOD_RED_SING" : "MOD_YELLOW_SING", locale, modList);
} else {
message = sender.parseToLegacy(max == ModType.RED ? "MOD_RED_PLUR" : "MOD_YELLOW_PLUR", locale, modList);
}
if(max == ModType.RED) {
PunishmentCommand.ban(user, Timestamp.from(Instant.now().plus(7, ChronoUnit.DAYS)), message, SteamwarUser.get(-1), false);
VelocityCore.getLogger().log(Level.SEVERE, "%s %s wurde automatisch wegen der Mods %s gebannt.".formatted(user.getUserName(), user.getId(), modList));
}
disconnect.accept(LegacyComponentSerializer.legacySection().deserialize(message));
return false;
}
}
@@ -0,0 +1,64 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2024 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.mods;
import com.velocitypowered.api.event.Subscribe;
import com.velocitypowered.api.event.player.ServerPostConnectEvent;
import com.velocitypowered.api.proxy.Player;
import de.steamwar.persistent.Bauserver;
import de.steamwar.persistent.Builderserver;
import de.steamwar.velocitycore.VelocityCore;
import de.steamwar.persistent.Subserver;
import de.steamwar.velocitycore.listeners.BasicListener;
import de.steamwar.velocitycore.listeners.PluginMessage;
import java.util.Arrays;
public class ReplayMod extends BasicListener {
// https://gist.github.com/Johni0702/2547c463e51f65f312cb
// https://github.com/ReplayMod/replay-restrictions/blob/master/bungeecord/src/main/java/de/johni0702/replay/restrictions/BungeeCordPlugin.java
// https://github.com/ReplayMod/ReplayMod/blob/stable/src/main/java/com/replaymod/core/utils/Restrictions.java
private final byte[] restrict;
public ReplayMod() {
restrict = PluginMessage.genStreamPacket(out -> {
for(String restriction : Arrays.asList("no_xray", "no_noclip", "only_first_person", "only_recording_player")) {
byte[] bytes = restriction.getBytes();
out.writeByte(bytes.length);
out.write(bytes);
out.writeBoolean(true); // restrict
}
});
}
@Subscribe
public void onPlayerJoin(ServerPostConnectEvent event) {
Player player = event.getPlayer();
if(VelocityCore.get().getConfig().lobbyserver().getPlayersConnected().contains(player))
return;
Subserver subserver = Subserver.getSubserver(player.getCurrentServer().orElseThrow().getServerInfo());
if(subserver instanceof Builderserver || (subserver instanceof Bauserver bauserver && bauserver.getOwner().equals(player.getUniqueId())))
return;
PluginMessage.send(player, "Replay|Restrict", "replaymod:restrict", restrict);
}
}
@@ -0,0 +1,41 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2024 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.mods;
import com.velocitypowered.api.event.Subscribe;
import com.velocitypowered.api.event.connection.PostLoginEvent;
import com.velocitypowered.api.proxy.messages.LegacyChannelIdentifier;
import de.steamwar.velocitycore.listeners.BasicListener;
public class Schematica extends BasicListener {
// https://github.com/Lunatrius/SchematicaPlugin/blob/master/src/main/java/com/github/lunatrius/schematica/plugin/SchematicaPlugin.java
private final byte[] packet = new byte[] {
/* ProtocolVersion? */ 0,
/* PERM_PRINTER */ 1,
/* PERM_SAVE */ 0,
/* PERM_LOAD */ 1
};
@Subscribe
public void onPostLogin(PostLoginEvent event) {
event.getPlayer().sendPluginMessage(new LegacyChannelIdentifier("schematica"), packet);
}
}

Some files were not shown because too many files have changed in this diff Show More