diff --git a/BauSystem/BauSystem_Main/src/BauSystem.properties b/BauSystem/BauSystem_Main/src/BauSystem.properties index b90933b6..e6c490d7 100644 --- a/BauSystem/BauSystem_Main/src/BauSystem.properties +++ b/BauSystem/BauSystem_Main/src/BauSystem.properties @@ -1030,6 +1030,7 @@ SCHEMATIC_GUI_ITEM=§eSchematics # TNTListener TLS_MESSAGE_79=§7TLS§8> §7Tick §e{0} §8- §7TNT §e{1} TLS_MESSAGE_80=§7TLS§8> §7Tick §e{0} §8- §7TNT §e{1} §8(§e{2} §7with Fuse 80§8) -TLS_START_HELP=§8/§etls start §8: §7Start the TNT Listener -TLS_STOP_HELP=§8/§etls stop §8: §7Stop the TNT Listener -TLS_SCOREBOARD_ELEMENT=§eTLS§8: §aon \ No newline at end of file +TLS_START_HELP=§8/§etls start §8- §7Start the TNT Listener +TLS_STOP_HELP=§8/§etls stop §8- §7Stop the TNT Listener +TLS_SCOREBOARD_ELEMENT=§eTLS§8: §aon +TLS_TOGGLE_HELP=§8/§etls§8: §7Toggle the TNT Listener \ No newline at end of file diff --git a/BauSystem/BauSystem_Main/src/BauSystem_de.properties b/BauSystem/BauSystem_Main/src/BauSystem_de.properties index 453b4079..f0d55c5c 100644 --- a/BauSystem/BauSystem_Main/src/BauSystem_de.properties +++ b/BauSystem/BauSystem_Main/src/BauSystem_de.properties @@ -961,6 +961,7 @@ TYPEREPLACE_HELP=§8//§etyreplace §8[§7type§8] §8[§7type§8] §8- §7Erset # Schematics SCHEMATIC_GUI_ITEM=§eSchematics TLS_MESSAGE_80=§7TLS§8> §7Tick §e{0} §8- §7TNT §e{1} §8(§e{2} §7mit Fuse 80§8) -TLS_START_HELP=§8/§etls start §8: §7Starte den TNT Listener -TLS_STOP_HELP=§8/§etls stop §8: §7Stope den TNT Listener -TLS_SCOREBOARD_ELEMENT=§eTLS§8: §aan \ No newline at end of file +TLS_START_HELP=§8/§etls start §8- §7Starte den TNT Listener +TLS_STOP_HELP=§8/§etls stop §8- §7Stope den TNT Listener +TLS_SCOREBOARD_ELEMENT=§eTLS§8: §aan +TLS_TOGGLE_HELP=§8/§etls §8: §7Toggle den TNT Listener \ No newline at end of file diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/BauSystem.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/BauSystem.java index a854f082..261eb835 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/BauSystem.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/BauSystem.java @@ -46,6 +46,7 @@ import de.steamwar.linkage.SpigotLinker; import de.steamwar.message.Message; import lombok.Getter; import org.bukkit.Bukkit; +import org.bukkit.GameRule; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; @@ -128,6 +129,8 @@ public class BauSystem extends JavaPlugin implements Listener { TraceRecorder.instance.init(); new WorldEditRendererCUIEditor(); + + Bukkit.getWorlds().get(0).setGameRule(GameRule.SEND_COMMAND_FEEDBACK, false); } @EventHandler diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/config/BauServer.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/config/BauServer.java index fea634a2..67fd9e52 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/config/BauServer.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/config/BauServer.java @@ -36,7 +36,7 @@ public class BauServer { private Integer owner; public UUID getOwner() { - return SteamwarUser.get(getOwnerID()).getUUID(); + return SteamwarUser.byId(getOwnerID()).getUUID(); } public int getOwnerID() { diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/bau/InfoCommand.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/bau/InfoCommand.java index adeb4c45..e5bce542 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/bau/InfoCommand.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/bau/InfoCommand.java @@ -47,7 +47,7 @@ public class InfoCommand extends SWCommand { @Register(description = "BAU_INFO_COMMAND_HELP") public void genericCommand(Player p) { - BauSystem.MESSAGE.send("BAU_INFO_COMMAND_OWNER", p, SteamwarUser.get(bauServer.getOwnerID()).getUserName()); + BauSystem.MESSAGE.send("BAU_INFO_COMMAND_OWNER", p, SteamwarUser.byId(bauServer.getOwnerID()).getUserName()); Region region = Region.getRegion(p.getLocation()); for (Flag flag : Flag.getFlags()) { if (!region.getFlags().has(flag).isApplicable()) continue; @@ -97,7 +97,7 @@ public class InfoCommand extends SWCommand { st.append("§8, "); } st.append("§7"); - st.append(SteamwarUser.get(bauweltMembers.get(i).getMemberID()).getUserName()); + st.append(SteamwarUser.byId(bauweltMembers.get(i).getMemberID()).getUserName()); } return st.toString(); } diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/region/TestblockCommand.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/region/TestblockCommand.java index d09c06a0..52c53a63 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/region/TestblockCommand.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/region/TestblockCommand.java @@ -31,7 +31,6 @@ import de.steamwar.command.SWCommand; import de.steamwar.command.TypeMapper; import de.steamwar.linkage.Linked; import de.steamwar.linkage.LinkedInstance; -import de.steamwar.sql.BauweltMember; import de.steamwar.sql.Punishment; import de.steamwar.sql.SchematicNode; import de.steamwar.sql.SteamwarUser; @@ -73,6 +72,14 @@ public class TestblockCommand extends SWCommand { resetRegion(p, node, isExtension ? RegionExtensionType.EXTENSION : RegionExtensionType.NORMAL, isIgnoreAir, isOnlyColor, replaceTNT, replaceWater); } + @Register(value="default") + public void setTestblockDefault(Player p) { + Region region = regionCheck(p); + if (region == null) return; + region.getRegionData().setTestblockSchematic(null); + resetRegion(p, null, RegionExtensionType.EXTENSION, false, false, false, false); + } + private void resetRegion(Player p, SchematicNode node, RegionExtensionType regionExtensionType, boolean ignoreAir, boolean onlyColors, boolean removeTNT, boolean removeWater) { Region region = regionCheck(p); if (region == null) return; @@ -93,29 +100,10 @@ public class TestblockCommand extends SWCommand { } } - // Beta Tester enabled - switch (BauServer.getInstance().getOwnerID()) { - case 245: - case 403: - case 1898: - case 3320: - case 4603: - case 5262: - case 5399: - case 6032: - case 7862: - case 11077: - case 11888: - case 12258: - case 16009: - if (node == null) { - node = region.getRegionData().getTestblockSchematic(); - } else { - region.getRegionData().setTestblockSchematic(node); - } - break; - default: - break; + if (node == null) { + node = region.getRegionData().getTestblockSchematic(); + } else { + region.getRegionData().setTestblockSchematic(node); } PasteBuilder.ClipboardProvider clipboardProvider; @@ -198,7 +186,7 @@ public class TestblockCommand extends SWCommand { @Override public List tabCompletes(CommandSender commandSender, PreviousArguments previousArguments, String s) { List stringList = new ArrayList<>(SchematicNode.getNodeTabcomplete(SteamwarUser.get(((Player) commandSender).getUniqueId()), s)); - stringList.addAll(SchematicNode.getNodeTabcomplete(SteamwarUser.get(0), s)); + stringList.addAll(SchematicNode.getNodeTabcomplete(SteamwarUser.byId(0), s)); return stringList; } @@ -206,7 +194,7 @@ public class TestblockCommand extends SWCommand { public SchematicNode map(CommandSender commandSender, PreviousArguments previousArguments, String s) { SchematicNode node = SchematicNode.getNodeFromPath(SteamwarUser.get(((Player) commandSender).getUniqueId()), s); if (node == null) { - node = SchematicNode.getNodeFromPath(SteamwarUser.get(0), s); + node = SchematicNode.getNodeFromPath(SteamwarUser.byId(0), s); } return node; } diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/script/ScriptHelper.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/script/ScriptHelper.java index fd4bc826..bb486680 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/script/ScriptHelper.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/script/ScriptHelper.java @@ -38,7 +38,7 @@ public class ScriptHelper { BookMeta meta = (BookMeta) itemStack.getItemMeta(); if(!writeable) { meta.setTitle(script.getName()); - meta.setAuthor(SteamwarUser.get(script.getUserId()).getUserName()); + meta.setAuthor(SteamwarUser.byId(script.getUserId()).getUserName()); } meta.setPages(getScriptPages(script)); itemStack.setItemMeta(meta); diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/script/lua/libs/StorageLib.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/script/lua/libs/StorageLib.java index c1646d41..04cde3f0 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/script/lua/libs/StorageLib.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/script/lua/libs/StorageLib.java @@ -89,7 +89,7 @@ public class StorageLib implements LuaLib, Enable, Disable { jsonObject.keySet().forEach(key -> { map.put(key, fromJson(jsonObject.get(key))); }); - SteamwarUser steamwarUser = SteamwarUser.get(Integer.parseInt(playerStorage.getName().substring(0, playerStorage.getName().length() - ".json".length()))); + SteamwarUser steamwarUser = SteamwarUser.byId(Integer.parseInt(playerStorage.getName().substring(0, playerStorage.getName().length() - ".json".length()))); PLAYER_STORAGE.put(steamwarUser.getUUID(), map); } catch (Exception e) {} } diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/simulator/storage/YAPIONFormatSimulatorLoader.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/simulator/storage/YAPIONFormatSimulatorLoader.java index c138ecb1..6111eb32 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/simulator/storage/YAPIONFormatSimulatorLoader.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/simulator/storage/YAPIONFormatSimulatorLoader.java @@ -54,7 +54,7 @@ public class YAPIONFormatSimulatorLoader implements SimulatorLoader { } String name = file.getName().substring(0, file.getName().length() - 7); - SteamwarUser steamwarUser = SteamwarUser.get(Integer.parseInt(name)); + SteamwarUser steamwarUser = SteamwarUser.byId(Integer.parseInt(name)); List simulators = new ArrayList<>(); for (String s : yapionObject.getKeys()) { diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/tntlistener/TLSCommand.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/tls/TLSCommand.java similarity index 82% rename from BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/tntlistener/TLSCommand.java rename to BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/tls/TLSCommand.java index 355803dc..252482b6 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/tntlistener/TLSCommand.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/tls/TLSCommand.java @@ -17,7 +17,7 @@ * along with this program. If not, see . */ -package de.steamwar.bausystem.features.tntlistener; +package de.steamwar.bausystem.features.tls; import de.steamwar.command.SWCommand; import de.steamwar.linkage.Linked; @@ -43,4 +43,13 @@ public class TLSCommand extends SWCommand { public void stop(@Validator Player player) { listener.stopListening(player); } + + @Register(description = "TLS_TOGGLE_HELP") + public void toggle(@Validator Player player) { + if (listener.isActiveFor(player)) { + listener.stopListening(player); + } else { + listener.startListening(player); + } + } } diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/tntlistener/TLSListener.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/tls/TLSListener.java similarity index 98% rename from BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/tntlistener/TLSListener.java rename to BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/tls/TLSListener.java index 9b587d8f..20e7f161 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/tntlistener/TLSListener.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/tls/TLSListener.java @@ -17,7 +17,7 @@ * along with this program. If not, see . */ -package de.steamwar.bausystem.features.tntlistener; +package de.steamwar.bausystem.features.tls; import de.steamwar.bausystem.BauSystem; import de.steamwar.bausystem.features.tpslimit.TPSUtils; diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/tntlistener/TLSScoreboardElement.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/tls/TLSScoreboardElement.java similarity index 96% rename from BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/tntlistener/TLSScoreboardElement.java rename to BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/tls/TLSScoreboardElement.java index 0dec6609..8744e1f0 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/tntlistener/TLSScoreboardElement.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/tls/TLSScoreboardElement.java @@ -17,7 +17,7 @@ * along with this program. If not, see . */ -package de.steamwar.bausystem.features.tntlistener; +package de.steamwar.bausystem.features.tls; import de.steamwar.bausystem.BauSystem; import de.steamwar.bausystem.Permission; diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/AntiBauAddMemberFix.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/AntiBauAddMemberFix.java index c8c40cba..da75fe7c 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/AntiBauAddMemberFix.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/AntiBauAddMemberFix.java @@ -41,7 +41,7 @@ public class AntiBauAddMemberFix implements Listener { } if (BauweltMember.getBauMember(BauServer.getInstance().getOwner(), event.getPlayer().getUniqueId()) == null) { event.getPlayer().kickPlayer(""); - throw new SecurityException("The player " + event.getPlayer().getName() + " joined on the server of " + SteamwarUser.get(BauServer.getInstance().getOwnerID()).getUserName() + " without being added!"); + throw new SecurityException("The player " + event.getPlayer().getName() + " joined on the server of " + SteamwarUser.byId(BauServer.getInstance().getOwnerID()).getUserName() + " without being added!"); } } diff --git a/BauSystem/BauSystem_RegionFixed/src/de/steamwar/bausystem/region/fixed/FixedFlagStorage.java b/BauSystem/BauSystem_RegionFixed/src/de/steamwar/bausystem/region/fixed/FixedFlagStorage.java index 293e0121..c902f941 100644 --- a/BauSystem/BauSystem_RegionFixed/src/de/steamwar/bausystem/region/fixed/FixedFlagStorage.java +++ b/BauSystem/BauSystem_RegionFixed/src/de/steamwar/bausystem/region/fixed/FixedFlagStorage.java @@ -84,7 +84,12 @@ public class FixedFlagStorage implements FlagStorage { @Override public void clear() { - flagMap.clear(); + for (Flag flag : Flag.getFlags()) { + if (flag == Flag.TESTBLOCK) continue; + if (flag == Flag.COLOR) continue; + if (flag == Flag.CHANGED) continue; + flagMap.remove(flag); + } } @Override diff --git a/BauSystem/BauSystem_RegionFixed/src/de/steamwar/bausystem/region/fixed/FixedGlobalFlagStorage.java b/BauSystem/BauSystem_RegionFixed/src/de/steamwar/bausystem/region/fixed/FixedGlobalFlagStorage.java index 1fc4be27..69766fee 100644 --- a/BauSystem/BauSystem_RegionFixed/src/de/steamwar/bausystem/region/fixed/FixedGlobalFlagStorage.java +++ b/BauSystem/BauSystem_RegionFixed/src/de/steamwar/bausystem/region/fixed/FixedGlobalFlagStorage.java @@ -91,7 +91,12 @@ public class FixedGlobalFlagStorage implements FlagStorage { @Override public void clear() { - flagMap.clear(); + for (Flag flag : Flag.getFlags()) { + if (flag == Flag.TESTBLOCK) continue; + if (flag == Flag.COLOR) continue; + if (flag == Flag.CHANGED) continue; + flagMap.remove(flag); + } } @Override diff --git a/BauSystem/build.gradle.kts b/BauSystem/build.gradle.kts index aeade561..df33b12e 100644 --- a/BauSystem/build.gradle.kts +++ b/BauSystem/build.gradle.kts @@ -50,5 +50,6 @@ tasks.register("DevBau21") { description = "Run a 1.21 Dev Bau" dependsOn(":SpigotCore:shadowJar") dependsOn(":BauSystem:shadowJar") + dependsOn(":SchematicSystem:shadowJar") template = "Bau21" } diff --git a/CommonCore/SQL/build.gradle.kts b/CommonCore/SQL/build.gradle.kts index dc171dde..09528759 100644 --- a/CommonCore/SQL/build.gradle.kts +++ b/CommonCore/SQL/build.gradle.kts @@ -18,11 +18,26 @@ */ plugins { - steamwar.java + steamwar.kotlin +} + +kotlin { + jvmToolchain(8) +} + +java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 } dependencies { compileOnly(libs.sqlite) implementation("org.yaml:snakeyaml:2.2") -} \ No newline at end of file + + compileOnlyApi("org.jetbrains.kotlin:kotlin-stdlib:2.2.21") + compileOnlyApi(libs.exposedCore) + compileOnlyApi(libs.exposedDao) + compileOnlyApi(libs.exposedJdbc) + compileOnlyApi(libs.exposedTime) +} diff --git a/CommonCore/SQL/src/de/steamwar/sql/AuditLog.java b/CommonCore/SQL/src/de/steamwar/sql/AuditLog.java deleted file mode 100644 index 04f87d9d..00000000 --- a/CommonCore/SQL/src/de/steamwar/sql/AuditLog.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * 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 . - */ - -package de.steamwar.sql; - -import de.steamwar.sql.internal.Field; -import de.steamwar.sql.internal.SqlTypeMapper; -import de.steamwar.sql.internal.Statement; -import de.steamwar.sql.internal.Table; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NonNull; - -import java.sql.Timestamp; -import java.time.Instant; - -@AllArgsConstructor -public class AuditLog { - - static { - SqlTypeMapper.nameEnumMapper(AuditLog.Type.class); - } - - public static final String SERVER_NAME_VELOCITY = "Velocity"; - - private static final Table table = new Table<>(AuditLog.class); - - private static final Statement create = table.insertFields(true, "time", "serverName", "serverOwner", "actor", "actionType", "actionText"); - - @Getter - @Field - private final Timestamp time; - - @Getter - @Field - private final String serverName; - - @Field(nullable = true) - private final int serverOwner; - - @Field - private final int actor; - - @Getter - @Field - private final Type actionType; - - @Getter - @Field - private final String actionText; - - public enum Type { - JOIN, - LEAVE, - COMMAND, - SENSITIVE_COMMAND, - - CHAT, - GUI_OPEN, - GUI_CLOSE, - GUI_CLICK, - } - - private static void create(String serverName, SteamwarUser serverOwner, SteamwarUser actor, Type actionType, String text) { - create.insertGetKey(Timestamp.from(Instant.now()), serverName, serverOwner, actor, actionType, text); - } - - public static void createJoin(@NonNull String jointServerName, SteamwarUser serverOwner, @NonNull SteamwarUser joinedPlayer) { - create(jointServerName, serverOwner, joinedPlayer, Type.JOIN, ""); - } - - public static void createLeave(@NonNull String leftServerName, SteamwarUser serverOwner, @NonNull SteamwarUser joinedPlayer) { - create(leftServerName, serverOwner, joinedPlayer, Type.LEAVE, ""); - } - - public static void createCommand(@NonNull String serverName, SteamwarUser serverOwner, SteamwarUser player, @NonNull String command) { - if (player == null) return; - create(serverName, serverOwner, player, Type.COMMAND, command); - } - - public static void createSensitiveCommand(@NonNull String serverName, SteamwarUser serverOwner, SteamwarUser player, @NonNull String command) { - if (player == null) return; - create(serverName, serverOwner, player, Type.SENSITIVE_COMMAND, command); - } - - public static void createChat(@NonNull String serverName, SteamwarUser serverOwner, @NonNull SteamwarUser chatter, @NonNull String chat) { - create(serverName, serverOwner, chatter, Type.CHAT, chat); - } - - public static void createGuiOpen(@NonNull String serverName, SteamwarUser serverOwner, @NonNull SteamwarUser player, @NonNull String guiName) { - create(serverName, serverOwner, player, Type.GUI_OPEN, guiName); - } - - public static void createGuiClick(@NonNull String serverName, SteamwarUser serverOwner, @NonNull SteamwarUser player, @NonNull String guiName, @NonNull String clickType, int slot, @NonNull String itemName) { - create(serverName, serverOwner, player, Type.GUI_CLICK, "Gui: " + guiName + "\nSlot: " + slot + "\nClickType: " + clickType + "\nItemName: " + itemName); - } - - public static void createGuiClose(@NonNull String serverName, SteamwarUser serverOwner, @NonNull SteamwarUser player, @NonNull String guiName) { - create(serverName, serverOwner, player, Type.GUI_CLOSE, guiName); - } - - public SteamwarUser getServerOwner() { - return SteamwarUser.get(serverOwner); - } - - public SteamwarUser getActor() { - return SteamwarUser.get(actor); - } -} diff --git a/CommonCore/SQL/src/de/steamwar/sql/AuditLog.kt b/CommonCore/SQL/src/de/steamwar/sql/AuditLog.kt new file mode 100644 index 00000000..7ab0c8c4 --- /dev/null +++ b/CommonCore/SQL/src/de/steamwar/sql/AuditLog.kt @@ -0,0 +1,121 @@ +/* + * 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 . + */ + +package de.steamwar.sql + +import de.steamwar.sql.internal.useDb +import org.jetbrains.exposed.v1.core.dao.id.EntityID +import org.jetbrains.exposed.v1.core.dao.id.IntIdTable +import org.jetbrains.exposed.v1.dao.IntEntity +import org.jetbrains.exposed.v1.dao.IntEntityClass +import org.jetbrains.exposed.v1.javatime.timestamp +import java.time.Instant + +object AuditLogTable: IntIdTable("AuditLog", "AuditLogId") { + val time = timestamp("Time") + val server = varchar("ServerName", 255) + val serverOwner = reference("ServerOwner", SteamwarUserTable).nullable() + val actor = reference("Actor", SteamwarUserTable) + val action = enumerationByName("ActionType", 255, AuditLog.Type::class) + val actionText = text("ActionText") +} + +class AuditLog(id: EntityID): IntEntity(id) { + companion object: IntEntityClass(AuditLogTable) { + const val SERVER_NAME_VELOCITY: String = "Velocity" + + private fun create( + serverName: String, + serverOwner: SteamwarUser?, + actor: SteamwarUser, + actionType: Type, + text: String = "" + ) = useDb { + new { + this.time = Instant.now() + this.server = serverName + this.serverOwner = serverOwner?.id + this.actor = actor.id + this.action = actionType + this.actionText = text + } + } + + @JvmStatic + fun createJoin(jointServerName: String, serverOwner: SteamwarUser?, joinedPlayer: SteamwarUser) = create(jointServerName, serverOwner, joinedPlayer, Type.JOIN) + + @JvmStatic + fun createLeave(leftServerName: String, serverOwner: SteamwarUser?, joinedPlayer: SteamwarUser) = create(leftServerName, serverOwner, joinedPlayer, Type.LEAVE) + + @JvmStatic + fun createCommand(serverName: String, serverOwner: SteamwarUser?, player: SteamwarUser?, command: String) = player?.let { create(serverName, serverOwner, it, Type.COMMAND, command) } + + @JvmStatic + fun createSensitiveCommand( + serverName: String, + serverOwner: SteamwarUser?, + player: SteamwarUser?, + command: String + ) = player?.let { create(serverName, serverOwner, it, Type.SENSITIVE_COMMAND, command) } + + @JvmStatic + fun createChat(serverName: String, serverOwner: SteamwarUser?, chatter: SteamwarUser, chat: String) = create(serverName, serverOwner, chatter, Type.CHAT, chat) + + @JvmStatic + fun createGuiOpen(serverName: String, serverOwner: SteamwarUser?, player: SteamwarUser, guiName: String) = create(serverName, serverOwner, player, Type.GUI_OPEN, guiName) + + @JvmStatic + fun createGuiClick( + serverName: String, + serverOwner: SteamwarUser?, + player: SteamwarUser, + guiName: String, + clickType: String, + slot: Int, + itemName: String + ) = create( + serverName, + serverOwner, + player, + Type.GUI_CLICK, + "Gui: $guiName\nSlot: $slot\nClickType: $clickType\nItemName: $itemName" + ) + + @JvmStatic + fun createGuiClose(serverName: String, serverOwner: SteamwarUser?, player: SteamwarUser, guiName: String) = create(serverName, serverOwner, player, Type.GUI_CLOSE, guiName) + } + + var time by AuditLogTable.time + var server by AuditLogTable.server + var serverOwner by AuditLogTable.serverOwner + var actor by AuditLogTable.actor + var action by AuditLogTable.action + var actionText by AuditLogTable.actionText + + enum class Type { + JOIN, + LEAVE, + COMMAND, + SENSITIVE_COMMAND, + CHAT, + GUI_OPEN, + GUI_CLOSE, + GUI_CLICK, + } +} \ No newline at end of file diff --git a/CommonCore/SQL/src/de/steamwar/sql/BannedUserIPs.java b/CommonCore/SQL/src/de/steamwar/sql/BannedUserIPs.java deleted file mode 100644 index f16e0343..00000000 --- a/CommonCore/SQL/src/de/steamwar/sql/BannedUserIPs.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * 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 . - */ - -package de.steamwar.sql; - -import de.steamwar.sql.internal.Field; -import de.steamwar.sql.internal.SelectStatement; -import de.steamwar.sql.internal.Statement; -import de.steamwar.sql.internal.Table; -import lombok.AllArgsConstructor; -import lombok.Getter; - -import java.sql.Timestamp; -import java.time.Instant; -import java.util.List; - -@AllArgsConstructor -public class BannedUserIPs { - - private static final Table table = new Table<>(BannedUserIPs.class); - - private static final SelectStatement getByID = table.selectFields("UserID"); - private static final SelectStatement getByIP = new SelectStatement<>(table, "SELECT * FROM BannedUserIPs WHERE IP = ? ORDER BY Timestamp DESC"); - private static final Statement banIP = table.insertAll(); - private static final Statement unbanIPs = table.deleteFields("UserID"); - - @Getter - @Field(keys = {Table.PRIMARY}) - private final int userID; - @Getter - @Field(def = "CURRENT_TIMESTAMP") - private final Timestamp timestamp; - @Field(keys = {Table.PRIMARY}) - private final String ip; - - public static List get(int userID) { - return getByID.listSelect(userID); - } - - public static List get(String ip) { - return getByIP.listSelect(ip); - } - - public static void banIP(int userID, String ip){ - banIP.update(userID, Timestamp.from(Instant.now()), ip); - } - - public static void unbanIPs(int userID) { - unbanIPs.update(userID); - } -} diff --git a/CommonCore/SQL/src/de/steamwar/sql/BannedUserIPs.kt b/CommonCore/SQL/src/de/steamwar/sql/BannedUserIPs.kt new file mode 100644 index 00000000..495238ad --- /dev/null +++ b/CommonCore/SQL/src/de/steamwar/sql/BannedUserIPs.kt @@ -0,0 +1,78 @@ +/* + * 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 . + */ + +package de.steamwar.sql + +import de.steamwar.sql.internal.useDb +import org.jetbrains.exposed.v1.core.dao.id.CompositeID +import org.jetbrains.exposed.v1.core.dao.id.CompositeIdTable +import org.jetbrains.exposed.v1.core.dao.id.EntityID +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.dao.CompositeEntity +import org.jetbrains.exposed.v1.dao.CompositeEntityClass +import org.jetbrains.exposed.v1.javatime.timestamp +import org.jetbrains.exposed.v1.jdbc.deleteWhere +import org.jetbrains.exposed.v1.jdbc.insertIgnore +import java.sql.Timestamp +import java.time.Instant + +object BannedUserIPsTable: CompositeIdTable("BannedUserIPs") { + val userId = reference("UserID", SteamwarUserTable) + val timestamp = timestamp("Timestamp") + val ip = varchar("IP", 45) + + override val primaryKey = PrimaryKey(userId, ip) +} + +class BannedUserIPs(id: EntityID): CompositeEntity(id) { + companion object: CompositeEntityClass(BannedUserIPsTable) { + + @JvmStatic + fun get(userId: Int) = useDb { + find { BannedUserIPsTable.userId eq userId }.toList() + } + + @JvmStatic + fun get(ip: String) = useDb { + find { BannedUserIPsTable.ip eq ip }.toList() + } + + @JvmStatic + fun banIP(userId: Int, ip: String) = useDb { + BannedUserIPsTable.insertIgnore { + it[BannedUserIPsTable.userId] = userId + it[BannedUserIPsTable.ip] = ip + it[BannedUserIPsTable.timestamp] = Instant.now() + } + } + + @JvmStatic + fun unbanIPs(userId: Int) = useDb { + BannedUserIPsTable.deleteWhere { BannedUserIPsTable.userId eq userId } + } + } + + val userID by BannedUserIPsTable.userId.transform({ EntityID(it, SteamwarUserTable) }, { it.value }) + val timestamp: Timestamp by BannedUserIPsTable.timestamp.transform({ it.toInstant() }, { Timestamp.from(it) }) + val ip by BannedUserIPsTable.ip + + fun remove() = useDb { + delete() + } +} \ No newline at end of file diff --git a/CommonCore/SQL/src/de/steamwar/sql/BauweltMember.java b/CommonCore/SQL/src/de/steamwar/sql/BauweltMember.java deleted file mode 100644 index 71fa3a46..00000000 --- a/CommonCore/SQL/src/de/steamwar/sql/BauweltMember.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * 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 . - */ - -package de.steamwar.sql; - -import de.steamwar.sql.internal.Field; -import de.steamwar.sql.internal.SelectStatement; -import de.steamwar.sql.internal.Statement; -import de.steamwar.sql.internal.Table; -import lombok.Getter; - -import java.util.*; - -public class BauweltMember { - private static final Map memberCache = new HashMap<>(); - - public static void clear() { - memberCache.clear(); - } - - private static final Table table = new Table<>(BauweltMember.class); - private static final SelectStatement getMember = table.select(Table.PRIMARY); - private static final SelectStatement getMembers = table.selectFields("BauweltID"); - private static final Statement update = table.insertAll(); - private static final Statement delete = table.delete(Table.PRIMARY); - - public static void addMember(UUID ownerID, UUID memberID) { - new BauweltMember(SteamwarUser.get(ownerID).getId(), SteamwarUser.get(memberID).getId(), false, false).updateDB(); - } - - public static BauweltMember getBauMember(UUID ownerID, UUID memberID){ - return getBauMember(SteamwarUser.get(ownerID).getId(), SteamwarUser.get(memberID).getId()); - } - - public static BauweltMember getBauMember(int ownerID, int memberID){ - BauweltMember member = memberCache.get(memberID); - if(member != null && member.bauweltID == ownerID) - return member; - return getMember.select(ownerID, memberID); - } - - public static List getMembers(UUID bauweltID){ - return getMembers(SteamwarUser.get(bauweltID).getId()); - } - - public static List getMembers(int bauweltID){ - return getMembers.listSelect(bauweltID); - } - - @Getter - @Field(keys = {Table.PRIMARY}) - private final int bauweltID; - @Getter - @Field(keys = {Table.PRIMARY}) - private final int memberID; - @Getter - @Field(def = "0") - private boolean worldEdit; - @Getter - @Field(def = "0") - private boolean world; - - public BauweltMember(int bauweltID, int memberID, boolean worldEdit, boolean world) { - this.bauweltID = bauweltID; - this.memberID = memberID; - this.worldEdit = worldEdit; - this.world = world; - memberCache.put(memberID, this); - } - - public void setWorldEdit(boolean worldEdit) { - this.worldEdit = worldEdit; - updateDB(); - } - - public void setWorld(boolean world) { - this.world = world; - updateDB(); - } - - private void updateDB(){ - update.update(bauweltID, memberID, worldEdit, world); - } - - public void remove(){ - delete.update(bauweltID, memberID); - memberCache.remove(memberID); - } - - public boolean isBuild() { - return worldEdit; - } - - public boolean isSupervisor() { - return world; - } - - public void setBuild(boolean build) { - this.worldEdit = build; - updateDB(); - } - - public void setSupervisor(boolean supervisor) { - this.world = supervisor; - updateDB(); - } -} diff --git a/CommonCore/SQL/src/de/steamwar/sql/BauweltMember.kt b/CommonCore/SQL/src/de/steamwar/sql/BauweltMember.kt new file mode 100644 index 00000000..66e3bb8b --- /dev/null +++ b/CommonCore/SQL/src/de/steamwar/sql/BauweltMember.kt @@ -0,0 +1,130 @@ +/* + * 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 . + */ + +package de.steamwar.sql + +import de.steamwar.sql.BauweltMemberTable.bauweltId +import de.steamwar.sql.internal.useDb +import org.jetbrains.exposed.v1.core.and +import org.jetbrains.exposed.v1.core.dao.id.CompositeID +import org.jetbrains.exposed.v1.core.dao.id.CompositeIdTable +import org.jetbrains.exposed.v1.core.dao.id.EntityID +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.dao.CompositeEntity +import org.jetbrains.exposed.v1.dao.CompositeEntityClass +import org.jetbrains.exposed.v1.jdbc.insertIgnore +import java.util.* + +object BauweltMemberTable: CompositeIdTable("BauweltMember") { + val bauweltId = reference("BauweltID", SteamwarUserTable) + val memberId = reference("MemberID", SteamwarUserTable) + val build = bool("Build") + val worldEdit = bool("WorldEdit") + val world = bool("World") + + override val primaryKey = PrimaryKey(bauweltId, memberId) + + init { + addIdColumn(bauweltId) + addIdColumn(memberId) + } +} + +class BauweltMember(id: EntityID): CompositeEntity(id) { + companion object: CompositeEntityClass(BauweltMemberTable) { + private val cache = mutableMapOf() + + private fun cache(member: BauweltMember) = cache.put(member.memberID, member) + + @JvmStatic + fun clear() = cache.clear() + + @JvmStatic + @Deprecated("Use addMember(ownerId: Int, newMemberId: Int)") + fun addMember(ownerId: UUID, newMemberId: UUID) = addMember(SteamwarUser.get(ownerId)!!.id, SteamwarUser.get(newMemberId)!!.id) + + @JvmStatic + fun addMember(ownerId: Int, newMemberId: Int) = addMember(EntityID(ownerId, SteamwarUserTable), EntityID(newMemberId, SteamwarUserTable)) + + fun addMember(ownerId: EntityID, newMemberId: EntityID) = useDb { + BauweltMemberTable.insertIgnore { + it[bauweltId] = ownerId + it[memberId] = newMemberId + it[build] = false + it[worldEdit] = false + it[world] = false + } + } + + @JvmStatic + @Deprecated("Use getBauMember(bauwelt: Int, member: Int)") + fun getBauMember(bauwelt: UUID, member: UUID) = useDb { + find { (bauweltId eq SteamwarUser.get(bauwelt)!!.id) and (BauweltMemberTable.memberId eq SteamwarUser.get(member)!!.id) }.firstOrNull()?.also { cache(it) } + } + + @JvmStatic + fun getBauMember(bauwelt: Int, member: Int) = useDb { + find { (bauweltId eq bauwelt) and (BauweltMemberTable.memberId eq member) }.firstOrNull()?.also { cache(it) } + } + + @JvmStatic + @Deprecated("Use getMembers(bauwelt: Int)") + fun getMembers(bauwelt: UUID) = getMembers(SteamwarUser.get(bauwelt)!!.id.value) + + @JvmStatic + fun getMembers(bauwelt: Int) = useDb { + find { bauweltId eq bauwelt }.toList().also { it.forEach { cache(it) } } + } + } + + val bauweltID by BauweltMemberTable.bauweltId.transform({ EntityID(it, SteamwarUserTable) }, { it.value }) + val memberID by BauweltMemberTable.memberId.transform({ EntityID(it, SteamwarUserTable) }, { it.value }) + private var worldEditInternal by BauweltMemberTable.worldEdit + var worldEdit: Boolean + get() = worldEditInternal + set(value) = useDb { + worldEditInternal = value + } + private var buildInternal by BauweltMemberTable.build + var build: Boolean + get() = buildInternal + set(value) = useDb { + buildInternal = value + } + private var worldInternal by BauweltMemberTable.world + var world: Boolean + get() = worldInternal + set(value) = useDb { + worldInternal = value + } + var supervisor: Boolean + get() = world + set(value) = useDb { + world = value + } + + fun isBuild() = build + fun isSupervisor() = world + fun isWorldEdit() = build + fun isWorld() = world + + fun remove() = useDb { + delete() + } +} \ No newline at end of file diff --git a/CommonCore/SQL/src/de/steamwar/sql/CheckedSchematic.java b/CommonCore/SQL/src/de/steamwar/sql/CheckedSchematic.java deleted file mode 100644 index 5a0e553c..00000000 --- a/CommonCore/SQL/src/de/steamwar/sql/CheckedSchematic.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * 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 . - */ - -package de.steamwar.sql; - -import de.steamwar.sql.internal.Field; -import de.steamwar.sql.internal.SelectStatement; -import de.steamwar.sql.internal.Statement; -import de.steamwar.sql.internal.Table; -import lombok.AllArgsConstructor; -import lombok.Getter; - -import java.sql.Timestamp; -import java.util.List; -import java.util.concurrent.CompletableFuture; - -@AllArgsConstructor -public class CheckedSchematic { - - private static final Table table = new Table<>(CheckedSchematic.class); - private static final SelectStatement statusOfNode = new SelectStatement<>(table, "SELECT * FROM CheckedSchematic WHERE NodeId = ? AND DeclineReason != 'Prüfvorgang abgebrochen' ORDER BY EndTime DESC"); - private static final SelectStatement nodeHistory = new SelectStatement<>(table, "SELECT * FROM CheckedSchematic WHERE NodeId = ? AND DeclineReason != '' AND DeclineReason != 'Prüfvorgang abgebrochen' ORDER BY EndTime DESC"); - private static final Statement insert = table.insertAll(); - - private static final SelectStatement getUnseen = new SelectStatement<>(table, "SELECT * FROM CheckedSchematic WHERE Seen = 0 AND NodeOwner = ? ORDER BY StartTime DESC"); - private static final Statement updateSeen = new Statement("UPDATE CheckedSchematic SET Seen = ? WHERE StartTime = ? AND EndTime = ? AND NodeName = ?"); - - public static void create(SchematicNode node, int validator, Timestamp startTime, Timestamp endTime, String reason, boolean seen) { - insert.update(node.getId(), node.getOwner(), node.getName(), validator, startTime, endTime, reason, seen, node.getSchemtype().toDB().substring(1)); - } - - public static List getLastDeclinedOfNode(int node) { - return statusOfNode.listSelect(node); - } - - public static List previousChecks(SchematicNode node) { - return nodeHistory.listSelect(node.getId()); - } - - public static List getUnseen(SteamwarUser owner) { - return getUnseen.listSelect(owner); - } - - @Field(nullable = true) - private final Integer nodeId; - @Field - private final int nodeOwner; - @Field - private final String nodeName; - @Getter - @Field - private final int validator; - @Getter - @Field - private final Timestamp startTime; - @Getter - @Field - private final Timestamp endTime; - @Getter - @Field - private final String declineReason; - @Getter - @Field - private boolean seen; - @Getter - @Field - private final String nodeType; - - public int getNode() { - return nodeId; - } - - public String getSchemName() { - return nodeName; - } - - public int getSchemOwner() { - return nodeOwner; - } - - public void setSeen(boolean seen) { - this.seen = seen; - updateSeen.update(seen, startTime, endTime, nodeName); - } -} diff --git a/CommonCore/SQL/src/de/steamwar/sql/CheckedSchematic.kt b/CommonCore/SQL/src/de/steamwar/sql/CheckedSchematic.kt new file mode 100644 index 00000000..3e51d24e --- /dev/null +++ b/CommonCore/SQL/src/de/steamwar/sql/CheckedSchematic.kt @@ -0,0 +1,100 @@ +/* + * 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 . + */ + +package de.steamwar.sql + +import de.steamwar.sql.internal.useDb +import org.jetbrains.exposed.v1.core.SortOrder +import org.jetbrains.exposed.v1.core.and +import org.jetbrains.exposed.v1.core.dao.id.CompositeID +import org.jetbrains.exposed.v1.core.dao.id.CompositeIdTable +import org.jetbrains.exposed.v1.core.dao.id.EntityID +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.core.neq +import org.jetbrains.exposed.v1.dao.CompositeEntity +import org.jetbrains.exposed.v1.dao.CompositeEntityClass +import org.jetbrains.exposed.v1.javatime.timestamp +import org.jetbrains.exposed.v1.jdbc.insertIgnore +import java.sql.Timestamp + +object CheckedSchematicTable: CompositeIdTable("CheckedSchematic") { + val nodeId = optReference("NodeId", SchematicNodeTable) + val nodeOwner = reference("NodeOwner", SteamwarUserTable) + val nodeName = varchar("NodeName", 64).entityId() + val validator = reference("Validator", SteamwarUserTable) + val startTime = timestamp("StartTime").entityId() + val endTime = timestamp("EndTime") + val declineReason = text("DeclineReason") + val seen = bool("Seen") + val nodeType = varchar("NodeType", 16) + + init { + addIdColumn(nodeOwner) + addIdColumn(nodeName) + } +} + +class CheckedSchematic(id: EntityID): CompositeEntity(id) { + companion object: CompositeEntityClass(CheckedSchematicTable) { + @JvmStatic + fun create(node: SchematicNode, validator: Int, startTime: Timestamp, endTime: Timestamp, reason: String, seen: Boolean) = useDb { + CheckedSchematicTable.insertIgnore { + it[this.nodeId] = node.id + it[this.nodeOwner] = EntityID(node.owner, SteamwarUserTable) + it[this.nodeName] = node.name + it[this.validator] = EntityID(validator, SteamwarUserTable) + it[this.startTime] = startTime.toInstant() + it[this.endTime] = endTime.toInstant() + it[this.declineReason] = reason + it[this.seen] = seen + it[this.nodeType] = node.schemtype.toDB().substring(1) + } + } + + @JvmStatic + fun getLastDeclinedOfNode(node: Int) = useDb { + find { (CheckedSchematicTable.nodeId eq node) and (CheckedSchematicTable.declineReason neq "Prüfvorgang abgebrochen") }.orderBy(CheckedSchematicTable.endTime to SortOrder.DESC).toList() + } + + @JvmStatic + fun previousChecks(node: SchematicNode) = useDb { + find { (CheckedSchematicTable.nodeId eq node.id) and (CheckedSchematicTable.declineReason neq "") and (CheckedSchematicTable.declineReason neq "Prüfvorgang abgebrochen") }.orderBy(CheckedSchematicTable.endTime to SortOrder.DESC).toList() + } + + @JvmStatic + fun getUnseen(owner: SteamwarUser) = useDb { + find { (CheckedSchematicTable.nodeOwner eq owner.id) and (CheckedSchematicTable.seen eq false) }.orderBy(CheckedSchematicTable.endTime to SortOrder.DESC).toList() + } + } + + val node by CheckedSchematicTable.nodeId.transform({ it?.let { EntityID(it, SchematicNodeTable) } }, { it?.value }) + val schemOwner by CheckedSchematicTable.nodeOwner.transform({ EntityID(it, SteamwarUserTable) }, { it.value }) + val nodeName by CheckedSchematicTable.nodeName + val schemName get() = nodeName.value + val validator by CheckedSchematicTable.validator.transform({ EntityID(it, SteamwarUserTable) }, { it.value }) + val startTimeId by CheckedSchematicTable.startTime + val startTime get() = Timestamp.from(startTimeId.value) + val endTime by CheckedSchematicTable.endTime.transform({ it.toInstant() }, { Timestamp.from(it) }) + val declineReason by CheckedSchematicTable.declineReason + private var wasSeen by CheckedSchematicTable.seen + var seen: Boolean + get() = wasSeen + set(value) = useDb { wasSeen = value } + val nodeType by CheckedSchematicTable.nodeType +} \ No newline at end of file diff --git a/CommonCore/SQL/src/de/steamwar/sql/Event.java b/CommonCore/SQL/src/de/steamwar/sql/Event.java deleted file mode 100644 index e65a1533..00000000 --- a/CommonCore/SQL/src/de/steamwar/sql/Event.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * 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 . - */ - -package de.steamwar.sql; - -import de.steamwar.sql.internal.Field; -import de.steamwar.sql.internal.SelectStatement; -import de.steamwar.sql.internal.Statement; -import de.steamwar.sql.internal.Table; -import lombok.AllArgsConstructor; -import lombok.Getter; - -import java.sql.Timestamp; -import java.time.Instant; -import java.util.List; - -@AllArgsConstructor -public class Event { - - static { - SchematicType.Normal.name(); // Ensure SchematicType is loaded. - } - - private static final Table table = new Table<>(Event.class); - - private static final SelectStatement byCurrent = new SelectStatement<>(table, "SELECT * FROM Event WHERE Start < now() AND End > now()"); - private static final SelectStatement byId = table.select(Table.PRIMARY); - private static final SelectStatement byName = table.select("eventName"); - private static final SelectStatement byComing = new SelectStatement<>(table, "SELECT * FROM Event WHERE Start > now()"); - private static final SelectStatement all = new SelectStatement<>(table, "SELECT * FROM Event"); - - private static final Statement create = table.insertFields(true, "eventName", "deadline", "start", "end", "maximumTeamMembers", "publicSchemsOnly"); - private static final Statement update = table.update(Table.PRIMARY, "eventName", "deadline", "start", "end", "schemType", "maximumTeamMembers", "publicSchemsOnly"); - private static final Statement delete = table.delete(Table.PRIMARY); - - private static Event current = null; - - public static Event get(){ - if(current != null && current.now()) - return current; - - current = byCurrent.select(); - return current; - } - - public static List getAll(){ - return all.listSelect(); - } - - public static Event create(String eventName, Timestamp start, Timestamp end){ - return get(create.insertGetKey(eventName, start, start, end, 5, false)); - } - - public static Event get(int eventID){ - return byId.select(eventID); - } - - public static Event get(String eventName) { - return byName.select(eventName); - } - - public static List getComing() { - return byComing.listSelect(); - } - - @Getter - @Field(keys = {Table.PRIMARY}, autoincrement = true) - private final int eventID; - @Getter - @Field(keys = {"eventName"}) - private final String eventName; - @Getter - @Field - private final Timestamp deadline; - @Getter - @Field - private final Timestamp start; - @Getter - @Field - private final Timestamp end; - @Getter - @Field - private final int maximumTeamMembers; - @Field(nullable = true) - private final SchematicType schemType; - @Field - private final boolean publicSchemsOnly; - - public boolean publicSchemsOnly() { - return publicSchemsOnly; - } - - public SchematicType getSchematicType() { - return schemType; - } - - private boolean now() { - Instant now = Instant.now(); - return now.isAfter(start.toInstant()) && now.isBefore(end.toInstant()); - } - - public void update(String eventName, Timestamp deadline, Timestamp start, Timestamp end, SchematicType schemType, int maximumTeamMembers, boolean publicSchemsOnly) { - update.update(eventName, deadline, start, end, schemType, maximumTeamMembers, publicSchemsOnly, eventID); - } - - public void delete() { - delete.update(eventID); - } -} diff --git a/CommonCore/SQL/src/de/steamwar/sql/Event.kt b/CommonCore/SQL/src/de/steamwar/sql/Event.kt new file mode 100644 index 00000000..ffc08b8d --- /dev/null +++ b/CommonCore/SQL/src/de/steamwar/sql/Event.kt @@ -0,0 +1,121 @@ +/* + * 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 . + */ + +package de.steamwar.sql + +import de.steamwar.sql.internal.useDb +import org.jetbrains.exposed.v1.core.and +import org.jetbrains.exposed.v1.core.dao.id.EntityID +import org.jetbrains.exposed.v1.core.dao.id.IntIdTable +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.core.greater +import org.jetbrains.exposed.v1.core.lessEq +import org.jetbrains.exposed.v1.dao.IntEntity +import org.jetbrains.exposed.v1.dao.IntEntityClass +import org.jetbrains.exposed.v1.javatime.timestamp +import org.jetbrains.exposed.v1.jdbc.insertAndGetId +import java.sql.Timestamp +import java.time.Instant + +object EventTable : IntIdTable("Event", "EventId") { + val name = varchar("EventName", 100).uniqueIndex() + val deadline = timestamp("Deadline") + val start = timestamp("Start") + val end = timestamp("End") + val maxPlayers = integer("MaximumTeamMembers") + val schemType = varchar("SchemType", 16).nullable() + val publicsOnly = bool("PublicSchemsOnly") +} + +class Event(id: EntityID) : IntEntity(id) { + companion object : IntEntityClass(EventTable) { + private var current: Event? = null + + @JvmStatic + fun get(): Event? = if (current?.now() == true) { + current + } else useDb { + find { EventTable.start.lessEq(Instant.now()) and EventTable.end.greater(Instant.now()) }.firstOrNull() + ?.also { current == it } + } + + @JvmStatic + fun getAll() = useDb { all().toList() } + + @JvmStatic + fun create(name: String, start: Timestamp, end: Timestamp) = useDb { + EventTable.insertAndGetId { + it[this.name] = name + it[this.deadline] = start.toInstant() + it[this.start] = start.toInstant() + it[this.end] = end.toInstant() + it[this.maxPlayers] = 5 + it[this.publicsOnly] = false + }.let { get(it) } + } + + @JvmStatic + fun byId(id: Int) = useDb { findById(id) } + + @JvmStatic + fun get(name: String) = useDb { find { EventTable.name eq name }.firstOrNull() } + + @JvmStatic + fun getComing() = useDb { find { EventTable.start greater Instant.now() }.toList() } + } + + val eventID by EventTable.id.transform({ EntityID(it, EventTable) }, { it.value }) + var eventName by EventTable.name + private set + var deadline: Timestamp by EventTable.deadline.transform({ it.toInstant() }, { Timestamp.from(it) }) + private set + var start: Timestamp by EventTable.start.transform({ it.toInstant() }, { Timestamp.from(it) }) + private set + var end: Timestamp by EventTable.end.transform({ it.toInstant() }, { Timestamp.from(it) }) + private set + var maximumTeamMembers by EventTable.maxPlayers + private set + var schematicType by EventTable.schemType.transform({ it?.toDB() }, { it?.let { SchematicType.fromDB(it) } }) + private set + var publicSchemsOnly by EventTable.publicsOnly + private set + + fun publicSchemsOnly() = publicSchemsOnly + fun now() = Instant.now().let { it.isAfter(start.toInstant()) && it.isBefore(end.toInstant()) } + + fun update( + name: String, + deadline: Timestamp, + start: Timestamp, + end: Timestamp, + schematicType: SchematicType?, + maxPlayers: Int, + publicSchemsOnly: Boolean + ) = useDb { + this@Event.eventName = name + this@Event.deadline = deadline + this@Event.start = start + this@Event.end = end + this@Event.maximumTeamMembers = maxPlayers + this@Event.schematicType = schematicType + this@Event.publicSchemsOnly = publicSchemsOnly + } + + override fun delete() = useDb { super.delete() } +} \ No newline at end of file diff --git a/CommonCore/SQL/src/de/steamwar/sql/EventFight.java b/CommonCore/SQL/src/de/steamwar/sql/EventFight.java deleted file mode 100644 index a1472918..00000000 --- a/CommonCore/SQL/src/de/steamwar/sql/EventFight.java +++ /dev/null @@ -1,216 +0,0 @@ -/* - * 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 . - */ - -package de.steamwar.sql; - -import de.steamwar.sql.internal.Field; -import de.steamwar.sql.internal.SelectStatement; -import de.steamwar.sql.internal.Statement; -import de.steamwar.sql.internal.Table; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.Setter; - -import java.sql.Timestamp; -import java.util.*; - -import static java.time.temporal.ChronoUnit.SECONDS; - -@AllArgsConstructor -public class EventFight implements Comparable { - - private static final Table table = new Table<>(EventFight.class); - private static final SelectStatement byId = table.select(Table.PRIMARY); - private static final SelectStatement byGroup = new SelectStatement(table, "SELECT * FROM EventFight WHERE GroupID = ? ORDER BY StartTime ASC"); - private static final SelectStatement byGroupLast = new SelectStatement(table, "SELECT * FROM EventFight WHERE GroupID = ? ORDER BY StartTime DESC LIMIT 1"); - private static final SelectStatement allComing = new SelectStatement<>(table, "SELECT * FROM EventFight WHERE StartTime > now() ORDER BY StartTime ASC"); - private static final SelectStatement event = new SelectStatement<>(table, "SELECT * FROM EventFight WHERE EventID = ? ORDER BY StartTime ASC"); - private static final SelectStatement activeFights = new SelectStatement<>(table, "SELECT * FROM EventFight WHERE EventID IN (SELECT EventID FROM Event WHERE Start < now() and End > now()) AND Fight IS NULL AND StartTime < now()"); - private static final Statement reschedule = table.update(Table.PRIMARY, "StartTime"); - private static final Statement setResult = table.update(Table.PRIMARY, "Ergebnis"); - private static final Statement setFight = table.update(Table.PRIMARY, "Fight"); - - private static final Statement create = table.insertFields(true, "eventID", "startTime", "spielmodus", "map", "teamBlue", "teamRed", "spectatePort"); - private static final Statement update = table.update(Table.PRIMARY, "startTime", "spielModus", "map", "teamBlue", "teamRed", "spectatePort"); - private static final Statement setGroup = table.update(Table.PRIMARY, "GroupID"); - private static final Statement delete = table.delete(Table.PRIMARY); - - @Getter - private static final Queue fights = new PriorityQueue<>(); - - public static EventFight get(int fightID) { - return byId.select(fightID); - } - - public static List get(EventGroup group) { - return byGroup.listSelect(group.getId()); - } - - public static Optional getLast(EventGroup group) { - return Optional.ofNullable(byGroupLast.select(group.getId())); - } - - public static void loadAllComingFights() { - fights.clear(); - fights.addAll(allComing.listSelect()); - } - - public static List getEvent(int eventID) { - return event.listSelect(eventID); - } - - private static List activeFightsCache = null; - - public static void clearActiveFightsCache() { - activeFightsCache = null; - } - - public static List getActiveFights() { - if (activeFightsCache == null) { - activeFightsCache = activeFights.listSelect(); - } - return activeFightsCache; - } - - public static EventFight create(int event, Timestamp from, String spielmodus, String map, int blueTeam, int redTeam, Integer spectatePort) { - return get(create.insertGetKey(event, from, spielmodus, map, blueTeam, redTeam, spectatePort)); - } - - @Getter - @Field - private final int eventID; - @Getter - @Field(keys = {Table.PRIMARY}, autoincrement = true) - private final int fightID; - @Getter - @Setter - @Field(nullable = true, def = "null") - private Integer groupId; - @Getter - @Setter - @Field - private Timestamp startTime; - @Getter - @Setter - @Field - private String spielmodus; - @Getter - @Setter - @Field - private String map; - @Getter - @Setter - @Field - private int teamBlue; - @Getter - @Setter - @Field - private int teamRed; - @Getter - @Setter - @Field(nullable = true) - private Integer spectatePort; - @Getter - @Setter - @Field(def = "1") - private int bestOf; - @Getter - @Field(def = "0") - private int ergebnis; - @Field(nullable = true) - private int fight; - - public Optional getGroup() { - return Optional.ofNullable(groupId).flatMap(EventGroup::get); - } - - public Optional getWinner() { - if(ergebnis == 0) - return Optional.empty(); - return Optional.ofNullable(ergebnis == 1 ? Team.get(teamBlue) : Team.get(teamRed)); - } - - public Optional getLosser() { - if(ergebnis == 0) - return Optional.empty(); - return Optional.ofNullable(ergebnis == 1 ? Team.get(teamRed) : Team.get(teamBlue)); - } - - public List getDependents() { - return EventRelation.getFightRelations(this); - } - - public void setErgebnis(int winner) { - this.ergebnis = winner; - setResult.update(winner, fightID); - } - - public void setFight(int fight) { - //Fight.FightID, not EventFight.FightID - this.fight = fight; - setFight.update(fight, fightID); - } - - public void setGroup(Integer group) { - setGroup.update(group, fightID); - this.groupId = group; - } - - public boolean hasFinished() { - return fight != 0 || ergebnis != 0; - } - - public void reschedule() { - startTime = Timestamp.from(new Date().toInstant().plus(30, SECONDS)); - reschedule.update(startTime, fightID); - } - - @Override - public int hashCode(){ - return fightID; - } - - @Override - public boolean equals(Object o){ - if(o == null) - return false; - if(!(o instanceof EventFight)) - return false; - return fightID == ((EventFight) o).fightID; - } - - @Override - public int compareTo(EventFight o) { - return startTime.compareTo(o.startTime); - } - - public void update(Timestamp startTime, String spielmodus, String map, int teamBlue, int teamRed, Integer spectatePort) { - update.update(startTime, spielmodus, map, teamBlue, teamRed, spectatePort, fightID); - this.startTime = startTime; - this.spielmodus = spielmodus; - this.map = map; - this.teamBlue = teamBlue; - this.teamRed = teamRed; - this.spectatePort = spectatePort; - } - - public void delete() { - delete.update(fightID); - } -} diff --git a/CommonCore/SQL/src/de/steamwar/sql/EventFight.kt b/CommonCore/SQL/src/de/steamwar/sql/EventFight.kt new file mode 100644 index 00000000..306c84b9 --- /dev/null +++ b/CommonCore/SQL/src/de/steamwar/sql/EventFight.kt @@ -0,0 +1,188 @@ +/* + * 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 . + */ + +package de.steamwar.sql + +import de.steamwar.sql.internal.useDb +import org.jetbrains.exposed.v1.core.* +import org.jetbrains.exposed.v1.core.dao.id.EntityID +import org.jetbrains.exposed.v1.core.dao.id.IntIdTable +import org.jetbrains.exposed.v1.dao.IntEntity +import org.jetbrains.exposed.v1.dao.IntEntityClass +import org.jetbrains.exposed.v1.javatime.timestamp +import org.jetbrains.exposed.v1.jdbc.insertAndGetId +import org.jetbrains.exposed.v1.jdbc.select +import java.sql.Timestamp +import java.time.Instant +import java.util.* + +object EventFightTable : IntIdTable("EventFight", "FightID") { + val eventId = reference("EventID", EventTable) + val startTime = timestamp("StartTime") + val gamemode = text("Spielmodus") + val map = text("Map") + val groupId = optReference("GroupId", EventGroupTable) + val teamBlue = reference("TeamBlue", TeamTable) + val teamRed = reference("TeamRed", TeamTable) + val spectatePort = integer("SpectatePort").nullable() + val bestOf = integer("BestOf") + val ergebnis = integer("Ergebnis") + val fight = optReference("Fight", FightTable) +} + +class EventFight(id: EntityID) : IntEntity(id), Comparable { + companion object : IntEntityClass(EventFightTable) { + val fights: Queue = PriorityQueue() + @JvmStatic get + + @JvmStatic + fun byId(fightId: Int) = useDb { findById(fightId) } + + @JvmStatic + fun byId(group: EventGroup) = useDb { + find { EventFightTable.groupId eq group.id }.orderBy(EventFightTable.startTime to SortOrder.DESC).toList() + } + + @JvmStatic + fun getLast(group: EventGroup) = useDb { + Optional.ofNullable( + find { EventFightTable.groupId eq group.id }.orderBy(EventFightTable.startTime to SortOrder.DESC) + .firstOrNull() + ) + } + + @JvmStatic + fun loadAllComingFights() = useDb { + fights.clear() + fights.addAll(find { EventFightTable.startTime greaterEq Instant.now() }.orderBy(EventFightTable.startTime to SortOrder.ASC)) + } + + @JvmStatic + fun getEvent(eventId: Int) = useDb { + find { EventFightTable.eventId eq eventId }.orderBy(EventFightTable.startTime to SortOrder.ASC).toList() + } + + private var activeFightsCache: List? = null + + @JvmStatic + fun clearActiveFightsCache() { + activeFightsCache = null + } + + @JvmStatic + fun getActiveFights(): List { + if (activeFightsCache == null) { + activeFightsCache = useDb { + find { + EventFightTable.fight.isNull() and (EventFightTable.startTime less Instant.now()) and (EventFightTable.eventId.inSubQuery( + EventTable.select( + EventTable.id + ) + .where { (EventTable.start less Instant.now()) and (EventTable.end greater Instant.now()) })) + }.orderBy(EventFightTable.startTime to SortOrder.ASC).toList() + } + } + + return activeFightsCache!! + } + + @JvmStatic + fun create( + event: Int, + from: Timestamp, + spielmodus: String, + map: String, + blueTeam: Int, + redTeam: Int, + spectatePort: Int? + ) = useDb { + get( + EventFightTable.insertAndGetId { + it[eventId] = EntityID(event, EventTable) + it[startTime] = from.toInstant() + it[gamemode] = spielmodus + it[EventFightTable.map] = map + it[teamBlue] = EntityID(blueTeam, TeamTable) + it[teamRed] = EntityID(redTeam, TeamTable) + it[EventFightTable.spectatePort] = spectatePort + } + ) + } + } + + val fightID by EventFightTable.id.transform({ EntityID(it, EventFightTable) }, { it.value }) + var teamBlue by EventFightTable.teamBlue.transform({ EntityID(it, TeamTable) }, { it.value }) + var teamRed by EventFightTable.teamRed.transform({ EntityID(it, TeamTable) }, { it.value }) + private var fightErgebnis by EventFightTable.ergebnis + var ergebnis: Int + get() = fightErgebnis + set(value) = useDb { + fightErgebnis = value + } + var eventID by EventFightTable.eventId.transform({ EntityID(it, EventTable) }, { it.value }) + var startTime by EventFightTable.startTime.transform({ it.toInstant() }, { Timestamp.from(it) }) + var spielmodus by EventFightTable.gamemode + var map by EventFightTable.map + var groupId by EventFightTable.groupId + val group by lazy { useDb { Optional.ofNullable(groupId).map { EventGroup[it] } } } + var spectatePort by EventFightTable.spectatePort + private var fightStat by EventFightTable.fight.transform({ it?.let { EntityID(it, EventFightTable) } }, { it?.value }) + var fight: Int? + get() = fightStat + set(value) = useDb { + fightStat = value + } + val dependents by lazy { useDb { EventRelation.getFightRelations(this@EventFight) } } + + val winner: Team? + get() = if (ergebnis == 1) Team[teamBlue] else if (ergebnis == 2) Team[teamRed] else null + val losser: Team? + get() = if (ergebnis == 1) Team[teamRed] else if (ergebnis == 2) Team[teamBlue] else null + + fun setGroup(group: Int?) = useDb { groupId = group?.let { EntityID(it, EventGroupTable) } } + fun hasFinished() = fight != null || ergebnis != 0 + + fun reschedule() = useDb { + startTime = Timestamp.from(Instant.now().plusSeconds(30)) + } + + override fun hashCode() = fightID + override fun equals(other: Any?) = other is EventFight && other.fightID == fightID + override fun compareTo(other: EventFight): Int = startTime.compareTo(other.startTime) + + fun update( + startTime: Timestamp, + spielmodus: String, + map: String, + teamBlue: Int, + teamRed: Int, + spectatePort: Int? + ) = useDb { + this@EventFight.startTime = startTime + this@EventFight.spielmodus = spielmodus + this@EventFight.map = map + this@EventFight.teamBlue = teamBlue + this@EventFight.teamRed = teamRed + this@EventFight.spectatePort = spectatePort + } + + override fun delete() = useDb { + super.delete() + } +} \ No newline at end of file diff --git a/CommonCore/SQL/src/de/steamwar/sql/EventGroup.java b/CommonCore/SQL/src/de/steamwar/sql/EventGroup.java deleted file mode 100644 index 68934765..00000000 --- a/CommonCore/SQL/src/de/steamwar/sql/EventGroup.java +++ /dev/null @@ -1,173 +0,0 @@ -/* - * 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 . - */ - -package de.steamwar.sql; - -import de.steamwar.sql.internal.*; -import lombok.Getter; -import lombok.Setter; - -import java.util.*; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -@Getter -@Setter -public class EventGroup { - static { - SqlTypeMapper.ordinalEnumMapper(EventGroupType.class); - } - - private static final Table table = new Table<>(EventGroup.class); - - private static final SelectStatement get = table.select(Table.PRIMARY); - private static final SelectStatement byEvent = new SelectStatement<>(table, "SELECT * FROM EventGroup WHERE EventID = ?"); - - private static final Statement insert = table.insertFields(true, "EventID", "Name", "Type"); - private static final Statement update = table.update(Table.PRIMARY, "Name", "Type", "PointsPerWin", "PointsPerLoss", "PointsPerDraw"); - private static final Statement delete = table.delete(Table.PRIMARY); - - public static List get(Event eventID) { - return byEvent.listSelect(eventID.getEventID()); - } - - public static EventGroup create(Event event, String name, EventGroupType type) { - int key = insert.insertGetKey(event.getEventID(), name, type); - return EventGroup.get(key).get(); - } - - public static Optional get(int id) { - return Optional.ofNullable(get.select(id)); - } - - @Field(keys = Table.PRIMARY) - private final int id; - - @Field(keys = "EVENT_NAME") - private int eventID; - - @Field(keys = "EVENT_NAME") - private String name; - - @Field - private EventGroupType type; - - @Field - private int pointsPerWin; - - @Field - private int pointsPerLoss; - - @Field - private int pointsPerDraw; - - public EventGroup(int id, int eventID, String name, EventGroupType type, int pointsPerWin, int pointsPerLoss, int pointsPerDraw) { - this.id = id; - this.eventID = eventID; - this.name = name; - this.type = type; - this.pointsPerWin = pointsPerWin; - this.pointsPerLoss = pointsPerLoss; - this.pointsPerDraw = pointsPerDraw; - } - - private Map points; - - public List getFights() { - return EventFight.get(this); - } - - public Set getTeamsId() { - return getFights().stream().flatMap(fight -> Stream.of(fight.getTeamBlue(), fight.getTeamRed())) - .collect(Collectors.toSet()); - } - - public Set getTeams() { - return getTeamsId().stream().map(Team::get).collect(Collectors.toSet()); - } - - public Optional getLastFight() { - return EventFight.getLast(this); - } - - public List getDependents() { - return EventRelation.getGroupRelations(this); - } - - public Map calculatePoints() { - if (points == null) { - Map p = getTeamsId().stream().collect(Collectors.toMap(team -> team, team -> 0)); - - for (EventFight fight : getFights()) { - int blueTeamAdd = 0; - int redTeamAdd = 0; - - if (!fight.hasFinished()) { - continue; - } - - switch (fight.getErgebnis()) { - case 1: - blueTeamAdd += pointsPerWin; - redTeamAdd += pointsPerLoss; - break; - case 2: - blueTeamAdd += pointsPerLoss; - redTeamAdd += pointsPerWin; - break; - case 0: - if (fight.getFightID() != 0) { - blueTeamAdd += pointsPerDraw; - redTeamAdd += pointsPerDraw; - } - break; - } - - p.put(fight.getTeamBlue(), p.get(fight.getTeamBlue()) + blueTeamAdd); - p.put(fight.getTeamRed(), p.get(fight.getTeamRed()) + redTeamAdd); - } - - points = p.entrySet().stream().collect(Collectors.toMap(integerIntegerEntry -> Team.get(integerIntegerEntry.getKey()), Map.Entry::getValue)); - } - - return points; - } - - public void update(String name, EventGroupType type, int pointsPerWin, int pointsPerLoss, int pointsPerDraw) { - update.update(name, type, pointsPerWin, pointsPerLoss, pointsPerDraw, id); - this.name = name; - this.type = type; - this.pointsPerWin = pointsPerWin; - this.pointsPerLoss = pointsPerLoss; - this.pointsPerDraw = pointsPerDraw; - } - - public boolean needsTieBreak() { - return calculatePoints().values().stream().sorted().limit(2).distinct().count() < 2; - } - - public void delete() { - delete.update(id); - } - - public static enum EventGroupType { - GROUP_STAGE, - ELIMINATION_STAGE - } -} diff --git a/CommonCore/SQL/src/de/steamwar/sql/EventGroup.kt b/CommonCore/SQL/src/de/steamwar/sql/EventGroup.kt new file mode 100644 index 00000000..5fb01f30 --- /dev/null +++ b/CommonCore/SQL/src/de/steamwar/sql/EventGroup.kt @@ -0,0 +1,153 @@ +/* + * 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 . + */ + +package de.steamwar.sql + +import de.steamwar.sql.internal.useDb +import org.jetbrains.exposed.v1.core.dao.id.EntityID +import org.jetbrains.exposed.v1.core.dao.id.IntIdTable +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.dao.IntEntity +import org.jetbrains.exposed.v1.dao.IntEntityClass +import java.util.* + +object EventGroupTable : IntIdTable("EventGroup", "Id") { + val event = reference("EventID", EventTable) + val name = varchar("Name", 64) + val type = enumeration("Type", EventGroup.EventGroupType::class) + val pointsPerWin = integer("PointsPerWin").default(3) + val pointsPerLoss = integer("PointsPerLoss").default(0) + val pointsPerDraw = integer("PointsPerDraw").default(1) +} + +class EventGroup(id: EntityID) : IntEntity(id) { + companion object : IntEntityClass(EventGroupTable) { + + @JvmStatic + fun get(event: Event) = useDb { find { EventGroupTable.event eq event.id }.toList() } + + @JvmStatic + fun byId(groupId: Int) = useDb { Optional.ofNullable(findById(groupId)) } + + @JvmStatic + fun create(event: Event, name: String, type: EventGroupType) = useDb { + new { + this.eventID = event.id.value + this.groupName = name + this.groupType = type + } + } + } + + var eventID by EventGroupTable.event.transform({ EntityID(it, EventTable) }, { it.value }) + private set + private var groupName by EventGroupTable.name + private var groupType by EventGroupTable.type + private var groupPointsPerWin by EventGroupTable.pointsPerWin + private var groupPointsPerLoss by EventGroupTable.pointsPerLoss + private var groupPointsPerDraw by EventGroupTable.pointsPerDraw + val fights by lazy { useDb { EventFight.find { EventFightTable.groupId eq id }.toList() } } + val teamsId by lazy { fights.flatMap { listOf(it.teamBlue, it.teamRed) }.toSet() } + + var name: String + get() = groupName + set(value) { + groupName = value + } + var type: EventGroupType + get() = groupType + set(value) { + groupType = value + } + var pointsPerWin: Int + get() = groupPointsPerWin + set(value) { + groupPointsPerWin = value + } + var pointsPerLoss: Int + get() = groupPointsPerLoss + set(value) { + groupPointsPerLoss = value + } + var pointsPerDraw: Int + get() = groupPointsPerDraw + set(value) { + groupPointsPerDraw = value + } + val dependents by lazy { EventRelation.getGroupRelations(this) } + val lastFight by lazy { Optional.ofNullable(fights.maxByOrNull { it.startTime }) } + + fun getId() = id.value + + private var points: Map? = null + + fun calculatePoints(): Map { + if (points == null) { + val p: MutableMap = teamsId.associateWith { 0 }.toMutableMap() + + for (fight in fights) { + var blueTeamAdd = 0 + var redTeamAdd = 0 + + if (!fight.hasFinished()) { + continue + } + + when (fight.ergebnis) { + 1 -> { + blueTeamAdd += pointsPerWin + redTeamAdd += pointsPerLoss + } + 2 -> { + blueTeamAdd += pointsPerLoss + redTeamAdd += pointsPerWin + } + 0 -> if (fight.fight != null) { + blueTeamAdd += pointsPerDraw + redTeamAdd += pointsPerDraw + } + } + + p[fight.teamBlue] = p[fight.teamBlue]?.plus(blueTeamAdd) ?: blueTeamAdd + p[fight.teamRed] = p[fight.teamRed]?.plus(redTeamAdd) ?: redTeamAdd + } + + return p.mapKeys { Team.byId(it.key) }.also { points = it } + } else { + return points!! + } + } + + fun needsTieBreak() = calculatePoints().values.let { it.size == it.toSet().size } + + fun update(name: String, type: EventGroupType, pointsPerWin: Int, pointsPerLoss: Int, pointsPerDraw: Int) = useDb { + this@EventGroup.name = name + this@EventGroup.type = type + this@EventGroup.pointsPerWin = pointsPerWin + this@EventGroup.pointsPerLoss = pointsPerLoss + this@EventGroup.pointsPerDraw = pointsPerDraw + } + + override fun delete() = useDb { super.delete() } + + enum class EventGroupType { + GROUP_STAGE, + ELIMINATION_STAGE + } +} \ No newline at end of file diff --git a/CommonCore/SQL/src/de/steamwar/sql/EventRelation.java b/CommonCore/SQL/src/de/steamwar/sql/EventRelation.java deleted file mode 100644 index 539908de..00000000 --- a/CommonCore/SQL/src/de/steamwar/sql/EventRelation.java +++ /dev/null @@ -1,192 +0,0 @@ -/* - * 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 . - */ - -package de.steamwar.sql; - -import de.steamwar.sql.internal.*; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.Setter; - -import java.util.Comparator; -import java.util.List; -import java.util.Map; -import java.util.Optional; - -@AllArgsConstructor -@Getter -@Setter -public class EventRelation { - - static { - SqlTypeMapper.ordinalEnumMapper(FightTeam.class); - SqlTypeMapper.ordinalEnumMapper(FromType.class); - } - - private static final Table table = new Table<>(EventRelation.class); - - private static final SelectStatement get = new SelectStatement<>(table, "SELECT * FROM EventRelation WHERE FromType = ? AND FromId = ?"); - private static final SelectStatement byId = new SelectStatement<>(table, "SELECT * FROM EventRelation WHERE id = ?"); - private static final SelectStatement byEvent = new SelectStatement<>(table, "SELECT ER.* FROM EventRelation ER JOIN EventFight EF ON EF.fightId = ER.fightId WHERE EF.EventID = ?"); - private static final Statement insert = table.insertFields(true, "fightId", "fightTeam", "fromType", "fromId", "fromPlace"); - private static final Statement update = table.update(Table.PRIMARY, "fromType", "fromId", "fromPlace"); - private static final Statement updateTeam = table.update(Table.PRIMARY, "fightTeam"); - private static final Statement delete = table.delete(Table.PRIMARY); - - public static List get(Event event) { - return byEvent.listSelect(event.getEventID()); - } - - public static EventRelation get(int id) { - return byId.select(id); - } - - public static List getFightRelations(EventFight fight) { - return get.listSelect(FromType.FIGHT, fight.getFightID()); - } - - public static List getGroupRelations(EventGroup group) { - return get.listSelect(FromType.GROUP, group.getId()); - } - - public static EventRelation create(EventFight fight, FightTeam fightTeam, FromType fromType, int fromId, int fromPlace) { - int id = insert.insertGetKey(fight.getFightID(), fightTeam, fromType, fromId, fromPlace); - return get(id); - } - - @Field(keys = Table.PRIMARY) - private final int id; - - @Field - private int fightId; - - @Field - private FightTeam fightTeam; - - @Field - private FromType fromType; - - @Field - private int fromId; - - @Field - private int fromPlace; - - public EventFight getFight() { - return EventFight.get(fightId); - } - - public Optional getFromFight() { - if(fromType == FromType.FIGHT) { - return Optional.of(EventFight.get(fromId)); - } else { - return Optional.empty(); - } - } - - public Optional getFromGroup() { - if(fromType == FromType.GROUP) { - return EventGroup.get(fromId); - } else { - return Optional.empty(); - } - } - - public void delete() { - delete.update(id); - } - - public void setUpdateTeam(FightTeam team) { - updateTeam.update(id, team); - this.fightTeam = team; - } - - public void setFromFight(EventFight fight, int place) { - setFrom(fight.getFightID(), place, FromType.FIGHT); - } - - public void setFromGroup(EventGroup group, int place) { - setFrom(group.getId(), place, FromType.GROUP); - } - - private void setFrom(int fightId, int place, FromType type) { - update.update(type, fightId, place, id); - this.fromType = type; - this.fromId = fightId; - this.fromPlace = place; - } - - public Optional getAdvancingTeam() { - if (fromType == FromType.FIGHT) { - if (fromPlace == 0) { - return getFromFight().flatMap(EventFight::getWinner); - } else { - return getFromFight().flatMap(EventFight::getLosser); - } - } else if (fromType == FromType.GROUP) { - return getFromGroup().map(EventGroup::calculatePoints) - .flatMap(points -> points.entrySet().stream() - .sorted(Map.Entry.comparingByValue().reversed()) - .skip(fromPlace) - .findFirst() - .map(Map.Entry::getKey)); - } else { - return Optional.empty(); - } - } - - public boolean apply() { - Optional team = getAdvancingTeam().map(Team::getTeamId); - if(!team.isPresent()) - return false; - - EventFight fight = getFight(); - if(fightTeam == FightTeam.RED) { - fight.update( - fight.getStartTime(), - fight.getSpielmodus(), - fight.getMap(), - fight.getTeamBlue(), - team.get(), - fight.getSpectatePort() - ); - } else { - fight.update( - fight.getStartTime(), - fight.getSpielmodus(), - fight.getMap(), - team.get(), - fight.getTeamRed(), - fight.getSpectatePort() - ); - } - - return true; - } - - public static enum FightTeam { - BLUE, - RED - } - - public static enum FromType { - FIGHT, - GROUP - } -} diff --git a/CommonCore/SQL/src/de/steamwar/sql/EventRelation.kt b/CommonCore/SQL/src/de/steamwar/sql/EventRelation.kt new file mode 100644 index 00000000..c1f0e53c --- /dev/null +++ b/CommonCore/SQL/src/de/steamwar/sql/EventRelation.kt @@ -0,0 +1,149 @@ +/* + * 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 . + */ + +package de.steamwar.sql + +import de.steamwar.sql.internal.useDb +import org.jetbrains.exposed.v1.core.and +import org.jetbrains.exposed.v1.core.dao.id.EntityID +import org.jetbrains.exposed.v1.core.dao.id.IntIdTable +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.dao.IntEntity +import org.jetbrains.exposed.v1.dao.IntEntityClass +import org.jetbrains.exposed.v1.jdbc.select + +object EventRelationTable : IntIdTable("EventRelation") { + val fightId = reference("FightId", EventFightTable) + val fightTeam = enumeration("FightTeam", EventRelation.FightTeam::class) + val fromType = enumeration("FromType", EventRelation.FromType::class) + val fromId = integer("FromId") + val fromPlace = integer("FromPlace") +} + +class EventRelation(id: EntityID) : IntEntity(id) { + companion object : IntEntityClass(EventRelationTable) { + @JvmStatic + fun get(event: Event) = useDb { + EventRelationTable.innerJoin(EventFightTable) + .select(EventRelationTable.columns) + .where { EventFightTable.eventId eq event.id } + .map { wrapRow(it) } + } + + @JvmStatic + fun byId(id: Int) = useDb { findById(id) } + + @JvmStatic + fun getFightRelations(fight: EventFight) = + useDb { find { (EventRelationTable.fromId eq fight.id.value) and (EventRelationTable.fromType eq FromType.FIGHT) } } + + @JvmStatic + fun getGroupRelations(group: EventGroup) = + useDb { find { (EventRelationTable.fromId eq group.id.value) and (EventRelationTable.fromType eq FromType.GROUP) } } + + @JvmStatic + fun create(fight: EventFight, fightTeam: FightTeam, fromType: FromType, fromId: Int, fromPlace: Int) = useDb { + new { + this.fightEntityId = fight.id + this.fightTeam = fightTeam + this.fromType = fromType + this.fromId = fromId + this.fromPlace = fromPlace + } + } + } + + var fightEntityId by EventRelationTable.fightId + var fightId: Int + get() = fightEntityId.value + set(value) = useDb { fightEntityId = EntityID(value, EventFightTable) } + val fight by lazy { useDb { EventFight[fightEntityId] } } + var fightTeam by EventRelationTable.fightTeam + private set + var fromType by EventRelationTable.fromType + private set + var fromId by EventRelationTable.fromId + private set + + var fromFightId: Int? + get() = if (fromType == FromType.FIGHT) fromId else null + set(value) = useDb { + fromType = FromType.FIGHT + fromId = value!! + } + val fromFight: EventFight? + get() = fromFightId?.let { useDb { EventFight[it] } } + var fromGroupId: Int? + get() = if (fromType == FromType.GROUP) fromId else null + set(value) = useDb { + fromType = FromType.GROUP + fromId = value!! + } + val fromGroup: EventGroup? + get() = fromGroupId?.let { useDb { EventGroup[it] } } + var fromPlace by EventRelationTable.fromPlace + private set + + fun getId() = id.value + fun setUpdateTeam(team: FightTeam) = useDb { + fightTeam = team + } + + fun setFromFight(fight: EventFight, place: Int) = useDb { + fromType = FromType.FIGHT + fromId = fight.id.value + fromPlace = place + } + + fun setFromGroup(group: EventGroup, place: Int) = useDb { + fromType = FromType.GROUP + fromId = group.id.value + fromPlace = place + } + + fun getAdvancingTeam(): Team? = when(fromType) { + FromType.FIGHT -> if (fromPlace == 0) fromFight?.winner else fromFight?.losser + FromType.GROUP -> fromGroup?.calculatePoints()?.toList()?.sortedBy { (_, v) -> v }?.reversed()?.elementAt(fromPlace)?.first + } + + fun apply(): Boolean { + val team = getAdvancingTeam() ?: return false + + useDb { + when(fightTeam) { + FightTeam.BLUE -> fight.teamBlue = team.teamId + FightTeam.RED -> fight.teamRed = team.teamId + } + } + + return true + } + + override fun delete() = useDb { + super.delete() + } + + enum class FightTeam { + BLUE, RED + } + + enum class FromType { + FIGHT, GROUP + } +} \ No newline at end of file diff --git a/CommonCore/SQL/src/de/steamwar/sql/Fight.java b/CommonCore/SQL/src/de/steamwar/sql/Fight.java deleted file mode 100644 index b1f122b2..00000000 --- a/CommonCore/SQL/src/de/steamwar/sql/Fight.java +++ /dev/null @@ -1,134 +0,0 @@ -/* - * 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 . - */ - -package de.steamwar.sql; - -import de.steamwar.sql.internal.Field; -import de.steamwar.sql.internal.SelectStatement; -import de.steamwar.sql.internal.Statement; -import de.steamwar.sql.internal.Table; -import lombok.AllArgsConstructor; -import lombok.Getter; - -import java.sql.Timestamp; -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Collectors; - -@AllArgsConstructor -public class Fight { - - private static final Table table = new Table<>(Fight.class); - private static final SelectStatement getPage = new SelectStatement<>(table, "SELECT f.*, (b.NodeId IS NULL OR b.Config & 2) AND (r.NodeId IS NULL OR r.Config & 2) AS ReplayAllowed FROM Fight f LEFT OUTER JOIN SchematicNode b ON f.BlueSchem = b.NodeId LEFT OUTER JOIN SchematicNode r ON f.RedSchem = r.NodeId ORDER BY FightID DESC LIMIT ?, ?"); - private static final SelectStatement getById = new SelectStatement<>(table, "SELECT f.*, (b.NodeId IS NULL OR b.Config & 2) AND (r.NodeId IS NULL OR r.Config & 2) AS ReplayAllowed FROM Fight f LEFT OUTER JOIN SchematicNode b ON f.BlueSchem = b.NodeId LEFT OUTER JOIN SchematicNode r ON f.RedSchem = r.NodeId WHERE FightId = ?"); - private static final Statement insert = table.insertFields(true, "GameMode", "Server", "StartTime", "Duration", "BlueLeader", "RedLeader", "BlueSchem", "RedSchem", "Win", "WinCondition"); - private static final Statement updateReplayAvailable = table.update(Table.PRIMARY, "ReplayAvailable"); - - public static List getPage(int page, int elementsPerPage) { - List fights = getPage.listSelect(page * elementsPerPage, elementsPerPage); - - List fightPlayers = FightPlayer.batchGet(fights.stream().map(f -> f.fightID)); - for(Fight fight : fights) { - fight.initPlayers(fightPlayers); - } - - SteamwarUser.batchCache(fightPlayers.stream().map(FightPlayer::getUserID).collect(Collectors.toSet())); - return fights; - } - - public static Fight getById(int fightID) { - return getById.select(fightID); - } - - public static int create(String gamemode, String server, Timestamp starttime, int duration, int blueleader, int redleader, Integer blueschem, Integer redschem, int win, String wincondition){ - return insert.insertGetKey(gamemode, server, starttime, duration, blueleader, redleader, blueschem, redschem, win, wincondition); - } - - public static void markReplayAvailable(int fightID) { - updateReplayAvailable.update(true, fightID); - } - - @Getter - @Field(keys = {Table.PRIMARY}, autoincrement = true) - private final int fightID; - @Field - private final String gameMode; - @Getter - @Field - private final String server; - @Getter - @Field - private final Timestamp startTime; - @Field - private final int duration; - @Field - private final int blueLeader; - @Field - private final int redLeader; - @Field(nullable = true) - private final Integer blueSchem; - @Field(nullable = true) - private final Integer redSchem; - @Getter - @Field - private final int win; - @Field - private final String wincondition; - @Field - private final boolean replayAvailable; - @Field // Virtual field for easy select - private final boolean replayAllowed; - - @Getter - private final List bluePlayers = new ArrayList<>(); - @Getter - private final List redPlayers = new ArrayList<>(); - - public SchematicType getSchemType() { - return SchematicType.fromDB(gameMode); - } - - public SteamwarUser getBlueLeader() { - return SteamwarUser.get(blueLeader); - } - - public SteamwarUser getRedLeader() { - return SteamwarUser.get(redLeader); - } - - public boolean replayAllowed() { - return replayExists() && replayAllowed; - } - - public boolean replayExists() { - return getSchemType() != null && replayAvailable; - } - - private void initPlayers(List fightPlayers) { - for(FightPlayer fp : fightPlayers) { - if(fp.getFightID() != fightID) - continue; - - if(fp.getTeam() == 1) - bluePlayers.add(fp); - else - redPlayers.add(fp); - } - } -} diff --git a/CommonCore/SQL/src/de/steamwar/sql/Fight.kt b/CommonCore/SQL/src/de/steamwar/sql/Fight.kt new file mode 100644 index 00000000..d64d7583 --- /dev/null +++ b/CommonCore/SQL/src/de/steamwar/sql/Fight.kt @@ -0,0 +1,162 @@ +/* + * 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 . + */ + +package de.steamwar.sql + +import de.steamwar.sql.internal.useDb +import org.jetbrains.exposed.v1.core.IntegerColumnType +import org.jetbrains.exposed.v1.core.ReferenceOption +import org.jetbrains.exposed.v1.core.SortOrder +import org.jetbrains.exposed.v1.core.dao.id.EntityID +import org.jetbrains.exposed.v1.core.dao.id.IntIdTable +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.dao.IntEntity +import org.jetbrains.exposed.v1.dao.IntEntityClass +import org.jetbrains.exposed.v1.javatime.timestamp +import org.jetbrains.exposed.v1.jdbc.insertAndGetId +import org.jetbrains.exposed.v1.jdbc.update +import java.sql.Timestamp + +object FightTable : IntIdTable("Fight", "FightId") { + val gamemode = varchar("Gamemode", 30) + val server = text("Server") + val startTime = timestamp("StartTime") + val duration = integer("Duration") + val blueLeader = reference("BlueLeader", SteamwarUserTable) + val redLeader = reference("RedLeader", SteamwarUserTable) + val blueSchem = optReference("BlueSchem", SchematicNodeTable, onDelete = ReferenceOption.SET_NULL) + val redSchem = optReference("RedSchem", SchematicNodeTable, onDelete = ReferenceOption.SET_NULL) + val win = enumeration("Win", Fight.WinningTeam::class) + val winCondition = varchar("WinCondition", 100) + val replayAvailable = bool("ReplayAvailable") +} + +class Fight(id: EntityID) : IntEntity(id) { + companion object : IntEntityClass(FightTable) { + @JvmStatic + fun getById(id: Int) = useDb { get(id) } + + @JvmStatic + fun create( + gamemode: String, + server: String, + starttime: Timestamp, + duration: Int, + blueleader: Int, + redleader: Int, + blueschem: Int?, + redschem: Int?, + win: Int, + wincondition: String + ): Int = useDb { + FightTable.insertAndGetId { + it[FightTable.gamemode] = gamemode + it[FightTable.server] = server + it[FightTable.startTime] = starttime.toInstant() + it[FightTable.duration] = duration + it[FightTable.blueLeader] = EntityID(blueleader, SteamwarUserTable) + it[FightTable.redLeader] = EntityID(redleader, SteamwarUserTable) + it[FightTable.blueSchem] = blueschem?.let { EntityID(it, SchematicNodeTable) } + it[FightTable.redSchem] = redschem?.let { EntityID(it, SchematicNodeTable) } + it[FightTable.win] = WinningTeam.entries[win] + it[FightTable.winCondition] = wincondition + }.value + } + + @JvmStatic + fun getPage(page: Int, pageSize: Int): List = useDb { + val fights = all().orderBy(FightTable.startTime to SortOrder.DESC).limit(pageSize).offset((pageSize * page).toLong()) + + val fightPlayer = FightPlayer.batchGet(fights.map { it.id.value }) + for (fight in fights) { + fight.initPlayers(fightPlayer) + } + + SteamwarUser.batchCache(fightPlayer.map { it.userID }.toMutableSet()) + fights.toList() + } + + @JvmStatic + fun markReplayAvailable(id: Int) = useDb { + FightTable.update({ FightTable.id eq id }) { + it[replayAvailable] = true + } + } + } + + val fightID by FightTable.id.transform({ EntityID(it, FightTable) }, { it.value }) + val gameMode by FightTable.gamemode + val server by FightTable.server + val startTime by FightTable.startTime.transform({ it.toInstant() }, { Timestamp.from(it) }) + val duration by FightTable.duration + val blueLeaderId by FightTable.blueLeader + val blueLeader by lazy { useDb { SteamwarUser[blueLeaderId] } } + val redLeaderId by FightTable.redLeader + val redLeader by lazy { useDb { SteamwarUser[redLeaderId] } } + val blueSchem by FightTable.blueSchem + val redSchem by FightTable.redSchem + val winner by FightTable.win + val win: Int + get() = winner.ordinal + val winCondition by FightTable.winCondition + val replayAvailable by FightTable.replayAvailable + + val schemType: SchematicType? + get() = SchematicType.fromDB(gameMode) + + val replayAllowed by lazy { + replayExists() && useDb { + exec( + "SELECT (b.NodeId IS NULL OR b.Config & 2) AND (r.NodeId IS NULL OR r.Config & 2) AS ReplayAllowed FROM Fight f LEFT OUTER JOIN SchematicNode b ON f.BlueSchem = b.NodeId LEFT OUTER JOIN SchematicNode r ON f.RedSchem = r.NodeId WHERE FightId = ?", + args = listOf(IntegerColumnType() to this@Fight.id.value) + ) { + if (it.next()) { + it.getBoolean("ReplayAllowed") + } else { + false + } + } ?: false + } + } + + fun replayExists() = schemType != null && replayAvailable + fun replayAllowed() = replayAllowed + + lateinit var bluePlayers: List + lateinit var redPlayers: List + + private fun initPlayers(fightPlayers: List) { + val blue = mutableListOf() + val red = mutableListOf() + + for (player in fightPlayers.filter { it.fightID == id.value }) { + if (player.team == 1) + blue.add(player) + else + red.add(player) + } + + bluePlayers = blue + redPlayers = red + } + + enum class WinningTeam { + NONE, BLUE, RED; + } +} \ No newline at end of file diff --git a/CommonCore/SQL/src/de/steamwar/sql/FightPlayer.java b/CommonCore/SQL/src/de/steamwar/sql/FightPlayer.java deleted file mode 100644 index c45ec806..00000000 --- a/CommonCore/SQL/src/de/steamwar/sql/FightPlayer.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * 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 . - */ - -package de.steamwar.sql; - -import de.steamwar.sql.internal.Field; -import de.steamwar.sql.internal.SelectStatement; -import de.steamwar.sql.internal.Statement; -import de.steamwar.sql.internal.Table; -import lombok.AllArgsConstructor; -import lombok.Getter; - -import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -@AllArgsConstructor -public class FightPlayer { - - private static final Table table = new Table<>(FightPlayer.class); - private static final Statement create = table.insertAll(); - private static final SelectStatement batchGet = new SelectStatement<>(table, "SELECT * FROM FightPlayer WHERE FightID IN ?"); - - @Getter - @Field(keys = {Table.PRIMARY}) - private final int fightID; - @Getter - @Field(keys = {Table.PRIMARY}) - private final int userID; - @Getter - @Field - private final int team; - @Field - private final String kit; - @Field - private final int kills; - @Field - private final boolean isOut; - - public static void create(int fightID, int userID, boolean blue, String kit, int kills, boolean isOut) { - create.update(fightID, userID, blue ? 1 : 2, kit, kills, isOut); - } - - public static List batchGet(Stream fightIds) { - try (SelectStatement batch = new SelectStatement<>(table, "SELECT * FROM FightPlayer WHERE FightID IN (" + fightIds.map(Object::toString).collect(Collectors.joining(", ")) + ")")) { - return batch.listSelect(); - } - } -} diff --git a/CommonCore/SQL/src/de/steamwar/sql/FightPlayer.kt b/CommonCore/SQL/src/de/steamwar/sql/FightPlayer.kt new file mode 100644 index 00000000..d0238c96 --- /dev/null +++ b/CommonCore/SQL/src/de/steamwar/sql/FightPlayer.kt @@ -0,0 +1,80 @@ +/* + * 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 . + */ + +package de.steamwar.sql + +import de.steamwar.sql.internal.useDb +import org.jetbrains.exposed.v1.core.dao.id.CompositeID +import org.jetbrains.exposed.v1.core.dao.id.CompositeIdTable +import org.jetbrains.exposed.v1.core.dao.id.EntityID +import org.jetbrains.exposed.v1.core.inList +import org.jetbrains.exposed.v1.dao.CompositeEntity +import org.jetbrains.exposed.v1.dao.CompositeEntityClass +import org.jetbrains.exposed.v1.jdbc.insertIgnore + +object FightPlayerTable : CompositeIdTable("FightPlayer") { + val fightId = reference("FightId", FightTable) + val userId = reference("UserId", SteamwarUserTable) + val team = integer("Team") + val kit = varchar("Kit", 64) + val kills = integer("Kills") + val out = bool("IsOut") + + init { + addIdColumn(fightId) + addIdColumn(userId) + } +} + +class FightPlayer(id: EntityID) : CompositeEntity(id) { + companion object : CompositeEntityClass(FightPlayerTable) { + @JvmStatic + fun create( + fightId: Int, + userId: Int, + blue: Boolean, + kit: String, + kills: Int, + out: Boolean + ) = useDb { + FightPlayerTable.insertIgnore { + it[this.fightId] = fightId + it[this.userId] = userId + it[this.team] = if (blue) 1 else 2 + it[this.kit] = kit + it[this.kills] = kills + it[this.out] = out + } + } + + @JvmStatic + fun batchGet(fightIds: List) = useDb { + find { FightPlayerTable.fightId inList fightIds.toList() }.toList() + } + } + + val fightID by FightPlayerTable.fightId.transform({ EntityID(it, FightTable) }, { it.value }) + val userID by FightPlayerTable.userId.transform({ EntityID(it, SteamwarUserTable) }, { it.value }) + val team by FightPlayerTable.team + val kit by FightPlayerTable.kit + val kills by FightPlayerTable.kills + val out by FightPlayerTable.out + + fun isOut() = out +} \ No newline at end of file diff --git a/CommonCore/SQL/src/de/steamwar/sql/GameModeConfig.java b/CommonCore/SQL/src/de/steamwar/sql/GameModeConfig.java index c7e9ecf8..2a737508 100644 --- a/CommonCore/SQL/src/de/steamwar/sql/GameModeConfig.java +++ b/CommonCore/SQL/src/de/steamwar/sql/GameModeConfig.java @@ -35,11 +35,19 @@ import java.util.stream.Collectors; public final class GameModeConfig { public static final Function ToString = Function.identity(); - public static final Function ToStaticWarGear = __ -> "WarGear"; - public static final Function ToInternalName = file -> file != null ? file.getName().replace(".yml", "") : "WarGear"; + public static final Function ToStaticWarGear = GameModeConfig::constWarGear; + public static final Function ToInternalName = GameModeConfig::internalName; public static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("dd.MM.yyyy HH:mm"); private static final Random random = new Random(); + private static String constWarGear(File f) { + return "WarGear"; + } + + private static String internalName(File f) { + return f != null ? f.getName().replace(".yml", "") : constWarGear(null); + } + private static final Map> byFileName; private static final Map> byGameName; private static final Map> bySchematicType; @@ -63,11 +71,15 @@ public final class GameModeConfig { } private static final Field Schematic_TypeField; + private static final Field Schematic_SubTypesField; static { try { Schematic_TypeField = SchematicConfig.class.getDeclaredField("Type"); Schematic_TypeField.setAccessible(true); + + Schematic_SubTypesField = SchematicConfig.class.getDeclaredField("SubTypes"); + Schematic_SubTypesField.setAccessible(true); } catch (NoSuchFieldException e) { throw new SecurityException(e.getMessage(), e); } @@ -77,6 +89,18 @@ public final class GameModeConfig { bySchematicType = new HashMap<>(); SchematicType.values(); DEFAULTS = SQLWrapper.impl.loadGameModeConfig(null); + + byFileName.values().forEach(gameModeConfig -> { + List subTypes = Collections.unmodifiableList(gameModeConfig.Schematic.SubTypesStrings.stream() + .map(SchematicType::fromDB) + .filter(Objects::nonNull) + .collect(Collectors.toList())); + try { + Schematic_SubTypesField.set(gameModeConfig.Schematic, subTypes); + } catch (IllegalAccessException e) { + throw new SecurityException(e.getMessage(), e); + } + }); } public final boolean loaded; @@ -516,6 +540,8 @@ public final class GameModeConfig { */ public final SchematicType Type; + private final List SubTypesStrings; + /** * The schematic types that are also allowed to be chosen in this arena */ @@ -605,6 +631,13 @@ public final class GameModeConfig { */ public final int MaxDispenserItems; + /** + * Maximal blast resistance for the blocks + * + * @implSpec {@code Double.MAX_VALUE} by default + */ + public final double MaxBlastResistance; + /** * Maximal blast resistance for the design blocks * @@ -623,6 +656,7 @@ public final class GameModeConfig { Size = new SizeConfig(loader.with("Size")); Inset = new InsetConfig(loader.with("Inset")); Type = loader.getSchematicType("Type", "Normal"); + SubTypesStrings = loader.getStringList("SubTypes"); SubTypes = loader.getSchematicTypeList("SubTypes"); Shortcut = loader.getString("Shortcut", ""); Material = loader.getMaterial("Material", "STONE_BUTTON"); @@ -636,6 +670,7 @@ public final class GameModeConfig { UnlimitedPrepare = loader.getBoolean("UnlimitedPrepare", false); MaxBlocks = loader.getInt("MaxBlocks", 0); MaxDispenserItems = loader.getInt("MaxDispenserItems", 128); + MaxBlastResistance = loader.getDouble("MaxBlastResistance", Double.MAX_VALUE); MaxDesignBlastResistance = loader.getDouble("MaxDesignBlastResistance", Double.MAX_VALUE); Map, Integer> Limited = new HashMap<>(); @@ -650,6 +685,10 @@ public final class GameModeConfig { Limited.put(Collections.unmodifiableSet(materials.stream().map(String::toUpperCase).map(loader.materialMapper).collect(Collectors.toSet())), amount); } } + SQLWrapper.impl.getMaterialWithGreaterBlastResistance(MaxBlastResistance).forEach(material -> { + if (Limited.entrySet().stream().anyMatch(entry -> entry.getKey().contains(material))) return; + Limited.put(Collections.singleton((M) material), 0); + }); this.Limited = Collections.unmodifiableMap(Limited); } diff --git a/CommonCore/SQL/src/de/steamwar/sql/IgnoreSystem.java b/CommonCore/SQL/src/de/steamwar/sql/IgnoreSystem.java deleted file mode 100644 index c0c38cc8..00000000 --- a/CommonCore/SQL/src/de/steamwar/sql/IgnoreSystem.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * 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 . - */ - -package de.steamwar.sql; - -import de.steamwar.sql.internal.Field; -import de.steamwar.sql.internal.SelectStatement; -import de.steamwar.sql.internal.Statement; -import de.steamwar.sql.internal.Table; -import lombok.AllArgsConstructor; - -import java.sql.ResultSet; -import java.util.UUID; - -@AllArgsConstructor -public class IgnoreSystem { - - private static final Table table = new Table<>(IgnoreSystem.class, "IgnoredPlayers"); - private static final SelectStatement select = table.select(Table.PRIMARY); - private static final Statement insert = table.insertAll(); - private static final Statement delete = table.delete(Table.PRIMARY); - - @Field(keys = {Table.PRIMARY}) - private final int ignorer; - @Field(keys = {Table.PRIMARY}) - private final int ignored; - - public static boolean isIgnored(UUID ignorer, UUID ignored){ - return isIgnored(SteamwarUser.get(ignorer), SteamwarUser.get(ignored)); - } - - public static boolean isIgnored(SteamwarUser ignorer, SteamwarUser ignored) { - return select.select(ResultSet::next, ignorer.getId(), ignored.getId()); - } - - public static void ignore(SteamwarUser ignorer, SteamwarUser ignored) { - insert.update(ignorer.getId(), ignored.getId()); - } - - public static void unIgnore(SteamwarUser ignorer, SteamwarUser ignored) { - delete.update(ignorer.getId(), ignored.getId()); - } -} diff --git a/CommonCore/SQL/src/de/steamwar/sql/IgnoreSystem.kt b/CommonCore/SQL/src/de/steamwar/sql/IgnoreSystem.kt new file mode 100644 index 00000000..9df3a8cf --- /dev/null +++ b/CommonCore/SQL/src/de/steamwar/sql/IgnoreSystem.kt @@ -0,0 +1,74 @@ +/* + * 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 . + */ + +package de.steamwar.sql + +import de.steamwar.sql.internal.useDb +import org.jetbrains.exposed.v1.core.and +import org.jetbrains.exposed.v1.core.dao.id.CompositeID +import org.jetbrains.exposed.v1.core.dao.id.CompositeIdTable +import org.jetbrains.exposed.v1.core.dao.id.EntityID +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.dao.CompositeEntity +import org.jetbrains.exposed.v1.dao.CompositeEntityClass +import java.util.* + +object IgnoreSystemTable: CompositeIdTable("IgnoredPlayers") { + val ignorer = reference("Ignorer", SteamwarUserTable) + val ignored = reference("Ignored", SteamwarUserTable) + + override val primaryKey = PrimaryKey(ignorer, ignored) + + init { + addIdColumn(ignorer) + addIdColumn(ignored) + } +} + +class IgnoreSystem(id: EntityID) : CompositeEntity(id) { + var ignorer by IgnoreSystemTable.ignorer + var ignored by IgnoreSystemTable.ignored + + companion object : CompositeEntityClass(IgnoreSystemTable) { + @JvmStatic + fun isIgnored(ignorer: UUID, ignored: UUID) = useDb { isIgnored(SteamwarUser.get(ignorer)!!, SteamwarUser.get(ignored)!!) } + + @JvmStatic + fun isIgnored(ignorer: SteamwarUser, ignored: SteamwarUser) = useDb { + find { + (IgnoreSystemTable.ignorer eq ignorer.id) and (IgnoreSystemTable.ignored eq ignored.id) + }.firstOrNull() != null + } + + @JvmStatic + fun ignore(ignorer: SteamwarUser, ignored: SteamwarUser) = useDb { + new { + this.ignorer = ignorer.id + this.ignored = ignored.id + } + } + + @JvmStatic + fun unIgnore(ignorer: SteamwarUser, ignored: SteamwarUser) = useDb { + find { + (IgnoreSystemTable.ignorer eq ignorer.id) and (IgnoreSystemTable.ignored eq ignored.id) + }.firstOrNull()?.delete() + } + } +} \ No newline at end of file diff --git a/CommonCore/SQL/src/de/steamwar/sql/Mod.java b/CommonCore/SQL/src/de/steamwar/sql/Mod.java deleted file mode 100644 index 4d8fcd9d..00000000 --- a/CommonCore/SQL/src/de/steamwar/sql/Mod.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * 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 . - */ - -package de.steamwar.sql; - -import de.steamwar.sql.internal.*; -import lombok.AllArgsConstructor; -import lombok.Getter; - -import java.util.List; - -@AllArgsConstructor -public class Mod { - - static { - SqlTypeMapper.ordinalEnumMapper(Platform.class); - SqlTypeMapper.ordinalEnumMapper(ModType.class); - } - - private static final Table table = new Table<>(Mod.class, "Mods"); - private static final SelectStatement get = table.select(Table.PRIMARY); - private static final SelectStatement findFirst = new SelectStatement<>(table, "SELECT * FROM Mods WHERE ModType = 0 LIMIT 1"); - private static final SelectStatement getPageOfType = new SelectStatement<>(table, "SELECT * FROM Mods WHERE ModType = ? ORDER BY ModName DESC LIMIT ?, ?"); - private static final Statement insert = table.insert(Table.PRIMARY); - private static final Statement set = table.update(Table.PRIMARY, "ModType"); - - public static Mod get(String name, Platform platform) { - return get.select(platform, name); - } - - public static Mod getOrCreate(String name, Platform platform) { - Mod mod = get(name, platform); - if(mod != null) - return mod; - - insert.update(platform, name); - return new Mod(platform, name, ModType.UNKLASSIFIED); - } - - public static List getAllModsFiltered(int page, int elementsPerPage, ModType filter) { - return Mod.getPageOfType.listSelect(filter, page * elementsPerPage, elementsPerPage); - } - - public static Mod findFirstMod() { - return findFirst.select(); - } - - @Getter - @Field(keys = {Table.PRIMARY}) - private final Platform platform; - @Getter - @Field(keys = {Table.PRIMARY}) - private final String modName; - @Getter - @Field(def = "0") - private ModType modType; - - public void setModType(ModType modType) { - set.update(modType, platform, modName); - this.modType = modType; - } - - public enum Platform { - FORGE, - LABYMOD, - FABRIC - } - - public enum ModType { - UNKLASSIFIED("7"), - GREEN("a"), - YELLOW("e"), - RED("c"), - YOUTUBER_ONLY("6"); - - ModType(String colorcode) { - this.colorcode = colorcode; - } - private final String colorcode; - - public String getColorCode() { - return colorcode; - } - } -} diff --git a/CommonCore/SQL/src/de/steamwar/sql/Mod.kt b/CommonCore/SQL/src/de/steamwar/sql/Mod.kt new file mode 100644 index 00000000..9e3b4d50 --- /dev/null +++ b/CommonCore/SQL/src/de/steamwar/sql/Mod.kt @@ -0,0 +1,88 @@ +/* + * 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 . + */ + +package de.steamwar.sql + +import de.steamwar.sql.internal.useDb +import org.jetbrains.exposed.v1.core.SortOrder +import org.jetbrains.exposed.v1.core.and +import org.jetbrains.exposed.v1.core.dao.id.CompositeID +import org.jetbrains.exposed.v1.core.dao.id.CompositeIdTable +import org.jetbrains.exposed.v1.core.dao.id.EntityID +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.dao.CompositeEntity +import org.jetbrains.exposed.v1.dao.CompositeEntityClass + +object ModTable : CompositeIdTable("Mods") { + val platform = enumeration("Platform", Mod.Platform::class) + val modName = varchar("ModName", 100) + val modeType = enumerationByName("ModType", 10, Mod.ModType::class) +} + +class Mod(id: EntityID) : CompositeEntity(id) { + companion object : CompositeEntityClass(ModTable) { + @JvmStatic + fun get(modName: String, platform: Platform) = useDb { + find { ModTable.platform eq platform and (ModTable.modName eq modName) }.firstOrNull() + } + + @JvmStatic + fun getOrCreate(modName: String, platform: Platform) = useDb { + get(modName, platform) ?: new { + this.platform = platform + this.modName = modName + this.type = ModType.UNKLASSIFIED + } + } + + @JvmStatic + fun getAllModsFiltered(page: Int, elementsPerPage: Int, filter: ModType) = useDb { + find { ModTable.modeType eq filter }.limit(elementsPerPage).offset((elementsPerPage * page).toLong()) + .orderBy( + ModTable.modName to SortOrder.DESC + ).toList() + } + + @JvmStatic + fun findFirstMod() = useDb { + find { ModTable.modeType eq ModType.UNKLASSIFIED }.limit(1).firstOrNull() + } + } + + var platform by ModTable.platform + var modName by ModTable.modName + private var type by ModTable.modeType + var modType: ModType + get() = type + set(value) = useDb { type = value } + + enum class Platform { + FORGE, + LABYMOD, + FABRIC + } + + enum class ModType(val colorCode: String) { + UNKLASSIFIED("7"), + GREEN("a"), + YELLOW("e"), + RED("c"), + YOUTUBER_ONLY("6"); + } +} \ No newline at end of file diff --git a/CommonCore/SQL/src/de/steamwar/sql/NodeData.java b/CommonCore/SQL/src/de/steamwar/sql/NodeData.java deleted file mode 100644 index be0bbc95..00000000 --- a/CommonCore/SQL/src/de/steamwar/sql/NodeData.java +++ /dev/null @@ -1,137 +0,0 @@ -/* - * 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 . - */ - -package de.steamwar.sql; - -import de.steamwar.sql.internal.*; -import lombok.AllArgsConstructor; -import lombok.Getter; - -import javax.swing.plaf.nimbus.State; -import java.io.*; -import java.sql.PreparedStatement; -import java.sql.Timestamp; -import java.time.Instant; -import java.util.List; -import java.util.Optional; -import java.util.zip.GZIPInputStream; - -@AllArgsConstructor -@Getter -public class NodeData { - static { - new SqlTypeMapper<>(PipedInputStream.class, "BLOB", (rs, identifier) -> { throw new SecurityException("PipedInputStream is write only datatype"); }, PreparedStatement::setBinaryStream); - new SqlTypeMapper<>(ByteArrayInputStream.class, "BLOB", (rs, identifier) -> { throw new SecurityException("ByteArrayInputStream is write only datatype"); }, PreparedStatement::setBinaryStream); - new SqlTypeMapper<>(BufferedInputStream.class, "BLOB", (rs, identifier) -> { throw new SecurityException("BufferedInputStream is write only datatype"); }, PreparedStatement::setBinaryStream); - - SqlTypeMapper.ordinalEnumMapper(SchematicFormat.class); - } - - private static final Table table = new Table<>(NodeData.class); - - private static final Statement updateDatabase = new Statement("INSERT INTO NodeData(NodeId, NodeFormat, SchemData) VALUES (?, ?, ?)", true); - private static final Statement selSchemData = new Statement("SELECT SchemData FROM NodeData WHERE NodeId = ? AND CreatedAt = ?"); - private static final Statement delete = table.delete(Table.PRIMARY); - - private static final SelectStatement get = new SelectStatement<>(table, "SELECT NodeId, CreatedAt, NodeFormat FROM NodeData WHERE NodeId = ? ORDER BY CreatedAt "); - private static final Statement getRevisions = new Statement("SELECT COUNT(DISTINCT CreatedAt) as CNT FROM NodeData WHERE NodeId = ?"); - private static final SelectStatement getLatest = new SelectStatement<>(table, "SELECT NodeId, CreatedAt, NodeFormat FROM NodeData WHERE NodeId = ? ORDER BY CreatedAt DESC LIMIT 1"); - - public static NodeData getLatest(SchematicNode node) { - if (node.isDir()) throw new IllegalArgumentException("Node is dir"); - return Optional.ofNullable(getLatest.select(node)).orElseGet(() -> new NodeData(node.getId(), Timestamp.from(Instant.now()), SchematicFormat.MCEDIT)); - } - - public static List get(SchematicNode node) { - return get.listSelect(node); - } - - public static NodeData get(SchematicNode node, int revision) { - return get.listSelect(node).get(revision - 1); - } - - public static int getRevisions(SchematicNode node) { - return getRevisions.select(rs -> { - if (rs.next()) { - return rs.getInt("CNT"); - } else { - return 0; - } - }, node); - } - - public static void saveFromStream(SchematicNode node, InputStream blob, SchematicFormat format) { - updateDatabase.update(node.getId(), format, blob); - } - - @Field(keys = {Table.PRIMARY}) - private final int nodeId; - - @Field(keys = {Table.PRIMARY}) - private Timestamp createdAt; - - @Field - private SchematicFormat nodeFormat; - - public InputStream schemData() throws IOException { - return schemData(true); - } - - public InputStream schemData(boolean decompress) throws IOException { - try { - return selSchemData.select(rs -> { - rs.next(); - InputStream schemData = rs.getBinaryStream("SchemData"); - try { - if(rs.wasNull() || schemData.available() == 0) { - throw new SecurityException("SchemData is null"); - } - if (decompress) { - return new GZIPInputStream(schemData); - } else { - return schemData; - } - } catch (IOException e) { - throw new SecurityException("SchemData is wrong", e); - } - }, nodeId, createdAt); - } catch (Exception e) { - throw new IOException(e); - } - } - - @Deprecated - public void saveFromStream(InputStream blob, SchematicFormat newFormat) { - saveFromStream(SchematicNode.getSchematicNode(nodeId), blob, newFormat); - } - - public void delete() { - delete.update(nodeId, createdAt); - } - - @AllArgsConstructor - @Getter - public enum SchematicFormat { - MCEDIT(".schematic"), - SPONGE_V2(".schem"), - SPONGE_V3(".schem"); - - private final String fileEnding; - } -} diff --git a/CommonCore/SQL/src/de/steamwar/sql/NodeData.kt b/CommonCore/SQL/src/de/steamwar/sql/NodeData.kt new file mode 100644 index 00000000..69552ac0 --- /dev/null +++ b/CommonCore/SQL/src/de/steamwar/sql/NodeData.kt @@ -0,0 +1,100 @@ +/* + * 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 . + */ + +package de.steamwar.sql + +import de.steamwar.sql.internal.useDb +import org.jetbrains.exposed.v1.core.SortOrder +import org.jetbrains.exposed.v1.core.dao.id.CompositeID +import org.jetbrains.exposed.v1.core.dao.id.CompositeIdTable +import org.jetbrains.exposed.v1.core.dao.id.EntityID +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.core.statements.api.ExposedBlob +import org.jetbrains.exposed.v1.dao.CompositeEntity +import org.jetbrains.exposed.v1.dao.CompositeEntityClass +import org.jetbrains.exposed.v1.javatime.CurrentTimestamp +import org.jetbrains.exposed.v1.javatime.timestamp +import org.jetbrains.exposed.v1.jdbc.insert +import java.io.InputStream +import java.util.zip.GZIPInputStream + +object NodeDataTable: CompositeIdTable("NodeData") { + val nodeId = reference("NodeId", SchematicNodeTable) + val createdAt = timestamp("CreatedAt").defaultExpression(CurrentTimestamp).entityId() + val nodeFormat = enumeration("NodeFormat", NodeData.SchematicFormat::class) + val schemData = blob("SchemData") + + override val primaryKey = PrimaryKey(nodeId, createdAt) + + init { + addIdColumn(nodeId) + } +} + +class NodeData(id: EntityID): CompositeEntity(id) { + val nodeId by NodeDataTable.nodeId + val createdAt by NodeDataTable.createdAt + val nodeFormat by NodeDataTable.nodeFormat + val schemData by NodeDataTable.schemData + + companion object: CompositeEntityClass(NodeDataTable) { + @JvmStatic + fun getLatest(node: SchematicNode) = useDb { + find { (NodeDataTable.nodeId eq node.nodeId) }.orderBy(NodeDataTable.createdAt to SortOrder.DESC).firstOrNull() + } + + @JvmStatic + fun get(node: SchematicNode) = useDb { + find { (NodeDataTable.nodeId eq node.nodeId) }.orderBy(NodeDataTable.createdAt to SortOrder.ASC).toList() + } + + @JvmStatic + fun get(node: SchematicNode, revision: Int) = useDb { + find { NodeDataTable.nodeId eq node.nodeId }.orderBy(NodeDataTable.createdAt to SortOrder.ASC).toList().get(revision - 1) + } + + @JvmStatic + fun getRevisions(node: SchematicNode) = useDb { + count(NodeDataTable.nodeId eq node.nodeId).toInt() + } + + @JvmStatic + fun saveFromStream(node: SchematicNode, blob: InputStream, format: SchematicFormat) = useDb { + NodeDataTable.insert { + it[NodeDataTable.nodeId] = EntityID(node.getId(), SchematicNodeTable) + it[NodeDataTable.nodeFormat] = format + it[NodeDataTable.schemData] = ExposedBlob(blob.readBytes()) + } + } + } + + fun schemData(decompress: Boolean) = useDb { + schemData.inputStream.let { if(decompress) GZIPInputStream(it) else it } + } + + fun schemData() = schemData(true) + + override fun delete() = useDb { super.delete() } + + enum class SchematicFormat(val fileEnding: String) { + MCEDIT(".schematic"), + SPONGE_V2(".schem"), + SPONGE_V3(".schem"); + } +} \ No newline at end of file diff --git a/CommonCore/SQL/src/de/steamwar/sql/NodeDownload.java b/CommonCore/SQL/src/de/steamwar/sql/NodeDownload.java deleted file mode 100644 index 6580b589..00000000 --- a/CommonCore/SQL/src/de/steamwar/sql/NodeDownload.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * 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 . - */ - -package de.steamwar.sql; - -import de.steamwar.sql.internal.Field; -import de.steamwar.sql.internal.SelectStatement; -import de.steamwar.sql.internal.Statement; -import de.steamwar.sql.internal.Table; -import lombok.AllArgsConstructor; -import lombok.Getter; - -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.sql.Timestamp; -import java.time.Instant; - -@AllArgsConstructor -public class NodeDownload { - - private static final char[] HEX = new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; - private static final String LINK_BASE = "https://api.steamwar.de/download/"; - - private static final Table table = new Table<>(NodeDownload.class); - private static final Statement insert = table.insertFields("NodeId", "Link"); - - private static final SelectStatement select = table.selectFields("link"); - - private static final Statement delete = table.delete(Table.PRIMARY); - - public static NodeDownload get(String link) { - return select.select(link); - } - - @Getter - @Field(keys = {Table.PRIMARY}) - private final int nodeId; - @Field - private final String link; - @Field(def = "CURRENT_TIMESTAMP") - @Getter - private final Timestamp timestamp; - - public static String getLink(SchematicNode schem){ - if(schem.isDir()) - throw new SecurityException("Can not Download Directorys"); - MessageDigest digest; - try { - digest = MessageDigest.getInstance("SHA-1"); - } catch (NoSuchAlgorithmException e) { - throw new SecurityException(e); - } - digest.reset(); - digest.update((Instant.now().toString() + schem.getOwner() + schem.getId()).getBytes()); - String hash = base16encode(digest.digest()); - insert.update(schem.getId(), hash); - return LINK_BASE + hash; - } - - public static String base16encode(byte[] byteArray) { - StringBuilder hexBuffer = new StringBuilder(byteArray.length * 2); - for (byte b : byteArray) - hexBuffer.append(HEX[(b >>> 4) & 0xF]).append(HEX[b & 0xF]); - return hexBuffer.toString(); - } - - public void delete() { - delete.update(nodeId); - } -} diff --git a/CommonCore/SQL/src/de/steamwar/sql/NodeDownload.kt b/CommonCore/SQL/src/de/steamwar/sql/NodeDownload.kt new file mode 100644 index 00000000..62de6038 --- /dev/null +++ b/CommonCore/SQL/src/de/steamwar/sql/NodeDownload.kt @@ -0,0 +1,76 @@ +/* + * 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 . + */ + +package de.steamwar.sql + +import de.steamwar.sql.internal.useDb +import org.jetbrains.exposed.v1.core.dao.id.EntityID +import org.jetbrains.exposed.v1.core.dao.id.IdTable +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.dao.IntEntity +import org.jetbrains.exposed.v1.dao.IntEntityClass +import org.jetbrains.exposed.v1.javatime.CurrentTimestamp +import org.jetbrains.exposed.v1.javatime.timestamp +import java.security.MessageDigest +import java.sql.Timestamp +import java.time.Instant + +object NodeDownloadTable: IdTable("NodeDownload") { + override val id = reference("NodeId", SchematicNodeTable).uniqueIndex() + val link = varchar("Link", 255) + val timestamp = timestamp("Timestamp").defaultExpression(CurrentTimestamp) +} + +class NodeDownload(id: EntityID) : IntEntity(id) { + companion object : IntEntityClass(NodeDownloadTable) { + const val LINK_BASE = "https://api.steamwar.de/download/" + + @JvmStatic + fun getLink(schem: SchematicNode): String { + if (schem.isDir()) + throw IllegalArgumentException("Cannot get link for directory") + + val digest = MessageDigest.getInstance("SHA-1") + digest.update("${Instant.now()}${schem.owner}${schem.nodeId}".toByteArray()) + val hash = digest.digest().joinToString("") { "%02x".format(it) } + useDb { + findByIdAndUpdate(schem.id.value) { + it.link = hash + } ?: new { + nodeId = schem.id.value + link = hash + } + } + return "${LINK_BASE}${hash}" + } + + @JvmStatic + fun get(link: String) = useDb { + find { NodeDownloadTable.link eq link }.firstOrNull() + } + } + + var nodeId by NodeDownloadTable.id.transform({ EntityID(it, SchematicNodeTable) }, { it.value }) + var link by NodeDownloadTable.link + var timestamp by NodeDownloadTable.timestamp.transform({ it.toInstant() }, { Timestamp.from(it) }) + + override fun delete() = useDb { + super.delete() + } +} \ No newline at end of file diff --git a/CommonCore/SQL/src/de/steamwar/sql/NodeMember.java b/CommonCore/SQL/src/de/steamwar/sql/NodeMember.java deleted file mode 100644 index 8b40b8be..00000000 --- a/CommonCore/SQL/src/de/steamwar/sql/NodeMember.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * 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 . - */ - -package de.steamwar.sql; - -import de.steamwar.sql.internal.Field; -import de.steamwar.sql.internal.SelectStatement; -import de.steamwar.sql.internal.Statement; -import de.steamwar.sql.internal.Table; -import lombok.AllArgsConstructor; - -import java.util.HashSet; -import java.util.Optional; -import java.util.Set; - -@AllArgsConstructor -public class NodeMember { - - public static void init() { - // enforce class initialization - } - - private static final Table table = new Table<>(NodeMember.class); - private static final SelectStatement getNodeMember = table.select(Table.PRIMARY); - private static final SelectStatement getNodeMembers = table.selectFields("NodeId"); - private static final SelectStatement getSchematics = table.selectFields("UserId"); - private static final Statement create = table.insert(Table.PRIMARY); - private static final Statement delete = table.delete(Table.PRIMARY); - private static final Statement updateParent = table.update(Table.PRIMARY, "ParentId"); - - @Field(keys = {Table.PRIMARY}) - private final int nodeId; - @Field(keys = {Table.PRIMARY}) - private final int userId; - @Field(nullable = true, def = "null") - private Integer parentId; - - public int getNode() { - return nodeId; - } - - public int getMember() { - return userId; - } - - public Optional getParent() { - return Optional.ofNullable(parentId); - } - - public void delete() { - delete.update(nodeId, userId); - } - - public static NodeMember createNodeMember(int node, int member) { - create.update(node, member); - return new NodeMember(node, member, null); - } - - public static NodeMember getNodeMember(int node, SteamwarUser member) { - return getNodeMember(node, member.getId()); - } - - public static NodeMember getNodeMember(int node, int member) { - return getNodeMember.select(node, member); - } - - public static Set getNodeMembers(int node) { - return new HashSet<>(getNodeMembers.listSelect(node)); - } - - public static Set getSchematics(int member) { - return new HashSet<>(getSchematics.listSelect(member)); - } - - public void setParentId(Integer parentId) { - this.parentId = parentId; - updateParent.update(this.parentId, nodeId, userId); - } -} diff --git a/CommonCore/SQL/src/de/steamwar/sql/NodeMember.kt b/CommonCore/SQL/src/de/steamwar/sql/NodeMember.kt new file mode 100644 index 00000000..410fc140 --- /dev/null +++ b/CommonCore/SQL/src/de/steamwar/sql/NodeMember.kt @@ -0,0 +1,92 @@ +/* + * 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 . + */ + +package de.steamwar.sql + +import de.steamwar.sql.internal.useDb +import org.jetbrains.exposed.v1.core.and +import org.jetbrains.exposed.v1.core.dao.id.CompositeID +import org.jetbrains.exposed.v1.core.dao.id.CompositeIdTable +import org.jetbrains.exposed.v1.core.dao.id.EntityID +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.dao.CompositeEntity +import org.jetbrains.exposed.v1.dao.CompositeEntityClass +import org.jetbrains.exposed.v1.jdbc.insertIgnore +import java.util.* +import kotlin.jvm.optionals.getOrNull + +object NodeMemberTable : CompositeIdTable("NodeMember") { + val node = reference("NodeId", SchematicNodeTable) + val userId = reference("UserId", SteamwarUserTable) + val parentNode = optReference("ParentId", SchematicNodeTable) + + override val primaryKey = PrimaryKey(node, userId) + + init { + addIdColumn(node) + addIdColumn(userId) + } +} + +class NodeMember(id: EntityID) : CompositeEntity(id) { + companion object : CompositeEntityClass(NodeMemberTable) { + @JvmStatic + fun createNodeMember(node: Int, member: Int): NodeMember = useDb { + NodeMemberTable.insertIgnore { + it[this.node] = EntityID(node, SchematicNodeTable) + it[this.userId] = EntityID(member, SteamwarUserTable) + } + getNodeMember(node, member) ?: throw IllegalStateException("NodeMember not created") + } + + @JvmStatic + fun createNodeMember(node: Int, member: SteamwarUser) = createNodeMember(node, member.id.value) + + @JvmStatic + fun getNodeMember(node: Int, member: Int) = useDb { + find { (NodeMemberTable.node eq node) and (NodeMemberTable.userId eq member) }.firstOrNull() + } + + @JvmStatic + fun getNodeMember(node: Int, member: SteamwarUser) = getNodeMember(node, member.id.value) + + @JvmStatic + fun getNodeMembers(node: Int) = useDb { find { NodeMemberTable.node eq node }.toSet() } + + @JvmStatic + fun getSchematics(member: Int) = useDb { find { NodeMemberTable.userId eq member }.toSet() } + + @JvmStatic + fun init() = Unit + } + + val node by NodeMemberTable.node.transform({ EntityID(it, SchematicNodeTable) }, { it.value }) + val member by NodeMemberTable.userId.transform({ EntityID(it, SteamwarUserTable) }, { it.value }) + var parent by NodeMemberTable.parentNode.transform( + { it.map { EntityID(it, SchematicNodeTable) }.getOrNull() }, + { Optional.ofNullable(it?.value) }) + + fun setParentId(id: Int?) { + parent = Optional.ofNullable(id) + } + + override fun delete() = useDb { + super.delete() + } +} diff --git a/CommonCore/SQL/src/de/steamwar/sql/PersonalKit.kt b/CommonCore/SQL/src/de/steamwar/sql/PersonalKit.kt new file mode 100644 index 00000000..cd774d3c --- /dev/null +++ b/CommonCore/SQL/src/de/steamwar/sql/PersonalKit.kt @@ -0,0 +1,117 @@ +/* + * 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 . + */ + +package de.steamwar.sql + +import de.steamwar.sql.internal.useDb +import org.jetbrains.exposed.v1.core.and +import org.jetbrains.exposed.v1.core.dao.id.CompositeID +import org.jetbrains.exposed.v1.core.dao.id.CompositeIdTable +import org.jetbrains.exposed.v1.core.dao.id.EntityID +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.dao.CompositeEntity +import org.jetbrains.exposed.v1.dao.CompositeEntityClass +import org.jetbrains.exposed.v1.jdbc.insert + +object PersonalKitTable: CompositeIdTable("PersonalKit") { + val userId = reference("UserId", SteamwarUserTable) + val gamemode = varchar("Gamemode", 64).entityId() + val kitName = varchar("Name", 64).entityId() + val inventory = text("Inventory") + val armor = text("Armor") + val inUse = bool("InUse") + + override val primaryKey = PrimaryKey(userId, gamemode, kitName) + + init { + addIdColumn(userId) + } +} + +class InternalKit(id: EntityID): CompositeEntity(id) { + companion object: CompositeEntityClass(PersonalKitTable) { + @JvmStatic + fun get(userId: Int, gamemode: String) = useDb { + find { PersonalKitTable.userId eq userId and (PersonalKitTable.gamemode eq gamemode) and (PersonalKitTable.inUse eq true) } + .toList() + } + + @JvmStatic + fun get(userId: Int, gamemode: String, kitName: String) = useDb { + find { PersonalKitTable.userId eq userId and (PersonalKitTable.gamemode eq gamemode) and (PersonalKitTable.kitName eq kitName) and (PersonalKitTable.inUse eq true) } + .firstOrNull() + } + + @JvmStatic + fun create(userId: Int, gamemode: String, kitName: String, rawInventory: String, rawArmor: String) = useDb { + new( + CompositeID { + it[PersonalKitTable.userId] = EntityID(userId, SteamwarUserTable) + it[PersonalKitTable.gamemode] = gamemode + it[PersonalKitTable.kitName] = kitName + } + ) { + this.inventory = rawInventory + this.armor = rawArmor + this.inUse = true + } + } + + @JvmStatic + fun getKitInUse(userId: Int, gamemode: String) = useDb { + find { PersonalKitTable.userId eq userId and (PersonalKitTable.gamemode eq gamemode) and (PersonalKitTable.inUse eq true) } + .firstOrNull() + } + } + + var userID by PersonalKitTable.userId.transform({ EntityID(it, SteamwarUserTable) }, { it.value }) + private set + private var gameMode by PersonalKitTable.gamemode + val gamemode: String + get() = gameMode.value + + var kitName by PersonalKitTable.kitName + private set + val name: String + get() = kitName.value + + var rawInventory by PersonalKitTable.inventory + private set + var rawArmor by PersonalKitTable.armor + private set + var inUse by PersonalKitTable.inUse + private set + + var inventory: String + get() = rawInventory + set(value) = useDb { rawInventory = value } + var armor: String + get() = rawArmor + set(value) = useDb { rawArmor = value } + + fun setDefault() = useDb { + find { PersonalKitTable.userId eq userID and (PersonalKitTable.gamemode eq gameMode) and (PersonalKitTable.inUse eq true) } + .forEach { it.inUse = false } + inUse = true + } + + override fun delete() = useDb { + super.delete() + } +} \ No newline at end of file diff --git a/CommonCore/SQL/src/de/steamwar/sql/PollAnswer.java b/CommonCore/SQL/src/de/steamwar/sql/PollAnswer.java deleted file mode 100644 index 9376ed18..00000000 --- a/CommonCore/SQL/src/de/steamwar/sql/PollAnswer.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * 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 . - */ - -package de.steamwar.sql; - -import de.steamwar.sql.internal.Field; -import de.steamwar.sql.internal.SelectStatement; -import de.steamwar.sql.internal.Statement; -import de.steamwar.sql.internal.Table; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.Setter; - -import java.util.HashMap; -import java.util.Map; - -@AllArgsConstructor -public class PollAnswer { - - @Getter - @Setter - private static String currentPoll; - - private static final Table table = new Table<>(PollAnswer.class); - - private static final SelectStatement get = table.select(Table.PRIMARY); - private static final Statement getResults = new Statement("SELECT Count(UserID) AS Times, Answer FROM PollAnswer WHERE Question = ? GROUP BY Answer ORDER BY Times ASC"); - private static final Statement insert = table.insertAll(); - - @Field(keys = {Table.PRIMARY}) - private final int userID; - @Field(keys = {Table.PRIMARY}) - private final String question; - @Field(def = "0") - private int answer; - - public static PollAnswer get(int userID) { - PollAnswer answer = get.select(userID, currentPoll); - if(answer == null) - return new PollAnswer(userID, currentPoll, 0); - return answer; - } - - public static Map getCurrentResults() { - return getResults.select(rs -> { - Map retMap = new HashMap<>(); - while (rs.next()) - retMap.put(rs.getInt("Answer")-1, rs.getInt("Times")); - return retMap; - }, currentPoll); - } - - public boolean hasAnswered(){ - return answer != 0; - } - - public void setAnswer(int answer){ - this.answer = answer; - insert.update(userID, question, answer); - } -} diff --git a/CommonCore/SQL/src/de/steamwar/sql/PollAnswer.kt b/CommonCore/SQL/src/de/steamwar/sql/PollAnswer.kt new file mode 100644 index 00000000..a4c20b26 --- /dev/null +++ b/CommonCore/SQL/src/de/steamwar/sql/PollAnswer.kt @@ -0,0 +1,78 @@ +/* + * 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 . + */ + +package de.steamwar.sql + +import de.steamwar.sql.internal.useDb +import org.jetbrains.exposed.v1.core.VarCharColumnType +import org.jetbrains.exposed.v1.core.and +import org.jetbrains.exposed.v1.core.dao.id.CompositeID +import org.jetbrains.exposed.v1.core.dao.id.CompositeIdTable +import org.jetbrains.exposed.v1.core.dao.id.EntityID +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.dao.CompositeEntity +import org.jetbrains.exposed.v1.dao.CompositeEntityClass + +object PollAnswerTable: CompositeIdTable("PollAnswer") { + val userId = reference("UserID", SteamwarUserTable) + val question = varchar("Question", 150) + val answer = integer("Answer") +} + +class PollAnswer(id: EntityID): CompositeEntity(id) { + var userId by PollAnswerTable.userId + private set + var question by PollAnswerTable.question + private set + private var answerId by PollAnswerTable.answer + var answer: Int + get() = answerId + set(value) = useDb { + answerId = value + } + + companion object: CompositeEntityClass(PollAnswerTable) { + @JvmStatic + var currentPoll: String? = null + + @JvmStatic + fun get(userId: Int) = useDb { + find { (PollAnswerTable.userId eq userId) and (PollAnswerTable.question eq currentPoll!!) }.firstOrNull() + ?: new { + this.userId = EntityID(userId, SteamwarUserTable) + this.question = currentPoll!! + this.answerId = 0 + } + } + + @JvmStatic + fun getCurrentResults(): Map = useDb { + exec("SELECT Count(UserID) AS Times, Answer FROM PollAnswer WHERE Question = ? GROUP BY Answer ORDER BY Times ASC", + args = listOf(VarCharColumnType() to currentPoll!!)) { + val result = mutableMapOf() + while (it.next()) { + result[it.getInt("Answer")] = it.getInt("Times") + } + result + } ?: emptyMap() + } + } + + fun hasAnswered() = answerId != 0 +} \ No newline at end of file diff --git a/CommonCore/SQL/src/de/steamwar/sql/Punishment.java b/CommonCore/SQL/src/de/steamwar/sql/Punishment.java deleted file mode 100644 index 1cd61078..00000000 --- a/CommonCore/SQL/src/de/steamwar/sql/Punishment.java +++ /dev/null @@ -1,140 +0,0 @@ -/* - * 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 . - */ - -package de.steamwar.sql; - -import de.steamwar.sql.internal.*; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.RequiredArgsConstructor; - -import java.sql.Timestamp; -import java.time.Instant; -import java.time.format.DateTimeFormatter; -import java.util.Date; -import java.util.List; -import java.util.Map; -import java.util.function.Consumer; -import java.util.stream.Collectors; - -@AllArgsConstructor -public class Punishment { - - static { - SqlTypeMapper.nameEnumMapper(PunishmentType.class); - } - - public static final Timestamp PERMA_TIME = Timestamp.from(Instant.ofEpochSecond(946674800)); - - private static final Table table = new Table<>(Punishment.class, "Punishments"); - private static final SelectStatement getPunishments = new SelectStatement<>(table, "SELECT * FROM Punishments WHERE PunishmentId IN (SELECT MAX(PunishmentId) FROM Punishments WHERE UserId = ? GROUP BY Type)"); - private static final SelectStatement getPunishment = new SelectStatement<>(table, "SELECT * FROM Punishments WHERE UserId = ? AND Type = ? ORDER BY PunishmentId DESC LIMIT 1"); - private static final SelectStatement getAllPunishments = new SelectStatement<>(table, "SELECT * FROM Punishments WHERE UserId = ? ORDER BY `PunishmentId` DESC"); - private static final Statement insert = table.insertFields(true, "UserId", "Punisher", "Type", "EndTime", "Perma", "Reason"); - - public static Punishment getPunishmentOfPlayer(int user, PunishmentType type) { - return getPunishment.select(user, type); - } - - public static Map getPunishmentsOfPlayer(int user) { - return getPunishments.listSelect(user).stream().collect(Collectors.toMap(Punishment::getType, punishment -> punishment)); - } - - public static List getAllPunishmentsOfPlayer(int user) { - return getAllPunishments.listSelect(user); - } - - public static boolean isPunished(SteamwarUser user, PunishmentType type, Consumer callback) { - Punishment punishment = Punishment.getPunishmentOfPlayer(user.getId(), type); - if(punishment == null || !punishment.isCurrent()) { - return false; - } else { - callback.accept(punishment); - return true; - } - } - - public static Punishment createPunishment(int user, int executor, PunishmentType type, String reason, Timestamp endTime, boolean perma) { - if(perma && !endTime.equals(PERMA_TIME)) { - throw new IllegalArgumentException("Permanent punishments must have an end time of `Punishment.PERMA_TIME`"); - } - int punishmentId = insert.insertGetKey(user, executor, type.name(), endTime, perma, reason); - return new Punishment(punishmentId, user, executor, type, Timestamp.from(Instant.now()), endTime, perma, reason); - } - - @Field(keys = {Table.PRIMARY}, autoincrement = true) - private final int punishmentId; - @Field - @Getter - private final int userId; - @Field - @Getter - private final int punisher; - @Field - @Getter - private final PunishmentType type; - @Field - @Getter - private final Timestamp startTime; - @Field - @Getter - private final Timestamp endTime; - @Field - @Getter - private final boolean perma; - @Field - @Getter - private final String reason; - - @Deprecated // Not multiling, misleading title - public String getBantime(Timestamp endTime, boolean perma) { - if (perma) { - return "permanent"; - } else { - return endTime.toLocalDateTime().format(DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm")); - } - } - - public boolean isCurrent() { - return isPerma() || getEndTime().after(new Date()); - } - - @AllArgsConstructor - @RequiredArgsConstructor - @Getter - public enum PunishmentType { - Ban(UserPerm.TEAM, "BAN_TEAM", "BAN_PERMA", "BAN_UNTIL", "UNBAN_ERROR", "UNBAN"), - Mute( UserPerm.TEAM, "MUTE_TEAM", "MUTE_PERMA", "MUTE_UNTIL", "UNMUTE_ERROR", "UNMUTE"), - NoSchemReceiving(UserPerm.MODERATION, "NOSCHEMRECEIVING_TEAM", "NOSCHEMRECEIVING_PERMA", "NOSCHEMRECEIVING_UNTIL", "UNNOSCHEMRECEIVING_ERROR", "UNNOSCHEMRECEIVING"), - NoSchemSharing(UserPerm.MODERATION, "NOSCHEMSHARING_TEAM", "NOSCHEMSHARING_PERMA", "NOSCHEMSHARING_UNTIL", "UNNOSCHEMSHARING_ERROR", "UNNOSCHEMSHARING"), - NoSchemSubmitting(UserPerm.TEAM, "NOSCHEMSUBMITTING_TEAM", "NOSCHEMSUBMITTING_PERMA", "NOSCHEMSUBMITTING_UNTIL", "UNNOSCHEMSUBMITTING_ERROR", "UNNOSCHEMSUBMITTING"), - NoDevServer(UserPerm.PREFIX_DEVELOPER, "NODEVSERVER_TEAM", "NODEVSERVER_PERMA", "NODEVSERVER_UNTIL", "UNNODEVSERVER_ERROR", "UNNODEVSERVER"), - NoFightServer(UserPerm.MODERATION, "NOFIGHTSERVER_TEAM", "NOFIGHTSERVER_PERMA", "NOFIGHTSERVER_UNTIL", "UNNOFIGHTSERVER_ERROR", "UNNOFIGHTSERVER"), - NoTeamServer(UserPerm.MODERATION, "NOTEAMSERVER_TEAM", "NOTEAMSERVER_PERMA", "NOTEAMSERVER_UNTIL", "UNNOTEAMSERVER_ERROR", "UNNOTEAMSERVER"), - Note(UserPerm.TEAM, "NOTE_TEAM", null, null, null, null, true); - - private final UserPerm userPerm; - private final String teamMessage; - private final String playerMessagePerma; - private final String playerMessageUntil; - private final String usageNotPunished; - private final String unpunishmentMessage; - private boolean multi = false; - } -} diff --git a/CommonCore/SQL/src/de/steamwar/sql/Punishment.kt b/CommonCore/SQL/src/de/steamwar/sql/Punishment.kt new file mode 100644 index 00000000..4eafce0c --- /dev/null +++ b/CommonCore/SQL/src/de/steamwar/sql/Punishment.kt @@ -0,0 +1,186 @@ +/* + * 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 . + */ + +package de.steamwar.sql + +import de.steamwar.sql.internal.useDb +import org.jetbrains.exposed.v1.core.* +import org.jetbrains.exposed.v1.core.dao.id.EntityID +import org.jetbrains.exposed.v1.core.dao.id.IntIdTable +import org.jetbrains.exposed.v1.dao.IntEntity +import org.jetbrains.exposed.v1.dao.IntEntityClass +import org.jetbrains.exposed.v1.javatime.timestamp +import org.jetbrains.exposed.v1.jdbc.select +import java.sql.Timestamp +import java.time.Instant +import java.util.* +import java.util.function.Consumer + +object PunishmentTable : IntIdTable("Punishments", "PunishmentId") { + val userId = reference("UserId", SteamwarUserTable) + val punisher = reference("Punisher", SteamwarUserTable) + val type = enumerationByName("Type", 32, Punishment.PunishmentType::class) + val startTime = timestamp("StartTime") + val endTime = timestamp("EndTime") + val perma = bool("Perma") + val reason = text("Reason") +} + +class Punishment(id: EntityID) : IntEntity(id) { + companion object : IntEntityClass(PunishmentTable) { + @JvmField + val PERMA_TIME: Timestamp = Timestamp.from(Instant.ofEpochSecond(946674800)) + + @JvmStatic + fun getPunsihmentOfPlayer(user: Int, type: PunishmentType) = useDb { + find { (PunishmentTable.userId eq user) and (PunishmentTable.type eq type) }.orderBy(PunishmentTable.id to SortOrder.DESC) + .firstOrNull() + } + + @JvmStatic + fun getPunishmentsOfPlayer(user: Int) = useDb { + find { + PunishmentTable.id inSubQuery PunishmentTable.select(PunishmentTable.id.max()) + .where { PunishmentTable.userId eq user }.groupBy( + PunishmentTable.type + ) + }.associateBy { it.type }.toMutableMap() + } + + @JvmStatic + fun getAllPunishmentsOfPlayer(user: Int) = useDb { + find { PunishmentTable.userId eq user }.orderBy(PunishmentTable.id to SortOrder.DESC).toList() + } + + @JvmStatic + fun isPunished(user: SteamwarUser, type: PunishmentType, callback: Consumer): Boolean = useDb { + val punishment = getPunsihmentOfPlayer(user.id.value, type) ?: return@useDb false + + if (punishment.isCurrent()) { + callback.accept(punishment) + return@useDb true + } + + return@useDb false + } + + @JvmStatic + fun createPunishment( + user: Int, + executor: Int, + type: PunishmentType, + reason: String, + endTime: Timestamp, + perma: Boolean + ) = useDb { + new { + this.userId = user + this.punisher = executor + this.type = type + this.startTime = Timestamp.from(Instant.now()) + this.endTime = endTime + this.perma = perma + this.reason = reason + } + } + } + + var userId by PunishmentTable.userId.transform({ EntityID(it, SteamwarUserTable) }, { it.value }) + private set + var punisher by PunishmentTable.punisher.transform({ EntityID(it, SteamwarUserTable) }, { it.value }) + private set + var type by PunishmentTable.type + private set + var startTime by PunishmentTable.startTime.transform({ it.toInstant() }, { Timestamp.from(it) }) + private set + var endTime by PunishmentTable.endTime.transform({ it.toInstant() }, { Timestamp.from(it) }) + private set + var perma by PunishmentTable.perma + private set + var reason by PunishmentTable.reason + private set + + fun isPerma() = perma + + fun isCurrent() = perma || endTime.after(Date()) + + enum class PunishmentType( + val teamMessage: String?, + val playerMessagePerma: String?, + val playerMessageUntil: String?, + val usageNotPunished: String?, + val unpunishmentMessage: String?, + val userPerm: UserPerm, + val multi: Boolean = false + ) { + Ban("BAN_TEAM", "BAN_PERMA", "BAN_UNTIL", "UNBAN_ERROR", "UNBAN", UserPerm.TEAM), + Mute("MUTE_TEAM", "MUTE_PERMA", "MUTE_UNTIL", "UNMUTE_ERROR", "UNMUTE", UserPerm.TEAM), + NoSchemReceiving( + "NOSCHEMRECEIVING_TEAM", + "NOSCHEMRECEIVING_PERMA", + "NOSCHEMRECEIVING_UNTIL", + "UNNOSCHEMRECEIVING_ERROR", + "UNNOSCHEMRECEIVING", + UserPerm.MODERATION + ), + NoSchemSharing( + "NOSCHEMSHARING_TEAM", + "NOSCHEMSHARING_PERMA", + "NOSCHEMSHARING_UNTIL", + "UNNOSCHEMSHARING_ERROR", + "UNNOSCHEMSHARING", + UserPerm.MODERATION + ), + NoSchemSubmitting( + "NOSCHEMSUBMITTING_TEAM", + "NOSCHEMSUBMITTING_PERMA", + "NOSCHEMSUBMITTING_UNTIL", + "UNNOSCHEMSUBMITTING_ERROR", + "UNNOSCHEMSUBMITTING", + UserPerm.TEAM + ), + NoDevServer( + "NODEVSERVER_TEAM", + "NODEVSERVER_PERMA", + "NODEVSERVER_UNTIL", + "UNNODEVSERVER_ERROR", + "UNNODEVSERVER", + UserPerm.PREFIX_DEVELOPER + ), + NoFightServer( + "NOFIGHTSERVER_TEAM", + "NOFIGHTSERVER_PERMA", + "NOFIGHTSERVER_UNTIL", + "UNNOFIGHTSERVER_ERROR", + "UNNOFIGHTSERVER", + UserPerm.MODERATION + ), + NoTeamServer( + "NOTEAMSERVER_TEAM", + "NOTEAMSERVER_PERMA", + "NOTEAMSERVER_UNTIL", + "UNNOTEAMSERVER_ERROR", + "UNNOTEAMSERVER", + UserPerm.MODERATION + ), + Note("NOTE_TEAM", null, null, null, null, UserPerm.TEAM, true); + + fun isMulti() = multi + } +} \ No newline at end of file diff --git a/CommonCore/SQL/src/de/steamwar/sql/Referee.java b/CommonCore/SQL/src/de/steamwar/sql/Referee.java deleted file mode 100644 index 3f909643..00000000 --- a/CommonCore/SQL/src/de/steamwar/sql/Referee.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * 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 . - */ - -package de.steamwar.sql; - -import de.steamwar.sql.internal.Field; -import de.steamwar.sql.internal.SelectStatement; -import de.steamwar.sql.internal.Statement; -import de.steamwar.sql.internal.Table; -import lombok.AllArgsConstructor; - -import java.util.Set; -import java.util.stream.Collectors; - -@AllArgsConstructor -public class Referee { - - private static final Table table = new Table<>(Referee.class); - private static final SelectStatement byEvent = table.selectFields("eventID"); - - private static final Statement insert = table.insertAll(); - private static final Statement delete = table.delete("eventReferee"); - - public static void add(int eventID, int userID) { - insert.update(eventID, userID); - } - - public static void remove(int eventID, int userID) { - delete.update(eventID, userID); - } - - public static Set get(int eventID) { - return byEvent.listSelect(eventID).stream().map(referee -> referee.userID).collect(Collectors.toSet()); - } - - @Field(keys = {"eventReferee"}) - private final int eventID; - @Field(keys = {"eventReferee"}) - private final int userID; -} diff --git a/CommonCore/SQL/src/de/steamwar/sql/Referee.kt b/CommonCore/SQL/src/de/steamwar/sql/Referee.kt new file mode 100644 index 00000000..594b12cd --- /dev/null +++ b/CommonCore/SQL/src/de/steamwar/sql/Referee.kt @@ -0,0 +1,66 @@ +/* + * 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 . + */ + +package de.steamwar.sql + +import de.steamwar.sql.internal.useDb +import org.jetbrains.exposed.v1.core.and +import org.jetbrains.exposed.v1.core.dao.id.CompositeID +import org.jetbrains.exposed.v1.core.dao.id.CompositeIdTable +import org.jetbrains.exposed.v1.core.dao.id.EntityID +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.dao.CompositeEntity +import org.jetbrains.exposed.v1.dao.CompositeEntityClass + +object RefereeTable: CompositeIdTable("Referee") { + val eventId = reference("EventId", EventTable) + val userId = reference("UserId", SteamwarUserTable) + + override val primaryKey = PrimaryKey(eventId, userId) + + init { + addIdColumn(eventId) + addIdColumn(userId) + } +} + +class Referee(id: EntityID): CompositeEntity(id) { + companion object: CompositeEntityClass(RefereeTable) { + @JvmStatic + fun add(eventId: Int, userId: Int) = useDb { + new { + this.eventID = eventId + this.userID = userId + } + } + + @JvmStatic + fun remove(eventId: Int, userId: Int) = useDb { + find { (RefereeTable.eventId eq eventId) and (RefereeTable.userId eq userId) }.firstOrNull()?.delete() + } + + @JvmStatic + fun get(event: Int) = useDb { + find { RefereeTable.eventId eq event }.map { it.userID }.toSet() + } + } + + var eventID by RefereeTable.eventId.transform({ EntityID(it, EventTable) }, { it.value }) + var userID by RefereeTable.userId.transform({ EntityID(it, SteamwarUserTable) }, { it.value }) +} \ No newline at end of file diff --git a/CommonCore/SQL/src/de/steamwar/sql/SQLWrapper.java b/CommonCore/SQL/src/de/steamwar/sql/SQLWrapper.java index 4fbe2541..47331406 100644 --- a/CommonCore/SQL/src/de/steamwar/sql/SQLWrapper.java +++ b/CommonCore/SQL/src/de/steamwar/sql/SQLWrapper.java @@ -22,6 +22,8 @@ package de.steamwar.sql; import de.steamwar.ImplementationProvider; import java.io.File; +import java.util.Collections; +import java.util.List; public interface SQLWrapper { SQLWrapper impl = ImplementationProvider.getImpl("de.steamwar.sql.SQLWrapperImpl"); @@ -30,6 +32,10 @@ public interface SQLWrapper { GameModeConfig loadGameModeConfig(File file); + default List getMaterialWithGreaterBlastResistance(double maxBlastResistance) { + return Collections.emptyList(); + } + default void processSchematicType(GameModeConfig gameModeConfig) { } diff --git a/CommonCore/SQL/src/de/steamwar/sql/SWException.java b/CommonCore/SQL/src/de/steamwar/sql/SWException.java deleted file mode 100644 index 945743f7..00000000 --- a/CommonCore/SQL/src/de/steamwar/sql/SWException.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * 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 . - */ - -package de.steamwar.sql; - -import de.steamwar.sql.internal.Field; -import de.steamwar.sql.internal.Statement; -import de.steamwar.sql.internal.Table; -import lombok.AllArgsConstructor; - -import java.io.File; -import java.sql.Timestamp; - -@AllArgsConstructor -public class SWException { - - public static void init() { - // force class initialialisation - } - - private static final String CWD = System.getProperty("user.dir"); - private static final String SERVER_NAME = new File(CWD).getName(); - - private static final Table table = new Table<>(SWException.class, "Exception"); - private static final Statement insert = table.insertFields(true, "server", "message", "stacktrace"); - - @Field(keys = {Table.PRIMARY}, autoincrement = true) - private final int id; - @Field(def = "CURRENT_TIMESTAMP") - private final Timestamp time; - @Field - private final String server; - @Field - private final String message; - @Field - private final String stacktrace; - - public static void log(String message, String stacktrace){ - insert.update(SERVER_NAME, generateMessage(message), stacktrace); - } - - public static int logGetId(String message, String stacktrace) { - return insert.insertGetKey(SERVER_NAME, generateMessage(message), stacktrace); - } - - private static String generateMessage(String message) { - StringBuilder msgBuilder = new StringBuilder(message); - SQLWrapper.impl.additionalExceptionMetadata(msgBuilder); - msgBuilder.append("\nCWD: ").append(CWD); - return msgBuilder.toString(); - } -} diff --git a/CommonCore/SQL/src/de/steamwar/sql/SWException.kt b/CommonCore/SQL/src/de/steamwar/sql/SWException.kt new file mode 100644 index 00000000..fb57e24f --- /dev/null +++ b/CommonCore/SQL/src/de/steamwar/sql/SWException.kt @@ -0,0 +1,72 @@ +/* + * 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 . + */ + +package de.steamwar.sql + +import de.steamwar.sql.internal.useDb +import org.jetbrains.exposed.v1.core.dao.id.IntIdTable +import org.jetbrains.exposed.v1.javatime.CurrentTimestamp +import org.jetbrains.exposed.v1.javatime.timestamp +import org.jetbrains.exposed.v1.jdbc.insert +import org.jetbrains.exposed.v1.jdbc.insertAndGetId +import java.io.File + +object ExceptionTable: IntIdTable("Exception") { + val time = timestamp("Time").defaultExpression(CurrentTimestamp) + val server = text("Server") + val message = text("Message") + val stackTrace = text("StackTrace") +} + +class SWException { + companion object { + val cwd = System.getProperty("user.dir") + val serverName = File(cwd).name + + @JvmStatic + fun init() = Unit + + @JvmStatic + fun log(message: String, stacktrace: String) = useDb { + ExceptionTable.insert { + it[ExceptionTable.server] = serverName + it[ExceptionTable.message] = generateMessage(message) + it[ExceptionTable.stackTrace] = stacktrace + } + + Unit + } + + @JvmStatic + fun logGetId(message: String, stacktrace: String) = useDb { + ExceptionTable.insertAndGetId { + it[ExceptionTable.server] = serverName + it[ExceptionTable.message] = generateMessage(message) + it[ExceptionTable.stackTrace] = stacktrace + }.value + } + + fun generateMessage(message: String): String { + val msgBuilder = StringBuilder(message) + SQLWrapper.impl.additionalExceptionMetadata(msgBuilder) + msgBuilder.append("\nCWD: ").append(cwd) + return msgBuilder.toString() + } + } +} \ No newline at end of file diff --git a/CommonCore/SQL/src/de/steamwar/sql/SchemElo.java b/CommonCore/SQL/src/de/steamwar/sql/SchemElo.java deleted file mode 100644 index 0a8641b0..00000000 --- a/CommonCore/SQL/src/de/steamwar/sql/SchemElo.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * 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 . - */ - -package de.steamwar.sql; - -import de.steamwar.sql.internal.Field; -import de.steamwar.sql.internal.SelectStatement; -import de.steamwar.sql.internal.Statement; -import de.steamwar.sql.internal.Table; -import lombok.AllArgsConstructor; - -@AllArgsConstructor -public class SchemElo { - private static final int ELO_DEFAULT = 1000; - - private static final Table table = new Table<>(SchemElo.class); - private static final SelectStatement select = table.select(Table.PRIMARY); - private static final Statement setElo = table.insertAll(); - - public static int getElo(SchematicNode node, int season) { - SchemElo elo = select.select(node, season); - return elo != null ? elo.elo : 0; - } - - public static int getCurrentElo(int schemID) { - SchemElo elo = select.select(schemID, Season.getSeason()); - return elo != null ? elo.elo : ELO_DEFAULT; - } - - public static void setElo(int schemID, int elo) { - setElo.update(schemID, elo, Season.getSeason()); - } - - @Field(keys = {Table.PRIMARY}) - private final int schemId; - @Field - private final int elo; - @Field(keys = {Table.PRIMARY}) - private final int season; -} diff --git a/CommonCore/SQL/src/de/steamwar/sql/SchemElo.kt b/CommonCore/SQL/src/de/steamwar/sql/SchemElo.kt new file mode 100644 index 00000000..b187186b --- /dev/null +++ b/CommonCore/SQL/src/de/steamwar/sql/SchemElo.kt @@ -0,0 +1,74 @@ +/* + * 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 . + */ + +package de.steamwar.sql + +import de.steamwar.sql.internal.useDb +import org.jetbrains.exposed.v1.core.and +import org.jetbrains.exposed.v1.core.dao.id.CompositeID +import org.jetbrains.exposed.v1.core.dao.id.CompositeIdTable +import org.jetbrains.exposed.v1.core.dao.id.EntityID +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.dao.CompositeEntity +import org.jetbrains.exposed.v1.dao.CompositeEntityClass +import org.jetbrains.exposed.v1.jdbc.insertIgnore + +object SchemEloTable: CompositeIdTable("SchemElo") { + val schemId = reference("SchemId", SchematicNodeTable) + val season = integer("Season").entityId() + val elo = integer("Elo") + + override val primaryKey = PrimaryKey(schemId, season) + + init { + addIdColumn(schemId) + } +} + +class SchemElo(id: EntityID): CompositeEntity(id) { + companion object: CompositeEntityClass(SchemEloTable) { + @JvmStatic + fun getElo(node: SchematicNode, season: Int, defaultElo: Int = 0) = useDb { + find { (SchemEloTable.schemId eq node.id) and (SchemEloTable.season eq season) }.firstOrNull()?.elo ?: defaultElo + } + + @JvmStatic + fun getCurrentElo(schemId: Int) = getElo(SchematicNode.byId(schemId)!!, Season.getSeason()) + + @JvmStatic + fun setElo(node: Int, elo: Int) = useDb { + findByIdAndUpdate(CompositeID { + it[SchemEloTable.schemId] = node + it[SchemEloTable.season] = Season.getSeason() + }) { + it.elo = elo + } ?: SchemEloTable.insertIgnore { + it[SchemEloTable.schemId] = node + it[SchemEloTable.season] = Season.getSeason() + it[SchemEloTable.elo] = elo + } + + return@useDb + } + } + + var node by SchemEloTable.schemId + var season by SchemEloTable.season + var elo by SchemEloTable.elo +} \ No newline at end of file diff --git a/CommonCore/SQL/src/de/steamwar/sql/SchematicNode.java b/CommonCore/SQL/src/de/steamwar/sql/SchematicNode.java deleted file mode 100644 index bac7929f..00000000 --- a/CommonCore/SQL/src/de/steamwar/sql/SchematicNode.java +++ /dev/null @@ -1,634 +0,0 @@ -/* - * 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 . - */ - -package de.steamwar.sql; - -import de.steamwar.sql.internal.*; -import lombok.Getter; - -import java.sql.Timestamp; -import java.time.Instant; -import java.util.*; -import java.util.function.Predicate; - -public class SchematicNode { - - static { - SchematicType.Normal.name(); // Ensure SchematicType is loaded. - new SqlTypeMapper<>(SchematicNode.class, null, (rs, identifier) -> { - throw new SecurityException("SchematicNode cannot be used as type (recursive select)"); - }, (st, index, value) -> st.setInt(index, value.nodeId)); - } - - private static final Map>> TAB_CACHE = new HashMap<>(); - - public static void clear() { - TAB_CACHE.clear(); - } - - private static final String nodeSelector = "SELECT NodeId, NodeOwner, NodeOwner AS EffectiveOwner, NodeName, ParentNode, LastUpdate, NodeItem, NodeType, NodeRank, Config FROM SchematicNode "; - - private static final Table table = new Table<>(SchematicNode.class); - private static final Statement create = table.insertFields(true, "NodeOwner", "NodeName", "ParentNode", "NodeItem", - "NodeType"); - private static final Statement update = table.update(Table.PRIMARY, "NodeName", "ParentNode", "NodeItem", - "NodeType", "NodeRank", "Config"); - private static final Statement delete = table.delete(Table.PRIMARY); - - private static final SelectStatement byId = new SelectStatement<>(table, - nodeSelector + "WHERE NodeId = ?"); - private static final SelectStatement byOwnerNameParent = new SelectStatement<>(table, - nodeSelector + "WHERE NodeOwner = ? AND NodeName = ? AND ParentNode " + Statement.NULL_SAFE_EQUALS + "?"); - private static final SelectStatement byParent = new SelectStatement<>(table, - nodeSelector + "WHERE ParentNode" + Statement.NULL_SAFE_EQUALS + "? ORDER BY NodeName"); - private static final SelectStatement dirsByParent = new SelectStatement<>(table, nodeSelector - + "WHERE ParentNode" + Statement.NULL_SAFE_EQUALS + "? AND NodeType is NULL ORDER BY NodeName"); - private static final SelectStatement byOwnerType = new SelectStatement<>(table, - nodeSelector + "WHERE NodeOwner = ? AND NodeType = ? ORDER BY NodeName"); - private static final SelectStatement byType = new SelectStatement<>(table, - nodeSelector + "WHERE NodeType = ? ORDER BY NodeName"); - private static final SelectStatement all = new SelectStatement<>(table, - "WITH RECURSIVE Nodes AS (SELECT NodeId, ParentId as ParentNode FROM NodeMember WHERE UserId = ? UNION SELECT NodeId, ParentNode FROM SchematicNode WHERE NodeOwner = ?), RSN AS ( SELECT NodeId, ParentNode FROM Nodes UNION SELECT SN.NodeId, SN.ParentNode FROM SchematicNode SN, RSN WHERE SN.ParentNode = RSN.NodeId ) SELECT SN.*, ? AS EffectiveOwner FROM RSN INNER JOIN SchematicNode SN ON RSN.NodeId = SN.NodeId"); - private static final SelectStatement list = new SelectStatement<>(table, - "SELECT SchematicNode.NodeId, NodeOwner, ? AS EffectiveOwner, NodeName, NM.ParentId AS ParentNode, LastUpdate, NodeItem, NodeType, NodeRank, Config FROM SchematicNode INNER JOIN NodeMember NM on SchematicNode.NodeId = NM.NodeId WHERE NM.ParentId " - + Statement.NULL_SAFE_EQUALS - + "? AND NM.UserId = ? UNION ALL SELECT SchematicNode.NodeId, NodeOwner, ? AS EffectiveOwner, NodeName, ParentNode, LastUpdate, NodeItem, NodeType, NodeRank, Config FROM SchematicNode WHERE (? IS NULL AND ParentNode IS NULL AND NodeOwner = ?) OR (? IS NOT NULL AND ParentNode = ?) ORDER BY NodeName"); - private static final SelectStatement byParentName = new SelectStatement<>(table, - "SELECT SchematicNode.NodeId, NodeOwner, ? AS EffectiveOwner, NodeName, NM.ParentId AS ParentNode, LastUpdate, NodeItem, NodeType, NodeRank, Config FROM SchematicNode INNER JOIN NodeMember NM on SchematicNode.NodeId = NM.NodeId WHERE NM.ParentId " - + Statement.NULL_SAFE_EQUALS - + "? AND NM.UserId = ? AND SchematicNode.NodeName = ? UNION ALL SELECT SchematicNode.NodeId, NodeOwner, ? AS EffectiveOwner, NodeName, ParentNode, LastUpdate, NodeItem, NodeType, NodeRank, Config FROM SchematicNode WHERE ((? IS NULL AND ParentNode IS NULL AND NodeOwner = ?) OR (? IS NOT NULL AND ParentNode = ?)) AND NodeName = ?"); - private static final SelectStatement schematicAccessibleForUser = new SelectStatement<>(table, - "WITH RECURSIVE Nodes AS (SELECT NodeId, ParentId as ParentNode FROM NodeMember WHERE UserId = ? UNION SELECT NodeId, ParentNode FROM SchematicNode WHERE NodeOwner = ?), RSN AS ( SELECT NodeId, ParentNode FROM Nodes UNION SELECT SN.NodeId, SN.ParentNode FROM SchematicNode SN, RSN WHERE SN.ParentNode = RSN.NodeId ) SELECT SN.*, ? AS EffectiveOwner FROM RSN INNER JOIN SchematicNode SN ON RSN.NodeId = SN.NodeId WHERE NodeId = ?"); - private static final SelectStatement accessibleByUserTypeInParent = new SelectStatement<>(table, - "WITH RECURSIVE RSASN AS(WITH RECURSIVE RSAN AS (WITH RSANH AS (WITH RECURSIVE RSA AS (SELECT SN.NodeId, NM.ParentId FROM SchematicNode SN LEFT JOIN NodeMember NM on SN.NodeId = NM.NodeId WHERE NM.UserId = ? UNION SELECT SN.NodeId, SN.ParentNode FROM SchematicNode SN INNER JOIN RSA ON RSA.NodeId = SN.ParentNode) SELECT * FROM RSA UNION SELECT NodeId, ParentNode FROM SchematicNode WHERE NodeOwner = ?) SELECT * FROM RSANH UNION SELECT SN.NodeId, SN.ParentNode FROM RSANH JOIN SchematicNode SN ON SN.ParentNode = RSANH.NodeId) SELECT RSAN.NodeId, RSAN.ParentId FROM RSAN JOIN SchematicNode SN ON SN.NodeId = RSAN.NodeId WHERE NodeType = ? UNION SELECT SN.NodeId, SN.ParentNode FROM SchematicNode SN JOIN RSASN ON SN.NodeId = RSASN.ParentId) SELECT SN.*, ? as EffectiveOwner, RSASN.ParentId AS ParentNode FROM RSASN JOIN SchematicNode SN ON SN.NodeId = RSASN.NodeId WHERE RSASN.ParentId" - + Statement.NULL_SAFE_EQUALS + "? ORDER BY NodeName"); - private static final SelectStatement accessibleByUserType = new SelectStatement<>(table, - "WITH RECURSIVE Nodes AS (SELECT NodeId, ParentId as ParentNode FROM NodeMember WHERE UserId = ? UNION SELECT NodeId, ParentNode FROM SchematicNode WHERE NodeOwner = ?), RSN AS ( SELECT NodeId, ParentNode FROM Nodes UNION SELECT SN.NodeId, SN.ParentNode FROM SchematicNode SN, RSN WHERE SN.ParentNode = RSN.NodeId ) SELECT SN.*, ? AS EffectiveOwner FROM RSN INNER JOIN SchematicNode SN ON RSN.NodeId = SN.NodeId WHERE NodeType = ?"); - private static final SelectStatement byIdAndUser = new SelectStatement<>(table, - "SELECT NodeId, NodeOwner, ? AS EffectiveOwner, NodeName, ParentNode, LastUpdate, NodeItem, NodeType, NodeRank, Config FROM SchematicNode WHERE NodeId = ?"); - private static final SelectStatement allParentsOfNode = new SelectStatement<>(table, - "WITH RECURSIVE R AS (SELECT NodeId, ParentNode FROM EffectiveSchematicNode WHERE NodeId = ? AND EffectiveOwner = ? UNION SELECT E.NodeId, E.ParentNode FROM R, EffectiveSchematicNode E WHERE R.ParentNode = E.NodeId AND E.EffectiveOwner = ?) SELECT SN.NodeId, SN.NodeOwner, ? AS EffectiveOwner, SN.NodeName, R.ParentNode, SN.LastUpdate, SN.NodeItem, SN.NodeType, SN.NodeRank, SN.Config FROM R INNER JOIN SchematicNode SN ON SN.NodeId = R.NodeId"); - - static { - NodeMember.init(); - } - - @Field(keys = { Table.PRIMARY }, autoincrement = true) - private final int nodeId; - @Field(keys = { "OwnerNameParent" }) - private final int nodeOwner; - @Field(def = "0") - @Getter - private final int effectiveOwner; - @Field(keys = { "OwnerNameParent" }) - private String nodeName; - @Field(keys = { "OwnerNameParent" }, nullable = true) - private Integer parentNode; - @Field(def = "CURRENT_TIMESTAMP") - private Timestamp lastUpdate; - @Field(def = "''") - private String nodeItem; - @Field(def = "'normal'", nullable = true) - private SchematicType nodeType; - @Field(def = "0") - private int nodeRank; - @Field - private int config; - - private String brCache; - - public SchematicNode( - int nodeId, - int nodeOwner, - int effectiveOwner, - String nodeName, - Integer parentNode, - Timestamp lastUpdate, - String nodeItem, - SchematicType nodeType, - int nodeRank, - int config) { - this.nodeId = nodeId; - this.nodeOwner = nodeOwner; - this.effectiveOwner = effectiveOwner; - this.nodeName = nodeName; - this.parentNode = parentNode; - this.nodeItem = nodeItem; - this.nodeType = nodeType; - this.lastUpdate = lastUpdate; - this.nodeRank = nodeRank; - this.config = config; - } - - public static List getAll(SteamwarUser user) { - return all.listSelect(user, user, user); - } - - public static Map> getAllMap(SteamwarUser user) { - return map(getAll(user)); - } - - public static List list(SteamwarUser user, Integer schematicId) { - return list.listSelect(user, schematicId, user, user, schematicId, user, schematicId, schematicId); - } - - public static SchematicNode byParentName(SteamwarUser user, Integer schematicId, String name) { - return byParentName.select(user, schematicId, user, name, user, schematicId, user, schematicId, schematicId, - name); - } - - public static List accessibleByUserType(SteamwarUser user, SchematicType type) { - return accessibleByUserType.listSelect(user, user, user, type); - } - - public static Map> accessibleByUserTypeMap(SteamwarUser user, SchematicType type) { - return map(accessibleByUserType(user, type)); - } - - public static boolean schematicAccessibleForUser(SteamwarUser user, Integer schematicId) { - return schematicAccessibleForUser.select(user, user, user, schematicId) != null; - } - - public static List accessibleByUserTypeParent(SteamwarUser user, SchematicType type, - Integer parentId) { - return accessibleByUserTypeInParent.listSelect(user, user, type, user, parentId); - } - - public static SchematicNode byIdAndUser(SteamwarUser user, Integer id) { - return byIdAndUser.select(user, id); - } - - public static List parentsOfNode(SteamwarUser user, Integer id) { - return allParentsOfNode.listSelect(id, user, user, user); - } - - private static Map> map(List in) { - Map> map = new HashMap<>(); - for (SchematicNode effectiveSchematicNode : in) { - map.computeIfAbsent(effectiveSchematicNode.getOptionalParent().orElse(0), k -> new ArrayList<>()) - .add(effectiveSchematicNode); - } - return map; - } - - public static SchematicNode createSchematic(int owner, String name, Integer parent) { - return createSchematicNode(owner, name, parent, SchematicType.Normal.toDB(), ""); - } - - public static SchematicNode createSchematicDirectory(int owner, String name, Integer parent) { - return createSchematicNode(owner, name, parent, null, ""); - } - - public static SchematicNode createSchematicNode(int owner, String name, Integer parent, String type, String item) { - if (parent != null && parent == 0) - parent = null; - int nodeId = create.insertGetKey(owner, name, parent, item, type); - return getSchematicNode(nodeId); - } - - public static SchematicNode getSchematicNode(int owner, String name, SchematicNode parent) { - return getSchematicNode(owner, name, parent.getId()); - } - - public static SchematicNode getSchematicNode(int owner, String name, Integer parent) { - return byOwnerNameParent.select(owner, name, parent); - } - - public static List getSchematicNodeInNode(SchematicNode parent) { - return getSchematicNodeInNode(parent.getId()); - } - - public static List getSchematicNodeInNode(Integer parent) { - return byParent.listSelect(parent); - } - - public static List getSchematicDirectoryInNode(Integer parent) { - return dirsByParent.listSelect(parent); - } - - @Deprecated - public static SchematicNode getSchematicDirectory(String name, SchematicNode parent) { - return getSchematicNode(name, parent.getId()); - } - - @Deprecated - public static SchematicNode getSchematicDirectory(String name, Integer parent) { - return getSchematicNode(name, parent); - } - - public static SchematicNode getSchematicNode(String name, Integer parent) { - return byParentName.select(name, parent); - } - - public static SchematicNode getSchematicNode(int id) { - return byId.select(id); - } - - public static List getAccessibleSchematicsOfTypeInParent(int owner, String schemType, - Integer parent) { - return accessibleByUserTypeParent(SteamwarUser.get(owner), SchematicType.fromDB(schemType), parent); - } - - public static List getAllAccessibleSchematicsOfType(int user, String schemType) { - return accessibleByUserType(SteamwarUser.get(user), SchematicType.fromDB(schemType)); - } - - public static List getAllSchematicsOfType(int owner, String schemType) { - return byOwnerType.listSelect(owner, schemType); - } - - @Deprecated - public static List getAllSchematicsOfType(String schemType) { - return byType.listSelect(schemType); - } - - public static List getAllSchematicsOfType(SchematicType schemType) { - return byType.listSelect(schemType); - } - - public static List deepGet(Integer parent, Predicate filter) { - List finalList = new ArrayList<>(); - List nodes = SchematicNode.getSchematicNodeInNode(parent); - nodes.forEach(node -> { - if (node.isDir()) { - finalList.addAll(deepGet(node.getId(), filter)); - } else { - if (filter.test(node)) - finalList.add(node); - } - }); - return finalList; - } - - @Deprecated - public static List getSchematicsAccessibleByUser(int user, Integer parent) { - return list(SteamwarUser.get(user), parent); - } - - @Deprecated - public static List getAllSchematicsAccessibleByUser(int user) { - return getAll(SteamwarUser.get(user)); - } - - public static List getAllParentsOfNode(SchematicNode node) { - return getAllParentsOfNode(node.getId()); - } - - public static List getAllParentsOfNode(int node) { - return allParentsOfNode.listSelect(node); - } - - public static SchematicNode getNodeFromPath(SteamwarUser user, String s) { - if (s.startsWith("/")) { - s = s.substring(1); - } - if (s.endsWith("/")) { - s = s.substring(0, s.length() - 1); - } - if (s.isEmpty()) { - return null; - } - if (s.contains("/")) { - String[] layers = s.split("/"); - Optional currentNode = Optional - .ofNullable(SchematicNode.byParentName(user, null, layers[0])); - for (int i = 1; i < layers.length; i++) { - int finalI = i; - Optional node = currentNode.map(effectiveSchematicNode -> SchematicNode - .byParentName(user, effectiveSchematicNode.getId(), layers[finalI])); - if (!node.isPresent()) { - return null; - } else { - currentNode = node; - if (!currentNode.map(SchematicNode::isDir).orElse(false) && i != layers.length - 1) { - return null; - } - } - } - return currentNode.orElse(null); - } else { - return SchematicNode.byParentName(user, null, s); - } - } - - public static List filterSchems(int user, Predicate filter) { - List finalList = new ArrayList<>(); - List nodes = getSchematicsAccessibleByUser(user, null); - nodes.forEach(node -> { - if (node.isDir()) { - finalList.addAll(deepGet(node.getId(), filter)); - } else { - if (filter.test(node)) - finalList.add(node); - } - }); - return finalList; - } - - public int getId() { - return nodeId; - } - - public int getOwner() { - return nodeOwner; - } - - public String getName() { - return nodeName; - } - - public void setName(String name) { - this.nodeName = name; - updateDB(); - } - - public Integer getParent() { - return parentNode; - } - - public Optional getOptionalParent() { - return Optional.ofNullable(parentNode); - } - - public void setParent(Integer parent) { - this.parentNode = parent; - updateDB(); - } - - public String getItem() { - if (nodeItem.isEmpty()) { - return isDir() ? "CHEST" : "CAULDRON_ITEM"; - } - return nodeItem; - } - - public void setItem(String item) { - this.nodeItem = item; - updateDB(); - } - - @Deprecated - public String getType() { - return nodeType.name(); - } - - @Deprecated - public void setType(String type) { - if (isDir()) - throw new SecurityException("Node is Directory"); - this.nodeType = SchematicType.fromDB(type); - updateDB(); - } - - public boolean isDir() { - return nodeType == null; - } - - public String getFileEnding() { - if (isDir()) - throw new SecurityException("Node is Directory"); - return NodeData.getLatest(this).getNodeFormat().getFileEnding(); - } - - public int getRank() { - if (isDir()) - throw new SecurityException("Node is Directory"); - return nodeRank; - } - - @Deprecated - public int getRankUnsafe() { - return nodeRank; - } - - public void setRank(int rank) { - if (isDir()) - throw new SecurityException("Node is Directory"); - this.nodeRank = rank; - } - - public SchematicType getSchemtype() { - if (isDir()) - throw new SecurityException("Is Directory"); - return nodeType; - } - - public void setSchemtype(SchematicType type) { - if (isDir()) - throw new SecurityException("Is Directory"); - this.nodeType = type; - updateDB(); - } - - public boolean replaceColor() { - return getConfig(ConfigFlags.REPLACE_COLOR); - } - - public void setReplaceColor(boolean replaceColor) { - if (isDir()) - throw new SecurityException("Is Directory"); - setConfig(ConfigFlags.REPLACE_COLOR, replaceColor); - } - - public boolean allowReplay() { - return getConfig(ConfigFlags.ALLOW_REPLAY); - } - - public void setAllowReplay(boolean allowReplay) { - if (isDir()) - throw new SecurityException("Is Directory"); - setConfig(ConfigFlags.ALLOW_REPLAY, allowReplay); - } - - public boolean isPrepared() { - return getConfig(ConfigFlags.IS_PREPARED); - } - - public void setPrepared(boolean prepared) { - if (isDir()) - throw new SecurityException("Is Directory"); - setConfig(ConfigFlags.IS_PREPARED, prepared); - } - - public boolean getConfig(ConfigFlags flag) { - return (config & (1 << flag.ordinal())) != 0; - } - - public void setConfig(ConfigFlags flag, boolean value) { - if (value) { - config |= (1 << flag.ordinal()); - } else { - config &= ~(1 << flag.ordinal()); - } - updateDB(); - } - - public SchematicNode getParentNode() { - if (parentNode == null) - return null; - return SchematicNode.getSchematicNode(parentNode); - } - - public int getElo(int season) { - return SchemElo.getElo(this, season); - } - - public boolean accessibleByUser(SteamwarUser user) { - return NodeMember.getNodeMember(nodeId, user) != null; - } - - public Set getMembers() { - return NodeMember.getNodeMembers(nodeId); - } - - public Timestamp getLastUpdate() { - return lastUpdate; - } - - private void updateDB() { - this.lastUpdate = Timestamp.from(Instant.now()); - update.update(nodeName, parentNode, nodeItem, nodeType, nodeRank, config, nodeId); - TAB_CACHE.clear(); - } - - public void delete() { - delete.update(nodeId); - } - - @Override - public int hashCode() { - return nodeId; - } - - @Override - public boolean equals(Object obj) { - if (!(obj instanceof SchematicNode)) - return false; - - return ((SchematicNode) obj).getId() == nodeId; - } - - public String generateBreadcrumbs(SteamwarUser user) { - return byIdAndUser(user, nodeId).generateBreadcrumbs(); - } - - public String generateBreadcrumbs(String split, SteamwarUser user) { - return byIdAndUser(user, nodeId).generateBreadcrumbs(split); - } - - public String generateBreadcrumbs() { - if (brCache == null) { - brCache = generateBreadcrumbs("/"); - } - return brCache; - } - - public String generateBreadcrumbs(String split) { - StringBuilder builder = new StringBuilder(getName()); - Optional currentNode = Optional.of(this); - if (currentNode.map(SchematicNode::isDir).orElse(false)) { - builder.append(split); - } - while (currentNode.isPresent()) { - currentNode = currentNode - .flatMap(schematicNode -> Optional - .ofNullable(NodeMember.getNodeMember(schematicNode.getId(), effectiveOwner)) - .map(NodeMember::getParent).orElse(schematicNode.getOptionalParent())) - .map(SchematicNode::getSchematicNode); - currentNode.ifPresent(node -> builder.insert(0, split).insert(0, node.getName())); - } - return builder.toString(); - } - - public List> generateBreadcrumbsMap(SteamwarUser user) { - List> map = new ArrayList<>(); - Optional currentNode = Optional.of(this); - if (currentNode.map(SchematicNode::isDir).orElse(false)) { - map.add(new AbstractMap.SimpleEntry<>(getName(), getId())); - } - while (currentNode.isPresent()) { - currentNode = currentNode - .flatMap(schematicNode -> Optional - .ofNullable(NodeMember.getNodeMember(schematicNode.getId(), effectiveOwner)) - .map(NodeMember::getParent).orElse(schematicNode.getOptionalParent())) - .map(SchematicNode::getSchematicNode); - currentNode.ifPresent(node -> map.add(0, new AbstractMap.SimpleEntry<>(node.getName(), node.getId()))); - } - return map; - } - - private static final List FORBIDDEN_NAMES = Collections.unmodifiableList(Arrays.asList("public")); - - public static boolean invalidSchemName(String[] layers) { - for (String layer : layers) { - if (layer.isEmpty()) { - return true; - } - if (layer.contains("/") || - layer.contains("\\") || - layer.contains("<") || - layer.contains(">") || - layer.contains("^") || - layer.contains("°") || - layer.contains("'") || - layer.contains("\"") || - layer.contains(" ")) { - return true; - } - if (FORBIDDEN_NAMES.contains(layer.toLowerCase())) { - return true; - } - } - return false; - } - - public static List getNodeTabcomplete(SteamwarUser user, String s) { - boolean sws = s.startsWith("/"); - if (sws) { - s = s.substring(1); - } - int index = s.lastIndexOf("/"); - String cacheKey = index == -1 ? "" : s.substring(0, index); - if (TAB_CACHE.containsKey(user.getId()) && TAB_CACHE.get(user.getId()).containsKey(cacheKey)) { - return new ArrayList<>(TAB_CACHE.get(user.getId()).get(cacheKey)); - } - List list = new ArrayList<>(); - if (s.contains("/")) { - String preTab = s.substring(0, s.lastIndexOf("/") + 1); - SchematicNode pa = SchematicNode.getNodeFromPath(user, preTab); - if (pa == null) - return new ArrayList<>(); - List nodes = SchematicNode.list(user, pa.getId()); - String br = pa.generateBreadcrumbs(); - nodes.forEach(node -> list.add((sws ? "/" : "") + br + node.getName() + (node.isDir() ? "/" : ""))); - } else { - List nodes = SchematicNode.list(user, null); - nodes.forEach(node -> list.add((sws ? "/" : "") + node.getName() + (node.isDir() ? "/" : ""))); - } - list.remove("//copy"); - TAB_CACHE.computeIfAbsent(user.getId(), integer -> new HashMap<>()).putIfAbsent(cacheKey, list); - return list; - } - - public static enum ConfigFlags { - REPLACE_COLOR, - ALLOW_REPLAY, - IS_PREPARED - } -} diff --git a/CommonCore/SQL/src/de/steamwar/sql/SchematicNode.kt b/CommonCore/SQL/src/de/steamwar/sql/SchematicNode.kt new file mode 100644 index 00000000..da0dde5a --- /dev/null +++ b/CommonCore/SQL/src/de/steamwar/sql/SchematicNode.kt @@ -0,0 +1,435 @@ +/* + * 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 . + */ + +package de.steamwar.sql + +import de.steamwar.sql.internal.fromSql +import de.steamwar.sql.internal.useDb +import org.jetbrains.exposed.v1.core.* +import org.jetbrains.exposed.v1.core.dao.id.EntityID +import org.jetbrains.exposed.v1.core.dao.id.IntIdTable +import org.jetbrains.exposed.v1.dao.IntEntity +import org.jetbrains.exposed.v1.dao.IntEntityClass +import org.jetbrains.exposed.v1.javatime.CurrentTimestamp +import org.jetbrains.exposed.v1.javatime.timestamp +import org.jetbrains.exposed.v1.jdbc.insertAndGetId +import java.sql.Timestamp +import java.util.* +import java.util.function.Consumer + +object SchematicNodeTable : IntIdTable("SchematicNode", "NodeId") { + val owner = reference("NodeOwner", SteamwarUserTable) + val name = varchar("NodeName", 64) + val parent = optReference("ParentNode", SchematicNodeTable) + val lastUpdate = timestamp("LastUpdate").defaultExpression(CurrentTimestamp) + val item = text("NodeItem") + val type = varchar("NodeType", 16).nullable() + val config = integer("Config") +} + +class SchematicNode(id: EntityID) : IntEntity(id) { + companion object : IntEntityClass(SchematicNodeTable) { + private val fieldIndex: Map, Int> = + SchematicNodeTable.columns.mapIndexed { index, column -> column to index }.toMap() + + private val FORBIDDEN_NAMES = listOf("public") + private val FORBIDDEN_CHARS = listOf('/', '\\', '<', '>', '^', '°', '\'', '"', ' ') + + val tabCache = mutableMapOf>>() + + @JvmStatic + fun clear() = tabCache.clear() + + private fun List.mapToIds(): Map = this.associateBy { it.nodeId } + + @JvmStatic + fun getAll(user: SteamwarUser) = fromSql( + "WITH RECURSIVE Nodes AS (SELECT NodeId, ParentId as ParentNode FROM NodeMember WHERE UserId = ? UNION SELECT NodeId, ParentNode FROM SchematicNode WHERE NodeOwner = ?), RSN AS ( SELECT NodeId, ParentNode FROM Nodes UNION SELECT SN.NodeId, SN.ParentNode FROM SchematicNode SN, RSN WHERE SN.ParentNode = RSN.NodeId ) SELECT SN.* FROM RSN INNER JOIN SchematicNode SN ON RSN.NodeId = SN.NodeId", + listOf( + IntegerColumnType() to user.getId(), + IntegerColumnType() to user.getId() + ), + fieldIndex + ) + + @JvmStatic + fun getAllMap(user: SteamwarUser) = getAll(user).mapToIds() + + @JvmStatic + fun list(user: SteamwarUser, schematicId: Int?) = fromSql( + "SELECT SchematicNode.NodeId, NodeOwner, NodeName, NM.ParentId AS ParentNode, LastUpdate, NodeItem, NodeType, NodeRank, Config FROM SchematicNode INNER JOIN NodeMember NM on SchematicNode.NodeId = NM.NodeId WHERE NM.ParentId <=> ? AND NM.UserId = ? UNION ALL SELECT SchematicNode.NodeId, NodeOwner, NodeName, ParentNode, LastUpdate, NodeItem, NodeType, NodeRank, Config FROM SchematicNode WHERE (? IS NULL AND ParentNode IS NULL AND NodeOwner = ?) OR (? IS NOT NULL AND ParentNode = ?) ORDER BY NodeName", + listOf( + IntegerColumnType() to schematicId, + IntegerColumnType() to user.getId(), + IntegerColumnType() to schematicId, + IntegerColumnType() to user.getId(), + IntegerColumnType() to schematicId, + IntegerColumnType() to schematicId, + ), fieldIndex + ) + + @JvmStatic + fun byParentName(user: SteamwarUser, schematicId: Int?, name: String) = fromSql( + "SELECT SchematicNode.NodeId, NodeOwner, NodeName, NM.ParentId AS ParentNode, LastUpdate, NodeItem, NodeType, NodeRank, Config FROM SchematicNode INNER JOIN NodeMember NM on SchematicNode.NodeId = NM.NodeId WHERE NM.ParentId <=> ? AND NM.UserId = ? AND SchematicNode.NodeName = ? UNION ALL SELECT SchematicNode.NodeId, NodeOwner, NodeName, ParentNode, LastUpdate, NodeItem, NodeType, NodeRank, Config FROM SchematicNode WHERE ((? IS NULL AND ParentNode IS NULL AND NodeOwner = ?) OR (? IS NOT NULL AND ParentNode = ?)) AND NodeName = ?", + listOf( + IntegerColumnType() to schematicId, + IntegerColumnType() to user.getId(), + VarCharColumnType() to name, + IntegerColumnType() to schematicId, + IntegerColumnType() to user.getId(), + IntegerColumnType() to schematicId, + IntegerColumnType() to schematicId, + VarCharColumnType() to name, + ), fieldIndex + ).firstOrNull() + + @JvmStatic + fun accessibleByUserType(user: SteamwarUser, type: SchematicType) = fromSql( + "WITH RECURSIVE Nodes AS (SELECT NodeId, ParentId as ParentNode FROM NodeMember WHERE UserId = ? UNION SELECT NodeId, ParentNode FROM SchematicNode WHERE NodeOwner = ?), RSN AS ( SELECT NodeId, ParentNode FROM Nodes UNION SELECT SN.NodeId, SN.ParentNode FROM SchematicNode SN, RSN WHERE SN.ParentNode = RSN.NodeId ) SELECT SN.*, ? AS EffectiveOwner FROM RSN INNER JOIN SchematicNode SN ON RSN.NodeId = SN.NodeId WHERE NodeType = ?", + listOf( + IntegerColumnType() to user.getId(), + IntegerColumnType() to user.getId(), + IntegerColumnType() to user.getId(), + VarCharColumnType() to type.toDB(), + ), fieldIndex + ) + + @JvmStatic + fun accessibleByUserTypeMap(user: SteamwarUser, type: SchematicType) = + accessibleByUserType(user, type).mapToIds() + + @JvmStatic + fun schematicAccessibleForUser(user: SteamwarUser, schematicId: Int?) = fromSql( + "WITH RECURSIVE Nodes AS (SELECT NodeId, ParentId as ParentNode FROM NodeMember WHERE UserId = ? UNION SELECT NodeId, ParentNode FROM SchematicNode WHERE NodeOwner = ?), RSN AS ( SELECT NodeId, ParentNode FROM Nodes UNION SELECT SN.NodeId, SN.ParentNode FROM SchematicNode SN, RSN WHERE SN.ParentNode = RSN.NodeId ) SELECT SN.*, ? AS EffectiveOwner FROM RSN INNER JOIN SchematicNode SN ON RSN.NodeId = SN.NodeId WHERE NodeId = ?", + listOf( + IntegerColumnType() to user.getId(), + IntegerColumnType() to user.getId(), + IntegerColumnType() to user.getId(), + IntegerColumnType() to schematicId, + ), + fieldIndex + ).isNotEmpty() + + @JvmStatic + fun accessibleByUserTypeParent(user: SteamwarUser, type: SchematicType, parentId: Int?) = fromSql( + "WITH RECURSIVE RSASN AS(WITH RECURSIVE RSAN AS (WITH RSANH AS (WITH RECURSIVE RSA AS (SELECT SN.NodeId, NM.ParentId FROM SchematicNode SN LEFT JOIN NodeMember NM on SN.NodeId = NM.NodeId WHERE NM.UserId = ? UNION SELECT SN.NodeId, SN.ParentNode FROM SchematicNode SN INNER JOIN RSA ON RSA.NodeId = SN.ParentNode) SELECT * FROM RSA UNION SELECT NodeId, ParentNode FROM SchematicNode WHERE NodeOwner = ?) SELECT * FROM RSANH UNION SELECT SN.NodeId, SN.ParentNode FROM RSANH JOIN SchematicNode SN ON SN.ParentNode = RSANH.NodeId) SELECT RSAN.NodeId, RSAN.ParentId FROM RSAN JOIN SchematicNode SN ON SN.NodeId = RSAN.NodeId WHERE NodeType = ? UNION SELECT SN.NodeId, SN.ParentNode FROM SchematicNode SN JOIN RSASN ON SN.NodeId = RSASN.ParentId) SELECT SN.*, ? as EffectiveOwner, RSASN.ParentId AS ParentNode FROM RSASN JOIN SchematicNode SN ON SN.NodeId = RSASN.NodeId WHERE RSASN.ParentId <=> ? ORDER BY NodeName", + listOf( + IntegerColumnType() to user.getId(), + IntegerColumnType() to user.getId(), + VarCharColumnType() to type.toDB(), + IntegerColumnType() to user.getId(), + IntegerColumnType() to parentId, + ), fieldIndex + ) + + @JvmStatic + @Deprecated("Use byId") + fun byIdAndUser(ignored: SteamwarUser, id: Int) = useDb { + findById(id) + } + + @JvmStatic + fun parentsOfNode(user: SteamwarUser, id: Int) = fromSql( + "WITH RECURSIVE R AS (SELECT NodeId, ParentNode FROM EffectiveSchematicNode WHERE NodeId = ? UNION SELECT E.NodeId, E.ParentNode FROM R, EffectiveSchematicNode E WHERE R.ParentNode = E.NodeId AND E.EffectiveOwner = ?) SELECT SN.NodeId, SN.NodeOwner, SN.NodeName, R.ParentNode, SN.LastUpdate, SN.NodeItem, SN.NodeType, SN.NodeRank, SN.Config FROM R INNER JOIN SchematicNode SN ON SN.NodeId = R.NodeId", + listOf( + IntegerColumnType() to id, + IntegerColumnType() to user.getId() + ), fieldIndex + ) + + @JvmStatic + fun createSchematic(owner: Int, name: String, parent: Int?) = createSchematicNode( + owner, name, parent, + SchematicType.Normal.toDB(), "" + ) + + @JvmStatic + fun createSchematicDirectory(owner: Int, name: String, parent: Int?) = + createSchematicNode(owner, name, parent, null, "") + + @JvmStatic + fun createSchematicNode(owner: Int, name: String, parent: Int?, type: String?, item: String) = useDb { + val id = SchematicNodeTable.insertAndGetId { + it[this.owner] = EntityID(owner, SteamwarUserTable) + it[this.name] = name + it[this.parent] = + parent?.let { p -> if (p == 0) null else p }?.let { p -> EntityID(p, SchematicNodeTable) } + it[this.item] = item + it[this.type] = type + } + return@useDb findById(id) ?: throw IllegalStateException("SchematicNode $id not found") + } + + @JvmStatic + fun byId(id: Int) = useDb { findById(id) } + + @JvmStatic + fun getSchematicNode(owner: Int, name: String, parent: Int?) = useDb { + find { (SchematicNodeTable.owner eq owner) and (SchematicNodeTable.name eq name) and (SchematicNodeTable.parent eq parent) }.firstOrNull() + } + + @JvmStatic + fun getSchematicNode(owner: Int, name: String, parent: SchematicNode) = + getSchematicNode(owner, name, parent.nodeId) + + @JvmStatic + fun getSchematicNodeInNode(parent: Int?) = useDb { + find { (SchematicNodeTable.parent eq parent) }.orderBy(SchematicNodeTable.name to SortOrder.ASC).toList() + } + + @JvmStatic + fun getSchematicNodeInNode(parent: SchematicNode) = getSchematicNodeInNode(parent.nodeId) + + @JvmStatic + fun getSchematicNode(name: String, parent: Int?) = useDb { + find { (SchematicNodeTable.name eq name) and (SchematicNodeTable.parent eq parent) }.firstOrNull() + } + + @JvmStatic + fun getSchematicNode(id: Int) = byId(id) + + @JvmStatic + fun getAllAccessibleSchematicsOfType(user: Int, type: String) = + accessibleByUserType(SteamwarUser.byId(user)!!, SchematicType.fromDB(type)!!) + + @JvmStatic + fun getAllSchematicsAccessibleByUser(user: Int) = getAll(SteamwarUser.byId(user)!!) + + @JvmStatic + fun getAllSchematicsOfType(owner: Int, type: String) = useDb { + find { (SchematicNodeTable.owner eq owner) and (SchematicNodeTable.type eq type) }.orderBy( + SchematicNodeTable.name to SortOrder.ASC + ).toList() + } + + @JvmStatic + fun getAllSchematicsOfType(type: SchematicType) = useDb { + find { (SchematicNodeTable.type eq type.toDB()) }.orderBy(SchematicNodeTable.name to SortOrder.ASC).toList() + } + + @JvmStatic + fun deepGet(parent: Int?, filter: (node: SchematicNode) -> Boolean): List = + getSchematicNodeInNode(parent) + .flatMap { + return@flatMap if (it.isDir()) { + deepGet(it.nodeId, filter) + } else { + if (filter(it)) listOf(it) else listOf() + } + } + + @JvmStatic + fun getNodeFromPath(user: SteamwarUser, path: String): SchematicNode? { + var s = path + if (s.startsWith("/")) { + s = s.drop(1) + } + if (s.endsWith("/")) { + s = s.dropLast(1) + } + if (s.isEmpty()) return null + if (s.contains("/")) { + val layers: Array = s.split("/".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() + var currentNode = byParentName(user, null, layers[0]!!) + for (i in 1.. byParentName(user, n.getId(), layers[i]!!) } + if (node == null) { + return null + } else { + currentNode = node + if (!currentNode.isDir() && i != layers.size - 1 + ) { + return null + } + } + } + return currentNode + } else { + return byParentName(user, null, s) + } + } + + @JvmStatic + fun invalidSchemName(layers: Array) = layers.any { + it.isEmpty() || FORBIDDEN_CHARS.any { c -> c in it } || FORBIDDEN_NAMES.any { n -> n == it.lowercase() } + } + + @JvmStatic + fun getNodeTabcomplete(user: SteamwarUser, s: String): List { + var s = s + val sws = s.startsWith("/") + if (sws) { + s = s.substring(1) + } + val index = s.lastIndexOf("/") + val cacheKey = if (index == -1) "" else s.take(index) + tabCache[user.getId()]?.get(cacheKey)?.also { return it } + + val list = mutableListOf() + if (s.contains("/")) { + val preTab = s.take(s.lastIndexOf("/") + 1) + val pa = getNodeFromPath(user, preTab) ?: return emptyList() + val nodes: List = list(user, pa.getId()) + val br = pa.generateBreadcrumbs(user) + nodes.forEach(Consumer { node: SchematicNode? -> list.add((if (sws) "/" else "") + br + node!!.name + (if (node.isDir()) "/" else "")) }) + } else { + val nodes: List = list(user, null) + nodes.forEach(Consumer { node: SchematicNode? -> list.add((if (sws) "/" else "") + node!!.name + (if (node.isDir()) "/" else "")) }) + } + list.remove("//copy") + tabCache.computeIfAbsent(user.getId()) { i -> mutableMapOf() }.putIfAbsent(cacheKey, list) + return list + } + } + + val nodeId by SchematicNodeTable.id.transform({ EntityID(it, SchematicNodeTable) }, { it.value }) + val nodeOwner by SchematicNodeTable.owner + val owner: Int get() = nodeOwner.value + private var nodeName by SchematicNodeTable.name + var name: String + get() = nodeName + set(value) = useDb { + nodeName = value + } + private var parentNodeId by SchematicNodeTable.parent + var parent: Int? + get() = parentNodeId?.value + set(value) = useDb { + parentNodeId = value?.let { EntityID(it, SchematicNodeTable) } + } + val parentNode: SchematicNode? + get() = parent?.let { findById(it) } + + val optionalParent: Optional get() = Optional.ofNullable(parent) + var lastUpdate by SchematicNodeTable.lastUpdate.transform({ it.toInstant() }, { Timestamp.from(it) }) + private var nodeItem by SchematicNodeTable.item + var item: String + get() = nodeItem.ifEmpty { + if (isDir()) "CHEST" else "CAULDRON_ITEM" + } + set(value) = useDb { + nodeItem = value + } + private var nodeType by SchematicNodeTable.type + var schemtype: SchematicType + get() = checkDir { SchematicType.fromDB(nodeType!!)!! } + set(value) = checkDir { useDb { nodeType = value.toDB() } } + var config by SchematicNodeTable.config + + val members: Set by lazy { NodeMember.getNodeMembers(nodeId) } + private lateinit var breadcrumbs: String + + fun getFileEnding(): String = checkDir { NodeData.getLatest(this)!!.nodeFormat.fileEnding } + fun getId() = nodeId + fun isDir() = nodeType == null + + private fun checkDir(block: () -> T): T { + if (isDir()) { + throw IllegalStateException("Node is a directory") + } + + return block() + } + + fun replaceColor() = getConfig(ConfigFlags.REPLACE_COLOR) + fun setReplaceColor(value: Boolean) = setConfig(ConfigFlags.REPLACE_COLOR, value) + + fun allowReplay() = getConfig(ConfigFlags.ALLOW_REPLAY) + fun setAllowReplay(value: Boolean) = setConfig(ConfigFlags.ALLOW_REPLAY, value) + + fun isPrepared() = getConfig(ConfigFlags.IS_PREPARED) + fun setPrepared(value: Boolean) = setConfig(ConfigFlags.IS_PREPARED, value) + + fun getConfig(flag: ConfigFlags) = config and (1 shl flag.ordinal) != 0 + fun setConfig(flag: ConfigFlags, value: Boolean) = useDb { + config = if (value) { + config or (1 shl flag.ordinal) + } else { + config and (1 shl flag.ordinal).inv() + } + } + + fun getElo(season: Int) = SchemElo.getElo(this, season) + + override fun delete() = useDb { + super.delete() + } + + override fun hashCode() = nodeId + + override fun equals(other: Any?): Boolean { + if (other !is SchematicNode) return false + return nodeId == other.nodeId + } + + fun generateBreadcrumbs(user: SteamwarUser): String { + if (!this::breadcrumbs.isInitialized) { + breadcrumbs = generateBreadcrumbs("/", user) + } + return breadcrumbs + } + + fun generateBreadcrumbs(split: String, user: SteamwarUser): String = useDb { + val builder: StringBuilder = StringBuilder(name) + if (isDir()) { + builder.append(split) + } + var currentNode: SchematicNode? = this@SchematicNode + while (currentNode != null) { + currentNode = currentNode + .let { + NodeMember.getNodeMember(it.nodeId, user.getId()) + ?.parent?.orElse(null) ?: it.parent + } + ?.let { findById(it) } + ?.also { + builder.insert(0, split).insert(0, it.name) + } + } + return@useDb builder.toString() + } + + fun generateBreadcrumbsMap(user: SteamwarUser): List> = useDb { + val map = mutableListOf>() + var currentNode: SchematicNode? = this@SchematicNode + while (currentNode != null) { + currentNode = currentNode + .also { + map.add(0, Pair(it.name, it.nodeId)) + } + .let { + NodeMember.getNodeMember(it.nodeId, user.getId()) + ?.parent?.orElse(null) ?: it.parent + } + ?.let { findById(it) } + } + return@useDb map + } + + fun accessibleByUser(user: SteamwarUser) = schematicAccessibleForUser(user, nodeId) + + enum class ConfigFlags { + REPLACE_COLOR, + ALLOW_REPLAY, + IS_PREPARED + } + + @Deprecated("Removed") + var rank = 0 +} diff --git a/CommonCore/SQL/src/de/steamwar/sql/SchematicType.java b/CommonCore/SQL/src/de/steamwar/sql/SchematicType.java deleted file mode 100644 index 6abaffca..00000000 --- a/CommonCore/SQL/src/de/steamwar/sql/SchematicType.java +++ /dev/null @@ -1,143 +0,0 @@ -/* - * 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 . - */ - -package de.steamwar.sql; - -import de.steamwar.sql.internal.SqlTypeMapper; -import lombok.Getter; - -import java.io.File; -import java.util.*; -import java.util.stream.Collectors; - -public class SchematicType { - - public static final SchematicType Normal = new SchematicType("Normal", "", Type.NORMAL, null, "STONE_BUTTON", false); - - private static final Map fromDB; - private static final List types; - - static { - List tmpTypes = new LinkedList<>(); - Map tmpFromDB = new HashMap<>(); - - tmpTypes.add(Normal); - tmpFromDB.put(Normal.name().toLowerCase(), Normal); - - File folder = SQLWrapper.impl.getSchemTypesFolder(); - if (folder.exists()) { - for (File configFile : Arrays.stream(folder.listFiles((file, name) -> name.endsWith(".yml") && !name.endsWith(".kits.yml"))).sorted().collect(Collectors.toList())) { - GameModeConfig gameModeConfig = SQLWrapper.impl.loadGameModeConfig(configFile); - if (gameModeConfig.Schematic.Type == null) continue; - if (tmpFromDB.containsKey(gameModeConfig.Schematic.Type.toDB())) continue; - SchematicType current = gameModeConfig.Schematic.Type; - if (!gameModeConfig.CheckQuestions.isEmpty()) { - SchematicType checkType = current.checkType; - tmpTypes.add(checkType); - tmpFromDB.put(checkType.toDB(), checkType); - } - tmpTypes.add(current); - tmpFromDB.put(current.toDB(), current); - SQLWrapper.impl.processSchematicType(gameModeConfig); - } - } - - fromDB = Collections.unmodifiableMap(tmpFromDB); - types = Collections.unmodifiableList(tmpTypes); - } - - static { - new SqlTypeMapper<>(SchematicType.class, "VARCHAR(16)", (rs, identifier) -> { - String t = rs.getString(identifier); - return t != null ? fromDB.get(t) : null; - }, (st, index, value) -> st.setString(index, value.toDB())); - } - - private final String name; - @Getter - private final String kuerzel; - private final Type type; - private final SchematicType checkType; - @Getter - private final String material; - @Getter - private final Date deadline; - @Getter - private final boolean manualCheck; - - SchematicType(String name, String kuerzel, Type type, SchematicType checkType, String material, boolean manualCheck){ - this(name, kuerzel, type, checkType, material, null, manualCheck); - } - - SchematicType(String name, String kuerzel, Type type, SchematicType checkType, String material, Date deadline, boolean manualCheck){ - this.name = name; - this.kuerzel = kuerzel; - this.type = type; - this.checkType = checkType; - this.material = material; - this.deadline = deadline; - this.manualCheck = manualCheck; - } - - public boolean isAssignable(){ - return type == Type.NORMAL || (type == Type.FIGHT_TYPE && checkType != null) || !manualCheck; - } - - public SchematicType checkType(){ - if (!manualCheck) { - return this; - } - return checkType; - } - - public boolean check(){ - return type == Type.CHECK_TYPE; - } - - public boolean fightType(){ - return type == Type.FIGHT_TYPE; - } - - public boolean writeable(){ - return type == Type.NORMAL; - } - - public String name(){ - return name; - } - - public String toDB(){ - return name.toLowerCase(); - } - - public static SchematicType fromDB(String input) { - if (fromDB == null) return null; - return fromDB.get(input.toLowerCase()); - } - - public static List values(){ - return types; - } - - enum Type{ - NORMAL, - CHECK_TYPE, - FIGHT_TYPE - } -} diff --git a/CommonCore/SQL/src/de/steamwar/sql/SchematicType.kt b/CommonCore/SQL/src/de/steamwar/sql/SchematicType.kt new file mode 100644 index 00000000..b7f7d5be --- /dev/null +++ b/CommonCore/SQL/src/de/steamwar/sql/SchematicType.kt @@ -0,0 +1,108 @@ +/* + * 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 . + */ + +package de.steamwar.sql + +import java.io.File +import java.util.* +import java.util.Locale +import java.util.Locale.getDefault +import java.util.stream.Collectors + +data class SchematicType( + val name: String, + val kuerzel: String, + val type: Type, + val checkType: SchematicType?, + val material: String, + val deadline: Date?, + val manualCheck: Boolean, +) { + constructor( + name: String, + kuerzel: String, + type: Type, + checkType: SchematicType?, + material: String, + manualCheck: Boolean + ) : this(name, kuerzel, type, checkType, material, null, manualCheck) + + companion object { + @JvmField + val Normal = SchematicType("Normal", "", Type.NORMAL, null, "STONE_BUTTON", false) + + private val types: List + private val fromDB: Map? + + init { + val tmpTypes = mutableListOf() + val tmpFromDB = mutableMapOf() + + tmpTypes.add(Normal) + tmpFromDB[Normal.toDB()] = Normal + + val folder = SQLWrapper.impl.schemTypesFolder + if (folder.exists()) { + for (configFile in Arrays.stream(folder.listFiles { _, name -> + name.endsWith( + ".yml" + ) && !name.endsWith(".kits.yml") + }).sorted().collect(Collectors.toList())) { + val gameModeConfig = SQLWrapper.impl.loadGameModeConfig(configFile) + if (gameModeConfig.Schematic.Type == null) continue + if (tmpFromDB.containsKey(gameModeConfig.Schematic.Type.toDB())) continue + val current = gameModeConfig.Schematic.Type + if (gameModeConfig.CheckQuestions.isNotEmpty()) { + val checkType = current.checkType + tmpTypes.add(checkType!!) + tmpFromDB[checkType.toDB()] = checkType + } + tmpTypes.add(current) + tmpFromDB[current.toDB()] = current + SQLWrapper.impl.processSchematicType(gameModeConfig) + } + } + + types = tmpTypes.toList() + fromDB = tmpFromDB.toMap() + } + + @JvmStatic + fun values() = types + + @JvmStatic + fun fromDB(value: String) = fromDB?.let { it[value.lowercase()] } + } + + fun name() = name + fun toDB() = name.lowercase() + + fun check() = type == Type.CHECK_TYPE + fun fightType() = type == Type.FIGHT_TYPE + fun writeable() = type == Type.NORMAL + + fun checkType() = if (manualCheck) checkType else this + fun isAssignable() = type == Type.NORMAL || (type == Type.FIGHT_TYPE && checkType != null) || !manualCheck + + enum class Type { + NORMAL, + CHECK_TYPE, + FIGHT_TYPE + } +} diff --git a/CommonCore/SQL/src/de/steamwar/sql/Script.java b/CommonCore/SQL/src/de/steamwar/sql/Script.java deleted file mode 100644 index 1a5ccd25..00000000 --- a/CommonCore/SQL/src/de/steamwar/sql/Script.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * 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 . - */ - -package de.steamwar.sql; - -import de.steamwar.sql.internal.Field; -import de.steamwar.sql.internal.SelectStatement; -import de.steamwar.sql.internal.Statement; -import de.steamwar.sql.internal.Table; -import lombok.AllArgsConstructor; -import lombok.Getter; - -import java.util.List; - -@AllArgsConstructor -@Getter -public class Script { - - private static final Table