84 Commits

Author SHA1 Message Date
d0c1413ea6 Refactor Leaderboard to Kotlin, leveraging Exposed library for database operations, and update LeaderboardManager for compatibility.
All checks were successful
SteamWarCI Build successful
Signed-off-by: Chaoscaot <max@maxsp.de>
2025-11-11 17:39:39 +01:00
5f7c5f0a18 Merge remote-tracking branch 'origin/Lobby/refactor-leaderboard' into Lobby/refactor-leaderboard
# Conflicts:
#	LobbySystem/src/de/steamwar/lobby/util/LeaderboardManager.java
2025-11-11 17:28:03 +01:00
9e9f405e30 Refactor leaderboard management: replace UserConfig-based implementation with new Leaderboard SQL class, update related classes to use LeaderboardManager, and fix query/logic for best time retrieval. 2025-11-11 17:27:24 +01:00
e6848b27a0 Refactor FightTable to use WinningTeam enum for win column and adjust related logic.
All checks were successful
SteamWarCI Build successful
Signed-off-by: Chaoscaot <max@maxsp.de>
2025-11-11 17:25:38 +01:00
93d0011b46 Update openMaterialSelector to exclude non-item materials in UtilGui.
All checks were successful
SteamWarCI Build successful
Signed-off-by: Chaoscaot <max@maxsp.de>
2025-11-11 17:20:33 +01:00
f56fedefe4 Fix Obsidian and Bedrock
All checks were successful
SteamWarCI Build successful
2025-11-11 17:07:56 +01:00
167bb4e266 Fix YMLWrapper
All checks were successful
SteamWarCI Build successful
2025-11-11 17:05:46 +01:00
f6a9ce184b Closes: #214
All checks were successful
SteamWarCI Build successful
Closes: #215
2025-11-11 16:40:26 +01:00
d5708c110c Refactor NodeDataTable to use default current timestamp for createdAt and ensure schemData reads blob as bytes.
All checks were successful
SteamWarCI Build successful
Signed-off-by: Chaoscaot <max@maxsp.de>
2025-11-11 00:18:16 +01:00
f23c1215b7 Refactor NodeDataTable to use insert instead of insertIgnore and add blobParam import.
All checks were successful
SteamWarCI Build successful
Signed-off-by: Chaoscaot <max@maxsp.de>
2025-11-11 00:15:19 +01:00
cfcaf1569c Add GameModeConfig.Schematic.MaxBlastResistance
All checks were successful
SteamWarCI Build successful
2025-11-10 21:39:35 +01:00
dc56b67ff6 Sort fights by start time in descending order and refactor saveFromStream method to use useDb block.
All checks were successful
SteamWarCI Build successful
Signed-off-by: Chaoscaot <max@maxsp.de>
2025-11-10 14:18:13 +01:00
13c4a3ff8b Sort fights by start time in descending order and refactor saveFromStream method to use useDb block.
All checks were successful
SteamWarCI Build successful
Signed-off-by: Chaoscaot <max@maxsp.de>
2025-11-10 12:27:02 +01:00
7e5a13989b Sort fights by start time in descending order and refactor saveFromStream method to use useDb block.
All checks were successful
SteamWarCI Build successful
Signed-off-by: Chaoscaot <max@maxsp.de>
2025-11-10 12:24:51 +01:00
990a03ca6c Add Exposed library dependencies to WebsiteBackend build script.
All checks were successful
SteamWarCI Build successful
Signed-off-by: Chaoscaot <max@maxsp.de>
2025-11-10 11:57:12 +01:00
4a608fe5b5 Fix SchematicType.fromDB
All checks were successful
SteamWarCI Build successful
2025-11-10 11:55:17 +01:00
5ca15aa117 Refactor PersonalKitTable to use explicit ID column initialization and update create method to utilize insert operation.
All checks were successful
SteamWarCI Build successful
Signed-off-by: Chaoscaot <max@maxsp.de>
2025-11-10 11:50:39 +01:00
9b459bd12c Refactor PersonalKitTable to use explicit ID column initialization and update create method to utilize insert operation.
Some checks failed
SteamWarCI Build failed
Signed-off-by: Chaoscaot <max@maxsp.de>
2025-11-10 11:40:34 +01:00
b20d37e252 Refactor PersonalKitTable to use explicit ID column initialization and update create method to utilize insert operation.
Some checks failed
SteamWarCI Build failed
Signed-off-by: Chaoscaot <max@maxsp.de>
2025-11-10 11:40:20 +01:00
a8c98594a9 Fix GameModeConfig.Schematic.SubTypes
All checks were successful
SteamWarCI Build successful
2025-11-10 11:32:03 +01:00
1c076b5bbf Fix incorrect field references in BauweltMember methods.
All checks were successful
SteamWarCI Build successful
Signed-off-by: Chaoscaot <max@maxsp.de>
2025-11-10 11:19:56 +01:00
133ddb39cc Add ID column initialization for BauweltMember
All checks were successful
SteamWarCI Build successful
Signed-off-by: Chaoscaot <max@maxsp.de>
2025-11-10 11:09:57 +01:00
ebb375d8d6 Improve messages
All checks were successful
SteamWarCI Build successful
2025-11-10 11:04:29 +01:00
8ada9a6335 Fix UserElo.getEloFromDb
All checks were successful
SteamWarCI Build successful
2025-11-10 11:01:31 +01:00
0760a5a08a Update build scripts: migrate to Spigot API, adjust JVM toolchains and Java version.
All checks were successful
SteamWarCI Build successful
Signed-off-by: Chaoscaot <max@maxsp.de>
2025-11-10 09:42:24 +01:00
852ff95757 Update build scripts: migrate to Spigot API, adjust JVM toolchains and Java version.
Some checks failed
SteamWarCI Build failed
Signed-off-by: Chaoscaot <max@maxsp.de>
2025-11-10 09:40:36 +01:00
5c7d388876 Remove unused SLF4J exclusion from shadowJar task.
All checks were successful
SteamWarCI Build successful
Signed-off-by: Chaoscaot <max@maxsp.de>
2025-11-10 09:34:44 +01:00
4f92f8132d Remove unused SLF4J exclusion from shadowJar task.
All checks were successful
SteamWarCI Build successful
Signed-off-by: Chaoscaot <max@maxsp.de>
2025-11-10 09:27:58 +01:00
56fb63f781 Add MySQL dependency to KotlinCore build script.
All checks were successful
SteamWarCI Build successful
Signed-off-by: Chaoscaot <max@maxsp.de>
2025-11-10 09:17:37 +01:00
273efff87d Remove unused api-version field from plugin.yml.
All checks were successful
SteamWarCI Build successful
Signed-off-by: Chaoscaot <max@maxsp.de>
2025-11-10 09:16:14 +01:00
0cac6f36c1 Remove unused api-version field from plugin.yml.
All checks were successful
SteamWarCI Build successful
Signed-off-by: Chaoscaot <max@maxsp.de>
2025-11-10 09:09:44 +01:00
8680b9e6cf Change api dependencies to compileOnlyApi in build.gradle.kts and rename KitName column to Name in PersonalKitTable.
All checks were successful
SteamWarCI Build successful
Signed-off-by: Chaoscaot <max@maxsp.de>
2025-11-10 09:02:23 +01:00
6c94efaf90 Improve TeamCommand.info
All checks were successful
SteamWarCI Build successful
2025-11-10 09:00:18 +01:00
f6261ad989 Improve output for DevCommand reloadmodes 2025-11-10 09:00:18 +01:00
614e989892 Rename IgnoreTable to IgnoredPlayers in IgnoreSystemTable for improved clarity.
All checks were successful
SteamWarCI Build successful
Signed-off-by: Chaoscaot <max@maxsp.de>
2025-11-10 08:47:32 +01:00
84e1f605bd Merge pull request 'Exposed!' (#179) from exposed into main
All checks were successful
SteamWarCI Build successful
Reviewed-on: #179
Reviewed-by: YoyoNow <yoyonow@noreply.localhost>
2025-11-10 08:42:59 +01:00
0ea5a62dbe Merge branch 'main' into exposed
All checks were successful
SteamWarCI Build successful
2025-11-10 08:40:42 +01:00
0eb6047139 Merge remote-tracking branch 'origin/exposed' into exposed
All checks were successful
SteamWarCI Build successful
2025-11-09 17:48:12 +01:00
fcebecf745 Add placement column to TeamTeilnahme table and corresponding property.
Signed-off-by: Chaoscaot <max@maxsp.de>
2025-11-09 17:48:02 +01:00
dd14e9f518 Cleanup Session
All checks were successful
SteamWarCI Build successful
2025-11-09 17:30:21 +01:00
6c5239c8fc Optimize imports across SQL module: replace explicit class imports with wildcard imports, remove unused imports for cleaner structure.
All checks were successful
SteamWarCI Build successful
Signed-off-by: Chaoscaot <max@maxsp.de>
2025-11-09 17:28:40 +01:00
fc71f47add Remove needsAdmin property from PunishmentType enum and refactor related declarations for cleaner structure.
All checks were successful
SteamWarCI Build successful
Signed-off-by: Chaoscaot <max@maxsp.de>
2025-11-09 17:25:19 +01:00
5d36393643 Refactor EventRelation, EventGroup, and EventFight classes: ensure database operations are enclosed in useDb, add default values for group points, and override delete methods. Update Gradle build script to include lombok dependencies.
All checks were successful
SteamWarCI Build successful
Signed-off-by: Chaoscaot <max@maxsp.de>
2025-11-08 22:44:22 +01:00
ebc6f50261 Refactor Gradle build scripts: update source set exclusions, add java-library plugin, and remove redundant configurations in SQL module.
Some checks failed
SteamWarCI Build failed
Signed-off-by: Chaoscaot <max@maxsp.de>
2025-11-08 13:52:27 +01:00
69cf219e39 Fix FlatteningWrapper21
All checks were successful
SteamWarCI Build successful
2025-11-07 22:47:05 +01:00
fb4e53165f Hotfix FixedFlagStorage.clear and FixedGlobalFlagStorage.clear
All checks were successful
SteamWarCI Build successful
2025-11-07 22:42:48 +01:00
415d783365 Hotfix SEND_COMMAND_FEEDBACK in 1.20+
All checks were successful
SteamWarCI Build successful
2025-11-07 22:17:30 +01:00
ee99708340 Hotfix (FA)WE Selection 1.20+
All checks were successful
SteamWarCI Build successful
2025-11-07 22:13:31 +01:00
c9d74fb656 Remove redundant SQL logger in useDb and extra newlines in SQLConfig.
All checks were successful
SteamWarCI Build successful
Signed-off-by: Chaoscaot <max@maxsp.de>
2025-11-07 18:59:01 +01:00
e50e52950b Refactor Token and SteamwarUser classes: enhance owner property with memoized transformation, increase hash generation key size, and ensure database usage in useDb block.
All checks were successful
SteamWarCI Build successful
Signed-off-by: Chaoscaot <max@maxsp.de>
2025-11-07 18:57:34 +01:00
cac0ae3e13 Hotfix MaterialLazyInit for 1.21
All checks were successful
SteamWarCI Build successful
2025-11-07 15:41:48 +01:00
f9b3dd34cf Trigger rebuild
All checks were successful
SteamWarCI Build successful
2025-11-07 15:25:20 +01:00
e5a61226ca Add newline at end of build.gradle.kts file for consistency.
All checks were successful
SteamWarCI Build successful
Signed-off-by: Chaoscaot <max@maxsp.de>
2025-11-07 15:06:37 +01:00
0c35ace5e2 Remove redundant Java installation paths in Gradle configuration.
Some checks failed
SteamWarCI Build failed
Signed-off-by: Chaoscaot <max@maxsp.de>
2025-11-07 14:54:30 +01:00
23bcf14ca5 Update Gradle configuration to specify multiple Java installation paths for compatibility.
Some checks failed
SteamWarCI Build failed
Signed-off-by: Chaoscaot <max@maxsp.de>
2025-11-07 14:53:07 +01:00
63a5fcff97 Add Kotlin plugin and configure build scripts for Kotlin compatibility, integrate exposed library dependencies, and enhance JVM settings.
Some checks failed
SteamWarCI Build failed
Signed-off-by: Chaoscaot <max@maxsp.de>
2025-11-07 14:40:10 +01:00
39b3cdb897 Introduce GDPRQuery command for GDPR data generation, refactor SQL statements, and improve null safety across modules.
Some checks failed
SteamWarCI Build failed
Signed-off-by: Chaoscaot <max@maxsp.de>
2025-11-07 12:42:15 +01:00
3ccfe92afb Remove DC prefix for discord send messages to cleanup the chat
All checks were successful
SteamWarCI Build successful
2025-11-07 08:46:26 +01:00
c17b76851d Improve chat from discord
All checks were successful
SteamWarCI Build successful
2025-11-07 08:44:07 +01:00
f0e18bfc72 Improve CouncilChannel and StaticMessageChannel
All checks were successful
SteamWarCI Build successful
2025-11-07 08:39:42 +01:00
160f982955 Improve ChatListener for STC Chat
All checks were successful
SteamWarCI Build successful
Add debug output for DiscordChannel
Fix CouncilChannel
2025-11-07 08:34:19 +01:00
6ac0143459 Remove outdated SQL-related Java classes (GDPRQuery, Field, SelectStatement, SqlTypeMapper, Statement) and update references across modules.
Some checks failed
SteamWarCI Build failed
Signed-off-by: Chaoscaot <max@maxsp.de>
2025-11-06 11:30:01 +01:00
98ca9e852c Merge pull request 'Add version dependant impl of tps warp to fight system' (#187) from FightSystem/Add-version-dependent-impl-of-tps-warp into main
All checks were successful
SteamWarCI Build successful
Reviewed-on: #187
Reviewed-by: Chaoscaot <max@chaoscaot.de>
2025-11-06 09:50:24 +01:00
D4rkr34lm
17910ec8a4 Add version dependant impl
All checks were successful
SteamWarCI Build successful
2025-11-06 02:07:20 +01:00
56bc75763b Migrate Token class to Kotlin, remove outdated Java implementation, and refactor references across modules. Update timestamp fields to use CurrentTimestamp where applicable.
Some checks failed
SteamWarCI Build failed
Signed-off-by: Chaoscaot <max@maxsp.de>
2025-11-05 21:44:52 +01:00
8272c73b48 Migrate SQL-related classes (Referee, SchemElo, Script, Session, SWException, TeamTeilnahme) to Kotlin and remove outdated Java implementations. Update references across modules.
Some checks failed
SteamWarCI Build failed
Signed-off-by: Chaoscaot <max@maxsp.de>
2025-11-05 17:47:30 +01:00
9acfb32ae0 Refactor SQL classes: update GameModeConfig functions, improve null safety in SchematicNode and SchematicType, and adjust NodeMemberTable structure.
Some checks failed
SteamWarCI Build failed
Signed-off-by: Chaoscaot <max@maxsp.de>
2025-11-05 17:04:01 +01:00
b2a4b05545 Merge branch 'main' into exposed
# Conflicts:
#	CommonCore/SQL/src/de/steamwar/sql/GameModeConfig.java
#	CommonCore/SQL/src/de/steamwar/sql/Punishment.java
2025-11-05 16:18:30 +01:00
89e2df0eb2 Migrate Punishment and PollAnswer classes to Kotlin and remove outdated Java implementations. Update references across modules.
Some checks failed
SteamWarCI Build failed
Signed-off-by: Chaoscaot <max@maxsp.de>
2025-11-04 21:17:53 +01:00
73da9179bf Migrate SQL-related classes (IgnoreSystem, Mod, NodeDownload, NodeMember) to Kotlin and remove old Java implementations. Update references across modules.
Some checks failed
SteamWarCI Build failed
Signed-off-by: Chaoscaot <max@maxsp.de>
2025-11-02 19:28:14 +01:00
0fbbcdacea Migrate Fight, FightPlayer, and SchematicType classes to Kotlin and remove old Java implementations. Update references across modules.
Some checks failed
SteamWarCI Build failed
Signed-off-by: Chaoscaot <max@maxsp.de>
2025-11-01 13:52:26 +01:00
4e6933f2fd Remove Event-related SQL classes and update relevant references across modules
Some checks failed
SteamWarCI Build failed
Signed-off-by: Chaoscaot <max@maxsp.de>
2025-10-30 23:14:25 +01:00
eea1073892 Migrate Event class to Kotlin and update references across modules
Some checks failed
SteamWarCI Build failed
Signed-off-by: Chaoscaot <max@maxsp.de>
2025-10-29 23:17:17 +01:00
702aa1cfa6 Refactor CheckedSchematic and migrate to Kotlin
Some checks failed
SteamWarCI Build failed
Signed-off-by: Chaoscaot <max@maxsp.de>
2025-10-29 17:42:29 +01:00
6ea73f4890 Refactor NodeData
Some checks failed
SteamWarCI Build failed
Signed-off-by: Chaoscaot <max@maxsp.de>
2025-10-28 22:25:31 +01:00
14e82f36a5 Rename .java to .kt
Signed-off-by: Chaoscaot <max@maxsp.de>
2025-10-28 22:25:31 +01:00
a8eaf3daa7 Fixes
Some checks failed
SteamWarCI Build failed
Signed-off-by: Chaoscaot <max@maxsp.de>
2025-10-28 21:31:57 +01:00
b51ea484e4 Refactor UserConfig
Some checks failed
SteamWarCI Build failed
Signed-off-by: Chaoscaot <max@maxsp.de>
2025-10-28 20:38:02 +01:00
8bef19ed8b Rename .java to .kt
Signed-off-by: Chaoscaot <max@maxsp.de>
2025-10-28 20:38:02 +01:00
1aff7b30a6 Refactor SchematicNode
Some checks failed
SteamWarCI Build failed
2025-10-28 19:03:30 +01:00
c8ac984ad3 Rename .java to .kt 2025-10-28 19:03:30 +01:00
a462231bab Starting...
Some checks failed
SteamWarCI Build failed
2025-10-27 18:34:31 +01:00
e6bbb76a0b Rename .java to .kt 2025-10-27 18:34:30 +01:00
eb63381b83 Refactor leaderboard management: replace UserConfig-based implementation with new Leaderboard SQL class, update related classes to use LeaderboardManager, and fix query/logic for best time retrieval.
All checks were successful
SteamWarCI Build successful
2025-10-20 16:39:35 +02:00
215 changed files with 6136 additions and 6712 deletions

View File

@@ -1,18 +1,20 @@
/*
* This file is a part of the SteamWar software.
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
* 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 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.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License along with this program.
* If not, see <https://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem;
@@ -44,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;
@@ -69,7 +72,6 @@ public class BauSystem extends JavaPlugin implements Listener {
@Getter
private static BauSystem instance;
@Getter
private SpigotLinker linker;
@Override
@@ -127,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
@@ -182,8 +186,7 @@ public class BauSystem extends JavaPlugin implements Listener {
@Override
public void run() {
if (TickManager.impl.isFrozen())
return;
if (TickManager.impl.isFrozen()) return;
if (counter >= delay) {
runnable.run();
cancel();
@@ -201,8 +204,7 @@ public class BauSystem extends JavaPlugin implements Listener {
@Override
public void run() {
if (TickManager.impl.isFrozen())
return;
if (TickManager.impl.isFrozen()) return;
if (counter >= (first ? delay : period)) {
first = false;
runnable.run();
@@ -218,4 +220,4 @@ public class BauSystem extends JavaPlugin implements Listener {
AtomicReference<BukkitTask> task = new AtomicReference<>();
task.set(runTaskTimer(plugin, () -> consumer.accept(task.get()), delay, period));
}
}
}

View File

@@ -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() {

View File

@@ -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();
}

View File

@@ -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;
@@ -198,7 +197,7 @@ public class TestblockCommand extends SWCommand {
@Override
public List<String> tabCompletes(CommandSender commandSender, PreviousArguments previousArguments, String s) {
List<String> 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 +205,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;
}

View File

@@ -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);

View File

@@ -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) {}
}

View File

@@ -1,18 +1,20 @@
/*
* This file is a part of the SteamWar software.
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
* 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 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.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License along with this program.
* If not, see <https://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.features.simulator;
@@ -20,9 +22,7 @@ package de.steamwar.bausystem.features.simulator;
import de.steamwar.bausystem.BauSystem;
import de.steamwar.bausystem.SWUtils;
import de.steamwar.bausystem.features.simulator.data.Simulator;
import de.steamwar.bausystem.features.simulator.display.SimulatorCursor;
import de.steamwar.bausystem.features.simulator.execute.SimulatorExecutor;
import de.steamwar.bausystem.features.simulator.SimulatorCursorManager;
import de.steamwar.command.PreviousArguments;
import de.steamwar.command.SWCommand;
import de.steamwar.command.TypeMapper;
@@ -40,7 +40,7 @@ import java.util.Collection;
public class SimulatorCommand extends SWCommand {
@LinkedInstance
public SimulatorCursorManager cursorManager;
public SimulatorCursor simulatorCursor;
public SimulatorCommand() {
super("sim", "simulator");
@@ -49,12 +49,12 @@ public class SimulatorCommand extends SWCommand {
@Register(description = "SIMULATOR_HELP")
public void genericCommand(@Validator Player p) {
SWUtils.giveItemToPlayer(p, SimulatorStorage.getWand(p));
cursorManager.showCursor(p, null);
simulatorCursor.calcCursor(p);
}
@Register(value = "change", description = "SIMULATOR_CHANGE_HELP")
public void change(@Validator Player p) {
if (!SimulatorUtils.isSimulatorItem(p.getInventory().getItemInMainHand()) && !SimulatorUtils.isSimulatorItem(p.getInventory().getItemInOffHand())) {
if (!SimulatorCursor.isSimulatorItem(p.getInventory().getItemInMainHand()) && !SimulatorCursor.isSimulatorItem(p.getInventory().getItemInOffHand())) {
BauSystem.MESSAGE.send("SIMULATOR_NO_SIM_IN_HAND", p);
return;
}

View File

@@ -0,0 +1,472 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.features.simulator;
import com.comphenix.tinyprotocol.TinyProtocol;
import de.steamwar.Reflection;
import de.steamwar.bausystem.BauSystem;
import de.steamwar.bausystem.Permission;
import de.steamwar.bausystem.SWUtils;
import de.steamwar.bausystem.features.simulator.data.Simulator;
import de.steamwar.bausystem.features.simulator.data.SimulatorElement;
import de.steamwar.bausystem.features.simulator.data.SimulatorGroup;
import de.steamwar.bausystem.features.simulator.data.observer.ObserverElement;
import de.steamwar.bausystem.features.simulator.data.observer.ObserverPhase;
import de.steamwar.bausystem.features.simulator.data.redstone.RedstoneElement;
import de.steamwar.bausystem.features.simulator.data.redstone.RedstonePhase;
import de.steamwar.bausystem.features.simulator.data.tnt.TNTElement;
import de.steamwar.bausystem.features.simulator.data.tnt.TNTPhase;
import de.steamwar.bausystem.features.simulator.execute.SimulatorExecutor;
import de.steamwar.bausystem.features.simulator.gui.SimulatorGroupGui;
import de.steamwar.bausystem.features.simulator.gui.SimulatorGui;
import de.steamwar.bausystem.features.simulator.gui.base.SimulatorBaseGui;
import de.steamwar.bausystem.utils.BauMemberUpdateEvent;
import de.steamwar.bausystem.utils.ItemUtils;
import de.steamwar.bausystem.utils.RayTraceUtils;
import de.steamwar.entity.REntity;
import de.steamwar.entity.REntityServer;
import de.steamwar.entity.RFallingBlockEntity;
import de.steamwar.inventory.SWAnvilInv;
import de.steamwar.linkage.Linked;
import de.steamwar.linkage.MinVersion;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.block.BlockFace;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.block.Action;
import org.bukkit.event.player.*;
import org.bukkit.inventory.ItemStack;
import org.bukkit.util.Vector;
import java.util.*;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.stream.Collectors;
@Linked
@MinVersion(19)
public class SimulatorCursor implements Listener {
private static final World WORLD = Bukkit.getWorlds().get(0);
private Class<?> position = Reflection.getClass("net.minecraft.network.protocol.game.ServerboundMovePlayerPacket$Pos");
private Class<?> look = Reflection.getClass("net.minecraft.network.protocol.game.ServerboundMovePlayerPacket$Rot");
private Class<?> positionLook = Reflection.getClass("net.minecraft.network.protocol.game.ServerboundMovePlayerPacket$PosRot");
private static Map<Player, CursorType> cursorType = Collections.synchronizedMap(new HashMap<>());
private static Map<Player, REntityServer> cursors = Collections.synchronizedMap(new HashMap<>());
private static final Set<Player> calculating = new HashSet<>();
public static boolean isSimulatorItem(ItemStack itemStack) {
return ItemUtils.isItem(itemStack, "simulator");
}
public SimulatorCursor() {
BiFunction<Player, Object, Object> function = (player, object) -> {
calcCursor(player);
return object;
};
TinyProtocol.instance.addFilter(position, function);
TinyProtocol.instance.addFilter(look, function);
TinyProtocol.instance.addFilter(positionLook, function);
}
@EventHandler
public void onPlayerJoin(PlayerJoinEvent event) {
if (!Permission.BUILD.hasPermission(event.getPlayer())) return;
Bukkit.getScheduler().runTaskLater(BauSystem.getInstance(), () -> {
calcCursor(event.getPlayer());
}, 0);
}
@EventHandler
public void onPlayerDropItem(PlayerDropItemEvent event) {
if (!Permission.BUILD.hasPermission(event.getPlayer())) return;
calcCursor(event.getPlayer());
}
@EventHandler
public void onPlayerItemHeld(PlayerItemHeldEvent event) {
if (!Permission.BUILD.hasPermission(event.getPlayer())) return;
Bukkit.getScheduler().runTaskLater(BauSystem.getInstance(), () -> {
calcCursor(event.getPlayer());
}, 1);
}
@EventHandler
public void onBauMemberUpdate(BauMemberUpdateEvent event) {
event.getChanged().forEach(SimulatorCursor::calcCursor);
}
@EventHandler
public void onPlayerQuit(PlayerQuitEvent event) {
cursorType.remove(event.getPlayer());
cursors.remove(event.getPlayer());
synchronized (calculating) {
calculating.remove(event.getPlayer());
}
}
private static final Map<Player, Long> LAST_SNEAKS = new HashMap<>();
static {
Bukkit.getScheduler().runTaskTimer(BauSystem.getInstance(), () -> {
long millis = System.currentTimeMillis();
LAST_SNEAKS.entrySet().removeIf(entry -> millis - entry.getValue() > 200);
}, 1, 1);
}
@EventHandler(priority = EventPriority.HIGH)
public void onPlayerToggleSneak(PlayerToggleSneakEvent event) {
if (!event.isSneaking()) return;
Player player = event.getPlayer();
if (!isSimulatorItem(player.getInventory().getItemInMainHand()) && !isSimulatorItem(player.getInventory().getItemInOffHand())) {
return;
}
if (LAST_SNEAKS.containsKey(player)) {
CursorType currentType = cursorType.getOrDefault(player, CursorType.TNT);
if (currentType == CursorType.TNT) {
cursorType.put(player, CursorType.REDSTONE_BLOCK);
} else {
cursorType.put(player, CursorType.TNT);
}
calcCursor(player);
} else {
LAST_SNEAKS.put(player, System.currentTimeMillis());
}
}
public static CursorType getCursorType(Player player) {
return cursorType.getOrDefault(player, CursorType.TNT);
}
public static void setCursorType(Player player, CursorType cursorType) {
SimulatorCursor.cursorType.put(player, cursorType);
calcCursor(player);
}
public static void calcCursor(Player player) {
synchronized (calculating) {
if (calculating.contains(player)) return;
calculating.add(player);
}
if (!Permission.BUILD.hasPermission(player) || (!isSimulatorItem(player.getInventory().getItemInMainHand()) && !isSimulatorItem(player.getInventory().getItemInOffHand()))) {
if (removeCursor(player) || SimulatorWatcher.show(null, player)) {
SWUtils.sendToActionbar(player, "");
}
synchronized (calculating) {
calculating.remove(player);
}
return;
}
Simulator simulator = SimulatorStorage.getSimulator(player);
if (simulator != null && simulator.getStabGenerator() != null) {
removeCursor(player);
SimulatorWatcher.show(null, player);
SWUtils.sendToActionbar(player, "§cGenerating Stab");
synchronized (calculating) {
calculating.remove(player);
}
return;
}
SimulatorWatcher.show(simulator, player);
List<REntity> entities = SimulatorWatcher.getEntitiesOfSimulator(simulator);
RayTraceUtils.RRayTraceResult rayTraceResult = RayTraceUtils.traceREntity(player, player.getLocation(), entities);
if (rayTraceResult == null) {
removeCursor(player);
if (simulator == null) {
SWUtils.sendToActionbar(player, "§eSelect Simulator");
} else {
SWUtils.sendToActionbar(player, "§eOpen Simulator");
}
synchronized (calculating) {
calculating.remove(player);
}
return;
}
showCursor(player, rayTraceResult, simulator != null);
synchronized (calculating) {
calculating.remove(player);
}
}
private static synchronized boolean removeCursor(Player player) {
REntityServer entityServer = cursors.get(player);
boolean hadCursor = entityServer != null && !entityServer.getEntities().isEmpty();
if (entityServer != null) {
entityServer.getEntities().forEach(REntity::die);
}
return hadCursor;
}
private static synchronized void showCursor(Player player, RayTraceUtils.RRayTraceResult rayTraceResult, boolean hasSimulatorSelected) {
REntityServer entityServer = cursors.computeIfAbsent(player, __ -> {
REntityServer rEntityServer = new REntityServer();
rEntityServer.addPlayer(player);
return rEntityServer;
});
CursorType type = cursorType.getOrDefault(player, CursorType.TNT);
REntity hitEntity = rayTraceResult.getHitEntity();
Location location = hitEntity != null ? new Vector(hitEntity.getX(), hitEntity.getY(), hitEntity.getZ()).toLocation(WORLD) :
type.position.apply(player, rayTraceResult).toLocation(WORLD);
Material material = hitEntity != null ? Material.GLASS : type.getMaterial();
List<RFallingBlockEntity> entities = entityServer.getEntitiesByType(RFallingBlockEntity.class);
entities.removeIf(rFallingBlockEntity -> {
if (rFallingBlockEntity.getMaterial() != material) {
rFallingBlockEntity.die();
return true;
}
rFallingBlockEntity.move(location);
return false;
});
if (entities.isEmpty()) {
RFallingBlockEntity rFallingBlockEntity = new RFallingBlockEntity(entityServer, location, material);
rFallingBlockEntity.setNoGravity(true);
if (material == Material.GLASS) {
rFallingBlockEntity.setGlowing(true);
}
}
if (hasSimulatorSelected) {
if (hitEntity != null) {
SWUtils.sendToActionbar(player, "§eEdit Position");
} else {
SWUtils.sendToActionbar(player, "§eAdd new " + type.name);
}
} else {
SWUtils.sendToActionbar(player, "§eCreate new Simulator");
}
}
public static Vector getPosFree(Player player, RayTraceUtils.RRayTraceResult result) {
Vector pos = result.getHitPosition();
BlockFace face = result.getHitBlockFace();
if (face != null) {
switch (face) {
case DOWN:
pos.setY(pos.getY() - 0.98);
break;
case EAST:
pos.setX(pos.getX() + 0.49);
break;
case WEST:
pos.setX(pos.getX() - 0.49);
break;
case NORTH:
pos.setZ(pos.getZ() - 0.49);
break;
case SOUTH:
pos.setZ(pos.getZ() + 0.49);
break;
default:
break;
}
if (face.getModY() == 0 && player.isSneaking()) {
pos.setY(pos.getY() - 0.49);
}
}
if (!player.isSneaking()) {
pos.setX(pos.getBlockX() + 0.5);
if (face == null || face.getModY() == 0)
pos.setY(pos.getBlockY() + 0.0);
pos.setZ(pos.getBlockZ() + 0.5);
}
return pos;
}
private static Vector getPosBlockAligned(Player player, RayTraceUtils.RRayTraceResult result) {
Vector pos = result.getHitPosition();
BlockFace face = result.getHitBlockFace();
if (face != null) {
switch (face) {
case DOWN:
pos.setY(pos.getY() - 0.98);
break;
case EAST:
pos.setX(pos.getX() + 0.49);
break;
case WEST:
pos.setX(pos.getX() - 0.49);
break;
case NORTH:
pos.setZ(pos.getZ() - 0.49);
break;
case SOUTH:
pos.setZ(pos.getZ() + 0.49);
break;
default:
break;
}
}
pos.setX(pos.getBlockX() + 0.5);
if (pos.getY() - pos.getBlockY() != 0 && face == BlockFace.UP) {
pos.setY(pos.getBlockY() + 1.0);
} else {
pos.setY(pos.getBlockY());
}
pos.setZ(pos.getBlockZ() + 0.5);
return pos;
}
@Getter
@AllArgsConstructor
public enum CursorType {
TNT(Material.TNT, Material.GUNPOWDER, SimulatorCursor::getPosFree, "TNT", vector -> new TNTElement(vector).add(new TNTPhase())),
REDSTONE_BLOCK(Material.REDSTONE_BLOCK, Material.REDSTONE, SimulatorCursor::getPosBlockAligned, "Redstone Block", vector -> new RedstoneElement(vector).add(new RedstonePhase())),
OBSERVER(Material.OBSERVER, Material.QUARTZ, SimulatorCursor::getPosBlockAligned, "Observer", vector -> new ObserverElement(vector).add(new ObserverPhase())),
;
public final Material material;
public final Material nonSelectedMaterial;
public final BiFunction<Player, RayTraceUtils.RRayTraceResult, Vector> position;
public final String name;
public final Function<Vector, SimulatorElement<?>> elementFunction;
}
@EventHandler
public void onPlayerInteract(PlayerInteractEvent event) {
if (!Permission.BUILD.hasPermission(event.getPlayer())) return;
if (!ItemUtils.isItem(event.getItem(), "simulator")) {
return;
}
event.setCancelled(true);
Player player = event.getPlayer();
Simulator simulator = SimulatorStorage.getSimulator(player);
if (event.getAction() == Action.LEFT_CLICK_BLOCK || event.getAction() == Action.LEFT_CLICK_AIR) {
if (simulator == null) {
return;
}
SimulatorExecutor.run(event.getPlayer(), simulator, null);
return;
}
if (event.getAction() != Action.RIGHT_CLICK_BLOCK && event.getAction() != Action.RIGHT_CLICK_AIR) {
return;
}
RayTraceUtils.RRayTraceResult rayTraceResult = RayTraceUtils.traceREntity(player, player.getLocation(), SimulatorWatcher.getEntitiesOfSimulator(simulator));
if (simulator == null) {
if (rayTraceResult == null) {
SimulatorStorage.openSimulatorSelector(player);
} else {
SWAnvilInv anvilInv = new SWAnvilInv(player, "Name");
anvilInv.setCallback(s -> {
Simulator sim = SimulatorStorage.getSimulator(s);
if (sim != null) {
BauSystem.MESSAGE.send("SIMULATOR_NAME_ALREADY_EXISTS", player);
return;
}
if (!s.matches("[a-zA-Z_0-9-]+")) {
BauSystem.MESSAGE.send("SIMULATOR_NAME_INVALID", player);
return;
}
sim = new Simulator(s);
SimulatorStorage.addSimulator(s, sim);
createElement(player, rayTraceResult, sim);
SimulatorStorage.setSimulator(player, sim);
});
anvilInv.open();
}
return;
}
if (rayTraceResult == null) {
new SimulatorGui(player, simulator).open();
return;
}
if (rayTraceResult.getHitEntity() != null) {
REntity hitEntity = rayTraceResult.getHitEntity();
Vector vector = new Vector(hitEntity.getX(), hitEntity.getY(), hitEntity.getZ());
List<SimulatorElement<?>> elements = simulator.getGroups().stream().map(SimulatorGroup::getElements).flatMap(List::stream).filter(element -> {
return element.getWorldPos().distanceSquared(vector) < (1 / 16.0) * (1 / 16.0);
}).collect(Collectors.toList());
switch (elements.size()) {
case 0:
return;
case 1:
// Open single element present in Simulator
SimulatorElement<?> element = elements.get(0);
SimulatorGroup group1 = element.getGroup(simulator);
SimulatorBaseGui back = new SimulatorGui(player, simulator);
if (group1.getElements().size() > 1) {
back = new SimulatorGroupGui(player, simulator, group1, back);
}
element.open(player, simulator, group1, back);
break;
default:
List<SimulatorGroup> parents = elements.stream().map(e -> e.getGroup(simulator)).distinct().collect(Collectors.toList());
if (parents.size() == 1) {
// Open multi element present in Simulator in existing group
SimulatorGui simulatorGui = new SimulatorGui(player, simulator);
new SimulatorGroupGui(player, simulator, parents.get(0), simulatorGui).open();
} else {
// Open multi element present in Simulator in implicit group
SimulatorGroup group2 = new SimulatorGroup();
group2.setMaterial(null);
group2.getElements().addAll(elements);
SimulatorGui simulatorGui = new SimulatorGui(player, simulator);
new SimulatorGroupGui(player, simulator, group2, simulatorGui).open();
}
break;
}
return;
}
// Add new Element to current simulator
createElement(player, rayTraceResult, simulator);
}
private void createElement(Player player, RayTraceUtils.RRayTraceResult rayTraceResult, Simulator simulator) {
CursorType type = cursorType.getOrDefault(player, CursorType.TNT);
Vector vector = type.position.apply(player, rayTraceResult);
if (type == CursorType.REDSTONE_BLOCK) {
vector.subtract(new Vector(0.5, 0, 0.5));
}
SimulatorElement<?> element = type.elementFunction.apply(vector);
SimulatorGroup group = new SimulatorGroup().add(element);
simulator.getGroups().add(group);
SimulatorGui simulatorGui = new SimulatorGui(player, simulator);
element.open(player, simulator, group, simulatorGui);
SimulatorWatcher.update(simulator);
calcCursor(player);
}
}

View File

@@ -1,104 +0,0 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2023 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify it under the terms of the
* GNU Affero General Public License as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License along with this program.
* If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.features.simulator;
import de.steamwar.bausystem.features.simulator.data.Simulator;
import de.steamwar.bausystem.features.simulator.display.ASimulatorCursor;
import de.steamwar.bausystem.features.simulator.display.EmptySimulatorCursor;
import de.steamwar.bausystem.features.simulator.display.SimulatorCursor;
import de.steamwar.bausystem.features.simulator.display.SimulatorCursorMode;
import de.steamwar.linkage.Linked;
import de.steamwar.linkage.MinVersion;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.*;
import org.bukkit.inventory.ItemStack;
import java.util.*;
// TODO fix memory leak when players leave the server
// TODO fix cursor not being hidden when player deequips simulator item
// TODO fix simulator onclick
@Linked
@MinVersion(19)
public class SimulatorCursorManager implements Listener {
public static final SimulatorCursorManager INSTANCE = new SimulatorCursorManager();
private final Map<Player, Map<Simulator, ASimulatorCursor>> openCursorsByPlayerBySimulator = new HashMap<>();
private final Map<Player, ASimulatorCursor> activeCursorByPlayer = new HashMap<>();
@EventHandler
public void onPlayerItemEquip(PlayerItemHeldEvent event) {
ItemStack newItem = event.getPlayer().getInventory().getItem(event.getNewSlot());
if (SimulatorUtils.isSimulatorItem(newItem)) {
Simulator simulator = SimulatorStorage.getSimulator(newItem);
showCursor(event.getPlayer(), simulator);
}
}
/**
* Shows the cursor for the given simulator to the given player. Shows an empty cursor if the
* simulator is null.
*/
public void showCursor(Player player, Simulator simulator) {
var currentActiveCursor = activeCursorByPlayer.get(player);
if (currentActiveCursor != null) {
currentActiveCursor.hide();
}
var cursorMode = currentActiveCursor != null ? currentActiveCursor.getCursorMode() : SimulatorCursorMode.TNT;
var cursorsBySimulator = openCursorsByPlayerBySimulator.computeIfAbsent(player, __ -> new HashMap<>());
var cursor = cursorsBySimulator.get(simulator);
if (cursor == null) {
if (simulator == null) {
ASimulatorCursor emptyCursor = new EmptySimulatorCursor(player, cursorMode);
cursorsBySimulator.put(simulator, emptyCursor);
cursor = emptyCursor;
} else {
ASimulatorCursor newCursor = new SimulatorCursor(simulator, player, cursorMode);
cursorsBySimulator.put(simulator, newCursor);
cursor = newCursor;
}
}
cursor.show();
}
public Optional<SimulatorCursorMode> getActiveCursorMode(Player player) {
var currentActiveCursor = activeCursorByPlayer.get(player);
if (currentActiveCursor != null) {
return Optional.of(currentActiveCursor.getCursorMode());
} else {
return Optional.empty();
}
}
public void setActiveCursorMode(Player player, SimulatorCursorMode mode) {
var currentActiveCursor = activeCursorByPlayer.get(player);
if (currentActiveCursor != null) {
currentActiveCursor.setCursorMode(mode);
}
}
}

View File

@@ -1,18 +1,20 @@
/*
* This file is a part of the SteamWar software.
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
* 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 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.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License along with this program.
* If not, see <https://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.features.simulator;
@@ -54,13 +56,12 @@ public class SimulatorStorage implements Enable {
public static Simulator getSimulator(Player player) {
Simulator simulator = getSimulator(player.getInventory().getItemInMainHand());
if (simulator != null)
return simulator;
if (simulator != null) return simulator;
return getSimulator(player.getInventory().getItemInOffHand());
}
public static Simulator getSimulator(ItemStack itemStack) {
if (!SimulatorUtils.isSimulatorItem(itemStack)) {
if (!SimulatorCursor.isSimulatorItem(itemStack)) {
return null;
}
String selection = ItemUtils.getTag(itemStack, simulatorSelection);
@@ -85,11 +86,11 @@ public class SimulatorStorage implements Enable {
YAPIONFormatSimulatorLoader yapionFormatSimulatorLoader = new YAPIONFormatSimulatorLoader();
File[] files = simulatorsDir.listFiles();
if (files == null)
return;
if (files == null) return;
for (File file : files) {
try {
List<Simulator> simulators = simFormatSimulatorLoader.load(file).orElse(null);
List<Simulator> simulators = simFormatSimulatorLoader.load(file)
.orElse(null);
if (simulators != null) {
simulators.forEach(simulator -> {
simulatorMap.put(simulator.getName(), simulator);
@@ -101,7 +102,8 @@ public class SimulatorStorage implements Enable {
}
try {
List<Simulator> simulators = simulatorFormatSimulatorLoader.load(file).orElse(null);
List<Simulator> simulators = simulatorFormatSimulatorLoader.load(file)
.orElse(null);
if (simulators != null) {
simulators.forEach(simulator -> {
simulatorMap.put(simulator.getName(), simulator);
@@ -114,7 +116,8 @@ public class SimulatorStorage implements Enable {
}
try {
List<Simulator> simulators = yapionFormatSimulatorLoader.load(file).orElse(null);
List<Simulator> simulators = yapionFormatSimulatorLoader.load(file)
.orElse(null);
if (simulators != null) {
simulators.forEach(simulator -> {
simulatorMap.put(simulator.getName(), simulator);
@@ -128,8 +131,7 @@ public class SimulatorStorage implements Enable {
}
public static void openSimulatorSelector(Player player) {
SimulatorPageGui<Simulator> simulatorPageGui = new SimulatorPageGui<Simulator>(player, null, 6 * 9,
simulatorMap.values().stream().sorted(Comparator.comparing(Simulator::getName)).collect(Collectors.toList())) {
SimulatorPageGui<Simulator> simulatorPageGui = new SimulatorPageGui<Simulator>(player, null, 6 * 9, simulatorMap.values().stream().sorted(Comparator.comparing(Simulator::getName)).collect(Collectors.toList())) {
@Override
public String baseTitle() {
return "Simulators";
@@ -169,11 +171,7 @@ public class SimulatorStorage implements Enable {
}
public static ItemStack getWand(Player p) {
ItemStack itemStack = new SWItem(Material.BLAZE_ROD, BauSystem.MESSAGE.parse("SIMULATOR_WAND_NAME", p),
Arrays.asList(BauSystem.MESSAGE.parse("SIMULATOR_WAND_LORE_1", p), BauSystem.MESSAGE.parse("SIMULATOR_WAND_LORE_2", p),
BauSystem.MESSAGE.parse("SIMULATOR_WAND_LORE_3", p), BauSystem.MESSAGE.parse("SIMULATOR_WAND_LORE_4", p),
BauSystem.MESSAGE.parse("SIMULATOR_WAND_LORE_5", p)),
false, null).getItemStack();
ItemStack itemStack = new SWItem(Material.BLAZE_ROD, BauSystem.MESSAGE.parse("SIMULATOR_WAND_NAME", p), Arrays.asList(BauSystem.MESSAGE.parse("SIMULATOR_WAND_LORE_1", p), BauSystem.MESSAGE.parse("SIMULATOR_WAND_LORE_2", p), BauSystem.MESSAGE.parse("SIMULATOR_WAND_LORE_3", p), BauSystem.MESSAGE.parse("SIMULATOR_WAND_LORE_4", p), BauSystem.MESSAGE.parse("SIMULATOR_WAND_LORE_5", p)), false, null).getItemStack();
ItemMeta itemMeta = itemStack.getItemMeta();
itemMeta.setCustomModelData(1);
itemStack.setItemMeta(itemMeta);
@@ -185,9 +183,9 @@ public class SimulatorStorage implements Enable {
ItemStack mainHand = player.getInventory().getItemInMainHand();
ItemStack offHand = player.getInventory().getItemInOffHand();
ItemStack itemStack;
if (SimulatorUtils.isSimulatorItem(mainHand)) {
if (SimulatorCursor.isSimulatorItem(mainHand)) {
itemStack = mainHand;
} else if (SimulatorUtils.isSimulatorItem(offHand)) {
} else if (SimulatorCursor.isSimulatorItem(offHand)) {
itemStack = offHand;
} else {
itemStack = null;

View File

@@ -1,12 +0,0 @@
package de.steamwar.bausystem.features.simulator;
import org.bukkit.inventory.ItemStack;
import de.steamwar.bausystem.utils.ItemUtils;
import lombok.experimental.UtilityClass;
@UtilityClass
public class SimulatorUtils {
public static boolean isSimulatorItem(ItemStack itemStack) {
return ItemUtils.isItem(itemStack, "simulator");
}
}

View File

@@ -0,0 +1,129 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.features.simulator;
import de.steamwar.bausystem.features.simulator.data.Simulator;
import de.steamwar.bausystem.features.simulator.data.SimulatorElement;
import de.steamwar.bausystem.features.simulator.data.SimulatorGroup;
import de.steamwar.bausystem.features.simulator.storage.SimulatorSaver;
import de.steamwar.bausystem.shared.Pair;
import de.steamwar.entity.REntity;
import de.steamwar.entity.REntityServer;
import de.steamwar.entity.RFallingBlockEntity;
import de.steamwar.linkage.Linked;
import de.steamwar.linkage.MinVersion;
import lombok.experimental.UtilityClass;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.util.Vector;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;
@UtilityClass
public class SimulatorWatcher {
private final World WORLD = Bukkit.getWorlds().get(0);
private Map<Simulator, REntityServer> entityServers = new HashMap<>();
private Map<Player, Pair<Simulator, Runnable>> watchers = new HashMap<>();
public void watch(Player player, Simulator simulator, Runnable watcher) {
if (simulator == null || watcher == null) {
watchers.remove(player);
} else {
watchers.put(player, new Pair<>(simulator, watcher));
}
}
public synchronized void update(Simulator simulator) {
REntityServer rEntityServer = entityServers.get(simulator);
if (rEntityServer != null) {
rEntityServer.getEntities().forEach(REntity::die);
createSim(rEntityServer, simulator);
}
new ArrayList<>(watchers.values()).forEach(simulatorRunnablePair -> {
if (simulatorRunnablePair.getKey() == simulator) {
simulatorRunnablePair.getValue().run();
}
});
SimulatorSaver.saveSimulator(SimulatorStorage.simulatorsDir, simulator);
}
@Linked
@MinVersion(19)
public static class QuitListener implements Listener {
@EventHandler
public void onPlayerQuit(PlayerQuitEvent event) {
watchers.remove(event.getPlayer());
show(null, event.getPlayer());
}
}
private REntityServer createSim(REntityServer server, Simulator simulator) {
if (simulator == null) {
return null;
}
Map<Vector, Set<Class<?>>> positionCache = new HashMap<>();
simulator.getGroups().stream().map(group -> group.getElements().stream().map(element -> new Pair<>(group, element)).collect(Collectors.toList())).flatMap(List::stream).forEach(pair -> {
SimulatorGroup group = pair.getKey();
SimulatorElement<?> element = pair.getValue();
boolean wasNotPresent = positionCache.computeIfAbsent(element.getPosition(), __ -> new HashSet<>())
.add(element.getClass());
if (!wasNotPresent) return;
Material material = group.isDisabled() || element.isDisabled() ? element.getWorldDisabledMaterial() : element.getWorldMaterial();
Location location = element.getWorldPos().toLocation(WORLD);
RFallingBlockEntity rFallingBlockEntity = new RFallingBlockEntity(server, location, material);
rFallingBlockEntity.setNoGravity(true);
});
return server;
}
public synchronized boolean show(Simulator sim, Player player) {
AtomicBoolean removed = new AtomicBoolean();
entityServers.forEach((simulator, rEntityServer) -> {
if (rEntityServer == null) return;
if (rEntityServer.getPlayers().contains(player) && sim != simulator) {
rEntityServer.removePlayer(player);
removed.set(true);
}
});
if (sim == null) return removed.get();
entityServers.computeIfAbsent(sim, __ -> createSim(new REntityServer(), sim)).addPlayer(player);
return removed.get();
}
synchronized List<REntity> getEntitiesOfSimulator(Simulator simulator) {
REntityServer entityServer = entityServers.get(simulator);
if (entityServer == null) {
return Collections.emptyList();
}
return entityServer.getEntities();
}
}

View File

@@ -1,54 +0,0 @@
package de.steamwar.bausystem.features.simulator.display;
import java.util.List;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerToggleSneakEvent;
import de.steamwar.bausystem.utils.cursor.Cursor;
import de.steamwar.entity.REntityServer;
import lombok.Getter;
public abstract class ASimulatorCursor extends Cursor implements Listener {
protected final Player owner;
@Getter
protected SimulatorCursorMode cursorMode;
protected ASimulatorCursor(Player owner, REntityServer targetServer, SimulatorCursorMode cursorMode) {
super(targetServer, owner, Material.GLASS, Material.TNT, List.of(Cursor.CursorMode.FREE, Cursor.CursorMode.BLOCK_ALIGNED));
this.cursorMode = cursorMode;
this.owner = owner;
}
public void swapCursorMode() {
cursorMode = cursorMode.switchType();
setCursorMaterial(cursorMode.getMaterial());
setAllowedCursorModes(cursorMode.getAllowedCursorModes());
}
public void setCursorMode(SimulatorCursorMode mode) {
this.cursorMode = mode;
setCursorMaterial(cursorMode.getMaterial());
setAllowedCursorModes(cursorMode.getAllowedCursorModes());
}
public void hide() {
super.hide();
super.getTargetServer().removePlayer(owner);
}
public void show() {
super.show();
super.getTargetServer().addPlayer(owner);
}
@EventHandler
public void onPlayerShift(PlayerToggleSneakEvent event) {
if (event.getPlayer() != owner) {
return;
}
swapCursorMode();
}
}

View File

@@ -1,47 +0,0 @@
package de.steamwar.bausystem.features.simulator.display;
import java.util.Optional;
import org.bukkit.Location;
import org.bukkit.entity.Player;
import org.bukkit.event.block.Action;
import de.steamwar.bausystem.BauSystem;
import de.steamwar.bausystem.features.simulator.SimulatorStorage;
import de.steamwar.bausystem.features.simulator.data.Simulator;
import de.steamwar.bausystem.features.simulator.data.SimulatorElement;
import de.steamwar.bausystem.features.simulator.data.SimulatorGroup;
import de.steamwar.entity.REntityServer;
import de.steamwar.inventory.SWAnvilInv;
public class EmptySimulatorCursor extends ASimulatorCursor {
public EmptySimulatorCursor(Player owner, SimulatorCursorMode cursorMode) {
super(owner, new REntityServer(), cursorMode);
}
@Override
public void onClick(Optional<Location> clickedLocation, boolean clickedOnEntity, Action clickAction) {
if (clickAction == Action.LEFT_CLICK_AIR) {
SimulatorStorage.openSimulatorSelector(owner);
} else if (clickAction == Action.LEFT_CLICK_BLOCK) {
SWAnvilInv anvilInv = new SWAnvilInv(owner, "Name");
anvilInv.setCallback(name -> {
Simulator sim = SimulatorStorage.getSimulator(name);
if (sim != null) {
BauSystem.MESSAGE.send("SIMULATOR_NAME_ALREADY_EXISTS", owner);
return;
}
if (!name.matches("[a-zA-Z_0-9-]+")) {
BauSystem.MESSAGE.send("SIMULATOR_NAME_INVALID", owner);
return;
}
sim = new Simulator(name);
SimulatorStorage.addSimulator(name, sim);
SimulatorElement<?> element = cursorMode.getElementFunction().apply(clickedLocation.get().toVector());
SimulatorGroup group = new SimulatorGroup();
group.add(element);
sim.add(group);
SimulatorStorage.setSimulator(owner, sim);
});
anvilInv.open();
}
}
}

View File

@@ -1,69 +0,0 @@
package de.steamwar.bausystem.features.simulator.display;
import java.util.List;
import java.util.Optional;
import org.bukkit.Location;
import org.bukkit.entity.Player;
import org.bukkit.event.block.Action;
import org.bukkit.util.Vector;
import de.steamwar.bausystem.features.simulator.data.Simulator;
import de.steamwar.bausystem.features.simulator.data.SimulatorElement;
import de.steamwar.bausystem.features.simulator.data.SimulatorGroup;
import de.steamwar.bausystem.features.simulator.execute.SimulatorExecutor;
import de.steamwar.bausystem.features.simulator.gui.SimulatorGroupGui;
import de.steamwar.bausystem.features.simulator.gui.SimulatorGui;
import de.steamwar.bausystem.features.simulator.gui.base.SimulatorBaseGui;
public class SimulatorCursor extends ASimulatorCursor {
private final Simulator simulator;
public SimulatorCursor(Simulator simulator, Player owner, SimulatorCursorMode cursorMode) {
super(owner, SimulatorRenderer.renderSimulator(simulator), cursorMode);
this.simulator = simulator;
}
@Override
public void onClick(Optional<Location> clickedLocation, boolean clickedOnEntity, Action clickAction) {
if (clickAction == Action.RIGHT_CLICK_AIR || clickAction == Action.RIGHT_CLICK_BLOCK) {
SimulatorExecutor.run(owner, simulator, null);
} else if (!clickedOnEntity || clickedLocation.isEmpty()) {
new SimulatorGui(owner, simulator);
} else {
Vector vector = clickedLocation.get().toVector();
List<SimulatorElement<?>> elements = simulator.getGroups().stream().map(SimulatorGroup::getElements).flatMap(List::stream)
.filter(element -> element.getWorldPos().distanceSquared(vector) < (1 / 16.0) * (1 / 16.0)).toList();
switch (elements.size()) {
case 0:
return;
case 1:
// Open single element present in Simulator
SimulatorElement<?> element = elements.get(0);
SimulatorGroup group1 = element.getGroup(simulator);
SimulatorBaseGui back = new SimulatorGui(owner, simulator);
if (group1.getElements().size() > 1) {
back = new SimulatorGroupGui(owner, simulator, group1, back);
}
element.open(owner, simulator, group1, back);
break;
default:
List<SimulatorGroup> parents = elements.stream().map(e -> e.getGroup(simulator)).distinct().toList();
if (parents.size() == 1) {
// Open multi element present in Simulator in existing group
SimulatorGui simulatorGui = new SimulatorGui(owner, simulator);
new SimulatorGroupGui(owner, simulator, parents.get(0), simulatorGui).open();
} else {
// Open multi element present in Simulator in implicit group
SimulatorGroup group2 = new SimulatorGroup();
group2.setMaterial(null);
group2.getElements().addAll(elements);
SimulatorGui simulatorGui = new SimulatorGui(owner, simulator);
new SimulatorGroupGui(owner, simulator, group2, simulatorGui).open();
}
break;
}
}
}
}

View File

@@ -1,41 +0,0 @@
package de.steamwar.bausystem.features.simulator.display;
import java.util.List;
import java.util.function.Function;
import org.bukkit.Material;
import org.bukkit.util.Vector;
import de.steamwar.bausystem.features.simulator.data.SimulatorElement;
import de.steamwar.bausystem.features.simulator.data.observer.ObserverElement;
import de.steamwar.bausystem.features.simulator.data.observer.ObserverPhase;
import de.steamwar.bausystem.features.simulator.data.redstone.RedstoneElement;
import de.steamwar.bausystem.features.simulator.data.redstone.RedstonePhase;
import de.steamwar.bausystem.features.simulator.data.tnt.TNTElement;
import de.steamwar.bausystem.features.simulator.data.tnt.TNTPhase;
import de.steamwar.bausystem.utils.cursor.Cursor.CursorMode;
import lombok.AllArgsConstructor;
import lombok.Getter;
@AllArgsConstructor
@Getter
public enum SimulatorCursorMode {
TNT(Material.TNT, Material.GUNPOWDER, "TNT", vector -> new TNTElement(vector).add(new TNTPhase()),
List.of(CursorMode.FREE, CursorMode.BLOCK_ALIGNED)), REDSTONE_BLOCK(Material.REDSTONE_BLOCK, Material.REDSTONE_WIRE, "Redstone Block",
vector -> new RedstoneElement(vector).add(new RedstonePhase()), List.of(CursorMode.BLOCK_ALIGNED)), OBSERVER(Material.OBSERVER, Material.QUARTZ,
"Observer", vector -> new ObserverElement(vector).add(new ObserverPhase()), List.of(CursorMode.BLOCK_ALIGNED)),;
public final Material material;
public final Material nonSelectedMaterial;
public final String name;
public final Function<Vector, SimulatorElement<?>> elementFunction;
public final List<CursorMode> allowedCursorModes;
public SimulatorCursorMode switchType() {
if (this == TNT) {
return REDSTONE_BLOCK;
}
if (this == REDSTONE_BLOCK) {
return OBSERVER;
}
return TNT;
}
}

View File

@@ -1,78 +0,0 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2023 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify it under the terms of the
* GNU Affero General Public License as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License along with this program.
* If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.features.simulator.display;
import de.steamwar.bausystem.features.simulator.SimulatorStorage;
import de.steamwar.bausystem.features.simulator.data.Simulator;
import de.steamwar.bausystem.features.simulator.data.SimulatorElement;
import de.steamwar.bausystem.features.simulator.data.SimulatorGroup;
import de.steamwar.bausystem.features.simulator.storage.SimulatorSaver;
import de.steamwar.bausystem.shared.Pair;
import de.steamwar.entity.REntity;
import de.steamwar.entity.REntityServer;
import de.steamwar.entity.RFallingBlockEntity;
import lombok.experimental.UtilityClass;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.event.Listener;
import org.bukkit.util.Vector;
import java.util.*;
import java.util.stream.Collectors;
@UtilityClass
public class SimulatorRenderer implements Listener {
private final World WORLD = Bukkit.getWorlds().get(0);
private Map<Simulator, REntityServer> entityServers = new HashMap<>();
public synchronized void update(Simulator simulator) {
renderSimulator(simulator);
SimulatorSaver.saveSimulator(SimulatorStorage.simulatorsDir, simulator);
}
public REntityServer renderSimulator(Simulator simulator) {
if (simulator == null) {
return null;
}
REntityServer server = entityServers.computeIfAbsent(simulator, __ -> new REntityServer());
server.getEntities().forEach(REntity::die);
Map<Vector, Set<Class<?>>> positionCache = new HashMap<>();
simulator.getGroups().stream().map(group -> group.getElements().stream().map(element -> new Pair<>(group, element)).toList()).flatMap(List::stream)
.forEach(pair -> {
SimulatorGroup group = pair.getKey();
SimulatorElement<?> element = pair.getValue();
boolean wasNotPresent = positionCache.computeIfAbsent(element.getPosition(), __ -> new HashSet<>()).add(element.getClass());
if (!wasNotPresent)
return;
Material material = group.isDisabled() || element.isDisabled() ? element.getWorldDisabledMaterial() : element.getWorldMaterial();
Location location = element.getWorldPos().toLocation(WORLD);
RFallingBlockEntity rFallingBlockEntity = new RFallingBlockEntity(server, location, material);
rFallingBlockEntity.setNoGravity(true);
});
return server;
}
}

View File

@@ -20,7 +20,7 @@
package de.steamwar.bausystem.features.simulator.execute;
import de.steamwar.bausystem.BauSystem;
import de.steamwar.bausystem.features.simulator.display.SimulatorRenderer;
import de.steamwar.bausystem.features.simulator.SimulatorWatcher;
import de.steamwar.bausystem.utils.bossbar.BauSystemBossbar;
import de.steamwar.bausystem.utils.bossbar.BossBarService;
import org.bukkit.Bukkit;
@@ -72,7 +72,7 @@ public abstract class StabStep {
protected final void stop() {
data.simulator.setStabGenerator(null);
SimulatorRenderer.update(data.simulator);
SimulatorWatcher.update(data.simulator);
for (Player player : Bukkit.getOnlinePlayers()) {
BauSystemBossbar bossbar = BossBarService.instance.get(player, data.region, "simulator_stab_generator");

View File

@@ -1,35 +1,35 @@
/*
* This file is a part of the SteamWar software.
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
* 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 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.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License along with this program.
* If not, see <https://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.features.simulator.gui;
import de.steamwar.bausystem.BauSystem;
import de.steamwar.bausystem.features.simulator.SimulatorCursorManager;
import de.steamwar.bausystem.features.simulator.SimulatorCursor;
import de.steamwar.bausystem.features.simulator.data.Simulator;
import de.steamwar.bausystem.features.simulator.display.SimulatorCursorMode;
import de.steamwar.bausystem.features.simulator.gui.base.SimulatorBaseGui;
import de.steamwar.data.CMDs;
import de.steamwar.inventory.SWItem;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import java.util.Collections;
public class SimulatorCursorSwitcherGui extends SimulatorBaseGui {
private final SimulatorCursorManager cursorManager = BauSystem.getInstance().getLinker().get(SimulatorCursorManager.class).orElseThrow();
private SimulatorBaseGui back;
@@ -50,12 +50,14 @@ public class SimulatorCursorSwitcherGui extends SimulatorBaseGui {
}).setCustomModelData(CMDs.BACK));
int slot = 2;
SimulatorCursorMode currentType = cursorManager.getActiveCursorMode(player).orElse(SimulatorCursorMode.TNT);
for (SimulatorCursorMode type : SimulatorCursorMode.values()) {
SimulatorCursor.CursorType currentType = SimulatorCursor.getCursorType(player);
for (SimulatorCursor.CursorType type : SimulatorCursor.CursorType.values()) {
boolean selected = type == currentType;
SWItem swItem = new SWItem(selected ? type.material : type.nonSelectedMaterial, "§e" + type.name)
.setCustomModelData(selected ? 0 : CMDs.Simulator.NEW_PHASE).setLore(Collections.singletonList("§eClick to select")).setCallback(click -> {
cursorManager.setActiveCursorMode(player, type);
.setCustomModelData(selected ? 0 : CMDs.Simulator.NEW_PHASE)
.setLore(Collections.singletonList("§eClick to select"))
.setCallback(click -> {
SimulatorCursor.setCursorType(player, type);
player.closeInventory();
});
inventory.setItem(slot, swItem);

View File

@@ -1,26 +1,28 @@
/*
* This file is a part of the SteamWar software.
* 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 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.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License along with this program.
* If not, see <https://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.features.simulator.gui;
import de.steamwar.bausystem.features.simulator.SimulatorWatcher;
import de.steamwar.bausystem.features.simulator.data.Simulator;
import de.steamwar.bausystem.features.simulator.data.SimulatorElement;
import de.steamwar.bausystem.features.simulator.data.SimulatorGroup;
import de.steamwar.bausystem.features.simulator.display.SimulatorRenderer;
import de.steamwar.bausystem.features.simulator.gui.base.SimulatorBaseGui;
import de.steamwar.bausystem.features.simulator.gui.base.SimulatorPageGui;
import de.steamwar.inventory.InvCallback;
@@ -36,11 +38,8 @@ public class SimulatorGroupChooserGui extends SimulatorPageGui<SimulatorGroup> {
private final SimulatorGroup parent;
private final SimulatorBaseGui back;
public SimulatorGroupChooserGui(Player player, Simulator simulator, SimulatorElement<?> subject,
SimulatorGroup parent, SimulatorBaseGui back) {
super(player, simulator, 6 * 9,
simulator.getGroups().stream().filter(e -> e != parent).filter(e -> subject.canBeInGroup(e))
.collect(Collectors.toList()));
public SimulatorGroupChooserGui(Player player, Simulator simulator, SimulatorElement<?> subject, SimulatorGroup parent, SimulatorBaseGui back) {
super(player, simulator, 6 * 9, simulator.getGroups().stream().filter(e -> e != parent).filter(e -> subject.canBeInGroup(e)).collect(Collectors.toList()));
this.subject = subject;
this.parent = parent;
this.back = back;
@@ -57,7 +56,7 @@ public class SimulatorGroupChooserGui extends SimulatorPageGui<SimulatorGroup> {
simulator.getGroups().add(newParent);
parent.getElements().remove(subject);
back.open();
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
if (parent.getElements().size() == 1) {
parent.setDisabled(false);
parent.setMaterial(Material.BARREL);
@@ -84,7 +83,7 @@ public class SimulatorGroupChooserGui extends SimulatorPageGui<SimulatorGroup> {
parent.setMaterial(Material.BARREL);
}
back.open();
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
};
return simulatorGroup.toItem(player, invCallback, invCallback);
}

View File

@@ -1,26 +1,28 @@
/*
* This file is a part of the SteamWar software.
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
* 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 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.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License along with this program.
* If not, see <https://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.features.simulator.gui;
import de.steamwar.bausystem.features.simulator.SimulatorWatcher;
import de.steamwar.bausystem.features.simulator.data.Simulator;
import de.steamwar.bausystem.features.simulator.data.SimulatorElement;
import de.steamwar.bausystem.features.simulator.data.SimulatorGroup;
import de.steamwar.bausystem.features.simulator.display.SimulatorRenderer;
import de.steamwar.bausystem.features.simulator.gui.base.SimulatorBaseGui;
import de.steamwar.bausystem.features.simulator.gui.base.SimulatorPageGui;
import de.steamwar.data.CMDs;
@@ -58,7 +60,7 @@ public class SimulatorGroupGui extends SimulatorPageGui<SimulatorElement<?>> {
}
if (simulatorGroup.getElements().removeIf(element -> element.getPhases().isEmpty())) {
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
}
if (simulatorGroup.getElements().size() < 2) {
back.open();
@@ -73,12 +75,11 @@ public class SimulatorGroupGui extends SimulatorPageGui<SimulatorElement<?>> {
inventory.setItem(8, new SWItem(Material.BARRIER, "§eDelete", clickType -> {
simulatorGroup.getElements().clear();
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.DELETE));
inventory.setItem(4, simulatorGroup.toItem(player, clickType -> {
if (simulatorGroup.getMaterial() == null)
return;
if (simulatorGroup.getMaterial() == null) return;
new SimulatorMaterialGui(player, simulator, simulatorGroup::getMaterial, simulatorGroup::setMaterial, this).open();
}, clickType -> {
}));
@@ -86,19 +87,17 @@ public class SimulatorGroupGui extends SimulatorPageGui<SimulatorElement<?>> {
inventory.setItem(48, new SWItem(Material.REPEATER, "§eSettings", clickType -> {
new SimulatorGroupSettingsGui(player, simulator, simulatorGroup, this).open();
}).setCustomModelData(CMDs.Simulator.SETTINGS));
boolean disabled = simulatorGroup.getMaterial() == null ? simulatorGroup.getElements().stream().allMatch(SimulatorElement::isDisabled)
: simulatorGroup.isDisabled();
inventory.setItem(50,
new SWItem(disabled ? Material.ENDER_PEARL : Material.ENDER_EYE, simulatorGroup.isDisabled() ? "§cDisabled" : "§aEnabled", clickType -> {
if (simulatorGroup.getMaterial() == null) {
simulatorGroup.getElements().forEach(simulatorElement -> {
simulatorElement.setDisabled(!disabled);
});
} else {
simulatorGroup.setDisabled(!disabled);
}
SimulatorRenderer.update(simulator);
}).setCustomModelData(CMDs.Simulator.ENABLED_OR_DISABLED));
boolean disabled = simulatorGroup.getMaterial() == null ? simulatorGroup.getElements().stream().allMatch(SimulatorElement::isDisabled) : simulatorGroup.isDisabled();
inventory.setItem(50, new SWItem(disabled ? Material.ENDER_PEARL : Material.ENDER_EYE, simulatorGroup.isDisabled() ? "§cDisabled" : "§aEnabled", clickType -> {
if (simulatorGroup.getMaterial() == null) {
simulatorGroup.getElements().forEach(simulatorElement -> {
simulatorElement.setDisabled(!disabled);
});
} else {
simulatorGroup.setDisabled(!disabled);
}
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.ENABLED_OR_DISABLED));
}
@Override

View File

@@ -1,26 +1,28 @@
/*
* This file is a part of the SteamWar software.
* 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 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.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License along with this program.
* If not, see <https://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.features.simulator.gui;
import de.steamwar.bausystem.features.simulator.SimulatorWatcher;
import de.steamwar.bausystem.features.simulator.data.Simulator;
import de.steamwar.bausystem.features.simulator.data.SimulatorGroup;
import de.steamwar.bausystem.features.simulator.data.tnt.TNTElement;
import de.steamwar.bausystem.features.simulator.display.SimulatorRenderer;
import de.steamwar.bausystem.features.simulator.gui.base.SimulatorAnvilGui;
import de.steamwar.bausystem.features.simulator.gui.base.SimulatorBaseGui;
import de.steamwar.data.CMDs;
@@ -35,8 +37,7 @@ public class SimulatorGroupSettingsGui extends SimulatorBaseGui {
private final SimulatorGroup simulatorGroup;
private final SimulatorBaseGui back;
public SimulatorGroupSettingsGui(Player player, Simulator simulator, SimulatorGroup simulatorGroup,
SimulatorBaseGui back) {
public SimulatorGroupSettingsGui(Player player, Simulator simulator, SimulatorGroup simulatorGroup, SimulatorBaseGui back) {
super(player, simulator, 5 * 9);
this.simulatorGroup = simulatorGroup;
this.back = back;
@@ -61,169 +62,164 @@ public class SimulatorGroupSettingsGui extends SimulatorBaseGui {
// Material Chooser
inventory.setItem(4, simulatorGroup.toItem(player, clickType -> {
if (simulatorGroup.getMaterial() == null)
return;
new SimulatorMaterialGui(player, simulator, simulatorGroup::getMaterial, simulatorGroup::setMaterial, this)
.open();
if (simulatorGroup.getMaterial() == null) return;
new SimulatorMaterialGui(player, simulator, simulatorGroup::getMaterial, simulatorGroup::setMaterial, this).open();
}, clickType -> {
}));
// Base Tick
int baseTicks = simulatorGroup.getBaseTick();
inventory.setItem(9,
new SWItem(SWItem.getDye(10), "§e+1", Arrays.asList("§7Shift§8: §e+5"), false, clickType -> {
simulatorGroup.changeBaseTicks(clickType.isShiftClick() ? 5 : 1);
SimulatorRenderer.update(simulator);
}).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED));
inventory.setItem(9, new SWItem(SWItem.getDye(10), "§e+1", Arrays.asList("§7Shift§8: §e+5"), false, clickType -> {
simulatorGroup.changeBaseTicks(clickType.isShiftClick() ? 5 : 1);
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED));
SWItem baseTick = new SWItem(Material.REPEATER, "§eTicks§8:§7 " + baseTicks, clickType -> {
new SimulatorAnvilGui<>(player, "Ticks", baseTicks + "", Integer::parseInt, integer -> {
if (integer < 0)
return false;
if (integer < 0) return false;
simulatorGroup.changeBaseTicks(integer - baseTicks);
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
return true;
}, this).setItem(Material.REPEATER).open();
});
baseTick.getItemStack().setAmount(Math.max(1, Math.min(baseTicks, 64)));
inventory.setItem(18, baseTick);
inventory.setItem(27, new SWItem(SWItem.getDye(baseTicks > 0 ? 1 : 8), "§e-1", Arrays.asList("§7Shift§8: §e-5"),
false, clickType -> {
if (baseTicks - (clickType.isShiftClick() ? 5 : 1) < 0) {
simulatorGroup.changeBaseTicks(-baseTicks);
} else {
simulatorGroup.changeBaseTicks(clickType.isShiftClick() ? -5 : -1);
}
SimulatorRenderer.update(simulator);
}).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED));
inventory.setItem(27, new SWItem(SWItem.getDye(baseTicks > 0 ? 1 : 8), "§e-1", Arrays.asList("§7Shift§8: §e-5"), false, clickType -> {
if (baseTicks - (clickType.isShiftClick() ? 5 : 1) < 0) {
simulatorGroup.changeBaseTicks(-baseTicks);
} else {
simulatorGroup.changeBaseTicks(clickType.isShiftClick() ? -5 : -1);
}
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED));
boolean allTNT = simulatorGroup.getElements().stream().allMatch(TNTElement.class::isInstance);
if (allTNT) {
// Subpixel Alignment
inventory.setItem(21, new SWItem(Material.SUNFLOWER, "§7Align§8: §eCenter", clickType -> {
simulatorGroup.getElements().stream().map(TNTElement.class::cast).forEach(tntElement -> {
tntElement.alignX(0);
tntElement.alignZ(0);
});
SimulatorRenderer.update(simulator);
simulatorGroup.getElements().stream()
.map(TNTElement.class::cast)
.forEach(tntElement -> {
tntElement.alignX(0);
tntElement.alignZ(0);
});
SimulatorWatcher.update(simulator);
}));
// Z
SWItem negativZItem = new SWItem(Material.OAK_BUTTON, "§7Align§8: §eNegativ Z", clickType -> {
simulatorGroup.getElements().stream().map(TNTElement.class::cast).forEach(tntElement -> {
if (tntElement.getAlignment().getZ() != -1)
tntElement.alignZ(-1);
});
SimulatorRenderer.update(simulator);
simulatorGroup.getElements().stream()
.map(TNTElement.class::cast)
.forEach(tntElement -> {
if (tntElement.getAlignment().getZ() != -1) tntElement.alignZ(-1);
});
SimulatorWatcher.update(simulator);
});
negativZItem.setEnchanted(
simulatorGroup.getElements().stream().map(TNTElement.class::cast)
.allMatch(tntElement -> tntElement.getAlignment().getZ() == -1));
negativZItem.setEnchanted(simulatorGroup.getElements().stream()
.map(TNTElement.class::cast)
.allMatch(tntElement -> tntElement.getAlignment().getZ() == -1));
inventory.setItem(20, negativZItem);
SWItem positivZItem = new SWItem(Material.OAK_BUTTON, "§7Align§8: §ePositiv Z", clickType -> {
simulatorGroup.getElements().stream().map(TNTElement.class::cast).forEach(tntElement -> {
if (tntElement.getAlignment().getZ() != 1)
tntElement.alignZ(1);
});
SimulatorRenderer.update(simulator);
simulatorGroup.getElements().stream()
.map(TNTElement.class::cast)
.forEach(tntElement -> {
if (tntElement.getAlignment().getZ() != 1) tntElement.alignZ(1);
});
SimulatorWatcher.update(simulator);
});
positivZItem.setEnchanted(
simulatorGroup.getElements().stream().map(TNTElement.class::cast)
.allMatch(tntElement -> tntElement.getAlignment().getZ() == 1));
positivZItem.setEnchanted(simulatorGroup.getElements().stream()
.map(TNTElement.class::cast)
.allMatch(tntElement -> tntElement.getAlignment().getZ() == 1));
inventory.setItem(22, positivZItem);
// X
SWItem negativXItem = new SWItem(Material.STONE_BUTTON, "§7Align§8: §eNegativ X", clickType -> {
simulatorGroup.getElements().stream().map(TNTElement.class::cast).forEach(tntElement -> {
if (tntElement.getAlignment().getX() != -1)
tntElement.alignX(-1);
});
simulatorGroup.getElements().stream()
.map(TNTElement.class::cast)
.forEach(tntElement -> {
if (tntElement.getAlignment().getX() != -1) tntElement.alignX(-1);
});
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
});
negativXItem.setEnchanted(
simulatorGroup.getElements().stream().map(TNTElement.class::cast)
.allMatch(tntElement -> tntElement.getAlignment().getX() == -1));
negativXItem.setEnchanted(simulatorGroup.getElements().stream()
.map(TNTElement.class::cast)
.allMatch(tntElement -> tntElement.getAlignment().getX() == -1));
inventory.setItem(12, negativXItem);
SWItem positivXItem = new SWItem(Material.STONE_BUTTON, "§7Align§8: §ePositiv X", clickType -> {
simulatorGroup.getElements().stream().map(TNTElement.class::cast).forEach(tntElement -> {
if (tntElement.getAlignment().getX() != 1)
tntElement.alignX(1);
});
SimulatorRenderer.update(simulator);
SWItem positivXItem = new SWItem(Material.STONE_BUTTON, "§7Align§8: §ePositiv X", clickType -> {
simulatorGroup.getElements().stream()
.map(TNTElement.class::cast)
.forEach(tntElement -> {
if (tntElement.getAlignment().getX() != 1) tntElement.alignX(1);
});
SimulatorWatcher.update(simulator);
});
positivXItem.setEnchanted(
simulatorGroup.getElements().stream().map(TNTElement.class::cast)
.allMatch(tntElement -> tntElement.getAlignment().getX() == -1));
positivXItem.setEnchanted(simulatorGroup.getElements().stream()
.map(TNTElement.class::cast)
.allMatch(tntElement -> tntElement.getAlignment().getX() == -1));
inventory.setItem(30, positivXItem);
}
// Pos X
inventory.setItem(15, new SWItem(SWItem.getDye(10), "§e+1",
Arrays.asList(allTNT ? "§7Shift§8: §e+0.0625" : "§7Shift§8: §e+5"), false, clickType -> {
simulatorGroup.move(clickType.isShiftClick() ? (allTNT ? 0.0625 : 5) : 1, 0, 0);
SimulatorRenderer.update(simulator);
}).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED));
//Pos X
inventory.setItem(15, new SWItem(SWItem.getDye(10), "§e+1", Arrays.asList(allTNT ? "§7Shift§8: §e+0.0625" : "§7Shift§8: §e+5"), false, clickType -> {
simulatorGroup.move(clickType.isShiftClick() ? (allTNT ? 0.0625 : 5) : 1, 0, 0);
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED));
inventory.setItem(24, new SWItem(Material.PAPER, "§eX", clickType -> {
new SimulatorAnvilGui<>(player, "Relative X", "", Double::parseDouble, number -> {
if (!allTNT) {
if(!allTNT){
number = (double) Math.round(number);
}
simulatorGroup.move(number, 0, 0);
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
return true;
}, this).setItem(Material.PAPER).open();
}));
inventory.setItem(33, new SWItem(SWItem.getDye(1), "§e-1",
Arrays.asList(allTNT ? "§7Shift§8: §e-0.0625" : "§7Shift§8: §e-5"), false, clickType -> {
simulatorGroup.move(clickType.isShiftClick() ? (allTNT ? -0.0625 : -5) : -1, 0, 0);
SimulatorRenderer.update(simulator);
}).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED));
inventory.setItem(33, new SWItem(SWItem.getDye(1), "§e-1", Arrays.asList(allTNT ? "§7Shift§8: §e-0.0625" : "§7Shift§8: §e-5"), false, clickType -> {
simulatorGroup.move(clickType.isShiftClick() ? (allTNT ? -0.0625 : -5) : -1, 0, 0);
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED));
// Pos Y
inventory.setItem(16, new SWItem(SWItem.getDye(10), "§e+1",
Arrays.asList(allTNT ? "§7Shift§8: §e+0.0625" : "§7Shift§8: §e+5"), false, clickType -> {
simulatorGroup.move(0, clickType.isShiftClick() ? (allTNT ? 0.0625 : 5) : 1, 0);
SimulatorRenderer.update(simulator);
}).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED));
//Pos Y
inventory.setItem(16, new SWItem(SWItem.getDye(10), "§e+1", Arrays.asList(allTNT ? "§7Shift§8: §e+0.0625" : "§7Shift§8: §e+5"), false, clickType -> {
simulatorGroup.move(0, clickType.isShiftClick() ? (allTNT ? 0.0625 : 5) : 1, 0);
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED));
inventory.setItem(25, new SWItem(Material.PAPER, "§eY", clickType -> {
new SimulatorAnvilGui<>(player, "Relative Y", "", Double::parseDouble, number -> {
if (!allTNT) {
if(!allTNT){
number = (double) Math.round(number);
}
simulatorGroup.move(0, number, 0);
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
return true;
}, this).setItem(Material.PAPER).open();
}));
inventory.setItem(34, new SWItem(SWItem.getDye(1), "§e-1",
Arrays.asList(allTNT ? "§7Shift§8: §e-0.0625" : "§7Shift§8: §e-5"), false, clickType -> {
simulatorGroup.move(0, clickType.isShiftClick() ? (allTNT ? -0.0625 : -5) : -1, 0);
SimulatorRenderer.update(simulator);
}).setCustomModelData(CMDs.Simulator.ENABLED_OR_DISABLED));
inventory.setItem(34, new SWItem(SWItem.getDye(1), "§e-1", Arrays.asList(allTNT ? "§7Shift§8: §e-0.0625" : "§7Shift§8: §e-5"), false, clickType -> {
simulatorGroup.move(0, clickType.isShiftClick() ? (allTNT ? -0.0625 : -5) : -1, 0);
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.ENABLED_OR_DISABLED));
// Pos Z
inventory.setItem(17, new SWItem(SWItem.getDye(10), "§e+1",
Arrays.asList(allTNT ? "§7Shift§8: §e+0.0625" : "§7Shift§8: §e+5"), false, clickType -> {
simulatorGroup.move(0, 0, clickType.isShiftClick() ? (allTNT ? 0.0625 : 5) : 1);
SimulatorRenderer.update(simulator);
}).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED));
//Pos Z
inventory.setItem(17, new SWItem(SWItem.getDye(10), "§e+1", Arrays.asList(allTNT ? "§7Shift§8: §e+0.0625" : "§7Shift§8: §e+5"), false, clickType -> {
simulatorGroup.move(0, 0, clickType.isShiftClick() ? (allTNT ? 0.0625 : 5) : 1);
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED));
inventory.setItem(26, new SWItem(Material.PAPER, "§eZ", clickType -> {
new SimulatorAnvilGui<>(player, "Relative Z", "", Double::parseDouble, number -> {
if (!allTNT) {
if(!allTNT){
number = (double) Math.round(number);
}
simulatorGroup.move(0, 0, number);
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
return true;
}, this).setItem(Material.PAPER).open();
}));
inventory.setItem(35, new SWItem(SWItem.getDye(1), "§e-1",
Arrays.asList(allTNT ? "§7Shift§8: §e-0.0625" : "§7Shift§8: §e-5"), false, clickType -> {
simulatorGroup.move(0, 0, clickType.isShiftClick() ? (allTNT ? -0.0625 : -5) : -1);
SimulatorRenderer.update(simulator);
}).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED));
inventory.setItem(35, new SWItem(SWItem.getDye(1), "§e-1", Arrays.asList(allTNT ? "§7Shift§8: §e-0.0625" : "§7Shift§8: §e-5"), false, clickType -> {
simulatorGroup.move(0, 0, clickType.isShiftClick() ? (allTNT ? -0.0625 : -5) : -1);
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED));
}
}

View File

@@ -1,30 +1,29 @@
/*
* This file is a part of the SteamWar software.
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
* 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 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.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License along with this program.
* If not, see <https://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.features.simulator.gui;
import de.steamwar.bausystem.BauSystem;
import de.steamwar.bausystem.features.simulator.SimulatorCursorManager;
import de.steamwar.bausystem.features.simulator.SimulatorCursor;
import de.steamwar.bausystem.features.simulator.SimulatorWatcher;
import de.steamwar.bausystem.features.simulator.data.Simulator;
import de.steamwar.bausystem.features.simulator.data.SimulatorElement;
import de.steamwar.bausystem.features.simulator.data.SimulatorGroup;
import de.steamwar.bausystem.features.simulator.display.SimulatorCursor;
import de.steamwar.bausystem.features.simulator.display.SimulatorCursorMode;
import de.steamwar.bausystem.features.simulator.display.SimulatorRenderer;
import de.steamwar.bausystem.features.simulator.gui.base.SimulatorPageGui;
import de.steamwar.data.CMDs;
import de.steamwar.inventory.SWItem;
@@ -33,8 +32,6 @@ import org.bukkit.entity.Player;
public class SimulatorGui extends SimulatorPageGui<SimulatorGroup> {
private final SimulatorCursorManager cursorManager = BauSystem.getInstance().getLinker().get(SimulatorCursorManager.class).orElseThrow();
public SimulatorGui(Player player, Simulator simulator) {
super(player, simulator, 6 * 9, simulator.getGroups());
}
@@ -46,15 +43,14 @@ public class SimulatorGui extends SimulatorPageGui<SimulatorGroup> {
@Override
public void headerAndFooter() {
if (simulator.getGroups().removeIf(element -> element.getElements().isEmpty()
|| element.getElements().stream().allMatch(simulatorElement -> simulatorElement.getPhases().isEmpty()))) {
SimulatorRenderer.update(simulator);
if (simulator.getGroups().removeIf(element -> element.getElements().isEmpty() || element.getElements().stream().allMatch(simulatorElement -> simulatorElement.getPhases().isEmpty()))) {
SimulatorWatcher.update(simulator);
}
inventory.setItem(4, simulator.toItem(player, clickType -> {
new SimulatorMaterialGui(player, simulator, simulator::getMaterial, simulator::setMaterial, this).open();
}));
SimulatorCursorMode cursorType = cursorManager.getActiveCursorMode(player).orElse(SimulatorCursorMode.TNT);
SimulatorCursor.CursorType cursorType = SimulatorCursor.getCursorType(player);
inventory.setItem(48, new SWItem(cursorType.material, "§7Placing §8-§e " + cursorType.name, clickType -> {
new SimulatorCursorSwitcherGui(player, simulator, this).open();
}));

View File

@@ -1,24 +1,26 @@
/*
* This file is a part of the SteamWar software.
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
* 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 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.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License along with this program.
* If not, see <https://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.features.simulator.gui;
import de.steamwar.bausystem.features.simulator.SimulatorWatcher;
import de.steamwar.bausystem.features.simulator.data.Simulator;
import de.steamwar.bausystem.features.simulator.display.SimulatorRenderer;
import de.steamwar.bausystem.features.simulator.gui.base.SimulatorBaseGui;
import de.steamwar.bausystem.features.simulator.gui.base.SimulatorPageGui;
import de.steamwar.data.CMDs;
@@ -34,8 +36,11 @@ import java.util.stream.Collectors;
public class SimulatorMaterialGui extends SimulatorPageGui<Material> {
private static final List<Material> MATERIALS = Arrays.stream(Material.values()).filter(material -> !material.isAir())
.filter(material -> !material.isLegacy()).filter(Material::isItem).collect(Collectors.toList());
private static final List<Material> MATERIALS = Arrays.stream(Material.values())
.filter(material -> !material.isAir())
.filter(material -> !material.isLegacy())
.filter(Material::isItem)
.collect(Collectors.toList());
private final Supplier<Material> currentMaterial;
private Material material;
@@ -55,7 +60,7 @@ public class SimulatorMaterialGui extends SimulatorPageGui<Material> {
return true;
}
change.accept(player.getItemOnCursor().getType());
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
return false;
}
@@ -76,10 +81,9 @@ public class SimulatorMaterialGui extends SimulatorPageGui<Material> {
@Override
public SWItem convert(Material material) {
return new SWItem(material, "§eNew Material", Arrays.asList(material == this.material ? "§eSelected" : "§eClick to select"), material == this.material,
clickType -> {
change.accept(material);
SimulatorRenderer.update(simulator);
});
return new SWItem(material, "§eNew Material", Arrays.asList(material == this.material ? "§eSelected" : "§eClick to select"), material == this.material, clickType -> {
change.accept(material);
SimulatorWatcher.update(simulator);
});
}
}

View File

@@ -1,27 +1,29 @@
/*
* This file is a part of the SteamWar software.
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
* 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 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.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License along with this program.
* If not, see <https://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.features.simulator.gui;
import de.steamwar.bausystem.features.simulator.SimulatorWatcher;
import de.steamwar.bausystem.features.simulator.data.Simulator;
import de.steamwar.bausystem.features.simulator.data.SimulatorGroup;
import de.steamwar.bausystem.features.simulator.data.observer.ObserverElement;
import de.steamwar.bausystem.features.simulator.data.observer.ObserverPhase;
import de.steamwar.bausystem.features.simulator.display.SimulatorRenderer;
import de.steamwar.bausystem.features.simulator.gui.base.SimulatorBaseGui;
import de.steamwar.bausystem.features.simulator.gui.base.SimulatorScrollGui;
import de.steamwar.data.CMDs;
@@ -58,7 +60,7 @@ public class SimulatorObserverGui extends SimulatorScrollGui<ObserverPhase> {
public void headerAndFooter() {
if (observer.getPhases().isEmpty()) {
back.open();
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
return;
}
@@ -85,7 +87,7 @@ public class SimulatorObserverGui extends SimulatorScrollGui<ObserverPhase> {
inventory.setItem(8, new SWItem(Material.BARRIER, "§eDelete", clickType -> {
observer.getPhases().clear();
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.DELETE));
// Material Chooser
@@ -99,11 +101,10 @@ public class SimulatorObserverGui extends SimulatorScrollGui<ObserverPhase> {
}).setCustomModelData(CMDs.Simulator.SETTINGS));
// Enable/Disable
inventory.setItem(48,
new SWItem(observer.isDisabled() ? Material.ENDER_PEARL : Material.ENDER_EYE, observer.isDisabled() ? "§cDisabled" : "§aEnabled", clickType -> {
observer.setDisabled(!observer.isDisabled());
SimulatorRenderer.update(simulator);
}).setCustomModelData(CMDs.Simulator.ENABLED_OR_DISABLED));
inventory.setItem(48, new SWItem(observer.isDisabled() ? Material.ENDER_PEARL : Material.ENDER_EYE, observer.isDisabled() ? "§cDisabled" : "§aEnabled", clickType -> {
observer.setDisabled(!observer.isDisabled());
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.ENABLED_OR_DISABLED));
// Group chooser
inventory.setItem(51, new SWItem(Material.LEAD, "§eJoin Group", clickType -> {
@@ -138,7 +139,7 @@ public class SimulatorObserverGui extends SimulatorScrollGui<ObserverPhase> {
SWItem observer = new SWItem(Material.OBSERVER, "§eObserver", lore, false, clickType -> {
if (clickType == ClickType.MIDDLE) {
this.observer.getPhases().remove(observerPhase);
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
} else {
new SimulatorObserverPhaseSettingsGui(player, simulator, this.observer, observerPhase, this).open();
}
@@ -147,35 +148,42 @@ public class SimulatorObserverGui extends SimulatorScrollGui<ObserverPhase> {
Supplier<Integer> getter = observerPhase::getTickOffset;
Consumer<Integer> setter = observerPhase::setTickOffset;
return new SWItem[] {new SWItem(SWItem.getDye(getter.get() < max ? 10 : 8), "§e+1", Arrays.asList("§7Shift§8:§e +5"), false, clickType -> {
setter.accept(Math.min(max, getter.get() + (clickType.isShiftClick() ? 5 : 1)));
SimulatorRenderer.update(simulator);
}).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED), observer,
return new SWItem[] {
new SWItem(SWItem.getDye(getter.get() < max ? 10 : 8), "§e+1", Arrays.asList("§7Shift§8:§e +5"), false, clickType -> {
setter.accept(Math.min(max, getter.get() + (clickType.isShiftClick() ? 5 : 1)));
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED),
observer,
new SWItem(SWItem.getDye(getter.get() > min ? 1 : 8), "§e-1", Arrays.asList("§7Shift§8:§e -5"), false, clickType -> {
setter.accept(Math.max(min, getter.get() - (clickType.isShiftClick() ? 5 : 1)));
SimulatorRenderer.update(simulator);
}).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED), new SWItem(Material.ANVIL, "§eEdit Activation", clickType -> {
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED),
new SWItem(Material.ANVIL, "§eEdit Activation", clickType -> {
new SimulatorObserverPhaseSettingsGui(player, simulator, this.observer, observerPhase, this).open();
}).setCustomModelData(CMDs.Simulator.EDIT_ACTIVATION),};
}).setCustomModelData(CMDs.Simulator.EDIT_ACTIVATION),
};
}
@Override
public SWItem[] lastColumn() {
return new SWItem[] {new SWItem(SWItem.getDye(10), "§e+1", Arrays.asList("§7Shift§8:§e +5"), false, clickType -> {
addNewPhase(clickType.isShiftClick());
}).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED), new SWItem(Material.QUARTZ, "§eObserver§8:§a New Phase", clickType -> {
addNewPhase(false);
}).setCustomModelData(CMDs.Simulator.NEW_PHASE), new SWItem(SWItem.getDye(8), "§7", clickType -> {
}).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED),};
return new SWItem[]{
new SWItem(SWItem.getDye(10), "§e+1", Arrays.asList("§7Shift§8:§e +5"), false, clickType -> {
addNewPhase(clickType.isShiftClick());
}).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED),
new SWItem(Material.QUARTZ, "§eObserver§8:§a New Phase", clickType -> {
addNewPhase(false);
}).setCustomModelData(CMDs.Simulator.NEW_PHASE),
new SWItem(SWItem.getDye(8), "§7", clickType -> {
}).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED),
};
}
private void addNewPhase(boolean shift) {
ObserverPhase lastElement = observer.getPhases().get(observer.getPhases().size() - 1);
ObserverPhase newPhase = new ObserverPhase(lastElement.getTickOffset() + 4);
if (shift)
newPhase.setTickOffset(newPhase.getTickOffset() + 5);
if (shift) newPhase.setTickOffset(newPhase.getTickOffset() + 5);
scroll += 2;
observer.add(newPhase);
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
}
}

View File

@@ -1,27 +1,29 @@
/*
* This file is a part of the SteamWar software.
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
* 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 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.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License along with this program.
* If not, see <https://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.features.simulator.gui;
import de.steamwar.bausystem.features.simulator.SimulatorWatcher;
import de.steamwar.bausystem.features.simulator.data.Simulator;
import de.steamwar.bausystem.features.simulator.data.SimulatorPhase;
import de.steamwar.bausystem.features.simulator.data.observer.ObserverElement;
import de.steamwar.bausystem.features.simulator.data.observer.ObserverPhase;
import de.steamwar.bausystem.features.simulator.display.SimulatorRenderer;
import de.steamwar.bausystem.features.simulator.gui.base.SimulatorAnvilGui;
import de.steamwar.bausystem.features.simulator.gui.base.SimulatorBaseGui;
import de.steamwar.core.Core;
@@ -39,8 +41,7 @@ public class SimulatorObserverPhaseSettingsGui extends SimulatorBaseGui {
private final ObserverPhase observer;
private final SimulatorBaseGui back;
public SimulatorObserverPhaseSettingsGui(Player player, Simulator simulator, ObserverElement observerElement, ObserverPhase observer,
SimulatorBaseGui back) {
public SimulatorObserverPhaseSettingsGui(Player player, Simulator simulator, ObserverElement observerElement, ObserverPhase observer, SimulatorBaseGui back) {
super(player, simulator, 5 * 9);
this.observerElement = observerElement;
this.observer = observer;
@@ -73,7 +74,7 @@ public class SimulatorObserverPhaseSettingsGui extends SimulatorBaseGui {
inventory.setItem(8, new SWItem(Material.BARRIER, "§eDelete", clickType -> {
observerElement.getPhases().remove(observer);
back.open();
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.DELETE));
int index = observerElement.getPhases().indexOf(observer);
@@ -93,19 +94,18 @@ public class SimulatorObserverPhaseSettingsGui extends SimulatorBaseGui {
max = Integer.MAX_VALUE - 4;
}
// Tick Offset
//Tick Offset
int offset = observer.getTickOffset();
inventory.setItem(10, new SWItem(SWItem.getDye(offset < max ? 10 : 8), "§e+1", Arrays.asList("§7Shift§8: §e+5"), false, clickType -> {
observer.setTickOffset(Math.min(max, offset + (clickType.isShiftClick() ? 5 : 1)));
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED));
SWItem offsetItem = new SWItem(Material.REPEATER, "§eStart at§8:§7 " + offset, clickType -> {
new SimulatorAnvilGui<>(player, "Start at", offset + "", Integer::parseInt, integer -> {
if (integer < 0)
return false;
if (integer < 0) return false;
observer.setTickOffset(Math.min(Math.max(integer, min), max));
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
return true;
}, this).setItem(Material.REPEATER).open();
});
@@ -114,66 +114,60 @@ public class SimulatorObserverPhaseSettingsGui extends SimulatorBaseGui {
inventory.setItem(28, new SWItem(SWItem.getDye(offset > min ? 1 : 8), "§e-1", Arrays.asList("§7Shift§8: §e-5"), false, clickType -> {
observer.setTickOffset(Math.max(min, offset - (clickType.isShiftClick() ? 5 : 1)));
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED));
// Order
//Order
int order = observer.getOrder();
inventory.setItem(13,
new SWItem(SWItem.getDye(order < SimulatorPhase.ORDER_LIMIT ? 10 : 8), "§e+1", Arrays.asList("§7Shift§8: §e+5"), false, clickType -> {
observer.setOrder(Math.min(SimulatorPhase.ORDER_LIMIT, order + (clickType.isShiftClick() ? 5 : 1)));
SimulatorRenderer.update(simulator);
}).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED));
inventory.setItem(13, new SWItem(SWItem.getDye(order < SimulatorPhase.ORDER_LIMIT ? 10 : 8), "§e+1", Arrays.asList("§7Shift§8: §e+5"), false, clickType -> {
observer.setOrder(Math.min(SimulatorPhase.ORDER_LIMIT, order + (clickType.isShiftClick() ? 5 : 1)));
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED));
Material negativeNumbers = Material.getMaterial(Core.getVersion() >= 19 ? "RECOVERY_COMPASS" : "FIREWORK_STAR");
SWItem orderItem = new SWItem(order >= 0 ? Material.COMPASS : negativeNumbers, "§eActivation Order§8:§7 " + order, clickType -> {
new SimulatorAnvilGui<>(player, "Activation Order", order + "", Integer::parseInt, integer -> {
if (integer < -SimulatorPhase.ORDER_LIMIT)
return false;
if (integer > SimulatorPhase.ORDER_LIMIT)
return false;
if (integer < -SimulatorPhase.ORDER_LIMIT) return false;
if (integer > SimulatorPhase.ORDER_LIMIT) return false;
observer.setOrder(integer);
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
return true;
}, this).setItem(order >= 0 ? Material.COMPASS : negativeNumbers).open();
});
orderItem.getItemStack().setAmount(Math.max(1, Math.min(Math.abs(order), 30)));
inventory.setItem(22, orderItem);
inventory.setItem(31,
new SWItem(SWItem.getDye(order > -SimulatorPhase.ORDER_LIMIT ? 1 : 8), "§e-1", Arrays.asList("§7Shift§8: §e-5"), false, clickType -> {
observer.setOrder(Math.max(-SimulatorPhase.ORDER_LIMIT, order - (clickType.isShiftClick() ? 5 : 1)));
SimulatorRenderer.update(simulator);
}).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED));
inventory.setItem(31, new SWItem(SWItem.getDye(order > -SimulatorPhase.ORDER_LIMIT ? 1 : 8), "§e-1", Arrays.asList("§7Shift§8: §e-5"), false, clickType -> {
observer.setOrder(Math.max(-SimulatorPhase.ORDER_LIMIT, order - (clickType.isShiftClick() ? 5 : 1)));
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED));
// Update orientation
inventory.setItem(25, new SWItem(Material.SUNFLOWER, "§7", clickType -> {
}));
inventory.setItem(15, new SWItem(observer.getOrientation() == BlockFace.UP ? Material.LIME_CONCRETE : Material.GRAY_CONCRETE, "§eUp", clickType -> {
observer.setOrientation(BlockFace.UP);
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
}));
inventory.setItem(33, new SWItem(observer.getOrientation() == BlockFace.DOWN ? Material.RED_CONCRETE : Material.GRAY_CONCRETE, "§eDown", clickType -> {
observer.setOrientation(BlockFace.DOWN);
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
}));
inventory.setItem(16, new SWItem(observer.getOrientation() == BlockFace.NORTH ? Material.LIME_WOOL : Material.GRAY_WOOL, "§eNorth", clickType -> {
observer.setOrientation(BlockFace.NORTH);
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
}));
inventory.setItem(34, new SWItem(observer.getOrientation() == BlockFace.SOUTH ? Material.RED_WOOL : Material.GRAY_WOOL, "§eSouth", clickType -> {
observer.setOrientation(BlockFace.SOUTH);
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
}));
inventory.setItem(24, new SWItem(observer.getOrientation() == BlockFace.EAST ? Material.LIME_STAINED_GLASS : Material.GRAY_STAINED_GLASS, "§eEast", clickType -> {
observer.setOrientation(BlockFace.EAST);
SimulatorWatcher.update(simulator);
}));
inventory.setItem(26, new SWItem(observer.getOrientation() == BlockFace.WEST ? Material.RED_STAINED_GLASS : Material.GRAY_STAINED_GLASS, "§eWest", clickType -> {
observer.setOrientation(BlockFace.WEST);
SimulatorWatcher.update(simulator);
}));
inventory.setItem(24,
new SWItem(observer.getOrientation() == BlockFace.EAST ? Material.LIME_STAINED_GLASS : Material.GRAY_STAINED_GLASS, "§eEast", clickType -> {
observer.setOrientation(BlockFace.EAST);
SimulatorRenderer.update(simulator);
}));
inventory.setItem(26,
new SWItem(observer.getOrientation() == BlockFace.WEST ? Material.RED_STAINED_GLASS : Material.GRAY_STAINED_GLASS, "§eWest", clickType -> {
observer.setOrientation(BlockFace.WEST);
SimulatorRenderer.update(simulator);
}));
}
}

View File

@@ -1,25 +1,27 @@
/*
* This file is a part of the SteamWar software.
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
* 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 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.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License along with this program.
* If not, see <https://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.features.simulator.gui;
import de.steamwar.bausystem.features.simulator.SimulatorWatcher;
import de.steamwar.bausystem.features.simulator.data.Simulator;
import de.steamwar.bausystem.features.simulator.data.observer.ObserverElement;
import de.steamwar.bausystem.features.simulator.display.SimulatorRenderer;
import de.steamwar.bausystem.features.simulator.gui.base.SimulatorAnvilGui;
import de.steamwar.bausystem.features.simulator.gui.base.SimulatorBaseGui;
import de.steamwar.data.CMDs;
@@ -66,14 +68,13 @@ public class SimulatorObserverSettingsGui extends SimulatorBaseGui {
int baseTicks = observer.getBaseTick();
inventory.setItem(9, new SWItem(SWItem.getDye(10), "§e+1", Arrays.asList("§7Shift§8: §e+5"), false, clickType -> {
observer.changeBaseTicks(clickType.isShiftClick() ? 5 : 1);
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED));
SWItem baseTick = new SWItem(Material.REPEATER, "§eTicks§8:§7 " + baseTicks, clickType -> {
new SimulatorAnvilGui<>(player, "Ticks", baseTicks + "", Integer::parseInt, integer -> {
if (integer < 0)
return false;
if (integer < 0) return false;
observer.changeBaseTicks(integer - baseTicks);
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
return true;
}, this).setItem(Material.REPEATER).open();
});
@@ -85,58 +86,58 @@ public class SimulatorObserverSettingsGui extends SimulatorBaseGui {
} else {
observer.changeBaseTicks(clickType.isShiftClick() ? -5 : -1);
}
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED));
// Pos X
inventory.setItem(15, new SWItem(SWItem.getDye(10), "§e+1", Arrays.asList("§7Shift§8: §e+5"), false, clickType -> {
//Pos X
inventory.setItem(15, new SWItem(SWItem.getDye(10), "§e+1", Arrays.asList("§7Shift§8: §e+5"), false, clickType -> {
observer.move(clickType.isShiftClick() ? 5 : 1, 0, 0);
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED));
inventory.setItem(24, new SWItem(Material.PAPER, "§eX§8:§7 " + observer.getPosition().getBlockX(), clickType -> {
new SimulatorAnvilGui<>(player, "X", observer.getPosition().getBlockX() + "", Integer::parseInt, i -> {
observer.getPosition().setX(i);
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
return true;
}, this).open();
}));
inventory.setItem(33, new SWItem(SWItem.getDye(1), "§e-1", Arrays.asList("§7Shift§8: §e-5"), false, clickType -> {
observer.move(clickType.isShiftClick() ? -5 : -1, 0, 0);
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED));
// Pos Y
//Pos Y
inventory.setItem(16, new SWItem(SWItem.getDye(10), "§e+1", Arrays.asList("§7Shift§8: §e+5"), false, clickType -> {
observer.move(0, clickType.isShiftClick() ? 5 : 1, 0);
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.ENABLED_OR_DISABLED));
inventory.setItem(25, new SWItem(Material.PAPER, "§eY§8:§7 " + observer.getPosition().getBlockY(), clickType -> {
new SimulatorAnvilGui<>(player, "Y", observer.getPosition().getBlockY() + "", Integer::parseInt, i -> {
observer.getPosition().setY(i);
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
return true;
}, this).open();
}));
inventory.setItem(34, new SWItem(SWItem.getDye(1), "§e-1", Arrays.asList("§7Shift§8: §e-5"), false, clickType -> {
observer.move(0, clickType.isShiftClick() ? -5 : -1, 0);
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED));
// Pos Z
//Pos Z
inventory.setItem(17, new SWItem(SWItem.getDye(10), "§e+1", Arrays.asList("§7Shift§8: §e+5"), false, clickType -> {
observer.move(0, 0, clickType.isShiftClick() ? 5 : 1);
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.ENABLED_OR_DISABLED));
inventory.setItem(26, new SWItem(Material.PAPER, "§eZ§8:§7 " + observer.getPosition().getBlockZ(), clickType -> {
new SimulatorAnvilGui<>(player, "Z", observer.getPosition().getBlockZ() + "", Integer::parseInt, i -> {
observer.getPosition().setZ(i);
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
return true;
}, this).open();
}));
inventory.setItem(35, new SWItem(SWItem.getDye(1), "§e-1", Arrays.asList("§7Shift§8: §e-5"), false, clickType -> {
observer.move(0, 0, clickType.isShiftClick() ? -5 : -1);
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED));
}
}

View File

@@ -1,27 +1,29 @@
/*
* This file is a part of the SteamWar software.
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
* 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 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.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License along with this program.
* If not, see <https://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.features.simulator.gui;
import de.steamwar.bausystem.features.simulator.SimulatorWatcher;
import de.steamwar.bausystem.features.simulator.data.Simulator;
import de.steamwar.bausystem.features.simulator.data.SimulatorGroup;
import de.steamwar.bausystem.features.simulator.data.redstone.RedstoneElement;
import de.steamwar.bausystem.features.simulator.data.redstone.RedstonePhase;
import de.steamwar.bausystem.features.simulator.display.SimulatorRenderer;
import de.steamwar.bausystem.features.simulator.gui.base.SimulatorBaseGui;
import de.steamwar.bausystem.features.simulator.gui.base.SimulatorScrollGui;
import de.steamwar.data.CMDs;
@@ -59,7 +61,7 @@ public class SimulatorRedstoneGui extends SimulatorScrollGui<SimulatorRedstoneGu
public void headerAndFooter() {
if (redstone.getPhases().isEmpty()) {
back.open();
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
return;
}
@@ -91,7 +93,7 @@ public class SimulatorRedstoneGui extends SimulatorScrollGui<SimulatorRedstoneGu
inventory.setItem(8, new SWItem(Material.BARRIER, "§eDelete", clickType -> {
redstone.getPhases().clear();
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.DELETE));
// Material Chooser
@@ -105,11 +107,10 @@ public class SimulatorRedstoneGui extends SimulatorScrollGui<SimulatorRedstoneGu
}).setCustomModelData(CMDs.Simulator.SETTINGS));
// Enable/Disable
inventory.setItem(48,
new SWItem(redstone.isDisabled() ? Material.ENDER_PEARL : Material.ENDER_EYE, redstone.isDisabled() ? "§cDisabled" : "§aEnabled", clickType -> {
redstone.setDisabled(!redstone.isDisabled());
SimulatorRenderer.update(simulator);
}).setCustomModelData(CMDs.Simulator.ENABLED_OR_DISABLED));
inventory.setItem(48, new SWItem(redstone.isDisabled() ? Material.ENDER_PEARL : Material.ENDER_EYE, redstone.isDisabled() ? "§cDisabled" : "§aEnabled", clickType -> {
redstone.setDisabled(!redstone.isDisabled());
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.ENABLED_OR_DISABLED));
// Group chooser
inventory.setItem(51, new SWItem(Material.LEAD, "§eJoin Group", clickType -> {
@@ -150,49 +151,55 @@ public class SimulatorRedstoneGui extends SimulatorScrollGui<SimulatorRedstoneGu
lore.add("");
lore.add("§7Click§8:§e Edit");
lore.add("§7Middle-Click§8:§e Remove");
SWItem redstone = new SWItem(redstoneSubPhase.place ? Material.REDSTONE_BLOCK : Material.STONE, redstoneSubPhase.place ? "§eActivate" : "§eDeactivate",
lore, false, clickType -> {
if (clickType == ClickType.MIDDLE) {
this.redstone.getPhases().remove(redstoneSubPhase.phase);
SimulatorRenderer.update(simulator);
} else {
new SimulatorRedstonePhaseSettingsGui(player, simulator, this.redstone, redstoneSubPhase.phase, this).open();
}
});
SWItem redstone = new SWItem(redstoneSubPhase.place ? Material.REDSTONE_BLOCK : Material.STONE, redstoneSubPhase.place ? "§eActivate" : "§eDeactivate", lore, false, clickType -> {
if (clickType == ClickType.MIDDLE) {
this.redstone.getPhases().remove(redstoneSubPhase.phase);
SimulatorWatcher.update(simulator);
} else {
new SimulatorRedstonePhaseSettingsGui(player, simulator, this.redstone, redstoneSubPhase.phase, this).open();
}
});
redstone.getItemStack().setAmount(Math.max(1, Math.min(time, 64)));
Supplier<Integer> getter = redstoneSubPhase.place ? redstoneSubPhase.phase::getTickOffset : redstoneSubPhase.phase::getLifetime;
Consumer<Integer> setter = redstoneSubPhase.place ? redstoneSubPhase.phase::setTickOffset : redstoneSubPhase.phase::setLifetime;
return new SWItem[] {new SWItem(SWItem.getDye(getter.get() < max ? 10 : 8), "§e+1", Arrays.asList("§7Shift§8:§e +5"), false, clickType -> {
setter.accept(Math.min(max, getter.get() + (clickType.isShiftClick() ? 5 : 1)));
SimulatorRenderer.update(simulator);
}).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED), redstone,
return new SWItem[] {
new SWItem(SWItem.getDye(getter.get() < max ? 10 : 8), "§e+1", Arrays.asList("§7Shift§8:§e +5"), false, clickType -> {
setter.accept(Math.min(max, getter.get() + (clickType.isShiftClick() ? 5 : 1)));
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED),
redstone,
new SWItem(SWItem.getDye(getter.get() > min ? 1 : 8), "§e-1", Arrays.asList("§7Shift§8:§e -5"), false, clickType -> {
setter.accept(Math.max(min, getter.get() - (clickType.isShiftClick() ? 5 : 1)));
SimulatorRenderer.update(simulator);
}).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED), new SWItem(Material.ANVIL, "§eEdit Activation", clickType -> {
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED),
new SWItem(Material.ANVIL, "§eEdit Activation", clickType -> {
new SimulatorRedstonePhaseSettingsGui(player, simulator, this.redstone, redstoneSubPhase.phase, this).open();
}).setCustomModelData(CMDs.Simulator.EDIT_ACTIVATION),};
}).setCustomModelData(CMDs.Simulator.EDIT_ACTIVATION),
};
}
@Override
public SWItem[] lastColumn() {
return new SWItem[] {new SWItem(SWItem.getDye(10), "§e+1", Arrays.asList("§7Shift§8:§e +5"), false, clickType -> {
addNewPhase(clickType.isShiftClick());
}).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED), new SWItem(Material.REDSTONE, "§eRedstone§8:§a New Phase", clickType -> {
addNewPhase(false);
}).setCustomModelData(CMDs.Simulator.NEW_PHASE), new SWItem(SWItem.getDye(8), "§7", clickType -> {
}).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED),};
return new SWItem[]{
new SWItem(SWItem.getDye(10), "§e+1", Arrays.asList("§7Shift§8:§e +5"), false, clickType -> {
addNewPhase(clickType.isShiftClick());
}).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED),
new SWItem(Material.REDSTONE, "§eRedstone§8:§a New Phase", clickType -> {
addNewPhase(false);
}).setCustomModelData(CMDs.Simulator.NEW_PHASE),
new SWItem(SWItem.getDye(8), "§7", clickType -> {
}).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED),
};
}
private void addNewPhase(boolean shift) {
RedstonePhase lastElement = redstone.getPhases().get(redstone.getPhases().size() - 1);
RedstonePhase newPhase = new RedstonePhase(lastElement.getTickOffset() + lastElement.getLifetime() + 1);
if (shift)
newPhase.setTickOffset(newPhase.getTickOffset() + 5);
if (shift) newPhase.setTickOffset(newPhase.getTickOffset() + 5);
scroll += 2;
redstone.add(newPhase);
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
}
@AllArgsConstructor

View File

@@ -1,27 +1,29 @@
/*
* This file is a part of the SteamWar software.
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
* 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 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.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License along with this program.
* If not, see <https://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.features.simulator.gui;
import de.steamwar.bausystem.features.simulator.SimulatorWatcher;
import de.steamwar.bausystem.features.simulator.data.Simulator;
import de.steamwar.bausystem.features.simulator.data.SimulatorPhase;
import de.steamwar.bausystem.features.simulator.data.redstone.RedstoneElement;
import de.steamwar.bausystem.features.simulator.data.redstone.RedstonePhase;
import de.steamwar.bausystem.features.simulator.display.SimulatorRenderer;
import de.steamwar.bausystem.features.simulator.gui.base.SimulatorAnvilGui;
import de.steamwar.bausystem.features.simulator.gui.base.SimulatorBaseGui;
import de.steamwar.core.Core;
@@ -37,8 +39,7 @@ public class SimulatorRedstonePhaseSettingsGui extends SimulatorBaseGui {
private final RedstonePhase redstone;
private final SimulatorBaseGui back;
public SimulatorRedstonePhaseSettingsGui(Player player, Simulator simulator, RedstoneElement redstoneElement, RedstonePhase redstone,
SimulatorBaseGui back) {
public SimulatorRedstonePhaseSettingsGui(Player player, Simulator simulator, RedstoneElement redstoneElement, RedstonePhase redstone, SimulatorBaseGui back) {
super(player, simulator, 5 * 9);
this.redstoneElement = redstoneElement;
this.redstone = redstone;
@@ -71,7 +72,7 @@ public class SimulatorRedstonePhaseSettingsGui extends SimulatorBaseGui {
inventory.setItem(8, new SWItem(Material.BARRIER, "§eDelete", clickType -> {
redstoneElement.getPhases().remove(redstone);
back.open();
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.DELETE));
int index = redstoneElement.getPhases().indexOf(redstone);
@@ -94,19 +95,18 @@ public class SimulatorRedstonePhaseSettingsGui extends SimulatorBaseGui {
maxOffset = Integer.MAX_VALUE - 5;
}
// Tick Offset
//Tick Offset
int offset = redstone.getTickOffset();
inventory.setItem(10, new SWItem(SWItem.getDye(offset < maxOffset ? 10 : 8), "§e+1", Arrays.asList("§7Shift§8: §e+5"), false, clickType -> {
redstone.setTickOffset(Math.min(maxOffset, offset + (clickType.isShiftClick() ? 5 : 1)));
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED));
SWItem offsetItem = new SWItem(Material.REPEATER, "§eStart at§8:§7 " + offset, clickType -> {
new SimulatorAnvilGui<>(player, "Start at", offset + "", Integer::parseInt, integer -> {
if (integer < 0)
return false;
if (integer < 0) return false;
redstone.setTickOffset(Math.min(Math.max(integer, min), maxOffset));
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
return true;
}, this).setItem(Material.REPEATER).open();
});
@@ -115,22 +115,21 @@ public class SimulatorRedstonePhaseSettingsGui extends SimulatorBaseGui {
inventory.setItem(28, new SWItem(SWItem.getDye(offset > min ? 1 : 8), "§e-1", Arrays.asList("§7Shift§8: §e-5"), false, clickType -> {
redstone.setTickOffset(Math.max(min, offset - (clickType.isShiftClick() ? 5 : 1)));
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED));
// Lifetime
//Lifetime
int lifetime = redstone.getLifetime();
inventory.setItem(11, new SWItem(SWItem.getDye(lifetime < maxLifetime ? 10 : 8), "§e+1", Arrays.asList("§7Shift§8: §e+5"), false, clickType -> {
redstone.setLifetime(Math.min(maxLifetime, lifetime + (clickType.isShiftClick() ? 5 : 1)));
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED));
SWItem lifetimeItem = new SWItem(Material.CLOCK, "§eActivation Time§8:§7 " + lifetime, clickType -> {
new SimulatorAnvilGui<>(player, "Activation Time", lifetime + "", Integer::parseInt, integer -> {
if (integer < 0)
return false;
if (integer < 0) return false;
redstone.setLifetime(Math.min(integer, maxLifetime));
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
return true;
}, this).setItem(Material.CLOCK).open();
});
@@ -139,36 +138,32 @@ public class SimulatorRedstonePhaseSettingsGui extends SimulatorBaseGui {
inventory.setItem(29, new SWItem(SWItem.getDye(lifetime > 0 ? 1 : 8), "§e-1", Arrays.asList("§7Shift§8: §e-5"), false, clickType -> {
redstone.setLifetime(Math.max(0, lifetime - (clickType.isShiftClick() ? 5 : 1)));
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED));
// Order
//Order
int order = redstone.getOrder();
inventory.setItem(13,
new SWItem(SWItem.getDye(order < SimulatorPhase.ORDER_LIMIT ? 10 : 8), "§e+1", Arrays.asList("§7Shift§8: §e+5"), false, clickType -> {
redstone.setOrder(Math.min(SimulatorPhase.ORDER_LIMIT, order + (clickType.isShiftClick() ? 5 : 1)));
SimulatorRenderer.update(simulator);
}).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED));
inventory.setItem(13, new SWItem(SWItem.getDye(order < SimulatorPhase.ORDER_LIMIT ? 10 : 8), "§e+1", Arrays.asList("§7Shift§8: §e+5"), false, clickType -> {
redstone.setOrder(Math.min(SimulatorPhase.ORDER_LIMIT, order + (clickType.isShiftClick() ? 5 : 1)));
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED));
Material negativeNumbers = Material.getMaterial(Core.getVersion() >= 19 ? "RECOVERY_COMPASS" : "FIREWORK_STAR");
SWItem orderItem = new SWItem(order >= 0 ? Material.COMPASS : negativeNumbers, "§eActivation Order§8:§7 " + order, clickType -> {
new SimulatorAnvilGui<>(player, "Activation Order", order + "", Integer::parseInt, integer -> {
if (integer < -SimulatorPhase.ORDER_LIMIT)
return false;
if (integer > SimulatorPhase.ORDER_LIMIT)
return false;
if (integer < -SimulatorPhase.ORDER_LIMIT) return false;
if (integer > SimulatorPhase.ORDER_LIMIT) return false;
redstone.setOrder(integer);
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
return true;
}, this).setItem(order >= 0 ? Material.COMPASS : negativeNumbers).open();
});
orderItem.getItemStack().setAmount(Math.max(1, Math.min(Math.abs(order), 30)));
inventory.setItem(22, orderItem);
inventory.setItem(31,
new SWItem(SWItem.getDye(order > -SimulatorPhase.ORDER_LIMIT ? 1 : 8), "§e-1", Arrays.asList("§7Shift§8: §e-5"), false, clickType -> {
redstone.setOrder(Math.max(-SimulatorPhase.ORDER_LIMIT, order - (clickType.isShiftClick() ? 5 : 1)));
SimulatorRenderer.update(simulator);
}).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED));
inventory.setItem(31, new SWItem(SWItem.getDye(order > -SimulatorPhase.ORDER_LIMIT ? 1 : 8), "§e-1", Arrays.asList("§7Shift§8: §e-5"), false, clickType -> {
redstone.setOrder(Math.max(-SimulatorPhase.ORDER_LIMIT, order - (clickType.isShiftClick() ? 5 : 1)));
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED));
}
}

View File

@@ -1,25 +1,27 @@
/*
* This file is a part of the SteamWar software.
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
* 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 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.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License along with this program.
* If not, see <https://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.features.simulator.gui;
import de.steamwar.bausystem.features.simulator.SimulatorWatcher;
import de.steamwar.bausystem.features.simulator.data.Simulator;
import de.steamwar.bausystem.features.simulator.data.redstone.RedstoneElement;
import de.steamwar.bausystem.features.simulator.display.SimulatorRenderer;
import de.steamwar.bausystem.features.simulator.gui.base.SimulatorAnvilGui;
import de.steamwar.bausystem.features.simulator.gui.base.SimulatorBaseGui;
import de.steamwar.data.CMDs;
@@ -65,14 +67,13 @@ public class SimulatorRedstoneSettingsGui extends SimulatorBaseGui {
int baseTicks = redstone.getBaseTick();
inventory.setItem(9, new SWItem(SWItem.getDye(10), "§e+1", Arrays.asList("§7Shift§8: §e+5"), false, clickType -> {
redstone.changeBaseTicks(clickType.isShiftClick() ? 5 : 1);
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED));
SWItem baseTick = new SWItem(Material.REPEATER, "§eTicks§8:§7 " + baseTicks, clickType -> {
new SimulatorAnvilGui<>(player, "Ticks", baseTicks + "", Integer::parseInt, integer -> {
if (integer < 0)
return false;
if (integer < 0) return false;
redstone.changeBaseTicks(integer - baseTicks);
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
return true;
}, this).setItem(Material.REPEATER).open();
});
@@ -84,58 +85,58 @@ public class SimulatorRedstoneSettingsGui extends SimulatorBaseGui {
} else {
redstone.changeBaseTicks(clickType.isShiftClick() ? -5 : -1);
}
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED));
// Pos X
//Pos X
inventory.setItem(15, new SWItem(SWItem.getDye(10), "§e+1", Arrays.asList("§7Shift§8: §e+5"), false, clickType -> {
redstone.move(clickType.isShiftClick() ? 5 : 1, 0, 0);
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED));
inventory.setItem(24, new SWItem(Material.PAPER, "§eX§8:§7 " + redstone.getPosition().getBlockX(), clickType -> {
new SimulatorAnvilGui<>(player, "X", redstone.getPosition().getBlockX() + "", Integer::parseInt, i -> {
redstone.getPosition().setX(i);
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
return true;
}, this).open();
}));
inventory.setItem(33, new SWItem(SWItem.getDye(1), "§e-1", Arrays.asList("§7Shift§8: §e-5"), false, clickType -> {
redstone.move(clickType.isShiftClick() ? -5 : -1, 0, 0);
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED));
// Pos Y
//Pos Y
inventory.setItem(16, new SWItem(SWItem.getDye(10), "§e+1", Arrays.asList("§7Shift§8: §e+5"), false, clickType -> {
redstone.move(0, clickType.isShiftClick() ? 5 : 1, 0);
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED));
inventory.setItem(25, new SWItem(Material.PAPER, "§eY§8:§7 " + redstone.getPosition().getBlockY(), clickType -> {
new SimulatorAnvilGui<>(player, "Y", redstone.getPosition().getBlockY() + "", Integer::parseInt, i -> {
redstone.getPosition().setY(i);
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
return true;
}, this).open();
}));
inventory.setItem(34, new SWItem(SWItem.getDye(1), "§e-1", Arrays.asList("§7Shift§8: §e-5"), false, clickType -> {
redstone.move(0, clickType.isShiftClick() ? -5 : -1, 0);
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED));
// Pos Z
//Pos Z
inventory.setItem(17, new SWItem(SWItem.getDye(10), "§e+1", Arrays.asList("§7Shift§8: §e+5"), false, clickType -> {
redstone.move(0, 0, clickType.isShiftClick() ? 5 : 1);
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED));
inventory.setItem(26, new SWItem(Material.PAPER, "§eZ§8:§7 " + redstone.getPosition().getBlockZ(), clickType -> {
new SimulatorAnvilGui<>(player, "Z", redstone.getPosition().getBlockZ() + "", Integer::parseInt, i -> {
redstone.getPosition().setZ(i);
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
return true;
}, this).open();
}));
inventory.setItem(35, new SWItem(SWItem.getDye(1), "§e-1", Arrays.asList("§7Shift§8: §e-5"), false, clickType -> {
redstone.move(0, 0, clickType.isShiftClick() ? -5 : -1);
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED));
}
}

View File

@@ -1,24 +1,26 @@
/*
* This file is a part of the SteamWar software.
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
* 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 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.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License along with this program.
* If not, see <https://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.features.simulator.gui;
import de.steamwar.bausystem.features.simulator.SimulatorWatcher;
import de.steamwar.bausystem.features.simulator.data.Simulator;
import de.steamwar.bausystem.features.simulator.display.SimulatorRenderer;
import de.steamwar.bausystem.features.simulator.gui.base.SimulatorBaseGui;
import de.steamwar.data.CMDs;
import de.steamwar.inventory.SWItem;
@@ -53,52 +55,50 @@ public class SimulatorSettingsGui extends SimulatorBaseGui {
new SimulatorMaterialGui(player, simulator, simulator::getMaterial, simulator::setMaterial, this).open();
}));
// AutoTrace
inventory.setItem(19, new SWItem(simulator.isAutoTrace() ? Material.CHAIN_COMMAND_BLOCK : Material.COMMAND_BLOCK,
"§eAutoTrace§8: " + (simulator.isAutoTrace() ? "§aOn" : "§cOff"), clickType -> {
simulator.setAutoTrace(!simulator.isAutoTrace());
SimulatorRenderer.update(simulator);
}));
inventory.setItem(20, new SWItem(simulator.isAutoTestblock() ? Material.END_STONE : Material.BARRIER,
"§eTestblock§8: " + (simulator.isAutoTestblock() ? "§aOn" : "§cOff"), clickType -> {
simulator.setAutoTestblock(!simulator.isAutoTestblock());
SimulatorRenderer.update(simulator);
}));
//AutoTrace
inventory.setItem(19, new SWItem(simulator.isAutoTrace() ? Material.CHAIN_COMMAND_BLOCK : Material.COMMAND_BLOCK, "§eAutoTrace§8: " + (simulator.isAutoTrace() ? "§aOn" : "§cOff"), clickType -> {
simulator.setAutoTrace(!simulator.isAutoTrace());
SimulatorWatcher.update(simulator);
}));
inventory.setItem(20, new SWItem(simulator.isAutoTestblock() ? Material.END_STONE : Material.BARRIER, "§eTestblock§8: " + (simulator.isAutoTestblock() ? "§aOn" : "§cOff"), clickType -> {
simulator.setAutoTestblock(!simulator.isAutoTestblock());
SimulatorWatcher.update(simulator);
}));
// Pos X
//Pos X
inventory.setItem(15, new SWItem(SWItem.getDye(10), "§e+1", Arrays.asList("§7Shift§8: §e+5"), false, clickType -> {
simulator.move(clickType.isShiftClick() ? 5 : 1, 0, 0);
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED));
inventory.setItem(24, new SWItem(Material.PAPER, "§eX", clickType -> {
}));
inventory.setItem(33, new SWItem(SWItem.getDye(1), "§e-1", Arrays.asList("§7Shift§8: §e-5"), false, clickType -> {
simulator.move(clickType.isShiftClick() ? -5 : -1, 0, 0);
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED));
// Pos Y
//Pos Y
inventory.setItem(16, new SWItem(SWItem.getDye(10), "§e+1", Arrays.asList("§7Shift§8: §e+5"), false, clickType -> {
simulator.move(0, clickType.isShiftClick() ? 5 : 1, 0);
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED));
inventory.setItem(25, new SWItem(Material.PAPER, "§eY", clickType -> {
}));
inventory.setItem(34, new SWItem(SWItem.getDye(1), "§e-1", Arrays.asList("§7Shift§8: §e-5"), false, clickType -> {
simulator.move(0, clickType.isShiftClick() ? -5 : -1, 0);
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED));
// Pos Z
//Pos Z
inventory.setItem(17, new SWItem(SWItem.getDye(10), "§e+1", Arrays.asList("§7Shift§8: §e+5"), false, clickType -> {
simulator.move(0, 0, clickType.isShiftClick() ? 5 : 1);
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED));
inventory.setItem(26, new SWItem(Material.PAPER, "§eZ", clickType -> {
}));
inventory.setItem(35, new SWItem(SWItem.getDye(1), "§e-1", Arrays.asList("§7Shift§8: §e-5"), false, clickType -> {
simulator.move(0, 0, clickType.isShiftClick() ? -5 : -1);
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED));
}
}

View File

@@ -1,27 +1,29 @@
/*
* This file is a part of the SteamWar software.
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
* 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 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.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License along with this program.
* If not, see <https://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.features.simulator.gui;
import de.steamwar.bausystem.features.simulator.SimulatorWatcher;
import de.steamwar.bausystem.features.simulator.data.Simulator;
import de.steamwar.bausystem.features.simulator.data.SimulatorGroup;
import de.steamwar.bausystem.features.simulator.data.tnt.TNTElement;
import de.steamwar.bausystem.features.simulator.data.tnt.TNTPhase;
import de.steamwar.bausystem.features.simulator.display.SimulatorRenderer;
import de.steamwar.bausystem.features.simulator.execute.SimulatorStabGenerator;
import de.steamwar.bausystem.features.simulator.gui.base.SimulatorAnvilGui;
import de.steamwar.bausystem.features.simulator.gui.base.SimulatorBaseGui;
@@ -58,7 +60,7 @@ public class SimulatorTNTGui extends SimulatorScrollGui<TNTPhase> {
public void headerAndFooter() {
if (tnt.getPhases().isEmpty()) {
back.open();
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
return;
}
@@ -85,7 +87,7 @@ public class SimulatorTNTGui extends SimulatorScrollGui<TNTPhase> {
inventory.setItem(8, new SWItem(Material.BARRIER, "§eDelete", clickType -> {
tnt.getPhases().clear();
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.DELETE));
// Material Chooser
@@ -96,18 +98,16 @@ public class SimulatorTNTGui extends SimulatorScrollGui<TNTPhase> {
inventory.setItem(47, new SWItem(Material.REPEATER, "§eSettings", clickType -> {
new SimulatorTNTSettingsGui(player, simulator, tnt, this).open();
}).setCustomModelData(CMDs.Simulator.SETTINGS));
inventory.setItem(48,
new SWItem(tnt.isDisabled() ? Material.ENDER_PEARL : Material.ENDER_EYE, tnt.isDisabled() ? "§cDisabled" : "§aEnabled", clickType -> {
tnt.setDisabled(!tnt.isDisabled());
SimulatorRenderer.update(simulator);
}).setCustomModelData(CMDs.Simulator.ENABLED_OR_DISABLED));
inventory.setItem(48, new SWItem(tnt.isDisabled() ? Material.ENDER_PEARL : Material.ENDER_EYE, tnt.isDisabled() ? "§cDisabled" : "§aEnabled", clickType -> {
tnt.setDisabled(!tnt.isDisabled());
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.ENABLED_OR_DISABLED));
if (Core.getVersion() > 19) {
inventory.setItem(49, new SWItem(Material.CALIBRATED_SCULK_SENSOR, "§eCreate Stab", click -> {
new SimulatorAnvilGui<>(player, "Depth Limit", "", Integer::parseInt, depthLimit -> {
if (depthLimit <= 0)
return false;
if (depthLimit <= 0) return false;
simulator.setStabGenerator(new SimulatorStabGenerator(Region.getRegion(player.getLocation()), simulator, tnt, depthLimit));
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
return true;
}, null).open();
}).setCustomModelData(CMDs.Simulator.CREATE_STAB));
@@ -117,7 +117,7 @@ public class SimulatorTNTGui extends SimulatorScrollGui<TNTPhase> {
tntElement.add(new TNTPhase());
parent.add(tntElement);
new SimulatorGroupGui(player, simulator, parent, new SimulatorGui(player, simulator)).open();
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.MAKE_GROUP));
inventory.setItem(51, new SWItem(Material.LEAD, "§eJoin Group", clickType -> {
new SimulatorGroupChooserGui(player, simulator, tnt, tnt.getGroup(simulator), this).open();
@@ -126,50 +126,52 @@ public class SimulatorTNTGui extends SimulatorScrollGui<TNTPhase> {
@Override
public SWItem[] column(TNTPhase tntSetting, int index) {
SWItem tnt = new SWItem(Material.TNT, "§eTNT§8:§7 " + tntSetting.getCount(),
Arrays.asList("§7Tick§8: §e" + tntSetting.getTickOffset(), "§7Fuse§8:§e " + tntSetting.getLifetime(), "",
"§7Order§8:§e " + tntSetting.getOrder(), "", "§7X-Jump§8: " + (tntSetting.isXJump() ? "§aOn" : "§cOff"),
"§7Y-Jump§8: " + (tntSetting.isYJump() ? "§aOn" : "§cOff"), "§7Z-Jump§8: " + (tntSetting.isZJump() ? "§aOn" : "§cOff"), "",
"§7Click§8:§e Edit", "§7Middle-Click§8:§e Remove"),
false, clickType -> {
if (clickType == ClickType.MIDDLE) {
this.tnt.getPhases().remove(tntSetting);
SimulatorRenderer.update(simulator);
} else {
new SimulatorTNTPhaseSettingsGui(player, simulator, this.tnt, tntSetting, this).open();
}
});
SWItem tnt = new SWItem(Material.TNT, "§eTNT§8:§7 " + tntSetting.getCount(), Arrays.asList("§7Tick§8: §e" + tntSetting.getTickOffset(), "§7Fuse§8:§e " + tntSetting.getLifetime(), "", "§7Order§8:§e " + tntSetting.getOrder(), "", "§7X-Jump§8: " + (tntSetting.isXJump() ? "§aOn" : "§cOff"), "§7Y-Jump§8: " + (tntSetting.isYJump() ? "§aOn" : "§cOff"), "§7Z-Jump§8: " + (tntSetting.isZJump() ? "§aOn" : "§cOff"), "", "§7Click§8:§e Edit", "§7Middle-Click§8:§e Remove"), false, clickType -> {
if (clickType == ClickType.MIDDLE) {
this.tnt.getPhases().remove(tntSetting);
SimulatorWatcher.update(simulator);
} else {
new SimulatorTNTPhaseSettingsGui(player, simulator, this.tnt, tntSetting, this).open();
}
});
tnt.getItemStack().setAmount(Math.min(tntSetting.getCount(), 64));
return new SWItem[] {new SWItem(SWItem.getDye(10), "§e+1", Arrays.asList("§7Shift§8:§e +5"), false, clickType -> {
tntSetting.setCount(tntSetting.getCount() + (clickType.isShiftClick() ? 5 : 1));
SimulatorRenderer.update(simulator);
}).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED), tnt,
return new SWItem[]{
new SWItem(SWItem.getDye(10), "§e+1", Arrays.asList("§7Shift§8:§e +5"), false, clickType -> {
tntSetting.setCount(tntSetting.getCount() + (clickType.isShiftClick() ? 5 : 1));
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED),
tnt,
new SWItem(SWItem.getDye(tntSetting.getCount() > 1 ? 1 : 8), "§e-1", Arrays.asList("§7Shift§8:§e -5"), false, clickType -> {
tntSetting.setCount(Math.max(1, tntSetting.getCount() - (clickType.isShiftClick() ? 5 : 1)));
SimulatorRenderer.update(simulator);
}).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED), new SWItem(Material.ANVIL, "§eEdit Phase", clickType -> {
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED),
new SWItem(Material.ANVIL, "§eEdit Phase", clickType -> {
new SimulatorTNTPhaseSettingsGui(player, simulator, this.tnt, tntSetting, this).open();
}).setCustomModelData(CMDs.Simulator.EDIT_ACTIVATION),};
}).setCustomModelData(CMDs.Simulator.EDIT_ACTIVATION),
};
}
@Override
public SWItem[] lastColumn() {
return new SWItem[] {new SWItem(SWItem.getDye(10), "§e+1", Arrays.asList("§7Shift§8:§e +5"), false, clickType -> {
addNewPhase(clickType.isShiftClick());
}).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED), new SWItem(Material.GUNPOWDER, "§eTNT§8:§a New Phase", clickType -> {
addNewPhase(false);
}).setCustomModelData(CMDs.Simulator.NEW_PHASE), new SWItem(SWItem.getDye(8), "§7", clickType -> {
}).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED),};
return new SWItem[]{
new SWItem(SWItem.getDye(10), "§e+1", Arrays.asList("§7Shift§8:§e +5"), false, clickType -> {
addNewPhase(clickType.isShiftClick());
}).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED),
new SWItem(Material.GUNPOWDER, "§eTNT§8:§a New Phase", clickType -> {
addNewPhase(false);
}).setCustomModelData(CMDs.Simulator.NEW_PHASE),
new SWItem(SWItem.getDye(8), "§7", clickType -> {
}).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED),
};
}
private void addNewPhase(boolean shift) {
TNTPhase lastElement = tnt.getPhases().get(tnt.getPhases().size() - 1);
TNTPhase newPhase = new TNTPhase(lastElement.getTickOffset() + 1);
if (shift)
newPhase.setCount(newPhase.getCount() + 5);
if (shift) newPhase.setCount(newPhase.getCount() + 5);
scroll++;
tnt.add(newPhase);
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
}
}

View File

@@ -1,27 +1,29 @@
/*
* This file is a part of the SteamWar software.
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
* 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 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.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License along with this program.
* If not, see <https://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.features.simulator.gui;
import de.steamwar.bausystem.features.simulator.SimulatorWatcher;
import de.steamwar.bausystem.features.simulator.data.Simulator;
import de.steamwar.bausystem.features.simulator.data.SimulatorPhase;
import de.steamwar.bausystem.features.simulator.data.tnt.TNTElement;
import de.steamwar.bausystem.features.simulator.data.tnt.TNTPhase;
import de.steamwar.bausystem.features.simulator.display.SimulatorRenderer;
import de.steamwar.bausystem.features.simulator.gui.base.SimulatorAnvilGui;
import de.steamwar.bausystem.features.simulator.gui.base.SimulatorBaseGui;
import de.steamwar.core.Core;
@@ -66,26 +68,25 @@ public class SimulatorTNTPhaseSettingsGui extends SimulatorBaseGui {
new SimulatorMaterialGui(player, simulator, tntElement::getMaterial, tntElement::setMaterial, this).open();
}));
// Delete
//Delete
inventory.setItem(8, new SWItem(Material.BARRIER, "§eDelete", clickType -> {
tntElement.getPhases().remove(tnt);
back.open();
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.DELETE));
// Count
//Count
int count = tnt.getCount();
inventory.setItem(9, new SWItem(SWItem.getDye(10), "§e+1", Arrays.asList("§7Shift§8: §e+5"), false, clickType -> {
tnt.setCount(count + (clickType.isShiftClick() ? 5 : 1));
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED));
SWItem countItem = new SWItem(Material.TNT, "§eCount§8:§7 " + count, clickType -> {
new SimulatorAnvilGui<>(player, "Count", count + "", Integer::parseInt, integer -> {
if (integer < 1)
return false;
if (integer < 1) return false;
tnt.setCount(integer);
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
return true;
}, this).setItem(Material.TNT).open();
});
@@ -94,22 +95,21 @@ public class SimulatorTNTPhaseSettingsGui extends SimulatorBaseGui {
inventory.setItem(27, new SWItem(SWItem.getDye(count > 1 ? 1 : 8), "§e-1", Arrays.asList("§7Shift§8: §e-5"), false, clickType -> {
tnt.setCount(Math.max(1, count - (clickType.isShiftClick() ? 5 : 1)));
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED));
// Tick Offset
//Tick Offset
int offset = tnt.getTickOffset();
inventory.setItem(10, new SWItem(SWItem.getDye(10), "§e+1", Arrays.asList("§7Shift§8: §e+5"), false, clickType -> {
tnt.setTickOffset(offset + (clickType.isShiftClick() ? 5 : 1));
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED));
SWItem offsetItem = new SWItem(Material.REPEATER, "§eStart at§8:§7 " + offset, clickType -> {
new SimulatorAnvilGui<>(player, "Start at", offset + "", Integer::parseInt, integer -> {
if (integer < 0)
return false;
if (integer < 0) return false;
tnt.setTickOffset(integer);
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
return true;
}, this).setItem(Material.REPEATER).open();
});
@@ -118,22 +118,21 @@ public class SimulatorTNTPhaseSettingsGui extends SimulatorBaseGui {
inventory.setItem(28, new SWItem(SWItem.getDye(offset > 0 ? 1 : 8), "§e-1", Arrays.asList("§7Shift§8: §e-5"), false, clickType -> {
tnt.setTickOffset(Math.max(0, offset - (clickType.isShiftClick() ? 5 : 1)));
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED));
// Lifetime
//Lifetime
int lifetime = tnt.getLifetime();
inventory.setItem(11, new SWItem(SWItem.getDye(10), "§e+1", Arrays.asList("§7Shift§8: §e+5"), false, clickType -> {
tnt.setLifetime(lifetime + (clickType.isShiftClick() ? 5 : 1));
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED));
SWItem lifetimeItem = new SWItem(Material.CLOCK, "§eLifetime§8:§7 " + lifetime, clickType -> {
new SimulatorAnvilGui<>(player, "Lifetime", lifetime + "", Integer::parseInt, integer -> {
if (integer < 1)
return false;
if (integer < 1) return false;
tnt.setLifetime(integer);
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
return true;
}, this).setItem(Material.CLOCK).open();
});
@@ -142,63 +141,56 @@ public class SimulatorTNTPhaseSettingsGui extends SimulatorBaseGui {
inventory.setItem(29, new SWItem(SWItem.getDye(lifetime > 0 ? 1 : 8), "§e-1", Arrays.asList("§7Shift§8: §e-5"), false, clickType -> {
tnt.setLifetime(Math.max(1, lifetime - (clickType.isShiftClick() ? 5 : 1)));
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED));
// Order
//Order
int order = tnt.getOrder();
inventory.setItem(13,
new SWItem(SWItem.getDye(order < SimulatorPhase.ORDER_LIMIT ? 10 : 8), "§e+1", Arrays.asList("§7Shift§8: §e+5"), false, clickType -> {
tnt.setOrder(Math.min(SimulatorPhase.ORDER_LIMIT, order + (clickType.isShiftClick() ? 5 : 1)));
SimulatorRenderer.update(simulator);
}).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED));
inventory.setItem(13, new SWItem(SWItem.getDye(order < SimulatorPhase.ORDER_LIMIT ? 10 : 8), "§e+1", Arrays.asList("§7Shift§8: §e+5"), false, clickType -> {
tnt.setOrder(Math.min(SimulatorPhase.ORDER_LIMIT, order + (clickType.isShiftClick() ? 5 : 1)));
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED));
Material negativeNumbers = Material.getMaterial(Core.getVersion() >= 19 ? "RECOVERY_COMPASS" : "FIREWORK_STAR");
SWItem orderItem = new SWItem(order >= 0 ? Material.COMPASS : negativeNumbers, "§eCalculation Order§8:§7 " + order, clickType -> {
new SimulatorAnvilGui<>(player, "Calculation Order", order + "", Integer::parseInt, integer -> {
if (integer < -SimulatorPhase.ORDER_LIMIT)
return false;
if (integer > SimulatorPhase.ORDER_LIMIT)
return false;
if (integer < -SimulatorPhase.ORDER_LIMIT) return false;
if (integer > SimulatorPhase.ORDER_LIMIT) return false;
tnt.setOrder(integer);
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
return true;
}, this).setItem(order >= 0 ? Material.COMPASS : negativeNumbers).open();
});
orderItem.getItemStack().setAmount(Math.max(1, Math.min(Math.abs(order), 30)));
inventory.setItem(22, orderItem);
inventory.setItem(31,
new SWItem(SWItem.getDye(order > -SimulatorPhase.ORDER_LIMIT ? 1 : 8), "§e-1", Arrays.asList("§7Shift§8: §e-5"), false, clickType -> {
tnt.setOrder(Math.max(-SimulatorPhase.ORDER_LIMIT, order - (clickType.isShiftClick() ? 5 : 1)));
SimulatorRenderer.update(simulator);
}).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED));
inventory.setItem(31, new SWItem(SWItem.getDye(order > -SimulatorPhase.ORDER_LIMIT ? 1 : 8), "§e-1", Arrays.asList("§7Shift§8: §e-5"), false, clickType -> {
tnt.setOrder(Math.max(-SimulatorPhase.ORDER_LIMIT, order - (clickType.isShiftClick() ? 5 : 1)));
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED));
// Jump
SWItem jumpX =
new SWItem(tnt.isXJump() ? Material.LIME_WOOL : Material.RED_WOOL, "§7TNT §eJump X§8: " + (tnt.isZJump() ? "§aon" : "§coff"), clickType -> {
tnt.setXJump(!tnt.isXJump());
SimulatorRenderer.update(simulator);
});
//Jump
SWItem jumpX = new SWItem(tnt.isXJump() ? Material.LIME_WOOL : Material.RED_WOOL, "§7TNT §eJump X§8: " + (tnt.isZJump() ? "§aon" : "§coff"), clickType -> {
tnt.setXJump(!tnt.isXJump());
SimulatorWatcher.update(simulator);
});
inventory.setItem(33, jumpX);
SWItem jumpY =
new SWItem(tnt.isYJump() ? Material.LIME_WOOL : Material.RED_WOOL, "§7TNT §eJump Y§8: " + (tnt.isYJump() ? "§aon" : "§coff"), clickType -> {
tnt.setYJump(!tnt.isYJump());
SimulatorRenderer.update(simulator);
});
SWItem jumpY = new SWItem(tnt.isYJump() ? Material.LIME_WOOL : Material.RED_WOOL, "§7TNT §eJump Y§8: " + (tnt.isYJump() ? "§aon" : "§coff"), clickType -> {
tnt.setYJump(!tnt.isYJump());
SimulatorWatcher.update(simulator);
});
inventory.setItem(16, jumpY);
SWItem jumpZ =
new SWItem(tnt.isZJump() ? Material.LIME_WOOL : Material.RED_WOOL, "§7TNT §eJump Z§8: " + (tnt.isZJump() ? "§aon" : "§coff"), clickType -> {
tnt.setZJump(!tnt.isZJump());
SimulatorRenderer.update(simulator);
});
SWItem jumpZ = new SWItem(tnt.isZJump() ? Material.LIME_WOOL : Material.RED_WOOL, "§7TNT §eJump Z§8: " + (tnt.isZJump() ? "§aon" : "§coff"), clickType -> {
tnt.setZJump(!tnt.isZJump());
SimulatorWatcher.update(simulator);
});
inventory.setItem(35, jumpZ);
SWItem jumpAll = new SWItem(Material.TNT, "§7TNT §eJump §8: " + (tnt.hasJump() ? "§aon" : "§coff"), clickType -> {
tnt.setJump(!tnt.hasJump());
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
});
inventory.setItem(25, jumpAll);
}

View File

@@ -1,25 +1,27 @@
/*
* This file is a part of the SteamWar software.
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
* 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 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.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License along with this program.
* If not, see <https://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.features.simulator.gui;
import de.steamwar.bausystem.features.simulator.SimulatorWatcher;
import de.steamwar.bausystem.features.simulator.data.Simulator;
import de.steamwar.bausystem.features.simulator.data.tnt.TNTElement;
import de.steamwar.bausystem.features.simulator.display.SimulatorRenderer;
import de.steamwar.bausystem.features.simulator.gui.base.SimulatorAnvilGui;
import de.steamwar.bausystem.features.simulator.gui.base.SimulatorBaseGui;
import de.steamwar.data.CMDs;
@@ -74,14 +76,13 @@ public class SimulatorTNTSettingsGui extends SimulatorBaseGui {
int baseTicks = tnt.getBaseTick();
inventory.setItem(9, new SWItem(SWItem.getDye(10), "§e+1", Arrays.asList("§7Shift§8: §e+5"), false, clickType -> {
tnt.changeBaseTicks(clickType.isShiftClick() ? 5 : 1);
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED));
SWItem baseTick = new SWItem(Material.REPEATER, "§eTicks§8:§7 " + baseTicks, clickType -> {
new SimulatorAnvilGui<>(player, "Ticks", baseTicks + "", Integer::parseInt, integer -> {
if (integer < 0)
return false;
if (integer < 0) return false;
tnt.changeBaseTicks(integer - baseTicks);
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
return true;
}, this).setItem(Material.REPEATER).open();
});
@@ -93,46 +94,42 @@ public class SimulatorTNTSettingsGui extends SimulatorBaseGui {
} else {
tnt.changeBaseTicks(clickType.isShiftClick() ? -5 : -1);
}
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED));
// Subpixel Alignment
inventory.setItem(21, new SWItem(Material.SUNFLOWER, "§7Align§8: §eCenter", clickType -> {
tnt.alignX(0);
tnt.alignZ(0);
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
}));
// Z
SWItem negativZItem = new SWItem(Material.OAK_BUTTON, "§7Align§8: §eNegativ Z", clickType -> {
if (tnt.getAlignment().getZ() != -1)
tnt.alignZ(-1);
SimulatorRenderer.update(simulator);
if (tnt.getAlignment().getZ() != -1) tnt.alignZ(-1);
SimulatorWatcher.update(simulator);
});
negativZItem.setEnchanted(tnt.getAlignment().getZ() == -1);
inventory.setItem(20, negativZItem);
SWItem positivZItem = new SWItem(Material.OAK_BUTTON, "§7Align§8: §ePositiv Z", clickType -> {
if (tnt.getAlignment().getZ() != 1)
tnt.alignZ(1);
SimulatorRenderer.update(simulator);
if (tnt.getAlignment().getZ() != 1) tnt.alignZ(1);
SimulatorWatcher.update(simulator);
});
positivZItem.setEnchanted(tnt.getAlignment().getZ() == 1);
inventory.setItem(22, positivZItem);
// X
SWItem negativXItem = new SWItem(Material.STONE_BUTTON, "§7Align§8: §eNegativ X", clickType -> {
if (tnt.getAlignment().getX() != -1)
tnt.alignX(-1);
SimulatorRenderer.update(simulator);
if (tnt.getAlignment().getX() != -1) tnt.alignX(-1);
SimulatorWatcher.update(simulator);
});
negativXItem.setEnchanted(tnt.getAlignment().getX() == -1);
inventory.setItem(12, negativXItem);
SWItem positivXItem = new SWItem(Material.STONE_BUTTON, "§7Align§8: §ePositiv X", clickType -> {
if (tnt.getAlignment().getX() != 1)
tnt.alignX(1);
SimulatorRenderer.update(simulator);
SWItem positivXItem = new SWItem(Material.STONE_BUTTON, "§7Align§8: §ePositiv X", clickType -> {
if(tnt.getAlignment().getX() != 1) tnt.alignX(1);
SimulatorWatcher.update(simulator);
});
positivXItem.setEnchanted(tnt.getAlignment().getX() == 1);
inventory.setItem(30, positivXItem);
@@ -140,52 +137,52 @@ public class SimulatorTNTSettingsGui extends SimulatorBaseGui {
// Pos X
inventory.setItem(15, new SWItem(SWItem.getDye(10), "§e+1", Arrays.asList("§7Shift§8: §e+0.0625"), false, clickType -> {
tnt.move(clickType.isShiftClick() ? 0.0625 : 1, 0, 0);
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED));
inventory.setItem(24, new SWItem(Material.PAPER, "§eX§8:§7 " + tnt.getPosition().getX(), clickType -> {
new SimulatorAnvilGui<>(player, "X", tnt.getPosition().getX() + "", Double::parseDouble, d -> {
tnt.getPosition().setX(d);
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
return true;
}, this).open();
}));
inventory.setItem(33, new SWItem(SWItem.getDye(1), "§e-1", Arrays.asList("§7Shift§8: §e-0.0625"), false, clickType -> {
tnt.move(clickType.isShiftClick() ? -0.0625 : -1, 0, 0);
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED));
// Pos Y
inventory.setItem(16, new SWItem(SWItem.getDye(10), "§e+1", Arrays.asList("§7Shift§8: §e+0.0625"), false, clickType -> {
tnt.move(0, clickType.isShiftClick() ? 0.0625 : 1, 0);
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED));
inventory.setItem(25, new SWItem(Material.PAPER, "§eY§8:§7 " + tnt.getPosition().getY(), clickType -> {
new SimulatorAnvilGui<>(player, "Y", tnt.getPosition().getY() + "", Double::parseDouble, d -> {
tnt.getPosition().setY(d);
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
return true;
}, this).open();
}));
inventory.setItem(34, new SWItem(SWItem.getDye(1), "§e-1", Arrays.asList("§7Shift§8: §e-0.0625"), false, clickType -> {
tnt.move(0, clickType.isShiftClick() ? -0.0625 : -1, 0);
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED));
// Pos Z
inventory.setItem(17, new SWItem(SWItem.getDye(10), "§e+1", Arrays.asList("§7Shift§8: §e+0.0625"), false, clickType -> {
tnt.move(0, 0, clickType.isShiftClick() ? 0.0625 : 1);
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED));
inventory.setItem(26, new SWItem(Material.PAPER, "§eZ§8:§7 " + tnt.getPosition().getZ(), clickType -> {
new SimulatorAnvilGui<>(player, "Z", tnt.getPosition().getZ() + "", Double::parseDouble, d -> {
tnt.getPosition().setZ(d);
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
return true;
}, this).open();
}));
inventory.setItem(35, new SWItem(SWItem.getDye(1), "§e-1", Arrays.asList("§7Shift§8: §e-0.0625"), false, clickType -> {
tnt.move(0, 0, clickType.isShiftClick() ? -0.0625 : -1);
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED));
}
}

View File

@@ -1,24 +1,26 @@
/*
* This file is a part of the SteamWar software.
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
* 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 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.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License along with this program.
* If not, see <https://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.features.simulator.gui.base;
import de.steamwar.bausystem.features.simulator.SimulatorWatcher;
import de.steamwar.bausystem.features.simulator.data.Simulator;
import de.steamwar.bausystem.features.simulator.display.SimulatorRenderer;
import de.steamwar.core.Core;
import de.steamwar.inventory.SWInventory;
import de.steamwar.inventory.SWItem;
@@ -43,8 +45,7 @@ public abstract class SimulatorBaseGui {
}
public final void open() {
if (!shouldOpen())
return;
if (!shouldOpen()) return;
String newTitle = title();
String originalTitle = player.getOpenInventory().getTitle();
@@ -57,6 +58,7 @@ public abstract class SimulatorBaseGui {
setup();
if (player.getOpenInventory().getTopInventory() != inv) {
inventory.open();
SimulatorWatcher.watch(player, simulator, this::open);
}
if (Core.getVersion() > 19) {
player.getOpenInventory().setTitle(title());
@@ -68,6 +70,7 @@ public abstract class SimulatorBaseGui {
}
if (player.getOpenInventory().getTopInventory() == inv) {
inventory.open();
SimulatorWatcher.watch(player, simulator, this::open);
}
return;
}
@@ -79,7 +82,11 @@ public abstract class SimulatorBaseGui {
return inv;
});
setup();
inventory.addCloseCallback(clickType -> {
SimulatorWatcher.watch(player, null, null);
});
SimulatorWatcher.watch(player, simulator, this::open);
if (simulator != null && simulator.getStabGenerator() != null) {
populateStabGenerator();
} else {
@@ -91,7 +98,7 @@ public abstract class SimulatorBaseGui {
private void populateStabGenerator() {
inventory.setItem(22, new SWItem(Material.BARRIER, "§cCancel Stab Generator", click -> {
simulator.getStabGenerator().cancel();
SimulatorRenderer.update(simulator);
SimulatorWatcher.update(simulator);
}));
}

View File

@@ -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<Simulator> simulators = new ArrayList<>();
for (String s : yapionObject.getKeys()) {

View File

@@ -87,6 +87,7 @@ public class MaterialLazyInit {
Block block = Bukkit.getWorlds().get(0).getBlockAt(0, 0, 0);
block.setType(material);
unmoveable = block.getPistonMoveReaction() == PistonMoveReaction.BLOCK || block.getPistonMoveReaction() == PistonMoveReaction.IGNORE || block.getState() instanceof TileState;
block.setType(Material.AIR);
}
if (material.isItem() && material != Material.AIR) {

View File

@@ -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!");
}
}

View File

@@ -1,203 +0,0 @@
package de.steamwar.bausystem.utils.cursor;
import de.steamwar.bausystem.utils.RayTraceUtils;
import de.steamwar.entity.REntity;
import de.steamwar.entity.REntityServer;
import de.steamwar.entity.RFallingBlockEntity;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.block.BlockFace;
import org.bukkit.entity.Player;
import org.bukkit.event.block.Action;
import org.bukkit.util.Vector;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.function.BiFunction;
import java.util.function.Predicate;
@Getter
public abstract class Cursor {
private final World WORLD = Bukkit.getWorlds().get(0);
private final REntityServer targetServer;
private final REntityServer cursorServer;
private final Player owner;
private final Material highlightMaterial;
private RFallingBlockEntity cursorEntity;
private Location cursorLocation;
private boolean isHittingEntity = false;
@Setter
private Material cursorMaterial;
@Setter
private List<CursorMode> allowedCursorModes;
private boolean visible = true;
protected Cursor(REntityServer targetServer, Player owner, Material highlightMaterial, Material cursorMaterial, List<CursorMode> allowedModes) {
this.targetServer = targetServer;
this.owner = owner;
this.highlightMaterial = highlightMaterial;
this.cursorMaterial = cursorMaterial;
this.allowedCursorModes = allowedModes;
cursorServer = new REntityServer();
cursorServer.addPlayer(owner);
CursorManager.getInstance().registerCursor(this);
}
public void render() {
if (!visible)
return;
RayTraceUtils.RRayTraceResult rayTraceResult = RayTraceUtils.traceREntity(owner, owner.getLocation(), targetServer.getEntities());
if (rayTraceResult == null) {
if (cursorEntity != null)
cursorEntity.die();
cursorEntity = null;
return;
}
REntity hitEntity = rayTraceResult.getHitEntity() == cursorEntity ? null : rayTraceResult.getHitEntity();
Material activeCursorMaterial = hitEntity == null ? cursorMaterial : highlightMaterial;
CursorMode activeCursorMode = allowedCursorModes.stream().filter((mode) -> mode.isActive.test(owner)).min(Comparator.comparingInt(a -> a.priority))
.orElse(CursorMode.BLOCK_ALIGNED);
Location activeCursorLocation = hitEntity == null ? activeCursorMode.positionTransform.apply(owner, rayTraceResult).toLocation(WORLD)
: new Vector(hitEntity.getX(), hitEntity.getY(), hitEntity.getZ()).toLocation(WORLD);
cursorLocation = activeCursorLocation;
isHittingEntity = hitEntity != null;
if (cursorEntity == null) {
cursorEntity = new RFallingBlockEntity(cursorServer, activeCursorLocation, activeCursorMaterial);
cursorEntity.setNoGravity(true);
} else if (cursorEntity.getMaterial() == activeCursorMaterial) {
cursorEntity.move(activeCursorLocation);
} else {
cursorEntity.die();
cursorEntity = new RFallingBlockEntity(cursorServer, activeCursorLocation, activeCursorMaterial);
cursorEntity.setNoGravity(true);
if (activeCursorMaterial == highlightMaterial) {
cursorEntity.setGlowing(true);
}
}
}
public void hide() {
visible = false;
if (cursorEntity != null) {
cursorEntity.die();
cursorEntity = null;
}
}
public void show() {
visible = true;
}
final void handleClick(Action action) {
if (!visible)
return;
onClick(Optional.ofNullable(this.cursorLocation), isHittingEntity, action);
}
public void close() {
cursorServer.close();
CursorManager.getInstance().unregisterCursor(this);
}
@AllArgsConstructor
public enum CursorMode {
FREE(1, (player, rayTraceResult) -> {
Vector pos = rayTraceResult.getHitPosition();
BlockFace face = rayTraceResult.getHitBlockFace();
if (face != null) {
switch (face) {
case DOWN:
pos.setY(pos.getY() - 0.98);
break;
case EAST:
pos.setX(pos.getX() + 0.49);
break;
case WEST:
pos.setX(pos.getX() - 0.49);
break;
case NORTH:
pos.setZ(pos.getZ() - 0.49);
break;
case SOUTH:
pos.setZ(pos.getZ() + 0.49);
break;
default:
break;
}
if (face.getModY() == 0 && player.isSneaking()) {
pos.setY(pos.getY() - 0.49);
}
}
return pos;
}, (player) -> player.isSneaking()),
BLOCK_ALIGNED(0, (player, rayTraceResult) -> {
Vector pos = rayTraceResult.getHitPosition();
BlockFace face = rayTraceResult.getHitBlockFace();
if (face != null) {
switch (face) {
case DOWN:
pos.setY(pos.getY() - 0.98);
break;
case EAST:
pos.setX(pos.getX() + 0.49);
break;
case WEST:
pos.setX(pos.getX() - 0.49);
break;
case NORTH:
pos.setZ(pos.getZ() - 0.49);
break;
case SOUTH:
pos.setZ(pos.getZ() + 0.49);
break;
default:
break;
}
}
pos.setX(pos.getBlockX() + 0.5);
if (pos.getY() - pos.getBlockY() != 0 && face == BlockFace.UP) {
pos.setY(pos.getBlockY() + 1.0);
} else {
pos.setY(pos.getBlockY());
}
pos.setZ(pos.getBlockZ() + 0.5);
return pos;
}, (player) -> true);
private final int priority;
private final BiFunction<Player, RayTraceUtils.RRayTraceResult, Vector> positionTransform;
private final Predicate<Player> isActive;
}
public abstract void onClick(Optional<Location> cursorLocation, boolean didHitEntity, Action action);
}

View File

@@ -1,118 +0,0 @@
package de.steamwar.bausystem.utils.cursor;
import com.comphenix.tinyprotocol.TinyProtocol;
import de.steamwar.Reflection;
import de.steamwar.bausystem.BauSystem;
import de.steamwar.linkage.Linked;
import lombok.Getter;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.BiFunction;
@Linked
public class CursorManager implements Listener {
@Getter
private static CursorManager instance;
private final Set<Player> calculationActive = new HashSet<>();
private final Map<Player, List<Cursor>> activeCursors = new HashMap<>();
public CursorManager() {
if (instance == null) {
instance = this;
}
BiFunction<Player, Object, Object> function = (player, object) -> {
updateCursors(player);
return object;
};
Bukkit.getScheduler().runTaskLater(BauSystem.getInstance(), () -> {
Set<Player> playersWithActiveCursor = activeCursors.keySet();
playersWithActiveCursor.forEach(this::updateCursors);
}, 0);
Class<?> positionPacketClass = Reflection.getClass("net.minecraft.network.protocol.game.ServerboundMovePlayerPacket$Pos");
Class<?> lookPacketClass = Reflection.getClass("net.minecraft.network.protocol.game.ServerboundMovePlayerPacket$Rot");
Class<?> positionLookPacketClass = Reflection.getClass("net.minecraft.network.protocol.game.ServerboundMovePlayerPacket$PosRot");
TinyProtocol.instance.addFilter(positionPacketClass, function);
TinyProtocol.instance.addFilter(lookPacketClass, function);
TinyProtocol.instance.addFilter(positionLookPacketClass, function);
}
void registerCursor(Cursor cursor) {
List<Cursor> cursorsOfPlayer = activeCursors.getOrDefault(cursor.getOwner(), new ArrayList<>());
cursorsOfPlayer.add(cursor);
activeCursors.put(cursor.getOwner(), cursorsOfPlayer);
}
void unregisterCursor(Cursor cursor) {
List<Cursor> cursorsOfPlayer = activeCursors.get(cursor.getOwner());
if (cursorsOfPlayer != null) {
cursorsOfPlayer.remove(cursor);
if (cursorsOfPlayer.isEmpty()) {
activeCursors.remove(cursor.getOwner());
}
}
}
private void closeCursorsOf(Player player) {
List<Cursor> cursorsOfPlayer = activeCursors.remove(player);
if (cursorsOfPlayer != null) {
new ArrayList<>(cursorsOfPlayer).forEach(Cursor::close);
}
}
private void updateCursors(Player player) {
if (!activeCursors.containsKey(player)) {
return;
}
synchronized (calculationActive) {
if (calculationActive.contains(player)) {
return;
} else {
calculationActive.add(player);
}
}
List<Cursor> cursors = activeCursors.get(player);
if (cursors != null) {
cursors.forEach(Cursor::render);
}
synchronized (calculationActive) {
calculationActive.remove(player);
}
}
@EventHandler
private void handlePlayerInteract(PlayerInteractEvent event) {
Player player = event.getPlayer();
List<Cursor> cursorsOfPlayer = activeCursors.get(player);
if (cursorsOfPlayer != null) {
cursorsOfPlayer.forEach(cursor -> cursor.handleClick(event.getAction()));
}
}
@EventHandler
private void handlePlayerQuit(PlayerQuitEvent event) {
closeCursorsOf(event.getPlayer());
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -50,5 +50,6 @@ tasks.register<DevServer>("DevBau21") {
description = "Run a 1.21 Dev Bau"
dependsOn(":SpigotCore:shadowJar")
dependsOn(":BauSystem:shadowJar")
dependsOn(":SchematicSystem:shadowJar")
template = "Bau21"
}

View File

@@ -1,18 +1,20 @@
/*
* This file is a part of the SteamWar software.
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
* 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 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.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License along with this program.
* If not, see <https://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.linkage;
@@ -30,7 +32,6 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
public abstract class AbstractLinker<T> {
@@ -51,13 +52,16 @@ public abstract class AbstractLinker<T> {
List<Class<?>> classes;
try {
classes = new BufferedReader(new InputStreamReader(plugin.getClass().getResourceAsStream("/META-INF/annotations/de.steamwar.linkage.Linked")))
.lines().map(s -> {
.lines()
.map(s -> {
try {
return Class.forName(s, false, plugin.getClass().getClassLoader());
} catch (ClassNotFoundException | NoClassDefFoundError e) {
throw new SecurityException(e.getMessage(), e);
}
}).filter(Objects::nonNull).collect(Collectors.toList());
})
.filter(Objects::nonNull)
.collect(Collectors.toList());
} catch (SecurityException e) {
Throwable cause = e.getCause();
throw new LinkException(cause.getMessage(), cause);
@@ -67,15 +71,12 @@ public abstract class AbstractLinker<T> {
classes.forEach(clazz -> {
MinVersion minVersion = clazz.getAnnotation(MinVersion.class);
MaxVersion maxVersion = clazz.getAnnotation(MaxVersion.class);
if (!versionCheck(clazz, minVersion, maxVersion))
return;
if (!versionCheck(clazz, minVersion, maxVersion)) return;
EventMode eventMode = clazz.getAnnotation(EventMode.class);
if (!eventModeCheck(clazz, eventMode))
return;
if (!eventModeCheck(clazz, eventMode)) return;
PluginCheck[] pluginChecks = clazz.getAnnotationsByType(PluginCheck.class);
for (PluginCheck pluginCheck : pluginChecks) {
if (!pluginCheck(clazz, pluginCheck))
return;
if (!pluginCheck(clazz, pluginCheck)) return;
}
Object any;
@@ -156,14 +157,12 @@ public abstract class AbstractLinker<T> {
/**
* There is no need in calling {@link Enable#enable()} by this method.
*/
protected void linkObject(Object any) {}
protected void linkObject(Object any) {
}
/**
* There is no need in calling {@link Disable#disable()} ()} by this method.
*/
protected void unlinkObject(Object any) {}
public final <L> Optional<L> get(Class<L> clazz) {
return Optional.ofNullable((L) instances.get(clazz));
protected void unlinkObject(Object any) {
}
}

View File

@@ -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")
}
compileOnlyApi("org.jetbrains.kotlin:kotlin-stdlib:2.2.21")
compileOnlyApi(libs.exposedCore)
compileOnlyApi(libs.exposedDao)
compileOnlyApi(libs.exposedJdbc)
compileOnlyApi(libs.exposedTime)
}

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
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<AuditLog> 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);
}
}

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
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<Int>): IntEntity(id) {
companion object: IntEntityClass<AuditLog>(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,
}
}

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
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<BannedUserIPs> table = new Table<>(BannedUserIPs.class);
private static final SelectStatement<BannedUserIPs> getByID = table.selectFields("UserID");
private static final SelectStatement<BannedUserIPs> 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<BannedUserIPs> get(int userID) {
return getByID.listSelect(userID);
}
public static List<BannedUserIPs> 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);
}
}

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
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<CompositeID>): CompositeEntity(id) {
companion object: CompositeEntityClass<BannedUserIPs>(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()
}
}

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
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<Integer, BauweltMember> memberCache = new HashMap<>();
public static void clear() {
memberCache.clear();
}
private static final Table<BauweltMember> table = new Table<>(BauweltMember.class);
private static final SelectStatement<BauweltMember> getMember = table.select(Table.PRIMARY);
private static final SelectStatement<BauweltMember> 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<BauweltMember> getMembers(UUID bauweltID){
return getMembers(SteamwarUser.get(bauweltID).getId());
}
public static List<BauweltMember> 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();
}
}

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
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<CompositeID>): CompositeEntity(id) {
companion object: CompositeEntityClass<BauweltMember>(BauweltMemberTable) {
private val cache = mutableMapOf<Int, BauweltMember>()
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<Int>, newMemberId: EntityID<Int>) = 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()
}
}

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
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<CheckedSchematic> table = new Table<>(CheckedSchematic.class);
private static final SelectStatement<CheckedSchematic> statusOfNode = new SelectStatement<>(table, "SELECT * FROM CheckedSchematic WHERE NodeId = ? AND DeclineReason != 'Prüfvorgang abgebrochen' ORDER BY EndTime DESC");
private static final SelectStatement<CheckedSchematic> 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<CheckedSchematic> 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<CheckedSchematic> getLastDeclinedOfNode(int node) {
return statusOfNode.listSelect(node);
}
public static List<CheckedSchematic> previousChecks(SchematicNode node) {
return nodeHistory.listSelect(node.getId());
}
public static List<CheckedSchematic> 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);
}
}

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
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<CompositeID>): CompositeEntity(id) {
companion object: CompositeEntityClass<CheckedSchematic>(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
}

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
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<Event> table = new Table<>(Event.class);
private static final SelectStatement<Event> byCurrent = new SelectStatement<>(table, "SELECT * FROM Event WHERE Start < now() AND End > now()");
private static final SelectStatement<Event> byId = table.select(Table.PRIMARY);
private static final SelectStatement<Event> byName = table.select("eventName");
private static final SelectStatement<Event> byComing = new SelectStatement<>(table, "SELECT * FROM Event WHERE Start > now()");
private static final SelectStatement<Event> 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<Event> 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<Event> 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);
}
}

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
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<Int>) : IntEntity(id) {
companion object : IntEntityClass<Event>(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() }
}

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
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<EventFight> {
private static final Table<EventFight> table = new Table<>(EventFight.class);
private static final SelectStatement<EventFight> byId = table.select(Table.PRIMARY);
private static final SelectStatement<EventFight> byGroup = new SelectStatement<EventFight>(table, "SELECT * FROM EventFight WHERE GroupID = ? ORDER BY StartTime ASC");
private static final SelectStatement<EventFight> byGroupLast = new SelectStatement<EventFight>(table, "SELECT * FROM EventFight WHERE GroupID = ? ORDER BY StartTime DESC LIMIT 1");
private static final SelectStatement<EventFight> allComing = new SelectStatement<>(table, "SELECT * FROM EventFight WHERE StartTime > now() ORDER BY StartTime ASC");
private static final SelectStatement<EventFight> event = new SelectStatement<>(table, "SELECT * FROM EventFight WHERE EventID = ? ORDER BY StartTime ASC");
private static final SelectStatement<EventFight> 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<EventFight> fights = new PriorityQueue<>();
public static EventFight get(int fightID) {
return byId.select(fightID);
}
public static List<EventFight> get(EventGroup group) {
return byGroup.listSelect(group.getId());
}
public static Optional<EventFight> getLast(EventGroup group) {
return Optional.ofNullable(byGroupLast.select(group.getId()));
}
public static void loadAllComingFights() {
fights.clear();
fights.addAll(allComing.listSelect());
}
public static List<EventFight> getEvent(int eventID) {
return event.listSelect(eventID);
}
private static List<EventFight> activeFightsCache = null;
public static void clearActiveFightsCache() {
activeFightsCache = null;
}
public static List<EventFight> 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<EventGroup> getGroup() {
return Optional.ofNullable(groupId).flatMap(EventGroup::get);
}
public Optional<Team> getWinner() {
if(ergebnis == 0)
return Optional.empty();
return Optional.ofNullable(ergebnis == 1 ? Team.get(teamBlue) : Team.get(teamRed));
}
public Optional<Team> getLosser() {
if(ergebnis == 0)
return Optional.empty();
return Optional.ofNullable(ergebnis == 1 ? Team.get(teamRed) : Team.get(teamBlue));
}
public List<EventRelation> 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);
}
}

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
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<Int>) : IntEntity(id), Comparable<EventFight> {
companion object : IntEntityClass<EventFight>(EventFightTable) {
val fights: Queue<EventFight> = 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<EventFight>? = null
@JvmStatic
fun clearActiveFightsCache() {
activeFightsCache = null
}
@JvmStatic
fun getActiveFights(): List<EventFight> {
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()
}
}

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
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<EventGroup> table = new Table<>(EventGroup.class);
private static final SelectStatement<EventGroup> get = table.select(Table.PRIMARY);
private static final SelectStatement<EventGroup> 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<EventGroup> 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<EventGroup> 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<Team, Integer> points;
public List<EventFight> getFights() {
return EventFight.get(this);
}
public Set<Integer> getTeamsId() {
return getFights().stream().flatMap(fight -> Stream.of(fight.getTeamBlue(), fight.getTeamRed()))
.collect(Collectors.toSet());
}
public Set<Team> getTeams() {
return getTeamsId().stream().map(Team::get).collect(Collectors.toSet());
}
public Optional<EventFight> getLastFight() {
return EventFight.getLast(this);
}
public List<EventRelation> getDependents() {
return EventRelation.getGroupRelations(this);
}
public Map<Team, Integer> calculatePoints() {
if (points == null) {
Map<Integer, Integer> 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
}
}

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
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<Int>) : IntEntity(id) {
companion object : IntEntityClass<EventGroup>(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<Team, Int>? = null
fun calculatePoints(): Map<Team, Int> {
if (points == null) {
val p: MutableMap<Int, Int> = 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
}
}

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
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<EventRelation> table = new Table<>(EventRelation.class);
private static final SelectStatement<EventRelation> get = new SelectStatement<>(table, "SELECT * FROM EventRelation WHERE FromType = ? AND FromId = ?");
private static final SelectStatement<EventRelation> byId = new SelectStatement<>(table, "SELECT * FROM EventRelation WHERE id = ?");
private static final SelectStatement<EventRelation> 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<EventRelation> get(Event event) {
return byEvent.listSelect(event.getEventID());
}
public static EventRelation get(int id) {
return byId.select(id);
}
public static List<EventRelation> getFightRelations(EventFight fight) {
return get.listSelect(FromType.FIGHT, fight.getFightID());
}
public static List<EventRelation> 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<EventFight> getFromFight() {
if(fromType == FromType.FIGHT) {
return Optional.of(EventFight.get(fromId));
} else {
return Optional.empty();
}
}
public Optional<EventGroup> 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<Team> 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.<Team, Integer>comparingByValue().reversed())
.skip(fromPlace)
.findFirst()
.map(Map.Entry::getKey));
} else {
return Optional.empty();
}
}
public boolean apply() {
Optional<Integer> 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
}
}

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
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<Int>) : IntEntity(id) {
companion object : IntEntityClass<EventRelation>(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
}
}

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
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<Fight> table = new Table<>(Fight.class);
private static final SelectStatement<Fight> 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<Fight> 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<Fight> getPage(int page, int elementsPerPage) {
List<Fight> fights = getPage.listSelect(page * elementsPerPage, elementsPerPage);
List<FightPlayer> 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<FightPlayer> bluePlayers = new ArrayList<>();
@Getter
private final List<FightPlayer> 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<FightPlayer> fightPlayers) {
for(FightPlayer fp : fightPlayers) {
if(fp.getFightID() != fightID)
continue;
if(fp.getTeam() == 1)
bluePlayers.add(fp);
else
redPlayers.add(fp);
}
}
}

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
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<Int>) : IntEntity(id) {
companion object : IntEntityClass<Fight>(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<Fight> = 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<FightPlayer>
lateinit var redPlayers: List<FightPlayer>
private fun initPlayers(fightPlayers: List<FightPlayer>) {
val blue = mutableListOf<FightPlayer>()
val red = mutableListOf<FightPlayer>()
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;
}
}

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
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<FightPlayer> table = new Table<>(FightPlayer.class);
private static final Statement create = table.insertAll();
private static final SelectStatement<FightPlayer> 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<FightPlayer> batchGet(Stream<Integer> fightIds) {
try (SelectStatement<FightPlayer> batch = new SelectStatement<>(table, "SELECT * FROM FightPlayer WHERE FightID IN (" + fightIds.map(Object::toString).collect(Collectors.joining(", ")) + ")")) {
return batch.listSelect();
}
}
}

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
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<CompositeID>) : CompositeEntity(id) {
companion object : CompositeEntityClass<FightPlayer>(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<Int>) = 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
}

View File

@@ -35,11 +35,19 @@ import java.util.stream.Collectors;
public final class GameModeConfig<M, W> {
public static final Function<String, String> ToString = Function.identity();
public static final Function<File, String> ToStaticWarGear = __ -> "WarGear";
public static final Function<File, String> ToInternalName = file -> file != null ? file.getName().replace(".yml", "") : "WarGear";
public static final Function<File, String> ToStaticWarGear = GameModeConfig::constWarGear;
public static final Function<File, String> 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<String, GameModeConfig<?, String>> byFileName;
private static final Map<String, GameModeConfig<?, String>> byGameName;
private static final Map<SchematicType, GameModeConfig<?, String>> bySchematicType;
@@ -63,11 +71,15 @@ public final class GameModeConfig<M, W> {
}
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<M, W> {
bySchematicType = new HashMap<>();
SchematicType.values();
DEFAULTS = SQLWrapper.impl.loadGameModeConfig(null);
byFileName.values().forEach(gameModeConfig -> {
List<SchematicType> 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<M, W> {
*/
public final SchematicType Type;
private final List<String> SubTypesStrings;
/**
* The schematic types that are also allowed to be chosen in this arena
*/
@@ -605,6 +631,13 @@ public final class GameModeConfig<M, W> {
*/
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<M, W> {
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<M, W> {
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<Set<M>, Integer> Limited = new HashMap<>();
@@ -650,6 +685,10 @@ public final class GameModeConfig<M, W> {
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);
}

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
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<IgnoreSystem> table = new Table<>(IgnoreSystem.class, "IgnoredPlayers");
private static final SelectStatement<IgnoreSystem> 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());
}
}

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
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<CompositeID>) : CompositeEntity(id) {
var ignorer by IgnoreSystemTable.ignorer
var ignored by IgnoreSystemTable.ignored
companion object : CompositeEntityClass<IgnoreSystem>(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()
}
}
}

View File

@@ -0,0 +1,96 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.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.count
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.lessSubQuery
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.select
object LeaderboardTable : CompositeIdTable("Leaderboard") {
val userId = reference("UserId", SteamwarUserTable)
val name = varchar("Name", 64).entityId()
val time = long("Time")
val updatedAt = timestamp("UpdatedAt").defaultExpression(CurrentTimestamp)
val bestTime = bool("BestTime")
}
class Leaderboard(id: EntityID<CompositeID>) : CompositeEntity(id) {
companion object : CompositeEntityClass<Leaderboard>(LeaderboardTable) {
@JvmStatic
fun getLeaderboard(name: String) = useDb {
find { LeaderboardTable.name eq name }.orderBy(LeaderboardTable.time to SortOrder.ASC).limit(5).toList()
}
@JvmStatic
fun getPlayerTime(user: SteamwarUser, name: String) = useDb {
findById(CompositeID {
it[LeaderboardTable.userId] = user.id.value
it[LeaderboardTable.name] = name
})
}
@JvmStatic
fun getPlayerPlacement(user: SteamwarUser, name: String) = useDb {
LeaderboardTable.select(LeaderboardTable.time.count())
.where {
(LeaderboardTable.name eq name) and (LeaderboardTable.time lessSubQuery LeaderboardTable.select(
LeaderboardTable.time
).where { (LeaderboardTable.userId eq user.id.value) and (LeaderboardTable.name eq name) })
}
.firstOrNull()?.get(LeaderboardTable.time.count())?.toInt() ?: Int.MAX_VALUE
}
@JvmStatic
fun upsert(userId: Int, name: String, time: Long, bestTime: Boolean) = useDb {
findByIdAndUpdate(CompositeID {
it[LeaderboardTable.userId] = userId
it[LeaderboardTable.name] = name
}) {
it.time = time
it.bestTime = bestTime
} ?: new(
CompositeID {
it[LeaderboardTable.userId] = userId
it[LeaderboardTable.name] = name
}
) {
this.time = time
this.bestTime = bestTime
}
}
}
val user by LeaderboardTable.userId.transform({ EntityID(it, SteamwarUserTable) }, { it.value })
val name by LeaderboardTable.name
var time by LeaderboardTable.time
var updatedAt by LeaderboardTable.updatedAt
var bestTime by LeaderboardTable.bestTime
}

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
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<Mod> table = new Table<>(Mod.class, "Mods");
private static final SelectStatement<Mod> get = table.select(Table.PRIMARY);
private static final SelectStatement<Mod> findFirst = new SelectStatement<>(table, "SELECT * FROM Mods WHERE ModType = 0 LIMIT 1");
private static final SelectStatement<Mod> 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<Mod> 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;
}
}
}

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
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<CompositeID>) : CompositeEntity(id) {
companion object : CompositeEntityClass<Mod>(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");
}
}

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
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<NodeData> 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<NodeData> 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<NodeData> 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<NodeData> 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;
}
}

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
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<CompositeID>): 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<NodeData>(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");
}
}

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
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<NodeDownload> table = new Table<>(NodeDownload.class);
private static final Statement insert = table.insertFields("NodeId", "Link");
private static final SelectStatement<NodeDownload> 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);
}
}

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
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<Int>("NodeDownload") {
override val id = reference("NodeId", SchematicNodeTable).uniqueIndex()
val link = varchar("Link", 255)
val timestamp = timestamp("Timestamp").defaultExpression(CurrentTimestamp)
}
class NodeDownload(id: EntityID<Int>) : IntEntity(id) {
companion object : IntEntityClass<NodeDownload>(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()
}
}

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
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<NodeMember> table = new Table<>(NodeMember.class);
private static final SelectStatement<NodeMember> getNodeMember = table.select(Table.PRIMARY);
private static final SelectStatement<NodeMember> getNodeMembers = table.selectFields("NodeId");
private static final SelectStatement<NodeMember> 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<Integer> 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<NodeMember> getNodeMembers(int node) {
return new HashSet<>(getNodeMembers.listSelect(node));
}
public static Set<NodeMember> getSchematics(int member) {
return new HashSet<>(getSchematics.listSelect(member));
}
public void setParentId(Integer parentId) {
this.parentId = parentId;
updateParent.update(this.parentId, nodeId, userId);
}
}

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
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<CompositeID>) : CompositeEntity(id) {
companion object : CompositeEntityClass<NodeMember>(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()
}
}

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
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<CompositeID>): CompositeEntity(id) {
companion object: CompositeEntityClass<InternalKit>(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()
}
}

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
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<PollAnswer> table = new Table<>(PollAnswer.class);
private static final SelectStatement<PollAnswer> 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<Integer, Integer> getCurrentResults() {
return getResults.select(rs -> {
Map<Integer, Integer> 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);
}
}

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
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<CompositeID>): 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<PollAnswer>(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<Int, Int> = 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<Int, Int>()
while (it.next()) {
result[it.getInt("Answer")] = it.getInt("Times")
}
result
} ?: emptyMap()
}
}
fun hasAnswered() = answerId != 0
}

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
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<Punishment> table = new Table<>(Punishment.class, "Punishments");
private static final SelectStatement<Punishment> getPunishments = new SelectStatement<>(table, "SELECT * FROM Punishments WHERE PunishmentId IN (SELECT MAX(PunishmentId) FROM Punishments WHERE UserId = ? GROUP BY Type)");
private static final SelectStatement<Punishment> getPunishment = new SelectStatement<>(table, "SELECT * FROM Punishments WHERE UserId = ? AND Type = ? ORDER BY PunishmentId DESC LIMIT 1");
private static final SelectStatement<Punishment> 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<PunishmentType, Punishment> getPunishmentsOfPlayer(int user) {
return getPunishments.listSelect(user).stream().collect(Collectors.toMap(Punishment::getType, punishment -> punishment));
}
public static List<Punishment> getAllPunishmentsOfPlayer(int user) {
return getAllPunishments.listSelect(user);
}
public static boolean isPunished(SteamwarUser user, PunishmentType type, Consumer<Punishment> 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;
}
}

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
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<Int>) : IntEntity(id) {
companion object : IntEntityClass<Punishment>(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<Punishment>): 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
}
}

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
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<Referee> table = new Table<>(Referee.class);
private static final SelectStatement<Referee> 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<Integer> 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;
}

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
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<CompositeID>): CompositeEntity(id) {
companion object: CompositeEntityClass<Referee>(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 })
}

View File

@@ -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<M> {
SQLWrapper<?> impl = ImplementationProvider.getImpl("de.steamwar.sql.SQLWrapperImpl");
@@ -30,6 +32,10 @@ public interface SQLWrapper<M> {
GameModeConfig<M, String> loadGameModeConfig(File file);
default List<M> getMaterialWithGreaterBlastResistance(double maxBlastResistance) {
return Collections.emptyList();
}
default void processSchematicType(GameModeConfig<?, String> gameModeConfig) {
}

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
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<SWException> 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();
}
}

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
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()
}
}
}

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
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<SchemElo> table = new Table<>(SchemElo.class);
private static final SelectStatement<SchemElo> 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;
}

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
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<CompositeID>): CompositeEntity(id) {
companion object: CompositeEntityClass<SchemElo>(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
}

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
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<Integer, Map<String, List<String>>> 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<SchematicNode> 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<SchematicNode> byId = new SelectStatement<>(table,
nodeSelector + "WHERE NodeId = ?");
private static final SelectStatement<SchematicNode> byOwnerNameParent = new SelectStatement<>(table,
nodeSelector + "WHERE NodeOwner = ? AND NodeName = ? AND ParentNode " + Statement.NULL_SAFE_EQUALS + "?");
private static final SelectStatement<SchematicNode> byParent = new SelectStatement<>(table,
nodeSelector + "WHERE ParentNode" + Statement.NULL_SAFE_EQUALS + "? ORDER BY NodeName");
private static final SelectStatement<SchematicNode> dirsByParent = new SelectStatement<>(table, nodeSelector
+ "WHERE ParentNode" + Statement.NULL_SAFE_EQUALS + "? AND NodeType is NULL ORDER BY NodeName");
private static final SelectStatement<SchematicNode> byOwnerType = new SelectStatement<>(table,
nodeSelector + "WHERE NodeOwner = ? AND NodeType = ? ORDER BY NodeName");
private static final SelectStatement<SchematicNode> byType = new SelectStatement<>(table,
nodeSelector + "WHERE NodeType = ? ORDER BY NodeName");
private static final SelectStatement<SchematicNode> 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<SchematicNode> 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<SchematicNode> 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<SchematicNode> 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<SchematicNode> 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<SchematicNode> 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<SchematicNode> byIdAndUser = new SelectStatement<>(table,
"SELECT NodeId, NodeOwner, ? AS EffectiveOwner, NodeName, ParentNode, LastUpdate, NodeItem, NodeType, NodeRank, Config FROM SchematicNode WHERE NodeId = ?");
private static final SelectStatement<SchematicNode> 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<SchematicNode> getAll(SteamwarUser user) {
return all.listSelect(user, user, user);
}
public static Map<Integer, List<SchematicNode>> getAllMap(SteamwarUser user) {
return map(getAll(user));
}
public static List<SchematicNode> 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<SchematicNode> accessibleByUserType(SteamwarUser user, SchematicType type) {
return accessibleByUserType.listSelect(user, user, user, type);
}
public static Map<Integer, List<SchematicNode>> 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<SchematicNode> 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<SchematicNode> parentsOfNode(SteamwarUser user, Integer id) {
return allParentsOfNode.listSelect(id, user, user, user);
}
private static Map<Integer, List<SchematicNode>> map(List<SchematicNode> in) {
Map<Integer, List<SchematicNode>> 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<SchematicNode> getSchematicNodeInNode(SchematicNode parent) {
return getSchematicNodeInNode(parent.getId());
}
public static List<SchematicNode> getSchematicNodeInNode(Integer parent) {
return byParent.listSelect(parent);
}
public static List<SchematicNode> 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<SchematicNode> getAccessibleSchematicsOfTypeInParent(int owner, String schemType,
Integer parent) {
return accessibleByUserTypeParent(SteamwarUser.get(owner), SchematicType.fromDB(schemType), parent);
}
public static List<SchematicNode> getAllAccessibleSchematicsOfType(int user, String schemType) {
return accessibleByUserType(SteamwarUser.get(user), SchematicType.fromDB(schemType));
}
public static List<SchematicNode> getAllSchematicsOfType(int owner, String schemType) {
return byOwnerType.listSelect(owner, schemType);
}
@Deprecated
public static List<SchematicNode> getAllSchematicsOfType(String schemType) {
return byType.listSelect(schemType);
}
public static List<SchematicNode> getAllSchematicsOfType(SchematicType schemType) {
return byType.listSelect(schemType);
}
public static List<SchematicNode> deepGet(Integer parent, Predicate<SchematicNode> filter) {
List<SchematicNode> finalList = new ArrayList<>();
List<SchematicNode> 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<SchematicNode> getSchematicsAccessibleByUser(int user, Integer parent) {
return list(SteamwarUser.get(user), parent);
}
@Deprecated
public static List<SchematicNode> getAllSchematicsAccessibleByUser(int user) {
return getAll(SteamwarUser.get(user));
}
public static List<SchematicNode> getAllParentsOfNode(SchematicNode node) {
return getAllParentsOfNode(node.getId());
}
public static List<SchematicNode> 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<SchematicNode> currentNode = Optional
.ofNullable(SchematicNode.byParentName(user, null, layers[0]));
for (int i = 1; i < layers.length; i++) {
int finalI = i;
Optional<SchematicNode> 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<SchematicNode> filterSchems(int user, Predicate<SchematicNode> filter) {
List<SchematicNode> finalList = new ArrayList<>();
List<SchematicNode> 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<Integer> 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<NodeMember> 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<SchematicNode> 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<Map.Entry<String, Integer>> generateBreadcrumbsMap(SteamwarUser user) {
List<Map.Entry<String, Integer>> map = new ArrayList<>();
Optional<SchematicNode> 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<String> 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<String> 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<String> 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<SchematicNode> nodes = SchematicNode.list(user, pa.getId());
String br = pa.generateBreadcrumbs();
nodes.forEach(node -> list.add((sws ? "/" : "") + br + node.getName() + (node.isDir() ? "/" : "")));
} else {
List<SchematicNode> 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
}
}

View File

@@ -0,0 +1,436 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.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<Int>) : IntEntity(id) {
companion object : IntEntityClass<SchematicNode>(SchematicNodeTable) {
private val fieldIndex: Map<Expression<*>, Int> =
SchematicNodeTable.columns.mapIndexed { index, column -> column to index }.toMap()
private val FORBIDDEN_NAMES = listOf("public")
private val FORBIDDEN_CHARS = listOf('/', '\\', '<', '>', '^', '°', '\'', '"', ' ')
val tabCache = mutableMapOf<Int, MutableMap<String, List<String>>>()
@JvmStatic
fun clear() = tabCache.clear()
private fun List<SchematicNode>.mapToIds(): Map<Int, SchematicNode> = 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(),
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<SchematicNode> =
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<String?> = s.split("/".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
var currentNode = byParentName(user, null, layers[0]!!)
for (i in 1..<layers.size) {
val node = currentNode?.let { n -> 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<String>) = 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<String> {
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<String>()
if (s.contains("/")) {
val preTab = s.take(s.lastIndexOf("/") + 1)
val pa = getNodeFromPath(user, preTab) ?: return emptyList()
val nodes: List<SchematicNode> = 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<SchematicNode?> = 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<Int> 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<NodeMember> 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 <T> 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<Pair<String, Int>> = useDb {
val map = mutableListOf<Pair<String, Int>>()
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
}

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
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<String, SchematicType> fromDB;
private static final List<SchematicType> types;
static {
List<SchematicType> tmpTypes = new LinkedList<>();
Map<String, SchematicType> 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<?, String> 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<SchematicType> values(){
return types;
}
enum Type{
NORMAL,
CHECK_TYPE,
FIGHT_TYPE
}
}

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
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<SchematicType>
private val fromDB: Map<String, SchematicType>?
init {
val tmpTypes = mutableListOf<SchematicType>()
val tmpFromDB = mutableMapOf<String, SchematicType>()
tmpTypes.add(Normal)
tmpFromDB[Normal.toDB()] = Normal
val folder = SQLWrapper.impl.schemTypesFolder
if (folder.exists()) {
for (configFile in Arrays.stream<File?>(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
}
}

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
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<Script> table = new Table<>(Script.class);
private static final SelectStatement<Script> byId = table.select(Table.PRIMARY);
private static final SelectStatement<Script> byUserName = table.select("nameUser");
private static final SelectStatement<Script> byUser = table.selectFields("userId");
private static final Statement insert = table.insertFields(true, "userId", "name", "code");
private static final Statement updateName = table.update(Table.PRIMARY, "name");
private static final Statement updateCode = table.update(Table.PRIMARY, "code");
private static final Statement delete = table.delete(Table.PRIMARY);
public static Script get(int id) {
return byId.select(id);
}
public static Script get(SteamwarUser user, String name) {
return byUserName.select(user, name);
}
public static Script create(SteamwarUser user, String name, String code) {
return new Script(insert.insertGetKey(user, name, code), user.getId(), name, code);
}
public static List<Script> list(SteamwarUser user) {
return byUser.listSelect(user);
}
@Field(keys = Table.PRIMARY, autoincrement = true)
private final int id;
@Field(keys = "nameUser")
private final int userId;
@Field(keys = "nameUser")
private String name;
@Field
private String code;
public void setName(String name) {
this.name = name;
updateName.update(name, id);
}
public void setCode(String code) {
this.code = code;
updateCode.update(code, id);
}
public void delete() {
delete.update(id);
}
}

View File

@@ -0,0 +1,75 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.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
object ScriptTable: IntIdTable("Script") {
val userId = reference("UserId", SteamwarUserTable)
val name = varchar("Name", 64)
val code = text("Code")
}
class Script(id: EntityID<Int>) : IntEntity(id) {
companion object : IntEntityClass<Script>(ScriptTable) {
@JvmStatic
fun byId(id: Int) = useDb { findById(id) }
@JvmStatic
fun get(user: SteamwarUser, name: String) = useDb {
find { ScriptTable.userId eq user.id and (ScriptTable.name eq name) }.firstOrNull()
}
@JvmStatic
fun create(user: SteamwarUser, name: String, code: String) = useDb {
new {
this.userId = user.id.value
this.scriptName = name
this.scriptCode = code
}
}
@JvmStatic
fun list(user: SteamwarUser) = useDb {
find { ScriptTable.userId eq user.id }.toList()
}
}
fun getId() = id.value
var userId by ScriptTable.userId.transform({ EntityID(it, SteamwarUserTable) }, { it.value })
private var scriptName by ScriptTable.name
private var scriptCode by ScriptTable.code
var name: String
get() = scriptName
set(value) = useDb { scriptName = value }
var code: String
get() = scriptCode
set(value) = useDb { scriptCode = value }
override fun delete() = useDb { super.delete() }
}

View File

@@ -46,14 +46,14 @@ public class Season {
if (season == -1) return "";
int yearSeason = season % 3;
int year = (season - yearSeason) / 3;
return String.format("%d-%d", year, yearSeason);
return String.format("%d-%d", year, yearSeason + 1);
}
public static int convertSeasonToNumber(String season){
if (season.isEmpty()) return -1;
String[] split = season.split("-");
try {
return Integer.parseInt(split[0]) * 3 + Integer.parseInt(split[1]);
return Integer.parseInt(split[0]) * 3 + Integer.parseInt(split[1]) - 1;
} catch (NumberFormatException e) {
return -1;
}

View File

@@ -1,45 +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 <https://www.gnu.org/licenses/>.
*/
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.sql.Timestamp;
@AllArgsConstructor
public class Session {
private static final Table<Session> table = new Table<>(Session.class);
private static final Statement insert = table.insert(Table.PRIMARY);
public static void insertSession(int userID, Timestamp startTime){
insert.update(userID, startTime);
}
@Field(keys = {Table.PRIMARY})
private int userId;
@Field(keys = {Table.PRIMARY})
private Timestamp startTime;
@Field(def = "CURRENT_TIMESTAMP")
private Timestamp endTime;
}

View File

@@ -0,0 +1,44 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.sql
import de.steamwar.sql.internal.useDb
import org.jetbrains.exposed.v1.core.Table
import org.jetbrains.exposed.v1.core.dao.id.EntityID
import org.jetbrains.exposed.v1.javatime.CurrentTimestamp
import org.jetbrains.exposed.v1.javatime.timestamp
import org.jetbrains.exposed.v1.jdbc.insert
import java.sql.Timestamp
object SessionTable: Table("Session") {
val userId = reference("UserId", SteamwarUserTable)
val startTime = timestamp("StartTime")
val endTime = timestamp("EndTime").defaultExpression(CurrentTimestamp)
}
object Session {
@JvmStatic
fun insertSession(userId: Int, startTime: Timestamp) = useDb {
SessionTable.insert {
it[SessionTable.userId] = EntityID(userId, SteamwarUserTable)
it[SessionTable.startTime] = startTime.toInstant()
}
}
}

View File

@@ -1,389 +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 <https://www.gnu.org/licenses/>.
*/
package de.steamwar.sql;
import de.steamwar.sql.internal.*;
import lombok.Getter;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.sql.Timestamp;
import java.util.*;
import java.util.function.Consumer;
import java.util.logging.Level;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class SteamwarUser {
private static final SecureRandom random = new SecureRandom();
private static final SecretKeyFactory factory;
static {
try {
factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512");
} catch (NoSuchAlgorithmException e) {
throw new SecurityException(e);
}
new SqlTypeMapper<>(UUID.class, "CHAR(36)", (rs, identifier) -> UUID.fromString(rs.getString(identifier)), (st, index, value) -> st.setString(index, value.toString()));
new SqlTypeMapper<>(Locale.class, "VARCHAR(32)", (rs, identifier) -> {
String l = rs.getString(identifier);
return l != null ? Locale.forLanguageTag(l) : null;
}, (st, index, value) -> st.setString(index, value.toLanguageTag()));
new SqlTypeMapper<>(SteamwarUser.class, null, (rs, identifier) -> { throw new SecurityException("SteamwarUser cannot be used as type (recursive select)"); }, (st, index, value) -> st.setInt(index, value.id));
}
private static final Table<SteamwarUser> table = new Table<>(SteamwarUser.class, "UserData");
private static final Statement insert = table.insertFields("UUID", "UserName");
private static final SelectStatement<SteamwarUser> byID = table.selectFields("id");
private static final SelectStatement<SteamwarUser> byUUID = table.selectFields("UUID");
private static final SelectStatement<SteamwarUser> byName = table.selectFields("UserName");
private static final SelectStatement<SteamwarUser> byDiscord = table.selectFields("DiscordId");
private static final SelectStatement<SteamwarUser> byTeam = table.selectFields("Team");
private static final SelectStatement<SteamwarUser> getUsersWithPerm = new SelectStatement<>(table, "SELECT S.* FROM UserData S JOIN UserPerm P ON S.id = P.User WHERE P.Perm = ?");
private static final SelectStatement<SteamwarUser> getAll = new SelectStatement<SteamwarUser>(table, "SELECT * FROM UserData");
private static final Statement updateName = table.update(Table.PRIMARY, "UserName");
private static final Statement updatePassword = table.update(Table.PRIMARY, "Password");
private static final Statement updateLocale = table.update(Table.PRIMARY, "Locale", "ManualLocale");
private static final Statement updateTeam = table.update(Table.PRIMARY, "Team");
private static final Statement updateLeader = table.update(Table.PRIMARY, "Leader");
private static final Statement updateDiscord = table.update(Table.PRIMARY, "DiscordId");
private static final Statement getPlaytime = new Statement("SELECT SUM(UNIX_TIMESTAMP(EndTime) - UNIX_TIMESTAMP(StartTime)) as Playtime FROM Session WHERE UserID = ?");
private static final Statement getFirstjoin = new Statement("SELECT MIN(StartTime) AS FirstJoin FROM Session WHERE UserID = ?");
private static final Statement getLastonline = new Statement("SELECT MAX(EndTime) AS LastOnline FROM Session WHERE UserID = ?");
private static final Map<Integer, SteamwarUser> usersById = new HashMap<>();
private static final Map<UUID, SteamwarUser> usersByUUID = new HashMap<>();
private static final Map<String, SteamwarUser> usersByName = new HashMap<>();
private static final Map<Long, SteamwarUser> usersByDiscord = new HashMap<>();
public static void clear() {
usersById.clear();
usersByName.clear();
usersByUUID.clear();
usersByDiscord.clear();
}
public static void invalidate(int userId) {
SteamwarUser user = usersById.remove(userId);
if (user == null)
return;
usersByName.remove(user.getUserName());
usersByUUID.remove(user.getUUID());
usersByDiscord.remove(user.getDiscordId());
}
public static SteamwarUser get(String userName){
SteamwarUser user = usersByName.get(userName.toLowerCase());
if(user != null)
return user;
return byName.select(userName);
}
public static SteamwarUser get(UUID uuid){
SteamwarUser user = usersByUUID.get(uuid);
if(user != null)
return user;
return byUUID.select(uuid);
}
public static SteamwarUser get(int id) {
SteamwarUser user = usersById.get(id);
if(user != null)
return user;
return byID.select(id);
}
public static SteamwarUser get(Long discordId) {
if(usersByDiscord.containsKey(discordId))
return usersByDiscord.get(discordId);
return byDiscord.select(discordId);
}
public static SteamwarUser getOrCreate(UUID uuid, String name, Consumer<UUID> newPlayer) {
SteamwarUser user = get(uuid);
if (user != null) {
if (!user.userName.equals(name)) {
updateName.update(name, user.id);
user.userName = name;
}
return user;
} else {
insert.update(uuid, name);
newPlayer.accept(uuid);
return get(uuid);
}
}
public static List<SteamwarUser> getUsersWithPerm(UserPerm userPerm) {
return getUsersWithPerm.listSelect(userPerm);
}
public static List<SteamwarUser> getServerTeam() {
return Stream.of(getUsersWithPerm(UserPerm.PREFIX_ADMIN),
getUsersWithPerm(UserPerm.PREFIX_DEVELOPER),
getUsersWithPerm(UserPerm.PREFIX_MODERATOR),
getUsersWithPerm(UserPerm.PREFIX_SUPPORTER),
getUsersWithPerm(UserPerm.PREFIX_BUILDER)
)
.flatMap(Collection::stream)
.collect(Collectors.toList());
}
public static List<SteamwarUser> getTeam(int teamId) {
return byTeam.listSelect(teamId);
}
public static void batchCache(Set<Integer> ids) {
ids.removeIf(usersById::containsKey);
if(ids.isEmpty())
return;
try (SelectStatement<SteamwarUser> batch = new SelectStatement<>(table, "SELECT * FROM UserData WHERE id IN (" + ids.stream().map(Object::toString).collect(Collectors.joining(", ")) + ")")) {
batch.listSelect();
}
}
@Getter
@Field(keys = {Table.PRIMARY}, autoincrement = true)
private final int id;
@Field(keys = {"uuid"})
private final UUID uuid;
@Getter
@Field
private String userName;
@Field(nullable = true)
private String password;
@Getter
@Field(def = "0")
private int team;
@Getter
@Field(def = "0")
private boolean leader;
@Field(nullable = true)
private Locale locale;
@Field(def = "0")
private boolean manualLocale;
@Getter
@Field(keys = {"discordId"}, nullable = true)
private Long discordId;
private Map<Punishment.PunishmentType, Punishment> punishments = null;
private Set<UserPerm> permissions = null;
private UserPerm.Prefix prefix = null;
public SteamwarUser(int id, UUID uuid, String userName, String password, int team, boolean leader, Locale locale, boolean manualLocale, Long discordId) {
this.id = id;
this.uuid = uuid;
this.userName = userName;
this.password = password;
this.team = team;
this.leader = leader;
this.locale = locale;
this.manualLocale = manualLocale;
this.discordId = discordId != null && discordId != 0 ? discordId : null;
usersById.put(id, this);
usersByName.put(userName.toLowerCase(), this);
usersByUUID.put(uuid, this);
if (this.discordId != null) {
usersByDiscord.put(discordId, this);
}
}
public UUID getUUID() {
return uuid;
}
public Locale getLocale() {
if(locale != null)
return locale;
return Locale.getDefault();
}
public Punishment getPunishment(Punishment.PunishmentType type) {
initPunishments();
return punishments.getOrDefault(type, null);
}
public boolean isPunished(Punishment.PunishmentType punishment) {
initPunishments();
if (!punishments.containsKey(punishment)) {
return false;
}
if (!punishments.get(punishment).isCurrent()) {
if (punishment == Punishment.PunishmentType.Ban) {
BannedUserIPs.unbanIPs(id);
}
punishments.remove(punishment);
return false;
}
return true;
}
public boolean hasPerm(UserPerm perm) {
initPerms();
return permissions.contains(perm);
}
public Set<UserPerm> perms() {
initPerms();
return permissions;
}
public UserPerm.Prefix prefix() {
initPerms();
return prefix;
}
public double getOnlinetime() {
return getPlaytime.select(rs -> {
if (rs.next() && rs.getBigDecimal("Playtime") != null)
return rs.getBigDecimal("Playtime").doubleValue();
return 0.0;
}, id);
}
public Timestamp getFirstjoin() {
return getFirstjoin.select(rs -> {
if (rs.next())
return rs.getTimestamp("FirstJoin");
return null;
}, id);
}
public Timestamp getLastOnline() {
return getLastonline.select(rs -> {
if (rs.next())
return rs.getTimestamp("LastOnline");
return null;
}, id);
}
public void punish(Punishment.PunishmentType punishment, Timestamp time, String banReason, int from, boolean perma) {
initPunishments();
punishments.remove(punishment);
punishments.put(punishment, Punishment.createPunishment(id, from, punishment, banReason, time, perma));
}
public void setTeam(int team) {
this.team = team;
updateTeam.update(team, id);
setLeader(false);
}
public void setLeader(boolean leader) {
this.leader = leader;
updateLeader.update(leader, id);
}
public void setLocale(Locale locale, boolean manualLocale) {
if (locale == null || (this.manualLocale && !manualLocale))
return;
this.locale = locale;
this.manualLocale = manualLocale;
updateLocale.update(locale.toLanguageTag(), manualLocale, id);
}
public void setDiscordId(Long discordId) {
usersByDiscord.remove(this.discordId);
this.discordId = discordId;
updateDiscord.update(discordId, id);
if (discordId != null) {
usersByDiscord.put(discordId, this);
}
}
public void setPassword(String password) {
try {
byte[] salt = new byte[16];
random.nextBytes(salt);
String saltString = Base64.getEncoder().encodeToString(salt);
byte[] hash = generateHash(password, salt);
String hashString = Base64.getEncoder().encodeToString(hash);
this.password = hashString + ":" + saltString;
updatePassword.update(this.password, id);
} catch (Exception e) {
throw new SecurityException(e);
}
}
public boolean verifyPassword(String password) {
try {
if (!hasPassword()) {
return false;
}
String[] parts = this.password.split(":");
if (parts.length != 2) {
SQLConfig.impl.getLogger().log(Level.SEVERE ,"Invalid password hash for user {0} ({1})", new Object[]{userName, id});
return false;
}
String hashString = parts[0];
byte[] realHash = Base64.getDecoder().decode(hashString);
String saltString = parts[1];
byte[] salt = Base64.getDecoder().decode(saltString);
byte[] hash = generateHash(password, salt);
return Arrays.equals(realHash, hash);
} catch (Exception e) {
SQLConfig.impl.getLogger().log(Level.SEVERE, "Error while verifying password for user " + userName + " (" + id + ")", e);
return false;
}
}
public boolean hasPassword() {
return this.password != null;
}
private byte[] generateHash(String password, byte[] salt)
throws InvalidKeySpecException {
PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 65536, 512);
return factory.generateSecret(spec).getEncoded();
}
private void initPunishments() {
if(punishments != null)
return;
punishments = Punishment.getPunishmentsOfPlayer(id);
}
private void initPerms() {
if(permissions != null)
return;
permissions = UserPerm.getPerms(id);
prefix = permissions.stream().filter(UserPerm.prefixes::containsKey).findAny().map(UserPerm.prefixes::get).orElse(UserPerm.emptyPrefix);
}
public static List<SteamwarUser> getAll() {
return getAll.listSelect();
}
}

View File

@@ -0,0 +1,289 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.sql
import de.steamwar.sql.internal.useDb
import org.jetbrains.exposed.v1.core.JoinType
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.inList
import org.jetbrains.exposed.v1.dao.IntEntity
import org.jetbrains.exposed.v1.dao.IntEntityClass
import org.jetbrains.exposed.v1.jdbc.insert
import org.jetbrains.exposed.v1.jdbc.select
import java.security.SecureRandom
import java.sql.Timestamp
import java.util.*
import java.util.function.Consumer
import javax.crypto.SecretKeyFactory
import javax.crypto.spec.PBEKeySpec
object SteamwarUserTable : IntIdTable("UserData", "id") {
val uuid = varchar("UUID", 36)
val username = varchar("UserName", 32)
val team = reference("Team", TeamTable)
val leader = bool("Leader")
val locale = varchar("Locale", 16).nullable()
val manualLocale = bool("ManualLocale")
val bedrock = bool("Bedrock")
val password = text("Password").nullable()
val discordId = long("DiscordId").nullable()
}
class SteamwarUser(id: EntityID<Int>): IntEntity(id) {
companion object: IntEntityClass<SteamwarUser>(SteamwarUserTable) {
private val random = SecureRandom()
private val factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512")
private val byId = mutableMapOf<Int, SteamwarUser>()
private val byUUID = mutableMapOf<UUID, SteamwarUser>()
private val byDiscordId = mutableMapOf<Long, SteamwarUser>()
private val byUsername = mutableMapOf<String, SteamwarUser>()
@JvmStatic
fun clear() {
byId.clear()
byUUID.clear()
byDiscordId.clear()
byUsername.clear()
}
@JvmStatic
fun invalidate(userId: Int) {
val user = byId.remove(userId)
if(user != null) {
byUUID.remove(user.getUUID())
byDiscordId.remove(user.discordId)
byUsername.remove(user.userName)
}
}
@JvmStatic
fun byId(id: Int) = byId[id] ?: useDb { findById(id)?.also { cache(it) } }
@JvmStatic
fun get(uuid: UUID) = byUUID[uuid] ?: useDb { find { SteamwarUserTable.uuid eq uuid.toString() }.firstOrNull()?.also { cache(it) } }
@JvmStatic
fun get(discordId: Long) = byDiscordId[discordId] ?: useDb { find { SteamwarUserTable.discordId eq discordId }.firstOrNull()?.also { cache(it) } }
@JvmStatic
fun get(username: String) = byUsername[username] ?: useDb { find { SteamwarUserTable.username eq username }.firstOrNull()?.also { cache(it) } }
private fun cache(user: SteamwarUser) {
byId[user.getId()] = user
byUUID[user.getUUID()] = user
user.discordId?.let { byDiscordId[it] = user }
byUsername[user.userName] = user
}
@JvmStatic
fun getOrCreate(uuid: UUID, name: String, newPlayer: Consumer<UUID>): SteamwarUser {
val user = get(uuid)
return if (user != null) {
if (user.userName != name) {
useDb {
user.userName = name
}
}
user
} else {
useDb {
SteamwarUserTable.insert {
it[SteamwarUserTable.uuid] = uuid.toString()
it[username] = name
}
}
newPlayer.accept(uuid)
get(uuid) ?: error("User $uuid not found after creation!")
}
}
@JvmStatic
fun getUsersWithPerm(perm: UserPerm) = useDb {
UserPermTable.join(SteamwarUserTable, JoinType.INNER, UserPermTable.user, SteamwarUserTable.id)
.select(SteamwarUserTable.fields).where { UserPermTable.perm eq perm }.map { wrapRow(it) }
}
@JvmStatic
fun getServerTeam() = useDb { listOf(
getUsersWithPerm(UserPerm.PREFIX_ADMIN),
getUsersWithPerm(UserPerm.PREFIX_DEVELOPER),
getUsersWithPerm(UserPerm.PREFIX_MODERATOR),
getUsersWithPerm(UserPerm.PREFIX_SUPPORTER),
getUsersWithPerm(UserPerm.PREFIX_BUILDER),
).flatten() }
@JvmStatic
fun getTeam(teamId: Int) = useDb {
find { SteamwarUserTable.team eq teamId }.toList()
}
@JvmStatic
fun batchCache(ids: MutableSet<Int>) {
ids.removeIf(byId::containsKey)
if(ids.isEmpty()) return
useDb {
find { SteamwarUserTable.id inList ids }.forEach { cache(it) }
}
}
}
val uuid: UUID by SteamwarUserTable.uuid.transform({ it.toString() }, { UUID.fromString(it) })
var userName by SteamwarUserTable.username
private var teamInternal by SteamwarUserTable.team
var team: Int
get() = teamInternal.value
set(value) = useDb {
teamInternal = EntityID(value, TeamTable)
leaderInternal = false
}
private var leaderInternal by SteamwarUserTable.leader
var leader: Boolean
get() = leaderInternal
set(value) = useDb {
leaderInternal = value
}
fun isLeader() = leader
var locale: Locale by SteamwarUserTable.locale
.transform({ it.toLanguageTag() }, { it?.let { Locale.forLanguageTag(it) } ?: Locale.getDefault()})
var manualLocale by SteamwarUserTable.manualLocale
var bedrock by SteamwarUserTable.bedrock
private var passwordInternal by SteamwarUserTable.password
var password: String?
get() = passwordInternal
set(value) {
if(value == null) {
useDb {
passwordInternal = null
}
return
}
val salt = ByteArray(16)
random.nextBytes(salt)
val saltString = Base64.getEncoder().encode(salt)
val hash = generateHash(value, salt)
val hashString = Base64.getEncoder().encode(hash)
useDb {
passwordInternal = "$hashString:$saltString"
}
}
var discordId by SteamwarUserTable.discordId
private val punishments by lazy { Punishment.getPunishmentsOfPlayer(id.value) }
private val perms by lazy { UserPerm.getPerms(id.value) }
private val prefix by lazy { perms.firstOrNull { UserPerm.prefixes.containsKey(it) }?.let { UserPerm.prefixes[it]} ?: UserPerm.emptyPrefix }
fun getUUID(): UUID = uuid
fun getId() = id.value
fun getPunishment(punishment: Punishment.PunishmentType) = punishments[punishment]
fun isPunished(punishment: Punishment.PunishmentType) = getPunishment(punishment)
?.let {
if (!it.isCurrent()) {
if (punishment == Punishment.PunishmentType.Ban) {
BannedUserIPs.unbanIPs(id.value)
}
punishments.remove(punishment)
return@let false
}
return@let true
} ?: false
fun hasPerm(perm: UserPerm) = perms.contains(perm)
fun perms() = perms
fun prefix() = prefix
fun getOnlinetime() = useDb {
exec("SELECT SUM(UNIX_TIMESTAMP(EndTime) - UNIX_TIMESTAMP(StartTime)) as Playtime FROM Session WHERE UserID = ${this@SteamwarUser.id.value}") { rs ->
return@exec if (rs.next()) {
rs.getBigDecimal("Playtime").toDouble()
} else {
0.0
}
} ?: 0.0
}
fun getFirstjoin() = useDb {
exec("SELECT MIN(StartTime) AS FirstJoin FROM Session WHERE UserID = ${this@SteamwarUser.id.value}") { rs ->
return@exec if (rs.next()) {
rs.getTimestamp("FirstJoin")
} else { null }
}
}
fun getLastOnline() = useDb {
exec("SELECT MAX(EndTime) AS LastOnline FROM Session WHERE UserID = ${this@SteamwarUser.id.value}") { rs ->
return@exec if (rs.next()) {
rs.getTimestamp("LastOnline")
} else { null }
}
}
fun punish(punishment: Punishment.PunishmentType, time: Timestamp, reason: String, from: Int, perma: Boolean) = useDb {
punishments.remove(punishment)
punishments[punishment] = Punishment.createPunishment(this@SteamwarUser.id.value, from, punishment, reason, time, perma)
}
fun setLocale(locale: Locale?, manualeLocale: Boolean) {
if (locale == null || (this.manualLocale && !manualLocale)) return
useDb {
this@SteamwarUser.locale = locale
this@SteamwarUser.manualLocale = manualeLocale
}
}
fun setDiscordId(discordId: Long) {
byDiscordId.remove(this.discordId)
useDb {
this@SteamwarUser.discordId = discordId
}
byDiscordId[discordId] = this
}
fun verifyPassword(password: String): Boolean {
if (!hasPassword()) return false
val (hashString, saltString) = this.password!!.split(':')
val hash = Base64.getDecoder().decode(hashString)
val salt = Base64.getDecoder().decode(saltString)
return hash.contentEquals(generateHash(password, salt))
}
fun hasPassword() = password != null
fun generateHash(password: String, salt: ByteArray): ByteArray =
PBEKeySpec(password.toCharArray(), salt, 65536, 512).let { factory.generateSecret(it).encoded }
}

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