Merge branch 'main' into schematic-investigations

# Conflicts:
#	CommonCore/SQL/src/de/steamwar/sql/CheckedSchematic.java
#	VelocityCore/src/de/steamwar/velocitycore/commands/CheckCommand.java
This commit is contained in:
2025-07-16 14:32:55 +02:00
202 changed files with 4939 additions and 1724 deletions
@@ -0,0 +1,58 @@
/*
* 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/>.
*/
plugins {
steamwar.java
alias(libs.plugins.shadow)
}
tasks.shadowJar {
exclude("META-INF/*")
exclude("org/sqlite/native/FreeBSD/**', 'org/sqlite/native/Mac/**', 'org/sqlite/native/Windows/**', 'org/sqlite/native/Linux-Android/**', 'org/sqlite/native/Linux-Musl/**")
exclude("org/sqlite/native/Linux/aarch64/**', 'org/sqlite/native/Linux/arm/**', 'org/sqlite/native/Linux/armv6/**', 'org/sqlite/native/Linux/armv7/**', 'org/sqlite/native/Linux/ppc64/**', 'org/sqlite/native/Linux/x86/**")
exclude("org/slf4j/**")
//https://imperceptiblethoughts.com/shadow/configuration/minimizing/
duplicatesStrategy = DuplicatesStrategy.INCLUDE
}
tasks.build {
finalizedBy(tasks.shadowJar)
}
java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
dependencies {
compileOnly(libs.velocity)
annotationProcessor(libs.velocityapi)
implementation(libs.jda) {
exclude(module = "opus-java")
}
implementation(libs.sqlite)
implementation(libs.mysql)
implementation(libs.msgpack)
implementation(libs.apolloprotos)
implementation(libs.nbt)
}
@@ -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.discord;
import com.velocitypowered.api.plugin.Plugin;
@Plugin(
id = "depencendiesvelocitycore",
name = "DepencendiesVelocityCore"
)
public class Dependencies {
}
@@ -24,9 +24,11 @@ import com.google.inject.Inject;
import com.google.inject.Module;
import com.google.inject.name.Names;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.context.CommandContext;
import com.velocitypowered.api.command.BrigadierCommand;
import com.velocitypowered.api.command.CommandManager;
import com.velocitypowered.api.command.CommandMeta;
import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.event.EventManager;
import com.velocitypowered.api.event.Subscribe;
import com.velocitypowered.api.event.proxy.ProxyInitializeEvent;
@@ -71,6 +73,8 @@ public class Persistent {
private final Logger logger;
private final Path directory;
private boolean restartQueued = false;
@Inject
public Persistent(ProxyServer proxy, Logger logger, @DataDirectory Path dataDirectory) {
instance = this;
@@ -81,6 +85,12 @@ public class Persistent {
@Subscribe
public void onEnable(ProxyInitializeEvent event) {
proxy.getScheduler().buildTask(instance, () -> {
if (!restartQueued) return;
if (!proxy.getAllPlayers().isEmpty()) return;
proxy.shutdown();
}).repeat(10, TimeUnit.SECONDS).schedule();
proxy.getCommandManager().register(
new BrigadierCommand(
BrigadierCommand.literalArgumentBuilder("softreload")
@@ -89,6 +99,14 @@ public class Persistent {
.build()
)
);
proxy.getCommandManager().register(
new BrigadierCommand(
BrigadierCommand.literalArgumentBuilder("queuerestart")
.requires(commandSource -> commandSource.hasPermission("bungeecore.softreload"))
.executes(this::queueRestart)
.build()
)
);
}
@Subscribe
@@ -97,6 +115,7 @@ public class Persistent {
}
public int softreload() {
restartQueued = false;
PluginContainer container = null;
ReloadablePlugin plugin = null;
try {
@@ -200,4 +219,15 @@ public class Persistent {
ResourceBundle.clearCache(classLoader);
classLoader.close();
}
public int queueRestart(CommandContext<CommandSource> context) {
if (restartQueued) {
restartQueued = false;
context.getSource().sendPlainMessage("§eRestart dequeued§8.");
} else {
restartQueued = true;
context.getSource().sendPlainMessage("§eRestart queued§8.");
}
return Command.SINGLE_SUCCESS;
}
}
@@ -220,7 +220,7 @@ public class Subserver {
try {
if (checkpoint) {
start(process.getErrorStream(), line -> line.contains("Restore finished successfully."));
Thread.sleep(300); //Wait for port to be reopened
Thread.sleep(300);
} else {
start(process.getInputStream(), line -> {
if (line.contains("Loading libraries, please wait"))
+2 -25
View File
@@ -22,19 +22,6 @@ plugins {
alias(libs.plugins.shadow)
}
tasks.shadowJar {
exclude("META-INF/*")
exclude("org/sqlite/native/FreeBSD/**', 'org/sqlite/native/Mac/**', 'org/sqlite/native/Windows/**', 'org/sqlite/native/Linux-Android/**', 'org/sqlite/native/Linux-Musl/**")
exclude("org/sqlite/native/Linux/aarch64/**', 'org/sqlite/native/Linux/arm/**', 'org/sqlite/native/Linux/armv6/**', 'org/sqlite/native/Linux/armv7/**', 'org/sqlite/native/Linux/ppc64/**', 'org/sqlite/native/Linux/x86/**")
exclude("org/slf4j/**")
//https://imperceptiblethoughts.com/shadow/configuration/minimizing/
minimize {
exclude(project(":VelocityCore"))
exclude(dependency("mysql:mysql-connector-java:.*"))
}
duplicatesStrategy = DuplicatesStrategy.INCLUDE
}
tasks.build {
finalizedBy(tasks.shadowJar)
}
@@ -51,21 +38,10 @@ dependencies {
compileOnly(libs.viavelocity)
compileOnly(project(":VelocityCore:Persistent", "default"))
compileOnly(project(":VelocityCore:Dependencies", "default"))
implementation(project(":CommonCore"))
implementation(project(":CommandFramework"))
implementation(libs.sqlite)
implementation(libs.mysql)
implementation(libs.jda) {
exclude(module = "opus-java")
}
implementation(libs.msgpack)
implementation(libs.apolloprotos)
implementation(libs.nbt)
}
tasks.register<DevServer>("DevVelocity") {
@@ -73,5 +49,6 @@ tasks.register<DevServer>("DevVelocity") {
description = "Run a Dev Velocity"
dependsOn(":VelocityCore:shadowJar")
dependsOn(":VelocityCore:Persistent:jar")
dependsOn(":VelocityCore:Dependencies:shadowJar")
template = "DevVelocity"
}
@@ -326,12 +326,15 @@ CHECK_ABORT=§aThe test operation was canceled!
CHECK_NEXT=Next question
CHECK_ACCEPT=Accept
CHECK_DECLINE=Decline
CHECK_MARK_DECLINE=Mark Decline
CHECK_RANK=§aRank {0}: {1}
CHECK_RANK_HOVER=§aAccept with given rank
CHECK_ACCEPTED=§aYour §e{0} {1} §ewas accepted§8!
CHECK_ACCEPTED_TEAM=§7The schematic §e{0} §7from §e{1} §7is now approved!
CHECK_DECLINED=§cYour §e{0} {1} §cwas declined§8: §c{2}
CHECK_DECLINED_TEAM=§7The schematic §e{0} §7from §e{1} §7is now declined because §e{2}§7!
CHECK_DECLINED_QUESTIONS=§fQuestions answered declined:
CHECK_DECLINED_QUESTION_FORMAT=§c{0}: {1}
#HistoricCommand
HISTORIC_BROADCAST=§7Historic §e{0} §7fight by §e{1}§8!
@@ -603,7 +606,7 @@ TABLIST_PHASE_WEBSITE=§8Website: https://§eSteam§8War.de
TABLIST_PHASE_DISCORD=§8Discord: https://§eSteam§8War.de/discord
TABLIST_FOOTER=§e{0} {1}§8ms §ePlayers§8: §7{2}
TABLIST_BAU=§7§lBuild
LIST_COMMAND=§e{0}§8: §7{1}
LIST_COMMAND=§e{0}§8 [{1}]: §7{2}
#EventStarter
EVENT_FIGHT_BROADCAST=§eClick here §7for the fight §{0}{1} §8vs §{2}{3}
@@ -308,12 +308,14 @@ CHECK_ABORT=§aDer Prüfvorgang wurde abgebrochen!
CHECK_NEXT=Nächste Frage
CHECK_ACCEPT=Annehmen
CHECK_DECLINE=Ablehnen
CHECK_MARK_DECLINE=Ablehnen Markieren
CHECK_RANK=§aRang {0}: {1}
CHECK_RANK_HOVER=§aMit diesem Rang freigeben
CHECK_ACCEPTED=§aDein §e{0} {1} §ewurde freigegeben§8!
CHECK_ACCEPTED_TEAM=§7Die Schematic §e{0} §7von §e{1} §7ist nun freigegeben!
CHECK_DECLINED=§cDein §e{0} {1} §cwurde abgelehnt§8: §c{2}
CHECK_DECLINED_TEAM=§7Die Schematic §e{0} §7von §e{1} §7wurde aufgrund von §e{2} §7abgelehnt!
CHECK_DECLINED_QUESTIONS=§fAls abgelehnt markierte Fragen:
#HistoricCommand
HISTORIC_BROADCAST=§7Historischer §e{0}§8-§7Kampf von §e{1}§8!
@@ -574,11 +576,8 @@ POLL_ANSWER=§7{0}
POLL_ANSWER_HOVER=§e{0} §ewählen
#TablistManager
TABLIST_PHASE_WEBSITE=§8Website: https://§eSteam§8War.de
TABLIST_PHASE_DISCORD=§8Discord: https://§eSteam§8War.de/discord
TABLIST_FOOTER=§e{0} {1}§8ms §eSpieler§8: §7{2}
TABLIST_BAU=§7§lBau
LIST_COMMAND=§e{0}§8: §7{1}
#EventStarter
EVENT_FIGHT_BROADCAST=§7Hier §eklicken §7für den Kampf §{0}{1} §8vs §{2}{3}
@@ -87,10 +87,11 @@ public interface Chatter {
}
default void withPlayerOrOffline(Consumer<Player> withPlayer, Runnable withOffline) {
Player player = getPlayer();
if(player == null)
if(player == null) {
withOffline.run();
else
} else {
withPlayer.accept(player);
}
}
default void withPlayer(Consumer<Player> function) {
withPlayerOrOffline(function, () -> {});
@@ -23,7 +23,9 @@ import de.steamwar.messages.Chatter;
import de.steamwar.messages.Message;
import de.steamwar.persistent.Subserver;
import de.steamwar.sql.EventFight;
import de.steamwar.sql.SteamwarUser;
import de.steamwar.sql.Team;
import lombok.Getter;
import net.kyori.adventure.text.event.ClickEvent;
import java.sql.Timestamp;
@@ -36,6 +38,7 @@ import static de.steamwar.persistent.Storage.eventServer;
public class EventStarter {
@Getter
private static final Map<Integer, String> spectatePorts = new HashMap<>();
public static void addSpectateServer(int port, String command) {
@@ -68,6 +71,15 @@ public class EventStarter {
starter.callback(subserver -> {
eventServer.put(blue.getTeamId(), subserver);
eventServer.put(red.getTeamId(), subserver);
if (VelocityCore.get().getConfig().isEventmode()) {
VelocityCore.getProxy().getAllPlayers().forEach(player -> {
SteamwarUser user = SteamwarUser.get(player.getUniqueId());
if (user.getTeam() == blue.getTeamId() || user.getTeam() == red.getTeamId()) {
subserver.sendPlayer(player);
}
});
}
}).start();
command = "/event " + blue.getTeamKuerzel();
@@ -76,6 +88,7 @@ public class EventStarter {
}
Chatter.broadcast().system("EVENT_FIGHT_BROADCAST", new Message("EVENT_FIGHT_BROADCAST_HOVER"), ClickEvent.runCommand(command), blue.getTeamColor(), blue.getTeamName(), red.getTeamColor(), red.getTeamName());
}
EventFight.clearActiveFightsCache();
}
private EventFight nextFight(Queue<EventFight> fights){
@@ -52,7 +52,6 @@ public class ServerStarter {
public static final String TEMP_WORLD_PATH = TMP_DATA + "arenaserver/";
private static final String WORLDS_FOLDER = "/worlds";
public static final String TUTORIAL_PATH = WORLDS_FOLDER + "/tutorials/";
public static final String WORLDS_BASE_PATH = WORLDS_FOLDER + "/userworlds";
public static final String BUILDER_BASE_PATH = WORLDS_FOLDER + "/builder";
@@ -194,15 +193,6 @@ public class ServerStarter {
return this;
}
public ServerStarter tutorial(Player owner, Tutorial tutorial) {
version = ServerVersion.SPIGOT_15;
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);
@@ -286,6 +276,7 @@ public class ServerStarter {
int port = portrange.freePort();
String serverName = serverNameProvider.apply(port);
arguments.put("serverName", serverName);
if(node == null) {
node = Node.getNode();
@@ -44,15 +44,14 @@ public enum ServerVersion {
PAPER_18("paper-1.18.2.jar", 15, ProtocolVersion.MINECRAFT_1_18_2),
PAPER_19("paper-1.19.3.jar", 19, ProtocolVersion.MINECRAFT_1_19_3),
PAPER_20("paper-1.20.1.jar", 20, ProtocolVersion.MINECRAFT_1_20),
DEVEL_21("paper-1.21.5.jar", 21, ProtocolVersion.MINECRAFT_1_21_5),
PAPER_21("paper-1.21.3.jar", 21, ProtocolVersion.MINECRAFT_1_21_2);
PAPER_21("paper-1.21.6.jar", 21, ProtocolVersion.MINECRAFT_1_21_6);
private static final Map<String, ServerVersion> chatMap = new HashMap<>();
static {
chatMap.put("21", ServerVersion.PAPER_21);
chatMap.put("1.21", ServerVersion.PAPER_21);
chatMap.put("1.21.3", ServerVersion.PAPER_21);
chatMap.put("1.21.6", ServerVersion.PAPER_21);
chatMap.put("20", ServerVersion.PAPER_20);
chatMap.put("1.20", ServerVersion.PAPER_20);
@@ -95,10 +94,6 @@ public enum ServerVersion {
}
public static ServerVersion get(int version) {
if (version == 21) {
return DEVEL_21;
}
return versionMap.get(version);
}
@@ -60,7 +60,7 @@ import java.util.logging.Logger;
@Plugin(
id = "velocitycore",
name = "VelocityCore",
dependencies = { @Dependency(id = "persistentvelocitycore") }
dependencies = { @Dependency(id = "persistentvelocitycore"), @Dependency(id = "depencendiesvelocitycore") }
)
public class VelocityCore implements ReloadablePlugin {
@@ -153,6 +153,7 @@ public class VelocityCore implements ReloadablePlugin {
new CheckListener();
new IPSanitizer();
new VersionAnnouncer();
new TexturePackSystem();
local = new Node.LocalNode();
if(MAIN_SERVER) {
@@ -214,16 +215,16 @@ public class VelocityCore implements ReloadablePlugin {
new ChallengeCommand();
new HistoricCommand();
new ReplayCommand();
new TutorialCommand();
new Broadcaster();
new CookieEvents();
}else{
new EventModeListener();
}
for(PacketHandler handler : new PacketHandler[] {
new EloPlayerHandler(), new EloSchemHandler(), new ExecuteCommandHandler(), new FightInfoHandler(),
new ImALobbyHandler(), new InventoryCallbackHandler(), new PrepareSchemHandler()
new ImALobbyHandler(), new InventoryCallbackHandler(), new PrepareSchemHandler(), new PlayerSkinHandler()
})
handler.register();
@@ -288,7 +289,7 @@ public class VelocityCore implements ReloadablePlugin {
if(server.getSpectatePort() != 0)
EventStarter.addSpectateServer(server.getSpectatePort(), cmd);
new ServerSwitchCommand(cmd, entry.getKey(), cmds.toArray(new String[0]));
new ServerSwitchCommand(cmd, entry.getKey(), server.getSpectatePort() != 0, cmds.toArray(new String[0]));
}
}
}
@@ -41,6 +41,7 @@ import java.time.Instant;
import java.util.List;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.function.BooleanSupplier;
import java.util.logging.Level;
public class CheckCommand extends SWCommand {
@@ -98,12 +99,11 @@ public class CheckCommand extends SWCommand {
for (SchematicNode schematic : schematicList) {
CheckSession current = currentSchems.get(schematic.getId());
if (current == null) {
Optional<CheckedSchematic> lastCheck = CheckedSchematic.getLastCheck(schematic.getId());
sender.prefixless("CHECK_LIST_TO_CHECK",
lastCheck.map(CheckedSchematic::isInvestigationPending).orElse(false) ? new Message("PLAIN_STRING", lastCheck.map(CheckedSchematic::getInvestigationPendingReason).orElseThrow()) : new Message("CHECK_LIST_TO_CHECK_HOVER"),
!lastCheck.map(CheckedSchematic::isInvestigationPending).orElse(false) || sender.user().hasPerm(UserPerm.MODERATION) ? ClickEvent.runCommand("/check schematic " + schematic.getId()) : ClickEvent.suggestCommand(""),
new Message("CHECK_LIST_TO_CHECK_HOVER"),
ClickEvent.runCommand("/check schematic " + schematic.getId()),
getWaitTime(schematic),
schematic.getSchemtype().getKuerzel(), SteamwarUser.get(schematic.getOwner()).getUserName(), (lastCheck.map(CheckedSchematic::isInvestigationPending).orElse(false) ? "§c" : "") + schematic.getName());
schematic.getSchemtype().getKuerzel(), SteamwarUser.get(schematic.getOwner()).getUserName(), schematic.getName());
} else {
sender.prefixless("CHECK_LIST_CHECKING",
new Message("CHECK_LIST_CHECKING_HOVER"),
@@ -129,10 +129,6 @@ public class CheckCommand extends SWCommand {
sender.system("CHECK_SCHEMATIC_OWN");
return;
}
Optional<CheckedSchematic> lastCheck = CheckedSchematic.getLastCheck(schem.getId());
if(!lastCheck.map(CheckedSchematic::isInvestigationPending).orElse(false) || sender.user().hasPerm(UserPerm.MODERATION)) {
sender.system("CHECK_SCHEMATIC_INVESTIGATION_PENDING");
}
int playerTeam = sender.user().hasPerm(UserPerm.MODERATION) ? 0 : sender.user().getTeam();
if (playerTeam != 0 && SteamwarUser.get(schem.getOwner()).getTeam() == playerTeam) {
@@ -169,6 +165,14 @@ public class CheckCommand extends SWCommand {
next(sender);
}
@Register(value = "decline", description = "CHECK_HELP_DECLINE")
public void decline(PlayerChatter sender) {
if(notChecking(sender.getPlayer()))
return;
currentCheckers.get(sender.getPlayer().getUniqueId()).markDeclined();
}
@Register(value = "decline", description = "CHECK_HELP_DECLINE")
public void decline(PlayerChatter sender, String... message) {
if(notChecking(sender.getPlayer()))
@@ -177,14 +181,6 @@ public class CheckCommand extends SWCommand {
currentCheckers.get(sender.getPlayer().getUniqueId()).decline(String.join(" ", message));
}
@Register(value = "block")
public void block(PlayerChatter sender, String... message) {
if(notChecking(sender.getPlayer()))
return;
currentCheckers.get(sender.getPlayer().getUniqueId()).block(String.join(" ", message));
}
public static List<SchematicNode> getSchemsToCheck(){
List<SchematicNode> schematicList = new ArrayList<>();
@@ -213,6 +209,8 @@ public class CheckCommand extends SWCommand {
private final SchematicNode schematic;
private final Timestamp startTime;
private final ListIterator<String> checkList;
private String currentQuestion;
private final List<String> declinedQuestions = new ArrayList<>();
private CheckSession(PlayerChatter checker, SchematicNode schematic){
this.checker = checker;
@@ -225,7 +223,7 @@ public class CheckCommand extends SWCommand {
currentCheckers.put(checker.user().getUUID(), this);
currentSchems.put(schematic.getId(), this);
for(CheckedSchematic previous : CheckedSchematic.getLastDeclinedOfNode(schematic.getId()))
for(CheckedSchematic previous : CheckedSchematic.previousChecks(schematic))
checker.prefixless("CHECK_SCHEMATIC_PREVIOUS", previous.getEndTime(), SteamwarUser.get(previous.getValidator()).getUserName(), previous.getDeclineReason());
next();
}).start();
@@ -233,42 +231,70 @@ public class CheckCommand extends SWCommand {
private void next() {
if(!checkList.hasNext()){
accept();
if (declinedQuestions.isEmpty()) {
accept();
} else {
checker.system("CHECK_DECLINED_QUESTIONS");
int i = 1;
for (String s : declinedQuestions) {
checker.prefixless("CHECK_DECLINED_QUESTION_FORMAT", i++, s);
}
declinedQuestions.clear();
checker.sendMessage(Component
.text(checker.parseToPlain("CHECK_ACCEPT"))
.color(NamedTextColor.GREEN)
.clickEvent(ClickEvent.suggestCommand("/check accept"))
.append(Component
.text(" " + checker.parseToPlain("CHECK_DECLINE"))
.color(NamedTextColor.RED)
.clickEvent(ClickEvent.suggestCommand("/check decline "))));
}
return;
}
checker.prefixless("PLAIN_STRING", checkList.next());
currentQuestion = checkList.next();
checker.prefixless("PLAIN_STRING", currentQuestion);
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"))
.text(" " + checker.parseToPlain("CHECK_MARK_DECLINE"))
.color(NamedTextColor.RED)
.clickEvent(ClickEvent.suggestCommand("/check decline "))));
.clickEvent(ClickEvent.runCommand("/check decline"))));
}
private void markDeclined() {
declinedQuestions.add(currentQuestion);
next();
}
private void accept(){
if(concludeCheckSession("freigegeben", fightTypes.get(schematic.getSchemtype()), false)) {
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()));
}
return owner.getPlayer() != null;
});
}
private void decline(String reason){
if(concludeCheckSession(reason, SchematicType.Normal, false)) {
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));
}
return owner.getPlayer() != null;
});
}
private void notifyTeam(Message message) {
@@ -277,20 +303,18 @@ public class CheckCommand extends SWCommand {
}
private void abort(){
concludeCheckSession("Prüfvorgang abgebrochen", null, false);
concludeCheckSession("Prüfvorgang abgebrochen", null, () -> true);
}
private boolean concludeCheckSession(String reason, SchematicType type, boolean investigation) {
boolean exists = SchematicNode.getSchematicNode(schematic.getId()) != null;
if(exists) {
if (investigation) {
CheckedSchematic.createInvestigationPending(schematic, startTime, Timestamp.from(Instant.now()), checker.user().getId(), reason);
} else {
CheckedSchematic.create(schematic, checker.user().getId(), startTime, Timestamp.from(Instant.now()), reason);
}
if(type != null)
private void concludeCheckSession(String reason, SchematicType type, BooleanSupplier sendMessageIsOnline) {
if(SchematicNode.getSchematicNode(schematic.getId()) != null) {
CheckedSchematic.create(schematic, checker.user().getId(), startTime, Timestamp.from(Instant.now()), reason, sendMessageIsOnline.getAsBoolean());
if(type != null) {
schematic.setSchemtype(type);
if (type == SchematicType.Normal) {
schematic.setPrepared(false);
}
}
}
remove();
@@ -299,16 +323,11 @@ public class CheckCommand extends SWCommand {
if(subserver != null)
subserver.stop();
}).schedule();
return exists;
}
private void remove() {
currentCheckers.remove(checker.user().getUUID());
currentSchems.remove(schematic.getId());
}
public void block(String reason) {
concludeCheckSession(reason, null, true);
}
}
}
@@ -63,7 +63,7 @@ public class ListCommand extends SWCommand {
if (server.equals("Bau")) {
serverName = sender.parseToLegacy("TABLIST_BAU");
}
sender.prefixless("LIST_COMMAND", serverName, playerMap.get(server).stream().map(Player::getUsername).collect(Collectors.joining(", ")));
sender.prefixless("LIST_COMMAND", serverName, playerMap.get(server).size(), playerMap.get(server).stream().map(Player::getUsername).collect(Collectors.joining(", ")));
}
}
}
@@ -19,22 +19,41 @@
package de.steamwar.velocitycore.commands;
import java.net.InetSocketAddress;
import java.util.List;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.api.proxy.server.RegisteredServer;
import de.steamwar.velocitycore.VelocityCore;
import de.steamwar.command.SWCommand;
import de.steamwar.messages.PlayerChatter;
import de.steamwar.sql.EventFight;
import de.steamwar.sql.SteamwarUser;
public class ServerSwitchCommand extends SWCommand {
private final RegisteredServer server;
private final boolean isSpectateServer;
public ServerSwitchCommand(String cmd, String name, String... aliases) {
public ServerSwitchCommand(String cmd, String name, boolean isSpectateServer, String... aliases) {
super(cmd, null, aliases);
server = VelocityCore.getProxy().getServer(name).orElseThrow();
this.isSpectateServer = isSpectateServer;
}
@Register
public void genericCommand(PlayerChatter sender) {
if (isSpectateServer && sender.getPlayer().getProtocolVersion().noLessThan(ProtocolVersion.MINECRAFT_1_20_5)) {
SteamwarUser user = SteamwarUser.get(sender.getPlayer().getUniqueId());
List<EventFight> activeFights = EventFight.getActiveFights();
if (activeFights.stream()
.anyMatch(fight -> fight.getTeamRed() == user.getTeam() || fight.getTeamBlue() == user.getTeam())) {
sender.getPlayer().transferToHost(new InetSocketAddress("steamwar.de", 25566));
return;
}
}
sender.getPlayer().createConnectionRequest(server).fireAndForget();
}
}
@@ -1,164 +0,0 @@
/*
* 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.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.Subserver;
import de.steamwar.sql.SteamwarUser;
import de.steamwar.sql.Tutorial;
import de.steamwar.sql.UserPerm;
import de.steamwar.velocitycore.ServerStarter;
import de.steamwar.velocitycore.SubserverSystem;
import de.steamwar.velocitycore.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 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.isBuild(subserver) || !subserver.isStarted() || !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()));
}
}
@@ -85,9 +85,9 @@ public class DiscordChannel extends Chatter.PlayerlessChatter {
public void send(String message) {
message = message
.replace("&", "")
.replace("@everyone", "`@everyone`")
.replace("@here", "`@here`")
.replaceAll("<[@#]!?\\d+>", "`$0`");
.replace("@everyone", "@\u200Beveryone")
.replace("@here", "@\u200Bhere")
.replaceAll("<([@#])(!?\\d+)>", "<$1\u200B$2>");
if (maxNumberOfWebhooks > 0 && getChannel() instanceof TextChannel && message.contains("»")) {
String[] strings = message.split("»", 2);
@@ -94,7 +94,7 @@ public class DiscordSchemUpload extends ListenerAdapter {
version = NodeData.SchematicFormat.MCEDIT;
}
NodeData.get(node).saveFromStream(new ByteArrayInputStream(bytes), version);
NodeData.saveFromStream(node, new ByteArrayInputStream(bytes), version);
sender.system("DC_SCHEMUPLOAD_SUCCESS", name);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
@@ -42,6 +42,7 @@ public class SWItem {
@Getter
private InvCallback callback;
private int color = 0;
private int customModelData = 0;
public SWItem(String material, Message title) {
this.material = material.toUpperCase();
@@ -64,6 +65,11 @@ public class SWItem {
return this;
}
public SWItem setCustomModelData(int customModelData) {
this.customModelData = customModelData;
return this;
}
public JsonObject writeToString(Chatter player, int position) {
JsonObject object = new JsonObject();
object.addProperty("material", material);
@@ -84,6 +90,9 @@ public class SWItem {
}
object.add("lore", array);
}
if (customModelData > 0) {
object.addProperty("customModelData", customModelData);
}
return object;
}
@@ -27,6 +27,8 @@ 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 com.velocitypowered.api.proxy.ServerConnection;
import com.velocitypowered.api.proxy.server.ServerInfo;
import de.steamwar.messages.Chatter;
import de.steamwar.messages.ChatterGroup;
import de.steamwar.messages.Message;
@@ -80,17 +82,32 @@ public class ChatListener extends BasicListener {
if(VelocityCore.getProxy().getCommandManager().hasCommand(cmd)) {
CommandSource source = e.getCommandSource();
String name;
if(source instanceof Player player)
SteamwarUser user = null;
if (source instanceof Player player) {
user = SteamwarUser.get(player.getUniqueId());
name = player.getUsername();
else if(source instanceof ConsoleCommandSource)
} else if (source instanceof ConsoleCommandSource) {
user = SteamwarUser.get(-1);
name = "«CONSOLE»";
else
} else {
name = source.toString();
}
if (noLogCommands.contains(cmd)) {
return;
}
switch (cmd) {
case "msg":
case "r":
case "tc":
AuditLog.createSensitiveCommand(AuditLog.SERVER_NAME_VELOCITY, null, user, "/" + command);
break;
default:
AuditLog.createCommand(AuditLog.SERVER_NAME_VELOCITY, null, user, "/" + command);
break;
}
cmdLogger.log(Level.INFO, "%s -> executed command /%s".formatted(name, command));
} else if (e.getCommandSource() instanceof Player player) {
// System.out.println("spoofChatInput " + e);
@@ -106,8 +123,8 @@ public class ChatListener extends BasicListener {
e.setResult(PlayerChatEvent.ChatResult.denied());
SteamwarUser user = SteamwarUser.get(player.getUniqueId());
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;
@@ -117,13 +134,20 @@ public class ChatListener extends BasicListener {
return;
Subserver subserver = Subserver.getSubserver(player);
String serverName = AuditLog.SERVER_NAME_VELOCITY;
if(Subserver.isArena(subserver) && subserver.getServer() == player.getCurrentServer().orElseThrow().getServerInfo()) {
serverName = subserver.getServer().getName();
localChat(Chatter.of(player), message);
} else if (message.startsWith("+")) {
serverName = player.getCurrentServer()
.map(ServerConnection::getServerInfo)
.map(ServerInfo::getName)
.orElse(serverName);
localChat(Chatter.of(player), message.substring(1));
} else {
sendChat(Chatter.of(player), Chatter.globalChat(), "CHAT_GLOBAL", null, message);
}
AuditLog.createChat(serverName, null, user, message);
}
private static boolean isMistypedCommand(Player player, String message) {
@@ -23,17 +23,23 @@ 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.event.player.KickedFromServerEvent;
import com.velocitypowered.api.network.ProtocolVersion;
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.Subserver;
import de.steamwar.sql.CheckedSchematic;
import de.steamwar.sql.SchematicType;
import de.steamwar.sql.SteamwarUser;
import de.steamwar.sql.UserPerm;
import de.steamwar.velocitycore.EventStarter;
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.Component;
import net.kyori.adventure.text.event.ClickEvent;
import java.util.HashSet;
@@ -82,12 +88,35 @@ public class ConnectionListener extends BasicListener {
}
}
for (CheckedSchematic checkedSchematic : CheckedSchematic.getUnseen(user)) {
SchematicType type = SchematicType.fromDB(checkedSchematic.getNodeType());
if(type == null) continue;
if (checkedSchematic.getDeclineReason().equals("freigegeben")) {
chatter.system("CHECK_ACCEPTED", type.name(), checkedSchematic.getSchemName());
} else {
chatter.system("CHECK_DECLINED", type.name(), checkedSchematic.getSchemName(), checkedSchematic.getDeclineReason());
}
checkedSchematic.setSeen(true);
}
if(newPlayers.contains(player.getUniqueId())){
Chatter.broadcast().system("JOIN_FIRST", player);
newPlayers.remove(player.getUniqueId());
}
DiscordBot.withBot(bot -> DiscordRanks.update(user));
if (player.getProtocolVersion().noLessThan(ProtocolVersion.MINECRAFT_1_20_5)) {
player.requestCookie(EventModeListener.EVENT_TO_SPECTATE_KEY);
}
}
@Subscribe
public void kickEvent(KickedFromServerEvent event) {
if (event.getResult() instanceof KickedFromServerEvent.RedirectPlayer red) {
event.setResult(KickedFromServerEvent.RedirectPlayer.create(red.getServer(), Component.empty()));
}
}
@Subscribe
@@ -0,0 +1,47 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.velocitycore.listeners;
import com.velocitypowered.api.event.Subscribe;
import com.velocitypowered.api.event.player.CookieReceiveEvent;
import com.velocitypowered.api.proxy.Player;
import de.steamwar.sql.EventFight;
import de.steamwar.sql.SteamwarUser;
import de.steamwar.velocitycore.EventStarter;
import de.steamwar.velocitycore.VelocityCore;
public class CookieEvents extends BasicListener {
@Subscribe
public void handleCookies(CookieReceiveEvent e) {
if (e.getOriginalKey().equals(EventModeListener.EVENT_TO_SPECTATE_KEY)) {
Player player = e.getPlayer();
SteamwarUser user = SteamwarUser.get(player.getUniqueId());
EventFight.getActiveFights().stream()
.filter(fight -> fight.getTeamRed() == user.getTeam() || fight.getTeamBlue() == user.getTeam())
.filter(fight -> fight.getSpectatePort() != null)
.filter(fight -> fight.getSpectatePort() != 0)
.findFirst()
.flatMap(fight -> VelocityCore.getProxy().getServer(EventStarter.getSpectatePorts().get(fight.getSpectatePort())))
.ifPresent(registeredServer -> player.createConnectionRequest(registeredServer).fireAndForget());
}
}
}
@@ -19,31 +19,88 @@
package de.steamwar.velocitycore.listeners;
import java.net.InetSocketAddress;
import java.nio.charset.Charset;
import java.util.List;
import com.velocitypowered.api.event.Subscribe;
import com.velocitypowered.api.event.connection.PostLoginEvent;
import com.velocitypowered.api.event.player.ServerConnectedEvent;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.api.proxy.Player;
import de.steamwar.messages.Chatter;
import de.steamwar.persistent.Subserver;
import de.steamwar.sql.Event;
import de.steamwar.sql.EventFight;
import de.steamwar.sql.Referee;
import de.steamwar.sql.SteamwarUser;
import de.steamwar.sql.TeamTeilnahme;
import de.steamwar.velocitycore.EventStarter;
import de.steamwar.velocitycore.VelocityCore;
import net.kyori.adventure.key.Key;
public class EventModeListener extends BasicListener {
public static final Key EVENT_TO_SPECTATE_KEY = Key.key("sw", "event_to_spectate");
@Subscribe
public void onPostLogin(PostLoginEvent e) {
Chatter sender = Chatter.disconnect(e.getPlayer());
Player player = e.getPlayer();
SteamwarUser user = SteamwarUser.get(player.getUniqueId());
Chatter sender = Chatter.disconnect(player);
Event event = Event.get();
if(event == null) {
sender.system("EVENTMODE_KICK");
if (event == null) {
if (player.getProtocolVersion().lessThan(ProtocolVersion.MINECRAFT_1_20_5)) {
sender.system("EVENTMODE_KICK");
} else {
player.transferToHost(new InetSocketAddress("steamwar.de", 25565));
}
return;
}
if(TeamTeilnahme.nimmtTeil(sender.user().getTeam(), event.getEventID()))
if (TeamTeilnahme.nimmtTeil(user.getTeam(), event.getEventID())) {
if (player.getProtocolVersion().noLessThan(ProtocolVersion.MINECRAFT_1_20_5) && VelocityCore.getProxy().getAllPlayers().stream().map(p -> SteamwarUser.get(p.getUniqueId())).filter(u -> u.getTeam() == user.getTeam()).count() > event.getMaximumTeamMembers()) {
player.storeCookie(EVENT_TO_SPECTATE_KEY, "TRUE".getBytes());
player.transferToHost(new InetSocketAddress("steamwar.de", 25565));
return;
}
Subserver server = EventStarter.getEventServer().get(user.getTeam());
if (server != null) {
server.sendPlayer(player);
}
return;
}
if (Referee.get(event.getEventID()).contains(user.getId()))
return;
if(Referee.get(event.getEventID()).contains(sender.user().getId()))
return;
if (player.getProtocolVersion().lessThan(ProtocolVersion.MINECRAFT_1_20_5)) {
sender.system("EVENTMODE_KICK");
} else {
player.transferToHost(new InetSocketAddress("steamwar.de", 25565));
}
}
sender.system("EVENTMODE_KICK");
@Subscribe
public void onLobby(ServerConnectedEvent e) {
Player player = e.getPlayer();
if (player.getProtocolVersion().lessThan(ProtocolVersion.MINECRAFT_1_20_5)) {
return;
}
SteamwarUser user = SteamwarUser.get(player.getUniqueId());
List<EventFight> activeFights = EventFight.getActiveFights();
if (activeFights.stream()
.noneMatch(fight -> fight.getTeamRed() == user.getTeam() || fight.getTeamBlue() == user.getTeam())) {
player.transferToHost(new InetSocketAddress("steamwar.de", 25565));
}
}
}
@@ -22,6 +22,7 @@ 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.sql.AuditLog;
import de.steamwar.velocitycore.VelocityCore;
import de.steamwar.sql.Session;
import de.steamwar.sql.SteamwarUser;
@@ -36,10 +37,12 @@ public class SessionManager extends BasicListener {
@Subscribe
public void onPostLogin(PostLoginEvent event){
sessions.put(event.getPlayer(), Timestamp.from(Instant.now()));
AuditLog.createJoin(AuditLog.SERVER_NAME_VELOCITY, null, SteamwarUser.get(event.getPlayer().getUniqueId()));
}
@Subscribe
public void onDisconnect(DisconnectEvent e){
AuditLog.createLeave(AuditLog.SERVER_NAME_VELOCITY, null, SteamwarUser.get(e.getPlayer().getUniqueId()));
Timestamp timestamp = sessions.remove(e.getPlayer());
if(timestamp != null) {
VelocityCore.schedule(() -> Session.insertSession(SteamwarUser.get(e.getPlayer().getUniqueId()).getId(), timestamp)).schedule();
@@ -0,0 +1,106 @@
/*
* 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.ServerPostConnectEvent;
import com.velocitypowered.api.proxy.player.ResourcePackInfo;
import de.steamwar.velocitycore.VelocityCore;
import net.kyori.adventure.text.Component;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.TreeMap;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
// https://jd.papermc.io/velocity/3.4.0/com/velocitypowered/api/proxy/player/ResourcePackInfo.Builder.html#setHash(byte%5B%5D)
public class TexturePackSystem extends BasicListener {
private static final File PACKS_DIR = new File("/var/www/packs");
private static final String BASE_ULR = "https://packs.steamwar.de/";
private TreeMap<Integer, Integer> protocolVersionToPackVersion = new TreeMap<>();
public TexturePackSystem() {
// https://minecraft.wiki/w/Pack_format#List_of_resource_pack_formats
// https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Protocol_version_numbers
protocolVersionToPackVersion.put(759, 9);
protocolVersionToPackVersion.put(761, 12);
protocolVersionToPackVersion.put(762, 13);
protocolVersionToPackVersion.put(763, 15);
protocolVersionToPackVersion.put(764, 18);
protocolVersionToPackVersion.put(765, 22);
protocolVersionToPackVersion.put(766, 32);
protocolVersionToPackVersion.put(767, 34);
protocolVersionToPackVersion.put(768, 42);
protocolVersionToPackVersion.put(769, 46);
protocolVersionToPackVersion.put(770, 55);
}
@Subscribe
public void onLogin(ServerPostConnectEvent event) {
if (event.getPreviousServer() != null) {
return;
}
VelocityCore.schedule(() -> {
TreeMap<Integer, File> fileTreeMap = new TreeMap<>();
for (File fileEntry : PACKS_DIR.listFiles()) {
try {
int packVersion = Integer.parseInt(fileEntry.getName().split("_")[0]);
fileTreeMap.put(packVersion, fileEntry);
} catch (NumberFormatException e) {
// Ignore
}
}
int playerVersion = event.getPlayer().getProtocolVersion().getProtocol();
Map.Entry<Integer, Integer> packVersionEntry = protocolVersionToPackVersion.floorEntry(playerVersion);
if (packVersionEntry == null) return;
Map.Entry<Integer, File> selectedPackEntry = fileTreeMap.floorEntry(packVersionEntry.getValue());
if (selectedPackEntry == null) return;
File selectedPack = selectedPackEntry.getValue();
String fileName = selectedPack.getName();
fileName = fileName.substring(fileName.indexOf('_') + 1, fileName.lastIndexOf('.'));
byte[] hash = hexStringToByteArray(fileName);
ResourcePackInfo resourcePackInfo = VelocityCore.getProxy().createResourcePackBuilder(BASE_ULR + selectedPack.getName())
.setId(UUID.nameUUIDFromBytes(fileName.getBytes(StandardCharsets.UTF_8)))
.setHash(hash)
.setShouldForce(false)
.setPrompt(Component.text("The SteamWar TexturePack improves GUIs!"))
.build();
event.getPlayer().sendResourcePacks(resourcePackInfo);
}).delay(500, TimeUnit.MILLISECONDS).schedule();
}
public static byte[] hexStringToByteArray(String s) {
int len = s.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
+ Character.digit(s.charAt(i+1), 16));
}
return data;
}
}
@@ -27,19 +27,36 @@ import com.velocitypowered.api.proxy.server.ServerInfo;
import com.viaversion.viaversion.api.Via;
import com.viaversion.viaversion.velocity.platform.VelocityViaConfig;
import de.steamwar.messages.Chatter;
import de.steamwar.network.packets.server.ClientVersionPacket;
import de.steamwar.persistent.Subserver;
import de.steamwar.velocitycore.VelocityCore;
import de.steamwar.velocitycore.network.NetworkSender;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
public class VersionAnnouncer extends BasicListener {
@Subscribe
public void postConnect(ServerConnectedEvent e) {
ServerInfo server = e.getServer().getServerInfo();
if(!Subserver.isBuild(Subserver.getSubserver(server)))
return;
Player player = e.getPlayer();
int serverVersion = ((VelocityViaConfig) Via.getConfig()).getVelocityServerProtocols().get(server.getName());
if(Via.getAPI().getPlayerVersion(player) == serverVersion)
int playerVersion = Via.getAPI().getPlayerVersion(player);
ProtocolVersion protocolVersion = ProtocolVersion.getProtocolVersion(serverVersion);
if (protocolVersion.isSupported()) {
// PluginChannel 'vv:proxy_details' from ViaVersion apparently does not work any longer!
VelocityCore.schedule(() -> {
String[] strings = protocolVersion.getVersionIntroducedIn().split("\\.");
NetworkSender.send(player, new ClientVersionPacket(player.getUniqueId(), Integer.parseInt(strings[1])));
}).delay(Duration.of(100, ChronoUnit.MILLIS)).schedule();
}
if(playerVersion == serverVersion)
return;
if(!Subserver.isBuild(Subserver.getSubserver(server)))
return;
player.sendActionBar(Chatter.of(player).parse("SERVER_VERSION", ProtocolVersion.getProtocolVersion(serverVersion).getMostRecentSupportedVersion()));
@@ -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.network.handlers;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.velocitypowered.api.event.Subscribe;
import com.velocitypowered.api.event.connection.PostLoginEvent;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.util.GameProfile;
import de.steamwar.network.packets.PacketHandler;
import de.steamwar.network.packets.common.PlayerSkinRequestPacket;
import de.steamwar.network.packets.common.PlayerSkinResponsePacket;
import de.steamwar.velocitycore.VelocityCore;
import de.steamwar.velocitycore.network.NetworkSender;
import de.steamwar.velocitycore.network.ServerMetaInfo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.SneakyThrows;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
public class PlayerSkinHandler extends PacketHandler {
private final int maxCacheSize = 1000;
public PlayerSkinHandler() {
VelocityCore.getProxy().getEventManager().register(VelocityCore.get(), this);
}
private Map<UUID, SkinData> skins = new LinkedHashMap<>() {
@Override
protected boolean removeEldestEntry(Map.Entry<UUID, SkinData> eldest) {
return size() > maxCacheSize;
}
};
@Handler
@SneakyThrows
public void handle(PlayerSkinRequestPacket packet) {
if (skins.containsKey(packet.getUuid())) {
SkinData skinData = skins.get(packet.getUuid());
NetworkSender.send(((ServerMetaInfo) packet.getMetaInfos()).sender().getServer(), new PlayerSkinResponsePacket(packet.getUuid(), skinData.skin, skinData.signature));
return;
}
String url = "https://sessionserver.mojang.com/session/minecraft/profile/" + packet.getUuid().toString().replace("-", "") + "?unsigned=false";
HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
connection.setReadTimeout(5000);
connection.setConnectTimeout(5000);
connection.setRequestProperty("User-Agent", "SkinFetcher");
if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
return;
}
InputStream is = connection.getInputStream();
String json = new BufferedReader(new InputStreamReader(is))
.lines().collect(Collectors.joining("\n"));
JsonObject obj = JsonParser.parseString(json).getAsJsonObject();
JsonArray properties = obj.getAsJsonArray("properties");
for (JsonElement propElement : properties) {
JsonObject prop = propElement.getAsJsonObject();
if (prop.get("name").getAsString().equals("textures")) {
String skin = prop.get("value").getAsString();
String signature = prop.get("signature").getAsString();
skins.put(packet.getUuid(), new SkinData(skin, signature));
NetworkSender.send(((ServerMetaInfo) packet.getMetaInfos()).sender().getServer(), new PlayerSkinResponsePacket(packet.getUuid(), skin, signature));
return;
}
}
}
@Subscribe
public void onPostLogin(PostLoginEvent event) {
Player player = event.getPlayer();
GameProfile gameProfile = player.getGameProfile();
GameProfile.Property property = gameProfile.getProperties().stream().filter(p -> p.getName().equals("textures")).findFirst().orElse(null);
if (property == null) return;
skins.put(player.getUniqueId(), new SkinData(property.getValue(), property.getSignature()));
Set<UUID> uuidSet = skins.keySet();
VelocityCore.getProxy().getAllServers().forEach(server -> {
for (UUID uuid : uuidSet) {
NetworkSender.send(server, new PlayerSkinResponsePacket(uuid, property.getValue(), property.getSignature()));
}
});
}
public record SkinData(String skin, String signature) {}
}
@@ -51,7 +51,7 @@ public class Tablist extends ChannelInboundHandlerAdapter {
private static final UUID[] swUuids = IntStream.range(0, 80).mapToObj(i -> UUID.randomUUID()).toArray(UUID[]::new);
private static final String[] swNames = IntStream.range(0, 80).mapToObj(i -> " »SW« " + String.format("%02d", i)).toArray(String[]::new);
public static final UpdateTeamsPacket createTeamPacket = new UpdateTeamsPacket("zzzzzsw-tab", UpdateTeamsPacket.Mode.CREATE, Component.empty(), Component.empty(), Component.empty(), UpdateTeamsPacket.NameTagVisibility.NEVER, UpdateTeamsPacket.CollisionRule.ALWAYS, 21, (byte)0x00, Arrays.stream(Tablist.swNames).toList());
public static final UpdateTeamsPacket createTeamPacket = new UpdateTeamsPacket21("zzzzzsw-tab", UpdateTeamsPacket.Mode.CREATE, Component.empty(), Component.empty(), Component.empty(), UpdateTeamsPacket.NameTagVisibility.NEVER, UpdateTeamsPacket.CollisionRule.ALWAYS, 21, (byte)0x00, Arrays.stream(Tablist.swNames).toList());
private final Map<UUID, UpsertPlayerInfoPacket.Entry> directTabItems;
private final List<UpsertPlayerInfoPacket.Entry> current = new ArrayList<>();
@@ -75,26 +75,12 @@ public class Tablist extends ChannelInboundHandlerAdapter {
List<TablistPart.Item> tablist = new ArrayList<>();
List<TablistPart.Item> direct = new ArrayList<>();
global.print(viewer, player, tablist, direct);
// NPC handling
List<UpsertPlayerInfoPacket.Entry> update = new ArrayList<>();
synchronized (directTabItems) {
for (TablistPart.Item item : direct) {
UpsertPlayerInfoPacket.Entry tabItem = directTabItems.get(item.getUuid());
if(tabItem == null) {
tablist.add(0, item);
} else if(!item.getDisplayName().equals(getDisplayName(tabItem))) {
tabItem.setDisplayName(new ComponentHolder(player.getProtocolVersion(), item.getDisplayName()));
tabItem.setListed(true);
update.add(tabItem);
}
}
}
tablist.addAll(0, direct);
// Main list handling
int i = 0;
List<UpsertPlayerInfoPacket.Entry> add = new ArrayList<>();
List<UpsertPlayerInfoPacket.Entry> update = new ArrayList<>();
List<UpsertPlayerInfoPacket.Entry> remove = new ArrayList<>();
for (; i < tablist.size() && i < 80; i++) {
TablistPart.Item item = tablist.get(i);
@@ -171,6 +157,8 @@ public class Tablist extends ChannelInboundHandlerAdapter {
}
public void disable() {
sendTabPacket(new ArrayList<>(directTabItems.values()), null);
directTabItems.clear();
sendTabPacket(current, null);
current.clear();
@@ -0,0 +1,103 @@
/*
* 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.tablist;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.packet.UpdateTeamsPacket;
import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder;
import io.netty.buffer.ByteBuf;
import net.kyori.adventure.text.Component;
import java.util.List;
public class UpdateTeamsPacket21 extends UpdateTeamsPacket {
private String name;
private Mode mode;
private Component displayName;
private Component prefix;
private Component suffix;
private NameTagVisibility nameTagVisibility;
private CollisionRule collisionRule;
private int color;
private byte friendlyFlags;
private List<String> players;
public UpdateTeamsPacket21(String name, Mode mode, Component displayName, Component prefix, Component suffix, NameTagVisibility nameTagVisibility, CollisionRule collisionRule, int color, byte friendlyFlags, List<String> players) {
super(name, mode, displayName, prefix, suffix, nameTagVisibility, collisionRule, color, friendlyFlags, players);
this.name = name;
this.mode = mode;
this.displayName = displayName;
this.prefix = prefix;
this.suffix = suffix;
this.nameTagVisibility = nameTagVisibility;
this.collisionRule = collisionRule;
this.color = color;
this.friendlyFlags = friendlyFlags;
this.players = players;
}
@Override
public void encode(ByteBuf byteBuf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
ProtocolUtils.writeString(byteBuf, this.name);
byteBuf.writeByte(this.mode.ordinal());
switch (this.mode) {
case CREATE:
case UPDATE:
(new ComponentHolder(protocolVersion, this.displayName)).write(byteBuf);
if (protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_13)) {
(new ComponentHolder(protocolVersion, this.prefix)).write(byteBuf);
(new ComponentHolder(protocolVersion, this.suffix)).write(byteBuf);
}
byteBuf.writeByte(this.friendlyFlags);
if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_21_5)) {
ProtocolUtils.writeVarInt(byteBuf, this.nameTagVisibility.ordinal());
ProtocolUtils.writeVarInt(byteBuf, this.collisionRule.ordinal());
} else {
ProtocolUtils.writeString(byteBuf, this.nameTagVisibility.getValue());
ProtocolUtils.writeString(byteBuf, this.collisionRule.getValue());
}
if (protocolVersion.greaterThan(ProtocolVersion.MINECRAFT_1_12_2)) {
ProtocolUtils.writeVarInt(byteBuf, this.color);
(new ComponentHolder(protocolVersion, this.prefix)).write(byteBuf);
(new ComponentHolder(protocolVersion, this.suffix)).write(byteBuf);
} else {
byteBuf.writeByte((byte)this.color);
}
ProtocolUtils.writeVarInt(byteBuf, this.players.size());
for(String player : this.players) {
ProtocolUtils.writeString(byteBuf, player);
}
break;
case ADD_PLAYER:
case REMOVE_PLAYER:
ProtocolUtils.writeVarInt(byteBuf, this.players.size());
for(String player : this.players) {
ProtocolUtils.writeString(byteBuf, player);
}
case REMOVE:
}
}
}
@@ -20,6 +20,7 @@
package de.steamwar.velocitycore.util;
import de.steamwar.messages.Chatter;
import de.steamwar.sql.BauweltMember;
import de.steamwar.sql.SteamwarUser;
import de.steamwar.sql.UserConfig;
import de.steamwar.sql.UserPerm;
@@ -44,6 +45,10 @@ public class BauLock {
case NOBODY:
locked = true;
break;
case SUPERVISOR:
BauweltMember member = BauweltMember.getBauMember(owner.getId(), target.getId());
locked = !member.isSupervisor();
break;
case SERVERTEAM:
locked = !target.hasPerm(UserPerm.TEAM);
break;
@@ -22,6 +22,7 @@ package de.steamwar.velocitycore.util;
public enum BauLockState {
NOBODY, // Locks the build server for all users
SUPERVISOR, // Locks the build server for supervisors
SERVERTEAM, // opens the build server only for every added user which is a server team member
TEAM_AND_SERVERTEAM, //opens the build server only for every added user which is in the same team as the buildOwner and every server team member
TEAM, //opens the build server only for every added user which is in the same team as the buildOwner