diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/BauSystem.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/BauSystem.java index e1f7b11a..a4d76e2c 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/BauSystem.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/BauSystem.java @@ -133,7 +133,12 @@ public class BauSystem extends JavaPlugin implements Listener { Bukkit.getWorlds().get(0).setGameRule(GameRule.SEND_COMMAND_FEEDBACK, false); - String identifier = BauServerInfo.getOwnerUser().getUUID().toString().replace("-", ""); + String identifier; + if (BauServerInfo.getWorldId() != null) { + identifier = BauServerInfo.getWorldId().toString().replace("-", ""); + } else { + identifier = BauServerInfo.getOwnerUser().getUUID().toString().replace("-", ""); + } WorldIdentifier.set("bau/" + Core.getVersion() + "/" + identifier); } @@ -224,4 +229,4 @@ public class BauSystem extends JavaPlugin implements Listener { AtomicReference task = new AtomicReference<>(); task.set(runTaskTimer(plugin, () -> consumer.accept(task.get()), delay, period)); } -} \ No newline at end of file +} diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/Permission.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/Permission.java index 1cbb5d9f..341a0e1f 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/Permission.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/Permission.java @@ -60,11 +60,16 @@ public enum Permission { } public boolean hasPermission(Player member) { - if (SteamwarUser.get(member.getUniqueId()).getId() == BauServer.getInstance().getOwnerID()) { + SteamwarUser steamwarUser = SteamwarUser.get(member.getUniqueId()); + BauServer server = BauServer.getInstance(); + if (!server.isTeamWorld() && steamwarUser.getId() == server.getOwnerID()) { return this != SPECTATOR; } - BauweltMember bauweltMember = BauweltMember.getBauMember(BauServer.getInstance().getOwner(), member.getUniqueId()); + if (server.isTeamWorld() && steamwarUser.getTeam() == server.getTeamID()) { + return this != SPECTATOR; + } + BauweltMember bauweltMember = BauweltMember.getBauMember(server.getWorldID(), steamwarUser.getId()); if (bauweltMember == null) return this == SPECTATOR; return permissionPredicate.test(bauweltMember); } -} \ No newline at end of file +} diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/config/BauServer.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/config/BauServer.java index 67fd9e52..20798c51 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/config/BauServer.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/config/BauServer.java @@ -34,16 +34,41 @@ public class BauServer { } private Integer owner; + private UUID world; + private Integer team; public UUID getOwner() { - return SteamwarUser.byId(getOwnerID()).getUUID(); + Integer ownerId = getOwnerID(); + return ownerId == null ? null : SteamwarUser.byId(ownerId).getUUID(); } - public int getOwnerID() { + public Integer getOwnerID() { //Lazy loading to improve startup time of the server in 1.15 if (owner == null) { owner = BauServerInfo.getOwnerId(); } return owner; } -} \ No newline at end of file + + public boolean hasOwner() { + return getOwnerID() != null; + } + + public UUID getWorldID() { + if (world == null) { + world = BauServerInfo.getWorldId(); + } + return world; + } + + public Integer getTeamID() { + if (team == null) { + team = BauServerInfo.getTeamId(); + } + return team; + } + + public boolean isTeamWorld() { + return getTeamID() != null; + } +} diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/bau/BauInfoBauGuiItem.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/bau/BauInfoBauGuiItem.java index b0f15859..b51c1a2e 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/bau/BauInfoBauGuiItem.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/bau/BauInfoBauGuiItem.java @@ -47,7 +47,7 @@ public class BauInfoBauGuiItem extends BauGuiItem { @Override public ItemStack getItem(Player player) { SWItem itemStack; - if (!player.getName().endsWith("⍇")) { + if (!player.getName().endsWith("⍇") && !BauServer.getInstance().isTeamWorld()) { itemStack = SWItem.getPlayerSkull(SteamwarUser.get(BauServer.getInstance().getOwner()).getUserName()); } else { itemStack = new SWItem(Material.PLAYER_HEAD, ""); diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/bau/InfoCommand.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/bau/InfoCommand.java index 909f5ed8..bed3978b 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/bau/InfoCommand.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/bau/InfoCommand.java @@ -47,7 +47,9 @@ 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.byId(bauServer.getOwnerID()).getUserName()); + if (!bauServer.isTeamWorld()) { + 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.getRegionData().has(flag).isApplicable()) continue; @@ -58,7 +60,7 @@ public class InfoCommand extends SWCommand { } if (Permission.BUILD.hasPermission(p)) { - List members = BauweltMember.getMembers(bauServer.getOwnerID()); + List members = BauweltMember.getWorldMembers(bauServer.getWorldID()); Map> memberByPermission = new HashMap<>(); members.forEach(member -> { if (Permission.SUPERVISOR.hasPermission(member)) { diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/region/ColorCommand.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/region/ColorCommand.java index 1bbfaaf2..a538a965 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/region/ColorCommand.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/region/ColorCommand.java @@ -20,6 +20,7 @@ package de.steamwar.bausystem.features.region; import de.steamwar.bausystem.BauSystem; +import de.steamwar.bausystem.Permission; import de.steamwar.bausystem.config.BauServer; import de.steamwar.bausystem.region.Region; import de.steamwar.bausystem.region.RegionUtils; @@ -83,6 +84,9 @@ public class ColorCommand extends SWCommand { @ClassValidator(value = Player.class, local = true) public TypeValidator validator() { return (commandSender, player, messageSender) -> { + if (bauServer.isTeamWorld()) { + return !messageSender.send(!Permission.SUPERVISOR.hasPermission(player), "NO_PERMISSION"); + } return !messageSender.send(!bauServer.getOwner().equals(player.getUniqueId()), "NO_PERMISSION"); }; } diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/region/ResetCommand.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/region/ResetCommand.java index 62790869..22522151 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/region/ResetCommand.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/region/ResetCommand.java @@ -68,7 +68,7 @@ public class ResetCommand extends SWCommand { Region region = regionCheck(p); if (region == null) return; - if (!p.getUniqueId().equals(bauServer.getOwner())) { + if (bauServer.hasOwner() && !p.getUniqueId().equals(bauServer.getOwner())) { if (Punishment.isPunished(SteamwarUser.get(bauServer.getOwner()), Punishment.PunishmentType.NoSchemReceiving, punishment -> BauSystem.MESSAGE.send("REGION_TB_NO_SCHEMRECEIVING", p, punishment.getEndTime()))) { return; } diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/region/TestblockCommand.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/region/TestblockCommand.java index 1644aaea..6549acf0 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/region/TestblockCommand.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/region/TestblockCommand.java @@ -91,7 +91,7 @@ public class TestblockCommand extends SWCommand { } } - if (!p.getUniqueId().equals(bauServer.getOwner())) { + if (bauServer.hasOwner() && !p.getUniqueId().equals(bauServer.getOwner())) { if (Punishment.isPunished(SteamwarUser.get(bauServer.getOwner()), Punishment.PunishmentType.NoSchemReceiving, punishment -> BauSystem.MESSAGE.send("REGION_TB_NO_SCHEMRECEIVING", p, punishment.getEndTime()))) { return; } diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/AntiBauAddMemberFix.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/AntiBauAddMemberFix.java index da75fe7c..a27bab21 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/AntiBauAddMemberFix.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/AntiBauAddMemberFix.java @@ -20,6 +20,7 @@ package de.steamwar.bausystem.features.world; import de.steamwar.bausystem.BauSystem; +import de.steamwar.bausystem.Permission; import de.steamwar.bausystem.config.BauServer; import de.steamwar.core.CRIUWakeupEvent; import de.steamwar.linkage.Linked; @@ -36,12 +37,13 @@ public class AntiBauAddMemberFix implements Listener { @EventHandler(priority = EventPriority.LOW) public void onPlayerJoin(PlayerJoinEvent event) { if (BauSystem.DEV_SERVER) return; - if (event.getPlayer().getUniqueId().equals(BauServer.getInstance().getOwner())) { + if (Permission.SUPERVISOR.hasPermission(event.getPlayer())) { return; } - if (BauweltMember.getBauMember(BauServer.getInstance().getOwner(), event.getPlayer().getUniqueId()) == null) { + if (BauweltMember.getBauMember(BauServer.getInstance().getWorldID(), event.getPlayer().getUniqueId()) == null) { event.getPlayer().kickPlayer(""); - throw new SecurityException("The player " + event.getPlayer().getName() + " joined on the server of " + SteamwarUser.byId(BauServer.getInstance().getOwnerID()).getUserName() + " without being added!"); + String owner = BauServer.getInstance().isTeamWorld() ? "team " + BauServer.getInstance().getTeamID() : SteamwarUser.byId(BauServer.getInstance().getOwnerID()).getUserName(); + throw new SecurityException("The player " + event.getPlayer().getName() + " joined on the server of " + owner + " without being added!"); } } diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/AxiomPermissionCheck.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/AxiomPermissionCheck.java index 92c496bc..98b340ca 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/AxiomPermissionCheck.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/AxiomPermissionCheck.java @@ -33,7 +33,7 @@ public class AxiomPermissionCheck implements Listener { @EventHandler public void onAxiomHandshake(AxiomHandshakeEvent event) { - if (Permission.SUPERVISOR.hasPermission(event.getPlayer()) || BauServer.getInstance().getOwner().equals(event.getPlayer().getUniqueId())) { + if (Permission.SUPERVISOR.hasPermission(event.getPlayer()) || (BauServer.getInstance().hasOwner() && BauServer.getInstance().getOwner().equals(event.getPlayer().getUniqueId()))) { event.setMaxBufferSize(Short.MAX_VALUE); return; } diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/BauLockStateScoreboard.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/BauLockStateScoreboard.java index 147c3c85..9e0ba970 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/BauLockStateScoreboard.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/BauLockStateScoreboard.java @@ -28,7 +28,7 @@ import de.steamwar.data.BauLockState; import de.steamwar.linkage.Linked; import de.steamwar.network.packets.PacketHandler; import de.steamwar.network.packets.server.BaulockUpdatePacket; -import de.steamwar.sql.UserConfig; +import de.steamwar.sql.SteamwarWorld; import lombok.Getter; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; @@ -37,13 +37,13 @@ import org.bukkit.event.Listener; @Linked public class BauLockStateScoreboard extends PacketHandler implements ScoreboardElement, Listener { - private static final String BAU_LOCK_CONFIG_NAME = "baulockstate"; - @Getter private BauLockState lockState = loadLockState(); private BauLockState loadLockState() { - String state = UserConfig.getConfig(BauServer.getInstance().getOwner(), BAU_LOCK_CONFIG_NAME); + if (BauServer.getInstance().isTeamWorld()) return BauLockState.OPEN; + SteamwarWorld world = SteamwarWorld.getWorld(BauServer.getInstance().getWorldID()); + String state = world == null ? null : world.getLockState(); return state == null ? BauLockState.OPEN : BauLockState.valueOf(state); } @@ -69,6 +69,7 @@ public class BauLockStateScoreboard extends PacketHandler implements ScoreboardE @Override public String get(Region region, Player p) { + if (BauServer.getInstance().isTeamWorld()) return null; if (!BauServer.getInstance().getOwner().equals(p.getUniqueId())) { return null; } diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/KickallCommand.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/KickallCommand.java index 3eaf3018..a5c8563c 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/KickallCommand.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/KickallCommand.java @@ -42,7 +42,11 @@ public class KickallCommand extends SWCommand { if (!Permission.OWNER.hasPermission(player)) return; Bukkit.getOnlinePlayers().forEach(p -> { - if (!bauServer.getOwner().equals(p.getUniqueId())) p.kickPlayer(""); + if (bauServer.isTeamWorld()) { + if (!Permission.SUPERVISOR.hasPermission(p)) p.kickPlayer(""); + } else if (!bauServer.getOwner().equals(p.getUniqueId())) { + p.kickPlayer(""); + } }); } } diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/SpectatorListener.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/SpectatorListener.java index c9e6a3a6..2b2f7621 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/SpectatorListener.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/SpectatorListener.java @@ -143,17 +143,14 @@ public class SpectatorListener implements Listener { public void onPlayerJoin(PlayerJoinEvent event) { enableOrDisableTechhider(); if (BauSystem.DEV_SERVER) return; - if (event.getPlayer().getUniqueId().equals(BauServer.getInstance().getOwner())) { + if (Permission.SUPERVISOR.hasPermission(event.getPlayer())) { return; } - BauweltMember bauweltMember = BauweltMember.getBauMember(BauServer.getInstance().getOwner(), event.getPlayer().getUniqueId()); + BauweltMember bauweltMember = BauweltMember.getBauMember(BauServer.getInstance().getWorldID(), event.getPlayer().getUniqueId()); if (bauweltMember == null) { event.getPlayer().kickPlayer(""); return; } - if (Permission.SUPERVISOR.hasPermission(event.getPlayer())) { - return; - } if (!anySupervisorOnline(null)) { Bukkit.getScheduler().runTaskLater(BauSystem.getInstance(), () -> { event.getPlayer().kickPlayer(""); diff --git a/CLI/src/Main.kt b/CLI/src/Main.kt index 2a70673b..ba057750 100644 --- a/CLI/src/Main.kt +++ b/CLI/src/Main.kt @@ -11,13 +11,23 @@ import de.steamwar.commands.profiler.ProfilerCommand import de.steamwar.commands.user.UserCommand import de.steamwar.commands.user.UserInfoCommand import de.steamwar.commands.user.UserSearchCommand +import de.steamwar.commands.world.MigrationCommand +import de.steamwar.commands.world.SaveToStorageCommand +import de.steamwar.commands.world.TemplateCommand +import de.steamwar.commands.world.TemplateCreateCommand +import de.steamwar.commands.world.WorldCommand fun main(args: Array) = SteamWar() .subcommands( DatabaseCommand().subcommands(InfoCommand(), ResetCommand()), UserCommand().subcommands(UserInfoCommand(), UserSearchCommand()), + WorldCommand().subcommands( + MigrationCommand(), + SaveToStorageCommand(), + TemplateCommand().subcommands(TemplateCreateCommand()) + ), DevCommand(), ProfilerCommand() ) - .main(args) \ No newline at end of file + .main(args) diff --git a/CLI/src/commands/world/MigrationCommand.kt b/CLI/src/commands/world/MigrationCommand.kt new file mode 100644 index 00000000..46da0742 --- /dev/null +++ b/CLI/src/commands/world/MigrationCommand.kt @@ -0,0 +1,243 @@ +package de.steamwar.commands.world + +import com.github.ajalt.clikt.core.CliktCommand +import de.steamwar.db.Database +import de.steamwar.sql.SteamwarUser +import de.steamwar.sql.SteamwarWorld +import de.steamwar.sql.UserConfig +import org.jetbrains.exposed.v1.jdbc.transactions.transaction +import java.io.File +import java.sql.SQLException +import java.util.UUID + +class MigrationCommand : CliktCommand(name = "migrate") { + private val userWorldPattern = Regex("""userworlds(\d+)""") + private val builderWorldPattern = Regex("""builder(\d+)""") + private val trailingVersionPattern = Regex("""(\d+)$""") + + override fun run() { + Database.ensureConnected() + + val bau = migrateBauWorlds() + val builder = migrateBuilderWorlds() + val arena = migrateArenaWorlds() + migrateBauLockConfig() + migrateBauweltMembers() + + echo("Imported ${bau.imported} build worlds, skipped ${bau.skipped}, missing users ${bau.missingOwner}.") + echo("Imported ${builder.imported} builder worlds, skipped ${builder.skipped}.") + echo("Imported ${arena.imported} arena worlds, skipped ${arena.skipped}.") + echo("World migration completed.") + } + + private fun migrateBauWorlds(): MigrationStats { + val stats = MigrationStats() + val worldsRoot = File("/worlds") + worldsRoot.listFiles { file -> file.isDirectory && userWorldPattern.matches(file.name) } + ?.forEach { versionFolder -> + val version = userWorldPattern.matchEntire(versionFolder.name)!!.groupValues[1].toInt() + versionFolder.listFiles { file -> file.isDirectory && isWorldDirectory(file) } + ?.sortedBy { it.name } + ?.forEach { legacyWorld -> + val owner = legacyWorld.ownerUser() + if (owner == null) { + stats.missingOwner++ + echo("Skipped build world ${legacyWorld.path}: no matching user.") + return@forEach + } + + val lockState = UserConfig.getConfig(owner.id.value, BAU_LOCK_CONFIG_NAME) + val world = SteamwarWorld.getOrCreateBauWorld(owner, owner.userName, version, legacyWorld) + if (world.lockState == null && lockState != null) { + world.changeLockState(lockState) + } + stats.imported++ + } + } ?: run { + stats.skipped++ + echo("Skipped build worlds: /worlds does not exist.") + } + return stats + } + + private fun migrateBuilderWorlds(): MigrationStats { + val stats = MigrationStats() + val worldsRoot = File("/worlds") + worldsRoot.listFiles { file -> file.isDirectory && builderWorldPattern.matches(file.name) } + ?.forEach { versionFolder -> + val version = builderWorldPattern.matchEntire(versionFolder.name)!!.groupValues[1].toInt() + versionFolder.listFiles { file -> file.isDirectory && isWorldDirectory(file) } + ?.sortedBy { it.name } + ?.forEach { legacyWorld -> + SteamwarWorld.getOrCreateBuilderWorld(legacyWorld.name, version, legacyWorld) + stats.imported++ + } + } ?: run { + stats.skipped++ + echo("Skipped builder worlds: /worlds does not exist.") + } + return stats + } + + private fun migrateArenaWorlds(): MigrationStats { + val stats = MigrationStats() + val configs = parseGameModeConfigs(File("/configs/GameModes")) + if (configs.isEmpty()) { + stats.skipped++ + echo("Skipped arena worlds: no game mode configs found in /configs/GameModes.") + return stats + } + + for (config in configs) { + val version = trailingVersionPattern.find(config.serverFolder)?.value?.toIntOrNull() + if (version == null) { + stats.skipped++ + echo("Skipped arena mode ${config.modeName}: server folder '${config.serverFolder}' has no version suffix.") + continue + } + + val arenaRoot = File("/servers/${config.serverFolder}/arenas") + for (map in config.maps) { + val legacyWorld = File(arenaRoot, map) + if (!isWorldDirectory(legacyWorld)) { + stats.skipped++ + continue + } + + SteamwarWorld.getOrCreateArenaWorld(config.modeName, map, version, legacyWorld) + stats.imported++ + } + } + + return stats + } + + private fun migrateBauLockConfig() { + transaction(Database.db) { + exec( + """ + UPDATE `world` w + JOIN `UserConfig` uc ON uc.`User` = w.`Owner` AND uc.`Config` = 'baulockstate' + SET w.`LockState` = uc.`Value` + WHERE w.`Owner` IS NOT NULL AND w.`LockState` IS NULL + """.trimIndent() + ) + } + } + + private fun migrateBauweltMembers() { + if (!columnExists("BauweltMember", "WorldID") || !columnExists("BauweltMember", "BauweltID")) { + echo("Skipped BauweltMember data migration: expected WorldID and BauweltID columns are not both present.") + return + } + + execIgnore( + """ + INSERT IGNORE INTO `BauweltMember` (`WorldID`, `MemberID`, `Build`, `WorldEdit`, `World`) + SELECT w.`id`, bm.`MemberID`, bm.`Build`, bm.`WorldEdit`, bm.`World` + FROM `BauweltMember` bm + JOIN `world` w ON w.`Owner` = bm.`BauweltID` AND w.`Deleted` = 0 + """.trimIndent(), + "Skipped BauweltMember data migration" + ) + } + + private fun parseGameModeConfigs(configRoot: File): List { + if (!configRoot.isDirectory) return emptyList() + + return configRoot.listFiles { file -> file.isFile && file.extension == "yml" && !file.name.endsWith(".kits.yml") } + ?.mapNotNull { file -> + val server = parseServerBlock(file) + val folder = server.folder ?: return@mapNotNull null + ArenaMigrationConfig(file.nameWithoutExtension, folder, server.maps) + } + ?: emptyList() + } + + private fun parseServerBlock(file: File): ParsedServerBlock { + var inServer = false + var inMaps = false + var folder: String? = null + val maps = mutableListOf() + + file.readLines().forEach { line -> + val trimmed = line.trim() + if (trimmed.isEmpty() || trimmed.startsWith("#")) return@forEach + + val indent = line.indexOfFirst { !it.isWhitespace() }.let { if (it == -1) 0 else it } + if (indent == 0) { + inServer = trimmed == "Server:" + inMaps = false + return@forEach + } + + if (!inServer) return@forEach + + when { + indent <= 2 && trimmed.startsWith("Folder:") -> folder = trimmed.substringAfter(":").cleanYamlValue() + indent <= 2 && trimmed.startsWith("Maps:") -> { + val value = trimmed.substringAfter(":").trim() + inMaps = true + if (value.startsWith("[") && value.endsWith("]")) { + maps += value.removePrefix("[").removeSuffix("]") + .split(",") + .map { it.cleanYamlValue() } + .filter { it.isNotEmpty() } + inMaps = false + } + } + inMaps && trimmed.startsWith("-") -> maps += trimmed.removePrefix("-").cleanYamlValue() + inMaps && indent <= 2 -> inMaps = false + } + } + + return ParsedServerBlock(folder, maps) + } + + private fun File.ownerUser(): SteamwarUser? { + name.toIntOrNull()?.let { return SteamwarUser.byId(it) } + return runCatching { SteamwarUser.get(UUID.fromString(name)) }.getOrNull() + } + + private fun isWorldDirectory(file: File): Boolean = + file.isDirectory && (File(file, "level.dat").isFile || File(file, "region").isDirectory) + + private fun String.cleanYamlValue(): String = + trim().trim('"').trim('\'') + + private fun columnExists(table: String, column: String): Boolean = + transaction(Database.db) { + exec("SHOW COLUMNS FROM `$table` LIKE '$column'") { result -> result.next() } ?: false + } + + private fun execIgnore(sql: String, prefix: String) { + try { + transaction(Database.db) { + exec(sql) + } + } catch (e: SQLException) { + echo("$prefix: ${e.message}") + } + } + + private data class MigrationStats( + var imported: Int = 0, + var skipped: Int = 0, + var missingOwner: Int = 0, + ) + + private data class ArenaMigrationConfig( + val modeName: String, + val serverFolder: String, + val maps: List, + ) + + private data class ParsedServerBlock( + val folder: String?, + val maps: List, + ) + + private companion object { + private const val BAU_LOCK_CONFIG_NAME = "baulockstate" + } +} diff --git a/CLI/src/commands/world/SaveToStorageCommand.kt b/CLI/src/commands/world/SaveToStorageCommand.kt new file mode 100644 index 00000000..6833fba4 --- /dev/null +++ b/CLI/src/commands/world/SaveToStorageCommand.kt @@ -0,0 +1,30 @@ +package de.steamwar.commands.world + +import com.github.ajalt.clikt.core.CliktCommand +import de.steamwar.db.Database +import de.steamwar.sql.SteamwarWorld +import java.time.LocalTime + +class SaveToStorageCommand : CliktCommand(name = "saveToStorage") { + override fun run() { + Database.ensureConnected() + + var archived = 0 + for (world in SteamwarWorld.getWorldsToArchive()) { + if (!beforeCutoff()) { + echo("Stopping before 04:00. Archived $archived worlds.") + return + } + + if (world.archiveIfNeeded()) { + archived++ + echo("Archived ${world.uuid} (${world.type}/${world.name}).") + } + } + + echo("Storage save completed. Archived $archived worlds.") + } + + private fun beforeCutoff(): Boolean = + LocalTime.now().isBefore(LocalTime.of(4, 0)) +} diff --git a/CLI/src/commands/world/TemplateCommand.kt b/CLI/src/commands/world/TemplateCommand.kt new file mode 100644 index 00000000..b75ada5c --- /dev/null +++ b/CLI/src/commands/world/TemplateCommand.kt @@ -0,0 +1,24 @@ +package de.steamwar.commands.world + +import com.github.ajalt.clikt.core.CliktCommand +import com.github.ajalt.clikt.parameters.arguments.argument +import com.github.ajalt.clikt.parameters.types.int +import com.github.ajalt.clikt.parameters.types.path +import de.steamwar.db.Database +import de.steamwar.sql.SteamwarWorld + +class TemplateCommand : CliktCommand(name = "template") { + override fun run() = Unit +} + +class TemplateCreateCommand : CliktCommand(name = "create") { + private val name by argument() + private val version by argument().int() + private val source by argument().path(canBeFile = false, mustExist = true) + + override fun run() { + Database.ensureConnected() + SteamwarWorld.getOrCreateTemplateWorld(name, version, source.toFile()) + echo("Template world '$name' created.") + } +} diff --git a/CLI/src/commands/world/WorldCommand.kt b/CLI/src/commands/world/WorldCommand.kt new file mode 100644 index 00000000..31055e75 --- /dev/null +++ b/CLI/src/commands/world/WorldCommand.kt @@ -0,0 +1,7 @@ +package de.steamwar.commands.world + +import com.github.ajalt.clikt.core.CliktCommand + +class WorldCommand : CliktCommand(name = "world") { + override fun run() = Unit +} diff --git a/CommonCore/SQL/src/de/steamwar/sql/BauweltMember.kt b/CommonCore/SQL/src/de/steamwar/sql/BauweltMember.kt index 8c94f52b..22f51e7d 100644 --- a/CommonCore/SQL/src/de/steamwar/sql/BauweltMember.kt +++ b/CommonCore/SQL/src/de/steamwar/sql/BauweltMember.kt @@ -19,7 +19,6 @@ 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 @@ -32,45 +31,37 @@ import org.jetbrains.exposed.v1.jdbc.insertIgnore import java.util.* object BauweltMemberTable : CompositeIdTable("BauweltMember") { - val bauweltId = reference("BauweltID", SteamwarUserTable).index() + val worldId = reference("WorldID", WorldTable).index() val memberId = reference("MemberID", SteamwarUserTable).index() val build = bool("Build") val worldEdit = bool("WorldEdit") val world = bool("World") - override val primaryKey = PrimaryKey(bauweltId, memberId) + override val primaryKey = PrimaryKey(worldId, memberId) init { - addIdColumn(bauweltId) + addIdColumn(worldId) addIdColumn(memberId) } } class BauweltMember(id: EntityID) : CompositeEntity(id) { companion object : CompositeEntityClass(BauweltMemberTable) { - private val cache = mutableMapOf() + private val cache = mutableMapOf, BauweltMember>() private fun cache(member: BauweltMember) = - cache.put(member.memberID, member) + cache.put(member.worldID to member.memberID, member) @JvmStatic fun clear() = cache.clear() @JvmStatic - @Deprecated("Use addMember(ownerId: Int, newMemberId: Int)") - fun addMember(ownerId: UUID, newMemberId: UUID) = - addMember(SteamwarUser.get(ownerId)!!.id, SteamwarUser.get(newMemberId)!!.id) - - @JvmStatic - fun addMember(ownerId: Int, newMemberId: Int) = - addMember(EntityID(ownerId, SteamwarUserTable), EntityID(newMemberId, SteamwarUserTable)) - - fun addMember(ownerId: EntityID, newMemberId: EntityID) = + fun addMember(worldId: UUID, newMemberId: Int) = useDb { BauweltMemberTable.insertIgnore { - it[bauweltId] = ownerId - it[memberId] = newMemberId + it[BauweltMemberTable.worldId] = EntityID(worldId, WorldTable) + it[memberId] = EntityID(newMemberId, SteamwarUserTable) it[build] = false it[worldEdit] = false it[world] = false @@ -78,31 +69,32 @@ class BauweltMember(id: EntityID) : CompositeEntity(id) { } @JvmStatic - @Deprecated("Use getBauMember(bauwelt: Int, member: Int)") - fun getBauMember(bauwelt: UUID, member: UUID) = + fun addMember(worldId: UUID, newMemberId: UUID) = + addMember(worldId, SteamwarUser.get(newMemberId)!!.id.value) + + @JvmStatic + fun getBauMember(world: UUID, member: Int) = + cache[world to member] + ?: useDb { + find { (BauweltMemberTable.worldId eq world) and (BauweltMemberTable.memberId eq member) }.firstOrNull()?.also { cache(it) } + } + + @JvmStatic + fun getBauMember(world: UUID, member: UUID) = + getBauMember(world, SteamwarUser.get(member)!!.id.value) + + @JvmStatic + fun getWorldMembers(world: UUID) = useDb { - find { (bauweltId eq SteamwarUser.get(bauwelt)!!.id) and (BauweltMemberTable.memberId eq SteamwarUser.get(member)!!.id) }.firstOrNull()?.also { cache(it) } + find { BauweltMemberTable.worldId eq world }.toList().also { it.forEach { 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) } } - } + fun getMembers(world: UUID) = + getWorldMembers(world) } - val bauweltID by BauweltMemberTable.bauweltId.transform({ EntityID(it, SteamwarUserTable) }, { it.value }) + val worldID by BauweltMemberTable.worldId.transform({ EntityID(it, WorldTable) }, { it.value }) val memberID by BauweltMemberTable.memberId.transform({ EntityID(it, SteamwarUserTable) }, { it.value }) private var worldEditInternal by BauweltMemberTable.worldEdit var worldEdit: Boolean @@ -144,4 +136,4 @@ class BauweltMember(id: EntityID) : CompositeEntity(id) { useDb { delete() } -} \ No newline at end of file +} diff --git a/CommonCore/SQL/src/de/steamwar/sql/UserPerm.kt b/CommonCore/SQL/src/de/steamwar/sql/UserPerm.kt index e22126a9..44cd1b87 100644 --- a/CommonCore/SQL/src/de/steamwar/sql/UserPerm.kt +++ b/CommonCore/SQL/src/de/steamwar/sql/UserPerm.kt @@ -50,6 +50,8 @@ enum class UserPerm { PUNISHMENTS, TICKET_LOG, BUILD, + BUILD_EXTRA_WORLDS, + BUILD_UNLIMITED_WORLDS, CHECK, MODERATION, ADMINISTRATION; @@ -95,4 +97,4 @@ enum class UserPerm { } data class Prefix(val colorCode: String, val chatPrefix: String) -} \ No newline at end of file +} diff --git a/CommonCore/SQL/src/de/steamwar/sql/World.kt b/CommonCore/SQL/src/de/steamwar/sql/World.kt index 18ef36e8..fb17d387 100644 --- a/CommonCore/SQL/src/de/steamwar/sql/World.kt +++ b/CommonCore/SQL/src/de/steamwar/sql/World.kt @@ -25,6 +25,8 @@ import org.jetbrains.exposed.v1.core.dao.id.EntityID import org.jetbrains.exposed.v1.core.dao.id.java.UUIDTable import org.jetbrains.exposed.v1.core.eq import org.jetbrains.exposed.v1.core.not +import org.jetbrains.exposed.v1.core.or +import org.jetbrains.exposed.v1.core.SortOrder import org.jetbrains.exposed.v1.dao.Entity import org.jetbrains.exposed.v1.dao.EntityClass import org.jetbrains.exposed.v1.javatime.CurrentTimestamp @@ -39,6 +41,8 @@ object WorldTable : UUIDTable("world") { val version = integer("Version") val type = enumeration("Type") val owner = reference("Owner", SteamwarUserTable).nullable() + val team = reference("Team", TeamTable).nullable() + val lockState = varchar("LockState", 32).nullable() val deleted = bool("Deleted").default(false) val created = timestamp("Created").defaultExpression(CurrentTimestamp) val lastUsed = timestamp("LastUsed").defaultExpression(CurrentTimestamp) @@ -47,6 +51,9 @@ object WorldTable : UUIDTable("world") { enum class WorldType { BAU, BUILDER, + TEAM, + ARENA, + TEMPLATE, } class SteamwarWorld(id: EntityID) : Entity(id) { @@ -58,6 +65,10 @@ class SteamwarWorld(id: EntityID) : Entity(id) { private set var owner by WorldTable.owner private set + var team by WorldTable.team + private set + var lockState by WorldTable.lockState + private set var deleted by WorldTable.deleted private set val created by WorldTable.created @@ -109,6 +120,21 @@ class SteamwarWorld(id: EntityID) : Entity(id) { name = newName } + fun archiveIfNeeded(): Boolean { + if (!shouldArchive) return false + + File(ARCHIVE_WORLD_STORAGE).mkdirs() + return archiveWorld() == 0 + } + + fun changeVersion(newVersion: Int) = useDb { + version = newVersion + } + + fun changeLockState(newLockState: String?) = useDb { + lockState = newLockState + } + private fun archiveWorld() = ProcessBuilder("zip", "-u9oymrqq", "$ARCHIVE_WORLD_STORAGE/${id.value}.zip", id.value.toString()) .directory(File(WORLD_STORAGE)) .inheritIO() @@ -124,6 +150,14 @@ class SteamwarWorld(id: EntityID) : Entity(id) { companion object : EntityClass(WorldTable) { const val WORLD_STORAGE = "/worlds/storage" const val ARCHIVE_WORLD_STORAGE = "/mnt/storage/worlds/storage" + const val DEFAULT_BAU_WORLD_LIMIT = 3 + const val EXTRA_BAU_WORLD_LIMIT = 10 + const val TEAM_WORLD_LIMIT = 2 + + @JvmStatic + fun getWorld(uuid: UUID) = useDb { + findById(uuid) + } @JvmStatic fun getBauWorld(user: SteamwarUser, version: Int) = useDb { @@ -135,11 +169,45 @@ class SteamwarWorld(id: EntityID) : Entity(id) { }.firstOrNull() } + @JvmStatic + fun getBauWorld(user: SteamwarUser, name: String, version: Int) = useDb { + find { + (WorldTable.owner eq user.id) and + (WorldTable.name eq name) and + (WorldTable.version eq version) and + (WorldTable.type eq WorldType.BAU) and + not(WorldTable.deleted) + }.firstOrNull() + } + + @JvmStatic + fun getBauWorlds(user: SteamwarUser) = useDb { + find { + (WorldTable.owner eq user.id) and + (WorldTable.type eq WorldType.BAU) and + not(WorldTable.deleted) + }.orderBy(WorldTable.created to SortOrder.ASC).toList() + } + + @JvmStatic + fun countBauWorlds(user: SteamwarUser) = useDb { + find { + (WorldTable.owner eq user.id) and + (WorldTable.type eq WorldType.BAU) and + not(WorldTable.deleted) + }.count() + } + @JvmStatic @JvmOverloads fun getOrCreateBauWorld(user: SteamwarUser, version: Int, prototype: File? = null): SteamwarWorld = getBauWorld(user, version) ?: createWorld(user, user.userName, version, WorldType.BAU, prototype) + @JvmStatic + @JvmOverloads + fun getOrCreateBauWorld(user: SteamwarUser, name: String, version: Int, prototype: File? = null): SteamwarWorld = + getBauWorld(user, name, version) ?: createWorld(user, name, version, WorldType.BAU, prototype) + @JvmStatic fun getBuilderWorld(name: String, version: Int) = useDb { find { @@ -164,14 +232,110 @@ class SteamwarWorld(id: EntityID) : Entity(id) { fun getOrCreateBuilderWorld(name: String, version: Int, prototype: File? = null): SteamwarWorld = getBuilderWorld(name, version) ?: createWorld(null, name, version, WorldType.BUILDER, prototype) + @JvmStatic + fun getTeamWorld(team: Team, name: String, version: Int) = useDb { + find { + (WorldTable.team eq team.id) and + (WorldTable.name eq name) and + (WorldTable.version eq version) and + (WorldTable.type eq WorldType.TEAM) and + not(WorldTable.deleted) + }.firstOrNull() + } + + @JvmStatic + fun getTeamWorlds(team: Team) = useDb { + find { + (WorldTable.team eq team.id) and + (WorldTable.type eq WorldType.TEAM) and + not(WorldTable.deleted) + }.orderBy(WorldTable.created to SortOrder.ASC).toList() + } + @JvmStatic @JvmOverloads - fun createWorld(user: SteamwarUser?, name: String, version: Int, type: WorldType, prototype: File? = null): SteamwarWorld { + fun getOrCreateTeamWorld(team: Team, name: String, version: Int, prototype: File? = null): SteamwarWorld = + getTeamWorld(team, name, version) ?: createWorld(null, name, version, WorldType.TEAM, prototype, team) + + @JvmStatic + fun countTeamWorlds(team: Team) = useDb { + find { + (WorldTable.team eq team.id) and + (WorldTable.type eq WorldType.TEAM) and + not(WorldTable.deleted) + }.count() + } + + @JvmStatic + fun getArenaWorld(mode: String, map: String, version: Int) = useDb { + find { + (WorldTable.name eq arenaWorldName(mode, map)) and + (WorldTable.version eq version) and + (WorldTable.type eq WorldType.ARENA) and + not(WorldTable.deleted) + }.firstOrNull() + } + + @JvmStatic + @JvmOverloads + fun getOrCreateArenaWorld(mode: String, map: String, version: Int, prototype: File? = null): SteamwarWorld = + getArenaWorld(mode, map, version) ?: createWorld(null, arenaWorldName(mode, map), version, WorldType.ARENA, prototype) + + @JvmStatic + fun getTemplateWorld(name: String, version: Int) = useDb { + find { + (WorldTable.name eq name) and + (WorldTable.version eq version) and + (WorldTable.type eq WorldType.TEMPLATE) and + not(WorldTable.deleted) + }.firstOrNull() + } + + @JvmStatic + fun getTemplateWorlds() = useDb { + find { + (WorldTable.type eq WorldType.TEMPLATE) and + not(WorldTable.deleted) + }.orderBy(WorldTable.name to SortOrder.ASC).toList() + } + + @JvmStatic + fun getTemplateWorlds(version: Int) = useDb { + find { + (WorldTable.version eq version) and + (WorldTable.type eq WorldType.TEMPLATE) and + not(WorldTable.deleted) + }.orderBy(WorldTable.name to SortOrder.ASC).toList() + } + + @JvmStatic + @JvmOverloads + fun getOrCreateTemplateWorld(name: String, version: Int, prototype: File? = null): SteamwarWorld = + getTemplateWorld(name, version) ?: createWorld(null, name, version, WorldType.TEMPLATE, prototype) + + @JvmStatic + fun getAllActiveWorlds() = useDb { + find { not(WorldTable.deleted) }.toList() + } + + @JvmStatic + fun getWorldsToArchive() = useDb { + find { + not(WorldTable.deleted) or (WorldTable.deleted eq true) + }.toList().filter { it.shouldArchive } + } + + private fun arenaWorldName(mode: String, map: String) = "$mode/$map" + + @JvmStatic + @JvmOverloads + fun createWorld(user: SteamwarUser?, name: String, version: Int, type: WorldType, prototype: File? = null, team: Team? = null): SteamwarWorld { val world = useDb { new { this.name = name this.version = version this.type = type this.owner = user?.id + this.team = team?.id } } world.setupAndGetStoragePath(prototype) diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/providers/BauServerInfo.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/providers/BauServerInfo.java index c5c88562..088a8e37 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/providers/BauServerInfo.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/providers/BauServerInfo.java @@ -20,14 +20,54 @@ package de.steamwar.providers; import de.steamwar.sql.SteamwarUser; +import de.steamwar.sql.WorldType; import org.bukkit.Bukkit; +import java.util.UUID; + public class BauServerInfo { private static Integer bauOwner = null; + private static Integer bauTeam = null; + private static UUID bauWorld = null; + private static WorldType bauType = null; static { + String ownerProperty = System.getProperty("bauOwner"); + if (ownerProperty != null && !ownerProperty.isBlank()) { + try { + bauOwner = Integer.parseInt(ownerProperty); + } catch (NumberFormatException ignored) { + } + } + + String teamProperty = System.getProperty("bauTeam"); + if (teamProperty != null && !teamProperty.isBlank()) { + try { + bauTeam = Integer.parseInt(teamProperty); + } catch (NumberFormatException ignored) { + } + } + + String worldProperty = System.getProperty("bauWorld"); + if (worldProperty != null && !worldProperty.isBlank()) { + try { + bauWorld = UUID.fromString(worldProperty); + } catch (IllegalArgumentException ignored) { + } + } + + String typeProperty = System.getProperty("bauType"); + if (typeProperty != null && !typeProperty.isBlank()) { + try { + bauType = WorldType.valueOf(typeProperty); + } catch (IllegalArgumentException ignored) { + } + } + try { - bauOwner = Integer.parseInt(Bukkit.getWorlds().get(0).getName()); + if (bauOwner == null) { + bauOwner = Integer.parseInt(Bukkit.getWorlds().get(0).getName()); + } } catch (NumberFormatException ignored) { } } @@ -37,11 +77,23 @@ public class BauServerInfo { } public static boolean isBauServer() { - return bauOwner != null; + return bauOwner != null || bauWorld != null || bauTeam != null; } public static SteamwarUser getOwnerUser() { if (bauOwner == null) return null; return SteamwarUser.byId(bauOwner); } + + public static UUID getWorldId() { + return bauWorld; + } + + public static Integer getTeamId() { + return bauTeam; + } + + public static WorldType getWorldType() { + return bauType; + } } diff --git a/VelocityCore/Persistent/src/de/steamwar/persistent/Bauserver.java b/VelocityCore/Persistent/src/de/steamwar/persistent/Bauserver.java index c9c33ac6..7d3f59ba 100644 --- a/VelocityCore/Persistent/src/de/steamwar/persistent/Bauserver.java +++ b/VelocityCore/Persistent/src/de/steamwar/persistent/Bauserver.java @@ -29,6 +29,7 @@ import java.util.function.Consumer; @Getter public class Bauserver extends Subserver { private static final Map servers = new HashMap<>(); + private static final Map serversByWorld = new HashMap<>(); public static Bauserver get(UUID owner) { synchronized (servers) { @@ -36,25 +37,43 @@ public class Bauserver extends Subserver { } } + public static Bauserver getByWorld(UUID world) { + synchronized (serversByWorld) { + return serversByWorld.get(world); + } + } + private final UUID owner; + private final UUID world; public Bauserver(String serverName, UUID owner, int port, ProcessBuilder processBuilder, Runnable shutdownCallback) { - this(serverName, owner, port, processBuilder, shutdownCallback, null); + this(serverName, owner, owner, port, processBuilder, shutdownCallback, null); } public Bauserver(String serverName, UUID owner, int port, ProcessBuilder processBuilder, Runnable shutdownCallback, Consumer failureCallback) { + this(serverName, owner, owner, port, processBuilder, shutdownCallback, failureCallback); + } + + public Bauserver(String serverName, UUID owner, UUID world, int port, ProcessBuilder processBuilder, Runnable shutdownCallback, Consumer failureCallback) { super(serverName, port, processBuilder, shutdownCallback, failureCallback); this.owner = owner; + this.world = world; synchronized (servers) { - servers.put(owner, this); + servers.putIfAbsent(owner, this); + } + synchronized (serversByWorld) { + serversByWorld.put(world, this); } } @Override protected void unregister() { synchronized (servers) { - servers.remove(owner); + servers.remove(owner, this); + } + synchronized (serversByWorld) { + serversByWorld.remove(world); } super.unregister(); } diff --git a/VelocityCore/deployarena.py b/VelocityCore/deployarena.py index 6cd5d62f..3c2666ad 100755 --- a/VelocityCore/deployarena.py +++ b/VelocityCore/deployarena.py @@ -38,7 +38,7 @@ if __name__ == "__main__": gamemode = yaml.load(file) builderworld = sys.argv[4] if len(sys.argv) > 4 else path.expanduser(f'/worlds/builder{version}/{worldname}') - arenaworld = f'/servers/{gamemode["Server"]["Folder"]}/arenas/{worldname}' + arenaworld = sys.argv[5] if len(sys.argv) > 5 else f'/servers/{gamemode["Server"]["Folder"]}/arenas/{worldname}' if path.exists(arenaworld): backupworld = path.expanduser(f'/mnt/storage/backup/arenas/{datetime.datetime.now()}-{worldname}-{version}.tar.xz') @@ -91,7 +91,7 @@ if __name__ == "__main__": if path.exists(arenaworld): shutil.rmtree(arenaworld) - os.makedirs(f'{arenaworld}/backup') + os.makedirs(f'{arenaworld}/backup', exist_ok=True) shutil.copy2(f'{builderworld}/level.dat', f'{arenaworld}/backup/level.dat') shutil.copytree(f'{builderworld}/region', f'{arenaworld}/backup/region') diff --git a/VelocityCore/src/de/steamwar/messages/BungeeCore.properties b/VelocityCore/src/de/steamwar/messages/BungeeCore.properties index 6f0c2054..79f4e23a 100644 --- a/VelocityCore/src/de/steamwar/messages/BungeeCore.properties +++ b/VelocityCore/src/de/steamwar/messages/BungeeCore.properties @@ -219,22 +219,22 @@ IGNORE_ALREADY = §cYou are already ignoring this player. IGNORE_MESSAGE = §7You are now ignoring §e{0}§8. #BauCommand -BAU_ADDMEMBER_USAGE = §8/§7build addmember §8[§eplayer§8] +BAU_ADDMEMBER_USAGE = §8/§7build addmember §8[§eworld/all§8] §8[§eplayer§8] BAU_ADDMEMBER_SELFADD = §cYou don't have to add yourself! BAU_ADDMEMBER_ISADDED = §cThis player is already a member of your world. BAU_ADDMEMBER_ADDED = §aThe player was added to your world. BAU_ADDMEMBER_ADDED_TARGET = §aYou have been added to the world of §e{0}§a. -BAU_TP_USAGE = §8/§7build tp §8[§eplayer§8] +BAU_TP_USAGE = §8/§7build tp §8[§eplayer§8] §8<§eworld§8> BAU_TP_NOALLOWED = §cYou are not allowed to teleport to this player's world. BAU_LOCKED_NOALLOWED = §cThe build server is currently locked. BAU_LOCK_BLOCKED = §cYour build lock has prevented §e{0} §cfrom joining. -BAU_LOCKED_OPTIONS = §7Build server lock options§8: §cnobody§8, §eserverteam§8, §eteam_and_serverteam§8, §eteam§8, §aopen +BAU_LOCKED_OPTIONS = §8/§7build lock §8[§eworld§8] §8[§eoption§8] §7Options§8: §cnobody§8, §eserverteam§8, §eteam_and_serverteam§8, §eteam§8, §aopen BAU_LOCKED_NOBODY = §7You have locked your build server for all players. BAU_LOCKED_SERVERTEAM = §7You have locked your build server for all players except added server team members. BAU_LOCKED_TEAM_AND_SERVERTEAM = §7You have locked your build server for all players except added team members and server team members. BAU_LOCKED_TEAM = §7You have locked your build server for all players except added team members. BAU_LOCKED_OPEN = §7You have opened your build server for all added players. -BAU_DELMEMBER_USAGE = §8/§7build delmember §8[§eplayer§8] +BAU_DELMEMBER_USAGE = §8/§7build delmember §8[§eworld/all§8] §8[§eplayer§8] BAU_DELMEMBER_SELFDEL = §cYou cannot remove yourself! BAU_DELMEMBER_DELETED = §cPlayer was removed. BAU_DELMEMBER_DELETED_TARGET = §cYou were removed from the world of §e{0}. @@ -244,9 +244,23 @@ BAU_DELETE_DELETED = §aYour world is being reset. BAU_DELETE_GUI_NAME = §eDo you really want to delete the world? BAU_DELETE_GUI_CANCEL = §cCancel BAU_DELETE_GUI_DELETE = §aDelete +BAU_WORLD_GUI_TITLE = §eYour worlds +BAU_WORLD_CREATE_USAGE = §8/§7build world create §8[§ename§8] §8[§eversion§8] §8[§etemplate§8] +BAU_WORLD_START_USAGE = §8/§7build world start §8[§eworld§8] +BAU_WORLD_RENAME_USAGE = §8/§7build world rename §8[§eworld§8] §8[§enew name§8] +BAU_WORLD_DELETE_USAGE = §8/§7build world delete §8[§eworld§8] +BAU_WORLD_UPGRADE_USAGE = §8/§7build world upgrade §8[§eworld§8] §8[§eversion§8] +BAU_WORLD_VERSION = §cUnknown version. +BAU_WORLD_UNKNOWN = §cUnknown world. +BAU_WORLD_EXISTS = §cA world with this name already exists for this version. +BAU_WORLD_LIMIT = §cYou have reached your build world limit of §e{0}§c. Delete one first. +BAU_WORLD_OVER_LIMIT_DELETE = §cYou currently have more than your allowed §e{0}§c build worlds. Delete one before creating, renaming, or upgrading worlds. +BAU_WORLD_CREATED = §aWorld created. +BAU_WORLD_RENAMED = §aWorld renamed. +BAU_WORLD_UPGRADED = §aWorld version updated. BAU_START_ALREADY = §cThis server is already starting. BAU_MEMBER_NOMEMBER = §cThis player is no member of your world! -BAU_MEMBER_SET_USAGE = §8/§7build {0} §8[§eplayer§8] +BAU_MEMBER_SET_USAGE = §8/§7build {0} §8[§eworld/all§8] §8[§eplayer§8] BAU_MEMBER_SET_TARGET = §7You are now a §e{1}§7 on the world of §e{0}§7. BAU_MEMBER_SET = §7The player is now a §e{0}§7. BAU_MEMBER_SET_SPECTATOR = spectator @@ -514,6 +528,20 @@ TEAM_EVENT_LEFT = §7Your team no longer takes part in this event TEAM_EVENT_JOINED = §7Your team now takes part in the event §e{0}§7! TEAM_EVENT_HOW_TO_LEAVE = §7To cancel the participation repeat the command. +#Team Worlds +TEAM_WORLD_GUI_TITLE = §eTeam worlds +TEAM_WORLD_CREATE_USAGE = §8/§7team world create §8[§ename§8] §8[§eversion§8] §8[§etemplate§8] +TEAM_WORLD_RENAME_USAGE = §8/§7team world rename §8[§eworld§8] §8[§enew name§8] +TEAM_WORLD_UPGRADE_USAGE = §8/§7team world upgrade §8[§eworld§8] §8[§eversion§8] +TEAM_WORLD_DELETE_USAGE = §8/§7team world delete §8[§eworld§8] +TEAM_WORLD_EXISTS = §cA team world with this name already exists for this version. +TEAM_WORLD_LIMIT = §cYour team already has the maximum of §e{0}§c worlds. Delete one first. +TEAM_WORLD_OVER_LIMIT_DELETE = §cYour team currently has more than the allowed §e{0}§c worlds. Delete one before creating, renaming, or upgrading worlds. +TEAM_WORLD_CREATED = §aTeam world created. +TEAM_WORLD_RENAMED = §aTeam world renamed. +TEAM_WORLD_UPGRADED = §aTeam world version updated. +TEAM_WORLD_DELETED = §aTeam world deleted. + #Team Color TEAM_COLOR_TITLE = Choose color @@ -672,7 +700,7 @@ LOCK_LOCALE_CHANGED = §aLanguage saved #Builder Cloud BUILDERCLOUD_USAGE = §8/§7buildercloud §8[§eversion§8] §8[§emap§8] -BUILDERCLOUD_CREATE_USAGE = §8/§7buildercloud create §8[§eversion§8] §8[§emap§8] §8<§7generator§8> +BUILDERCLOUD_CREATE_USAGE = §8/§7buildercloud create §8[§eversion§8] §8[§emap§8] §8[§etemplate§8] BUILDERCLOUD_RENAME_USAGE = §8/§7buildercloud rename §8[§eversion§8] §8[§emap§8] §8[§enew name§8] BUILDERCLOUD_DEPLOY_USAGE = §8/§7deployarena §8[§egamemode§8] §8[§eversion§8] §8[§emap§8] BUILDERCLOUD_DEPLOY_FINISHED = §7Map deployment finished. diff --git a/VelocityCore/src/de/steamwar/messages/BungeeCore_de.properties b/VelocityCore/src/de/steamwar/messages/BungeeCore_de.properties index 860d311b..8ac11bb8 100644 --- a/VelocityCore/src/de/steamwar/messages/BungeeCore_de.properties +++ b/VelocityCore/src/de/steamwar/messages/BungeeCore_de.properties @@ -201,22 +201,22 @@ IGNORE_ALREADY = §cDu ignorierst diesen Spieler bereits. IGNORE_MESSAGE = §7Du ignorierst nun §e{0}§8. #BauCommand -BAU_ADDMEMBER_USAGE = §8/§7bau addmember §8[§eSpieler§8] +BAU_ADDMEMBER_USAGE = §8/§7bau addmember §8[§eWelt/all§8] §8[§eSpieler§8] BAU_ADDMEMBER_SELFADD = §cDu brauchst dich nicht selbst hinzufügen! BAU_ADDMEMBER_ISADDED = §cDieser Spieler ist bereits Mitglied auf deiner Welt. BAU_ADDMEMBER_ADDED = §aDer Spieler wurde zu deiner Welt hinzugefügt. BAU_ADDMEMBER_ADDED_TARGET = §aDu wurdest zu der Welt von §e{0} §ahinzugefügt. -BAU_TP_USAGE = §8/§7bau tp §8[§eSpieler§8] +BAU_TP_USAGE = §8/§7bau tp §8[§eSpieler§8] §8<§eWelt§8> BAU_TP_NOALLOWED = §cDu darfst dich nicht auf diese Welt teleportieren. BAU_LOCKED_NOALLOWED = §cDer Bauserver ist momentan gesperrt. BAU_LOCK_BLOCKED = §cDeine Bausperre hat den Beitritt von §e{0} §cverhindert. -BAU_LOCKED_OPTIONS = §7Bauserver-Sperroptionen§8: §cnobody§8, §eserverteam§8, §eteam_and_serverteam§8, §eteam§8, §aopen +BAU_LOCKED_OPTIONS = §8/§7bau lock §8[§eWelt§8] §8[§eOption§8] §7Optionen§8: §cnobody§8, §eserverteam§8, §eteam_and_serverteam§8, §eteam§8, §aopen BAU_LOCKED_NOBODY = §7Du hast deinen Bau für alle Spieler geschlossen. BAU_LOCKED_SERVERTEAM = §7Du hast deinen Bau für alle außer hinzugefügte Serverteammitglieder gesperrt. BAU_LOCKED_TEAM_AND_SERVERTEAM = §7Du hast deinen Bau für alle außer hinzugefügte Teammitglieder und Serverteammitglieder gesperrt. BAU_LOCKED_TEAM = §7Du hast deinen Bau für alle außer hinzugefügte Teammitglieder gesperrt. BAU_LOCKED_OPEN = §7Du hast deinen Bau für alle hinzugefügten Spieler geöffnet. -BAU_DELMEMBER_USAGE = §8/§7bau delmember §8[§eSpieler§8] +BAU_DELMEMBER_USAGE = §8/§7bau delmember §8[§eWelt/all§8] §8[§eSpieler§8] BAU_DELMEMBER_SELFDEL = §cDu kannst dich nicht selbst entfernen! BAU_DELMEMBER_DELETED = §cDer Spieler wurde entfernt. BAU_DELMEMBER_DELETED_TARGET = §cDu wurdest von der Welt von §e{0} §centfernt. @@ -226,9 +226,23 @@ BAU_DELETE_DELETED = §aDeine Welt wird zurückgesetzt. BAU_DELETE_GUI_NAME = §eWirklich Welt löschen? BAU_DELETE_GUI_CANCEL = §cAbbrechen BAU_DELETE_GUI_DELETE = §aLöschen +BAU_WORLD_GUI_TITLE = §eDeine Welten +BAU_WORLD_CREATE_USAGE = §8/§7bau world create §8[§eName§8] §8[§eVersion§8] §8[§eTemplate§8] +BAU_WORLD_START_USAGE = §8/§7bau world start §8[§eWelt§8] +BAU_WORLD_RENAME_USAGE = §8/§7bau world rename §8[§eWelt§8] §8[§eNeuer Name§8] +BAU_WORLD_DELETE_USAGE = §8/§7bau world delete §8[§eWelt§8] +BAU_WORLD_UPGRADE_USAGE = §8/§7bau world upgrade §8[§eWelt§8] §8[§eVersion§8] +BAU_WORLD_VERSION = §cUnbekannte Version. +BAU_WORLD_UNKNOWN = §cUnbekannte Welt. +BAU_WORLD_EXISTS = §cEine Welt mit diesem Namen existiert bereits in dieser Version. +BAU_WORLD_LIMIT = §cDu hast dein Bauwelt-Limit von §e{0}§c erreicht. Lösche zuerst eine Welt. +BAU_WORLD_OVER_LIMIT_DELETE = §cDu hast aktuell mehr als die erlaubten §e{0}§c Bauwelten. Lösche eine Welt, bevor du Welten erstellst, umbenennst oder upgradest. +BAU_WORLD_CREATED = §aWelt erstellt. +BAU_WORLD_RENAMED = §aWelt umbenannt. +BAU_WORLD_UPGRADED = §aWelt-Version aktualisiert. BAU_START_ALREADY = §cDer Server startet bereits. BAU_MEMBER_NOMEMBER = §cDer Spieler ist kein Mitglied deiner Welt! -BAU_MEMBER_SET_USAGE = §8/§7bau {0} §8[§eSpieler§8] +BAU_MEMBER_SET_USAGE = §8/§7bau {0} §8[§eWelt/all§8] §8[§eSpieler§8] BAU_MEMBER_SET_TARGET = §7Du bist nun ein §e{1}§7 auf der Welt von §e{0}§7. BAU_MEMBER_SET = §7Der Spieler ist nun §e{0}§7. BAU_MEMBER_SET_SPECTATOR = Zuschauer @@ -486,6 +500,20 @@ TEAM_EVENT_LEFT = §7Dein Team nimmt nicht mehr am Event teil TEAM_EVENT_JOINED = §7Dein Team nimmt nun am Event §e{0} §7 teil! TEAM_EVENT_HOW_TO_LEAVE = §7Um die Teilnahme abzusagen, wiederhole den Befehl. +#Team Worlds +TEAM_WORLD_GUI_TITLE = §eTeamwelten +TEAM_WORLD_CREATE_USAGE = §8/§7team world create §8[§eName§8] §8[§eVersion§8] §8[§eTemplate§8] +TEAM_WORLD_RENAME_USAGE = §8/§7team world rename §8[§eWelt§8] §8[§eNeuer Name§8] +TEAM_WORLD_UPGRADE_USAGE = §8/§7team world upgrade §8[§eWelt§8] §8[§eVersion§8] +TEAM_WORLD_DELETE_USAGE = §8/§7team world delete §8[§eWelt§8] +TEAM_WORLD_EXISTS = §cEine Teamwelt mit diesem Namen existiert bereits in dieser Version. +TEAM_WORLD_LIMIT = §cDein Team hat bereits das Maximum von §e{0}§c Welten. Lösche zuerst eine Welt. +TEAM_WORLD_OVER_LIMIT_DELETE = §cDein Team hat aktuell mehr als die erlaubten §e{0}§c Welten. Lösche eine Welt, bevor du Welten erstellst, umbenennst oder upgradest. +TEAM_WORLD_CREATED = §aTeamwelt erstellt. +TEAM_WORLD_RENAMED = §aTeamwelt umbenannt. +TEAM_WORLD_UPGRADED = §aTeamwelt-Version aktualisiert. +TEAM_WORLD_DELETED = §aTeamwelt gelöscht. + #Team Color TEAM_COLOR_TITLE = Farbe wählen @@ -639,7 +667,7 @@ LOCK_LOCALE_CHANGED = §aSprache gespeichert #Builder Cloud BUILDERCLOUD_USAGE = §8/§7buildercloud §8[§eVersion§8] §8[§eWelt§8] -BUILDERCLOUD_CREATE_USAGE = §8/§7buildercloud create §8[§eVersion§8] §8[§eWelt§8] §8<§7Generator§8> +BUILDERCLOUD_CREATE_USAGE = §8/§7buildercloud create §8[§eVersion§8] §8[§eWelt§8] §8[§eTemplate§8] BUILDERCLOUD_RENAME_USAGE = §8/§7buildercloud rename §8[§eVersion§8] §8[§eWElt§8] §8[§eNeuer Name§8] BUILDERCLOUD_VERSION = §cUnbekannte Version. BUILDERCLOUD_EXISTING_MAP = §cWelt existiert bereits. diff --git a/VelocityCore/src/de/steamwar/velocitycore/ServerStarter.java b/VelocityCore/src/de/steamwar/velocitycore/ServerStarter.java index 5a05054e..cae89aa1 100644 --- a/VelocityCore/src/de/steamwar/velocitycore/ServerStarter.java +++ b/VelocityCore/src/de/steamwar/velocitycore/ServerStarter.java @@ -89,7 +89,8 @@ public class ServerStarter { gameMode = mode.configFile.getName().replace(".yml", ""); directory = new File(SERVER_PATH, mode.Server.Folder); arguments.put("config", mode.configFile.getName()); - tempWorld(SERVER_PATH + mode.Server.Folder + "/arenas/" + map); + SteamwarWorld arenaWorld = SteamwarWorld.getOrCreateArenaWorld(gameMode, map, version.getVersionSuffix()); + tempWorld(arenaWorld.setupAndGetStoragePath()); startCondition = () -> { if (playersToSend.stream().anyMatch(player -> Subserver.isArena(Subserver.getSubserver(player)))) { playersToSend.forEach(player -> Chatter.of(player).system("FIGHT_IN_ARENA")); @@ -152,20 +153,35 @@ public class ServerStarter { public ServerStarter build(ServerVersion version, UUID owner) { this.version = version; - directory = version.getServerDirectory("Bau"); SteamwarUser user = SteamwarUser.get(owner); - SteamwarWorld world = SteamwarWorld.getOrCreateBauWorld(user, version.getVersionSuffix()); + return build(version, SteamwarWorld.getOrCreateBauWorld(user, version.getVersionSuffix())); + } + + public ServerStarter build(ServerVersion version, SteamwarWorld world) { + this.version = version; + directory = version.getServerDirectory("Bau"); + SteamwarUser user = world.getOwner() == null ? null : SteamwarUser.byId(world.getOwner().getValue()); worldDir = WORLDS_BASE_PATH; worldName = world.getUuid().toString(); checkpoint = true; - build(owner); + if (world.getType() == WorldType.TEAM) { + build(world.getUuid()); + Team team = Team.byId(world.getTeam().getValue()); + serverNameProvider = port -> team.getTeamKuerzel() + "s Bau"; + arguments.put("bauTeam", String.valueOf(world.getTeam().getValue())); + } else { + build(user.getUUID(), world.getUuid()); + arguments.put("bauOwner", String.valueOf(user.getId())); + } + arguments.put("bauWorld", world.getUuid().toString()); + arguments.put("bauType", world.getType().name()); worldSetup = () -> world.setupAndGetStoragePath(new File(directory, "Bauwelt")); // Send players to existing server startCondition = () -> { - Bauserver subserver = Bauserver.get(owner); + Bauserver subserver = Bauserver.getByWorld(world.getUuid()); if (subserver != null) { for (Player p : playersToSend) { SubserverSystem.sendPlayer(subserver, p); @@ -173,9 +189,13 @@ public class ServerStarter { return false; } boolean atLeastOneSupervisor = playersToSend.stream().anyMatch(player -> { - if (player.getUniqueId().equals(owner)) return true; - BauweltMember bauweltMember = BauweltMember.getBauMember(owner, player.getUniqueId()); - return bauweltMember.isSupervisor(); + SteamwarUser playerUser = SteamwarUser.get(player.getUniqueId()); + if (world.getType() == WorldType.TEAM) { + return playerUser.getTeam() == world.getTeam().getValue(); + } + if (user != null && playerUser.getId() == user.getId()) return true; + BauweltMember bauweltMember = BauweltMember.getBauMember(world.getUuid(), playerUser.getId()); + return bauweltMember != null && bauweltMember.isSupervisor(); }); if (!atLeastOneSupervisor) { for (Player p : playersToSend) { @@ -209,11 +229,15 @@ public class ServerStarter { } private void build(UUID owner) { - constructor = (serverName, port, builder, shutdownCallback, failureCallback) -> new Bauserver(serverName, owner, port, builder, shutdownCallback, failureCallback); + build(owner, owner); + } + + private void build(UUID owner, UUID world) { + constructor = (serverName, port, builder, shutdownCallback, failureCallback) -> new Bauserver(serverName, owner, world, port, builder, shutdownCallback, failureCallback); serverNameProvider = port -> bauServerName(SteamwarUser.get(owner)); } - public ServerStarter builder(ServerVersion version, String map, File generator) { + public ServerStarter builder(ServerVersion version, String map, File prototype) { this.version = version; directory = version.getServerDirectory("Builder"); SteamwarWorld world = SteamwarWorld.getOrCreateBuilderWorld(map, version.getVersionSuffix()); @@ -235,18 +259,7 @@ public class ServerStarter { return true; }; - if (generator != null) { - worldSetup = () -> { - File leveldat = new File(world.setupAndGetStoragePath(), "level.dat"); - try { - Files.copy(generator.toPath(), leveldat.toPath()); - } catch (IOException e) { - throw new SecurityException(e); - } - }; - } else { - worldSetup = () -> world.setupAndGetStoragePath(legacyBuilderWorld(version, map)); - } + worldSetup = () -> world.setupAndGetStoragePath(prototype != null ? prototype : legacyBuilderWorld(version, map)); return this; } diff --git a/VelocityCore/src/de/steamwar/velocitycore/commands/BauCommand.java b/VelocityCore/src/de/steamwar/velocitycore/commands/BauCommand.java index ad04539a..5b412ac0 100644 --- a/VelocityCore/src/de/steamwar/velocitycore/commands/BauCommand.java +++ b/VelocityCore/src/de/steamwar/velocitycore/commands/BauCommand.java @@ -36,14 +36,18 @@ import de.steamwar.sql.BauweltMember; import de.steamwar.sql.GameModeConfig; import de.steamwar.sql.SteamwarUser; import de.steamwar.sql.SteamwarWorld; +import de.steamwar.sql.UserPerm; import de.steamwar.velocitycore.*; import de.steamwar.velocitycore.inventory.SWInventory; import de.steamwar.velocitycore.inventory.SWItem; +import de.steamwar.velocitycore.inventory.SWListInv; import de.steamwar.velocitycore.network.NetworkSender; import de.steamwar.velocitycore.util.BauLock; -import java.io.File; +import java.util.ArrayList; import java.util.Collection; +import java.util.List; +import java.util.Locale; import java.util.function.Consumer; @Linked @@ -63,13 +67,13 @@ public class BauCommand extends SWCommand { } @Register - public void toBau(PlayerChatter sender, @OptionalValue(value = "", onlyUINIG = true) ServerVersion version) { - new ServerStarter().build(version, sender.user().getUUID()).send(sender.getPlayer()).start(); + public void toBau(PlayerChatter sender, @Mapper("ownBauWorld") @OptionalValue(value = "", onlyUINIG = true) @AllowNull SteamwarWorld world) { + startPersonalWorld(sender, world); } @Register(value = "addmember", description = "BAU_ADDMEMBER_USAGE") - public void addmember(Chatter sender, @Validator("addMemberTarget") SteamwarUser target) { - BauweltMember.addMember(sender.user().getUUID(), target.getUUID()); + public void addmember(Chatter sender, @Mapper("bauWorldSelector") String world, @Validator("addMemberTarget") SteamwarUser target) { + if (!forEachSelectedWorld(sender, world, bauworld -> BauweltMember.addMember(bauworld.getUuid(), target.getId()))) return; sender.system("BAU_ADDMEMBER_ADDED"); Chatter.of(target.getUUID()).system("BAU_ADDMEMBER_ADDED_TARGET", sender); } @@ -85,18 +89,31 @@ public class BauCommand extends SWCommand { messageSender.send("BAU_ADDMEMBER_SELFADD"); return false; } - if (BauweltMember.getBauMember(sender.user().getId(), value.getId()) != null) { - messageSender.send("BAU_ADDMEMBER_ISADDED"); - return false; - } return true; }; } @Register(value = "tp", description = "BAU_TP_USAGE") @Register("teleport") - public void teleport(PlayerChatter sender, @Validator("teleportTarget") SteamwarUser worldOwner, @OptionalValue(value = "", onlyUINIG = true) ServerVersion version) { - new ServerStarter().build(version, worldOwner.getUUID()).send(sender.getPlayer()).start(); + public void teleport(PlayerChatter sender, @Validator("teleportTarget") SteamwarUser worldOwner, @Mapper("targetBauWorld") @OptionalValue(value = "", onlyUINIG = true) @AllowNull SteamwarWorld world) { + if (world == null) { + world = defaultPersonalWorld(worldOwner); + } + if (world == null) { + sender.system("BAU_WORLD_UNKNOWN"); + return; + } + if (sender.user().getId() != worldOwner.getId() && BauLock.isLocked(world, sender.user())) { + sender.system("BAU_LOCKED_NOALLOWED"); + Chatter.of(worldOwner.getUUID()).system("BAU_LOCK_BLOCKED", sender); + return; + } + if (sender.user().getId() != worldOwner.getId() && BauweltMember.getBauMember(world.getUuid(), sender.user().getId()) == null) { + SubserverSystem.sendDeniedMessage(sender, worldOwner.getUUID()); + sender.system("BAU_TP_NOALLOWED"); + return; + } + new ServerStarter().build(ServerVersion.get(world.getVersion()), world).send(sender.getPlayer()).start(); } @Validator(value = "teleportTarget", local = true) @@ -106,17 +123,6 @@ public class BauCommand extends SWCommand { messageSender.send("UNKNOWN_PLAYER"); return false; } - if (sender.user().getId() != owner.getId() && BauweltMember.getBauMember(owner.getId(), sender.user().getId()) == null) { - SubserverSystem.sendDeniedMessage(sender, owner.getUUID()); - messageSender.send("BAU_TP_NOALLOWED"); - return false; - } - - if (BauLock.isLocked(owner, sender.user())) { - messageSender.send("BAU_LOCKED_NOALLOWED"); - Chatter.of(owner.getUUID()).system("BAU_LOCK_BLOCKED", sender); - return false; - } return true; }; } @@ -127,39 +133,39 @@ public class BauCommand extends SWCommand { } @Register("setspectator") - public void setSpectator(Chatter sender, @Mapper("addedUsers") @AllowNull @OptionalValue("") SteamwarUser user) { - setPerms(sender, user, "setspectator", "BAU_MEMBER_SET_SPECTATOR", member -> { + public void setSpectator(Chatter sender, @Mapper("bauWorldSelector") String world, @Mapper("addedUsers") @AllowNull @OptionalValue("") SteamwarUser user) { + setPerms(sender, world, user, "setspectator", "BAU_MEMBER_SET_SPECTATOR", member -> { member.setBuild(false); member.setSupervisor(false); }); } @Register("setbuilder") - public void setBuilder(Chatter sender, @Mapper("addedUsers") @AllowNull @OptionalValue("") SteamwarUser user) { - setPerms(sender, user, "setbuild", "BAU_MEMBER_SET_BUILDER", member -> { + public void setBuilder(Chatter sender, @Mapper("bauWorldSelector") String world, @Mapper("addedUsers") @AllowNull @OptionalValue("") SteamwarUser user) { + setPerms(sender, world, user, "setbuild", "BAU_MEMBER_SET_BUILDER", member -> { member.setBuild(true); member.setSupervisor(false); }); } @Register("setsupervisor") - public void setSupervisor(Chatter sender, @Mapper("addedUsers") @AllowNull @OptionalValue("") SteamwarUser user) { - setPerms(sender, user, "setsupervisor", "BAU_MEMBER_SET_SUPERVISOR", member -> { + public void setSupervisor(Chatter sender, @Mapper("bauWorldSelector") String world, @Mapper("addedUsers") @AllowNull @OptionalValue("") SteamwarUser user) { + setPerms(sender, world, user, "setsupervisor", "BAU_MEMBER_SET_SUPERVISOR", member -> { member.setBuild(true); member.setSupervisor(true); }); } - private void setPerms(Chatter owner, SteamwarUser user, String name, String permName, Consumer setter) { + private void setPerms(Chatter owner, String world, SteamwarUser user, String name, String permName, Consumer setter) { if (user == null) { owner.system("BAU_MEMBER_SET_USAGE", name); return; } - withMember(owner, user, target -> { + withMembers(owner, world, user, target -> { setter.accept(target); - Bauserver bauserver = Bauserver.get(owner.user().getUUID()); + Bauserver bauserver = Bauserver.getByWorld(target.getWorldID()); if (bauserver != null) { bauserver.getRegisteredServer().getPlayersConnected().stream().findAny().ifPresent(player -> NetworkSender.send(player, new BaumemberUpdatePacket())); } @@ -170,11 +176,11 @@ public class BauCommand extends SWCommand { } @Register(value = "delmember", description = "BAU_DELMEMBER_USAGE") - public void delmember(Chatter owner, @Mapper("addedUsers") SteamwarUser user) { - withMember(owner, user, target -> { + public void delmember(Chatter owner, @Mapper("bauWorldSelector") String world, @Mapper("addedUsers") SteamwarUser user) { + withMembers(owner, world, user, target -> { target.remove(); - Bauserver bauserver = Bauserver.get(owner.user().getUUID()); + Bauserver bauserver = Bauserver.getByWorld(target.getWorldID()); Chatter member = Chatter.of(user.getUUID()); member.withPlayer(player -> { if (bauserver != null && bauserver.getRegisteredServer().getPlayersConnected().contains(player)) { @@ -191,6 +197,29 @@ public class BauCommand extends SWCommand { }); } + @Mapper(value = "bauWorldSelector", local = true) + public TypeMapper bauWorldSelector() { + return new TypeMapper<>() { + @Override + public String map(Chatter sender, PreviousArguments previousArguments, String s) { + if ("all".equalsIgnoreCase(s)) return "all"; + return SteamwarWorld.getBauWorlds(sender.user()).stream() + .map(SteamwarWorld::getName) + .filter(name -> name.equalsIgnoreCase(s)) + .findFirst() + .orElse(null); + } + + @Override + public Collection tabCompletes(Chatter sender, PreviousArguments previousArguments, String s) { + List worlds = new ArrayList<>(); + worlds.add("all"); + SteamwarWorld.getBauWorlds(sender.user()).stream().map(SteamwarWorld::getName).forEach(worlds::add); + return worlds; + } + }; + } + @Mapper(value = "addedUsers", local = true) public TypeMapper addedUsers() { return new TypeMapper() { @@ -201,8 +230,10 @@ public class BauCommand extends SWCommand { @Override public Collection tabCompletes(Chatter sender, PreviousArguments previousArguments, String s) { - return BauweltMember.getMembers(sender.user().getId()).stream() + return selectedWorlds(sender, previousArguments.userArgs.length == 0 ? "all" : previousArguments.userArgs[previousArguments.userArgs.length - 1]).stream() + .flatMap(world -> BauweltMember.getWorldMembers(world.getUuid()).stream()) .map(bauweltMember -> SteamwarUser.byId(bauweltMember.getMemberID()).getUserName()) + .distinct() .toList(); } }; @@ -212,9 +243,11 @@ public class BauCommand extends SWCommand { public void stop(PlayerChatter sender) { VelocityCore.schedule(() -> { sender.system("BAU_STOPPING"); - Bauserver subserver = Bauserver.get(sender.user().getUUID()); - if (subserver != null) { - subserver.stop(); + for (SteamwarWorld world : SteamwarWorld.getBauWorlds(sender.user())) { + Bauserver subserver = Bauserver.getByWorld(world.getUuid()); + if (subserver != null) { + subserver.stop(); + } } sender.system("BAU_STOPPED"); @@ -223,31 +256,50 @@ public class BauCommand extends SWCommand { @Register("resetall") @Register("delete") - public void delete(PlayerChatter sender, ServerVersion version) { - SWInventory inventory = new SWInventory(sender, 9, new Message("BAU_DELETE_GUI_NAME")); - inventory.addItem(0, new SWItem("LIME_DYE", new Message("BAU_DELETE_GUI_DELETE")), click -> { - SteamwarWorld world = SteamwarWorld.getBauWorld(sender.user(), version.getVersionSuffix()); + public void delete(PlayerChatter sender, @Mapper("ownBauWorld") SteamwarWorld world) { + openDeleteWorld(sender, world); + } - VelocityCore.schedule(() -> { - Bauserver subserver = Bauserver.get(sender.user().getUUID()); - if (subserver != null) { - subserver.stop(); - } + @Register("worlds") + @Register("world") + public void worlds(PlayerChatter sender) { + openWorldList(sender); + } - if(world != null) { - world.markDeleted(); - SubserverSystem.deleteFolder(VelocityCore.local, world.getStorageDirectory().getPath()); - } else { - File legacyWorld = new File(version.getWorldFolder(ServerStarter.LEGACY_WORLDS_BASE_PATH), String.valueOf(sender.user().getId())); - SubserverSystem.deleteFolder(VelocityCore.local, legacyWorld.getPath()); - } - sender.system("BAU_DELETE_DELETED"); - }).schedule(); + @Register(value = "world create", description = "BAU_WORLD_CREATE_USAGE") + public void createWorld(PlayerChatter sender, String name, @ErrorMessage("BAU_WORLD_VERSION") ServerVersion version, @Mapper("templateWorld") SteamwarWorld template) { + SteamwarWorld world = createPersonalWorld(sender, name, version, template); + if (world != null) { + sender.system("BAU_WORLD_CREATED"); + } + } - inventory.close(); - }); - inventory.addItem(8, new SWItem("RED_DYE", new Message("BAU_DELETE_GUI_CANCEL")), click -> inventory.close()); - inventory.open(); + @Register(value = "world start", description = "BAU_WORLD_START_USAGE") + public void startWorld(PlayerChatter sender, @Mapper("ownBauWorld") SteamwarWorld world) { + new ServerStarter().build(ServerVersion.get(world.getVersion()), world).send(sender.getPlayer()).start(); + } + + @Register(value = "world rename", description = "BAU_WORLD_RENAME_USAGE") + public void renameWorld(Chatter sender, @Mapper("ownBauWorld") SteamwarWorld world, String newName) { + if (hasTooManyPersonalWorlds(sender)) return; + if (SteamwarWorld.getBauWorld(sender.user(), newName, world.getVersion()) != null) { + sender.system("BAU_WORLD_EXISTS"); + return; + } + world.rename(newName); + sender.system("BAU_WORLD_RENAMED"); + } + + @Register(value = "world delete", description = "BAU_WORLD_DELETE_USAGE") + public void deleteWorld(PlayerChatter sender, @Mapper("ownBauWorld") SteamwarWorld world) { + openDeleteWorld(sender, world); + } + + @Register(value = "world upgrade", description = "BAU_WORLD_UPGRADE_USAGE") + public void upgradeWorld(Chatter sender, @Mapper("ownBauWorld") SteamwarWorld world, @ErrorMessage("BAU_WORLD_VERSION") ServerVersion version) { + if (hasTooManyPersonalWorlds(sender)) return; + world.changeVersion(version.getVersionSuffix()); + sender.system("BAU_WORLD_UPGRADED"); } @Register("test") @@ -259,27 +311,244 @@ public class BauCommand extends SWCommand { } @Register(value = "lock", description = "BAU_LOCKED_OPTIONS") - public void lock(Chatter sender, BauLockState bauLockState) { - BauLock.setLocked(sender, bauLockState); + public void lock(Chatter sender, @Mapper("ownBauWorld") SteamwarWorld world, BauLockState bauLockState) { + BauLock.setLocked(sender, world, bauLockState); } @Register("unlock") - public void unlock(Chatter sender) { - BauLock.setLocked(sender, BauLockState.OPEN); + public void unlock(Chatter sender, @Mapper("ownBauWorld") SteamwarWorld world) { + BauLock.setLocked(sender, world, BauLockState.OPEN); } - private static void withMember(Chatter owner, SteamwarUser member, Consumer function) { + private static void withMembers(Chatter owner, String worldSelector, SteamwarUser member, Consumer function) { if (member == null) { owner.system("UNKNOWN_PLAYER"); return; } - BauweltMember target = BauweltMember.getBauMember(owner.user().getId(), member.getId()); - if (target == null) { - owner.system("BAU_MEMBER_NOMEMBER"); + List worlds = selectedWorlds(owner, worldSelector); + if (worlds.isEmpty()) { + owner.system("BAU_WORLD_UNKNOWN"); return; } - function.accept(target); + boolean found = false; + for (SteamwarWorld world : worlds) { + BauweltMember target = BauweltMember.getBauMember(world.getUuid(), member.getId()); + if (target == null) continue; + found = true; + function.accept(target); + } + + if (!found) { + owner.system("BAU_MEMBER_NOMEMBER"); + } + } + + @Mapper(value = "ownBauWorld", local = true) + private TypeMapper ownBauWorldMapper() { + return new TypeMapper() { + @Override + public SteamwarWorld map(Chatter sender, PreviousArguments previousArguments, String s) { + if (s == null || s.isEmpty()) return defaultPersonalWorld(sender); + return SteamwarWorld.getBauWorlds(sender.user()).stream() + .filter(world -> world.getName().equalsIgnoreCase(s) || world.getUuid().toString().equalsIgnoreCase(s)) + .findFirst() + .orElse(null); + } + + @Override + public Collection tabCompletes(Chatter sender, PreviousArguments previousArguments, String s) { + return SteamwarWorld.getBauWorlds(sender.user()).stream().map(SteamwarWorld::getName).toList(); + } + }; + } + + @Mapper(value = "targetBauWorld", local = true) + private TypeMapper targetBauWorldMapper() { + return new TypeMapper<>() { + @Override + public SteamwarWorld map(Chatter sender, PreviousArguments previousArguments, String s) { + SteamwarUser owner = previousArguments.getAll(SteamwarUser.class).stream().findFirst().orElse(null); + if (owner == null) return null; + if (s == null || s.isEmpty()) return defaultPersonalWorld(owner); + return SteamwarWorld.getBauWorlds(owner).stream() + .filter(world -> world.getName().equalsIgnoreCase(s) || world.getUuid().toString().equalsIgnoreCase(s)) + .findFirst() + .orElse(null); + } + + @Override + public Collection tabCompletes(Chatter sender, PreviousArguments previousArguments, String s) { + SteamwarUser owner = previousArguments.getAll(SteamwarUser.class).stream().findFirst().orElse(null); + if (owner == null) return List.of(); + return SteamwarWorld.getBauWorlds(owner).stream().map(SteamwarWorld::getName).toList(); + } + }; + } + + @Mapper(value = "templateWorld", local = true) + private TypeMapper templateWorldMapper() { + return new TypeMapper<>() { + @Override + public SteamwarWorld map(Chatter sender, PreviousArguments previousArguments, String s) { + ServerVersion version = previousArguments.getAll(ServerVersion.class).stream().findFirst().orElse(null); + if (version == null) return null; + return SteamwarWorld.getTemplateWorld(s, version.getVersionSuffix()); + } + + @Override + public Collection tabCompletes(Chatter sender, PreviousArguments previousArguments, String s) { + ServerVersion version = previousArguments.getAll(ServerVersion.class).stream().findFirst().orElse(null); + if (version == null) return List.of(); + return SteamwarWorld.getTemplateWorlds(version.getVersionSuffix()).stream().map(SteamwarWorld::getName).toList(); + } + }; + } + + private void openWorldList(PlayerChatter sender) { + List> entries = new ArrayList<>(); + for (SteamwarWorld world : SteamwarWorld.getBauWorlds(sender.user())) { + SWItem item = new SWItem("GRASS_BLOCK", new Message("PLAIN_STRING", "§e" + world.getName())) + .addLore(new Message("PLAIN_STRING", "§7Version: §e" + world.getVersion())) + .addLore(new Message("PLAIN_STRING", "§7Left: start, right: manage")); + entries.add(new SWListInv.SWListEntry<>(item, world)); + } + SWListInv inventory = new SWListInv<>(sender, new Message("BAU_WORLD_GUI_TITLE"), entries, (click, world) -> { + if (click.isRightClick()) { + openWorldManagement(sender, world); + } else { + new ServerStarter().build(ServerVersion.get(world.getVersion()), world).send(sender.getPlayer()).start(); + } + }); + inventory.open(); + } + + private void openWorldManagement(PlayerChatter sender, SteamwarWorld world) { + SWInventory inventory = new SWInventory(sender, 9, new Message("PLAIN_STRING", "§e" + world.getName())); + inventory.addItem(0, new SWItem("ENDER_PEARL", new Message("PLAIN_STRING", "§aStart")), click -> { + inventory.close(); + new ServerStarter().build(ServerVersion.get(world.getVersion()), world).send(sender.getPlayer()).start(); + }); + inventory.addItem(3, new SWItem("EXPERIENCE_BOTTLE", new Message("PLAIN_STRING", "§eUpgrade to latest")), click -> { + if (hasTooManyPersonalWorlds(sender)) { + inventory.close(); + return; + } + world.changeVersion(ServerVersion.PAPER_21.getVersionSuffix()); + sender.system("BAU_WORLD_UPGRADED"); + inventory.close(); + }); + inventory.addItem(8, new SWItem("BARRIER", new Message("PLAIN_STRING", "§cDelete")), click -> openDeleteWorld(sender, world)); + inventory.open(); + } + + private void openDeleteWorld(PlayerChatter sender, SteamwarWorld world) { + SWInventory inventory = new SWInventory(sender, 9, new Message("BAU_DELETE_GUI_NAME")); + inventory.addItem(0, new SWItem("LIME_DYE", new Message("BAU_DELETE_GUI_DELETE")), click -> { + VelocityCore.schedule(() -> { + Bauserver subserver = Bauserver.getByWorld(world.getUuid()); + if (subserver != null) { + subserver.stop(); + } + world.markDeleted(); + SubserverSystem.deleteFolder(VelocityCore.local, world.getStorageDirectory().getPath()); + sender.system("BAU_DELETE_DELETED"); + }).schedule(); + inventory.close(); + }); + inventory.addItem(8, new SWItem("RED_DYE", new Message("BAU_DELETE_GUI_CANCEL")), click -> inventory.close()); + inventory.open(); + } + + private void startPersonalWorld(PlayerChatter sender, SteamwarWorld world) { + if (world == null) { + world = defaultPersonalWorld(sender); + } + if (world == null) { + sender.system("BAU_WORLD_UNKNOWN"); + return; + } + new ServerStarter().build(ServerVersion.get(world.getVersion()), world).send(sender.getPlayer()).start(); + } + + private static SteamwarWorld defaultPersonalWorld(Chatter sender) { + return defaultPersonalWorld(sender.user()); + } + + private static SteamwarWorld defaultPersonalWorld(SteamwarUser user) { + return SteamwarWorld.getBauWorlds(user).stream().findFirst().orElse(null); + } + + private static List selectedWorlds(Chatter sender, String selector) { + List worlds = SteamwarWorld.getBauWorlds(sender.user()); + if ("all".equalsIgnoreCase(selector)) { + return worlds; + } + String normalized = selector.toLowerCase(Locale.ROOT); + return worlds.stream() + .filter(world -> world.getName().toLowerCase(Locale.ROOT).equals(normalized)) + .toList(); + } + + private static boolean forEachSelectedWorld(Chatter sender, String selector, Consumer consumer) { + List worlds = selectedWorlds(sender, selector); + if (worlds.isEmpty()) { + sender.system("BAU_WORLD_UNKNOWN"); + return false; + } + worlds.forEach(consumer); + return true; + } + + private SteamwarWorld createPersonalWorld(Chatter sender, String name, ServerVersion version, SteamwarWorld template) { + if (template == null || template.getVersion() != version.getVersionSuffix()) { + sender.system("BAU_WORLD_UNKNOWN"); + return null; + } + + if (sender.user().hasPerm(UserPerm.BUILD_UNLIMITED_WORLDS)) { + if (SteamwarWorld.getBauWorld(sender.user(), name, version.getVersionSuffix()) != null) { + sender.system("BAU_WORLD_EXISTS"); + return null; + } + return SteamwarWorld.getOrCreateBauWorld(sender.user(), name, version.getVersionSuffix(), template.getStorageDirectory()); + } + + long limit = personalWorldLimit(sender); + long count = SteamwarWorld.countBauWorlds(sender.user()); + if (count > limit) { + sender.system("BAU_WORLD_OVER_LIMIT_DELETE", limit); + return null; + } + if (count >= limit) { + sender.system("BAU_WORLD_LIMIT", limit); + return null; + } + + if (SteamwarWorld.getBauWorld(sender.user(), name, version.getVersionSuffix()) != null) { + sender.system("BAU_WORLD_EXISTS"); + return null; + } + + return SteamwarWorld.getOrCreateBauWorld(sender.user(), name, version.getVersionSuffix(), template.getStorageDirectory()); + } + + private boolean hasTooManyPersonalWorlds(Chatter sender) { + if (sender.user().hasPerm(UserPerm.BUILD_UNLIMITED_WORLDS)) { + return false; + } + + long limit = personalWorldLimit(sender); + if (SteamwarWorld.countBauWorlds(sender.user()) <= limit) { + return false; + } + + sender.system("BAU_WORLD_OVER_LIMIT_DELETE", limit); + return true; + } + + private long personalWorldLimit(Chatter sender) { + return sender.user().hasPerm(UserPerm.BUILD_EXTRA_WORLDS) ? SteamwarWorld.EXTRA_BAU_WORLD_LIMIT : SteamwarWorld.DEFAULT_BAU_WORLD_LIMIT; } } diff --git a/VelocityCore/src/de/steamwar/velocitycore/commands/BuilderCloudCommand.java b/VelocityCore/src/de/steamwar/velocitycore/commands/BuilderCloudCommand.java index 9a4502a3..d5a2838b 100644 --- a/VelocityCore/src/de/steamwar/velocitycore/commands/BuilderCloudCommand.java +++ b/VelocityCore/src/de/steamwar/velocitycore/commands/BuilderCloudCommand.java @@ -19,12 +19,6 @@ package de.steamwar.velocitycore.commands; -import de.steamwar.sql.GameModeConfig; -import de.steamwar.linkage.Linked; -import de.steamwar.sql.SteamwarWorld; -import de.steamwar.velocitycore.ArenaMode; -import de.steamwar.velocitycore.ServerStarter; -import de.steamwar.velocitycore.VelocityCore; import de.steamwar.command.PreviousArguments; import de.steamwar.command.SWCommand; import de.steamwar.command.TypeMapper; @@ -32,6 +26,7 @@ import de.steamwar.linkage.Linked; import de.steamwar.messages.Chatter; import de.steamwar.messages.PlayerChatter; import de.steamwar.sql.GameModeConfig; +import de.steamwar.sql.SteamwarWorld; import de.steamwar.sql.UserPerm; import de.steamwar.velocitycore.ArenaMode; import de.steamwar.velocitycore.ServerStarter; @@ -53,14 +48,18 @@ public class BuilderCloudCommand extends SWCommand { } @Register(value = "create", description = "BUILDERCLOUD_CREATE_USAGE") - public void create(Chatter sender, @ErrorMessage("BUILDERCLOUD_VERSION") ServerVersion version, @Mapper("map") String map, @OptionalValue("") @Mapper("generator") @AllowNull File generator) { + public void create(Chatter sender, @ErrorMessage("BUILDERCLOUD_VERSION") ServerVersion version, @Mapper("map") String map, @Mapper("templateWorld") SteamwarWorld template) { if(mapExists(version, map)) { sender.system("BUILDERCLOUD_EXISTING_MAP"); return; } + if (template == null || template.getVersion() != version.getVersionSuffix()) { + sender.system("BAU_WORLD_UNKNOWN"); + return; + } - SteamwarWorld.getOrCreateBuilderWorld(map, version.getVersionSuffix()).setupAndGetStoragePath(); - sender.withPlayer(p -> new ServerStarter().builder(version, map, generator).send(p).start()); + SteamwarWorld.getOrCreateBuilderWorld(map, version.getVersionSuffix(), template.getStorageDirectory()); + sender.withPlayer(p -> new ServerStarter().builder(version, map, template.getStorageDirectory()).send(p).start()); } @Register(description = "BUILDERCLOUD_USAGE") @@ -100,7 +99,9 @@ public class BuilderCloudCommand extends SWCommand { } VelocityCore.schedule(() -> { - VelocityCore.local.execute("deployarena.py", arenaMode.configFile.getName(), Integer.toString(version.getVersionSuffix()), map, builderWorld.setupAndGetStoragePath()); + String modeName = arenaMode.configFile.getName().replace(".yml", ""); + SteamwarWorld arenaWorld = SteamwarWorld.getOrCreateArenaWorld(modeName, map, version.getVersionSuffix()); + VelocityCore.local.execute("deployarena.py", arenaMode.configFile.getName(), Integer.toString(version.getVersionSuffix()), map, builderWorld.setupAndGetStoragePath(), arenaWorld.setupAndGetStoragePath()); ArenaMode.init(); sender.system("BUILDERCLOUD_DEPLOY_FINISHED"); }).schedule(); @@ -141,36 +142,27 @@ public class BuilderCloudCommand extends SWCommand { } @Cached(global = true) - @Mapper(value = "generator", local = true) - private TypeMapper generatorTypeMapper() { + @Mapper(value = "templateWorld", local = true) + private TypeMapper templateWorldMapper() { - return new TypeMapper() { + return new TypeMapper<>() { @Override - public File map(Chatter sender, PreviousArguments previousArguments, String s) { - if (s.isEmpty()) return null; - - File folder = getLegacyWorldFolder(previousArguments, 2); - - if (folder == null) throw new SecurityException(); - - File generator = new File(folder, s + ".dat"); - if (!generator.exists() || !generator.isFile()) { - throw new SecurityException(); - } - - return generator; + public SteamwarWorld map(Chatter sender, PreviousArguments previousArguments, String s) { + ServerVersion version = previousArguments.getAll(ServerVersion.class).stream().findFirst().orElse(null); + if (version == null) return null; + return SteamwarWorld.getTemplateWorld(s, version.getVersionSuffix()); } @Override public Collection tabCompletes(Chatter sender, PreviousArguments previousArguments, String s) { - File folder = getLegacyWorldFolder(previousArguments, 2); - - String[] files; - if (folder == null || (files = folder.list()) == null) { + ServerVersion version = previousArguments.getAll(ServerVersion.class).stream().findFirst().orElse(null); + if (version == null) { return Collections.emptyList(); } - return Arrays.stream(files).filter(file -> new File(folder, file).isFile()).filter(file -> file.endsWith(".dat")).map(file -> file.substring(0, file.length() - 4)).toList(); + return SteamwarWorld.getTemplateWorlds(version.getVersionSuffix()).stream() + .map(SteamwarWorld::getName) + .toList(); } }; } @@ -195,14 +187,6 @@ public class BuilderCloudCommand extends SWCommand { return new File(version.getWorldFolder(ServerStarter.LEGACY_BUILDER_BASE_PATH), map); } - private File getLegacyWorldFolder(PreviousArguments previousArguments, int offset) { - ServerVersion v = getVersion(previousArguments, offset); - if (v == null) { - return null; - } - return new File(v.getWorldFolder(ServerStarter.LEGACY_BUILDER_BASE_PATH)); - } - private ServerVersion getVersion(PreviousArguments previousArguments, int offset) { if (previousArguments.userArgs.length < offset) { return null; diff --git a/VelocityCore/src/de/steamwar/velocitycore/commands/GDPRQuery.java b/VelocityCore/src/de/steamwar/velocitycore/commands/GDPRQuery.java index 237cca87..8d1ed362 100644 --- a/VelocityCore/src/de/steamwar/velocitycore/commands/GDPRQuery.java +++ b/VelocityCore/src/de/steamwar/velocitycore/commands/GDPRQuery.java @@ -113,8 +113,8 @@ public class GDPRQuery extends SWCommand { } private static final Statement bannedIPs = new Statement("SELECT Timestamp, IP FROM BannedUserIPs WHERE UserID = ?"); - private static final Statement bauweltMember = new Statement("SELECT BauweltID AS Bauwelt, WorldEdit, World FROM BauweltMember WHERE MemberID = ?"); - private static final Statement bauweltMembers = new Statement("SELECT u.UserName AS 'User', m.WorldEdit AS WorldEdit, m.World AS World FROM BauweltMember m INNER JOIN UserData u ON m.MemberID = u.id WHERE m.BauweltID = ?"); + private static final Statement bauweltMember = new Statement("SELECT WorldID AS Bauwelt, WorldEdit, World FROM BauweltMember WHERE MemberID = ?"); + private static final Statement bauweltMembers = new Statement("SELECT w.Name AS World, u.UserName AS 'User', m.WorldEdit AS WorldEdit, m.World AS World FROM BauweltMember m INNER JOIN UserData u ON m.MemberID = u.id INNER JOIN world w ON m.WorldID = w.id WHERE w.Owner = ?"); private static final Statement checkedSchems = new Statement("SELECT NodeName AS Schematic, StartTime, EndTime, DeclineReason AS Result FROM CheckedSchematic WHERE NodeOwner = ? ORDER BY StartTime ASC"); private static final Statement fights = new Statement("SELECT p.Team AS Team, p.Kit AS Kit, p.Kills AS Kills, p.IsOut AS Died, f.GameMode AS GameMode, f.Server AS Server, f.StartTime AS StartTime, f.Duration AS Duration, (f.BlueLeader = p.UserID) AS IsBlueLeader, (f.RedLeader = p.UserID) AS IsRedLeader, f.Win AS Winner, f.WinCondition AS WinCondition FROM Fight f INNER JOIN FightPlayer p ON f.FightID = p.FightID WHERE p.UserID = ? ORDER BY StartTime ASC"); private static final Statement ignoredPlayers = new Statement("SELECT u.UserName AS IgnoredPlayer FROM IgnoredPlayers i INNER JOIN UserData u ON i.Ignored = u.id WHERE Ignorer = ?"); diff --git a/VelocityCore/src/de/steamwar/velocitycore/commands/TeamCommand.java b/VelocityCore/src/de/steamwar/velocitycore/commands/TeamCommand.java index c549eec3..0b8e44a4 100644 --- a/VelocityCore/src/de/steamwar/velocitycore/commands/TeamCommand.java +++ b/VelocityCore/src/de/steamwar/velocitycore/commands/TeamCommand.java @@ -27,12 +27,18 @@ import de.steamwar.linkage.Linked; import de.steamwar.messages.Chatter; import de.steamwar.messages.Message; import de.steamwar.messages.PlayerChatter; +import de.steamwar.persistent.Bauserver; import de.steamwar.sql.Event; import de.steamwar.sql.SteamwarUser; +import de.steamwar.sql.SteamwarWorld; import de.steamwar.sql.Team; import de.steamwar.sql.TeamTeilnahme; +import de.steamwar.velocitycore.ServerStarter; +import de.steamwar.velocitycore.ServerVersion; +import de.steamwar.velocitycore.SubserverSystem; import de.steamwar.velocitycore.VelocityCore; import de.steamwar.velocitycore.discord.DiscordBot; +import de.steamwar.velocitycore.inventory.SWInventory; import de.steamwar.velocitycore.inventory.SWItem; import de.steamwar.velocitycore.inventory.SWListInv; import net.kyori.adventure.text.Component; @@ -399,6 +405,117 @@ public class TeamCommand extends SWCommand { } } + @Register("worlds") + @Register("world") + public void worlds(@Validator("isInTeam") PlayerChatter sender) { + List> entries = SteamwarWorld.getTeamWorlds(Team.byId(sender.user().getTeam())).stream() + .map(world -> new SWListInv.SWListEntry<>( + new SWItem("GRASS_BLOCK", new Message("PLAIN_STRING", "§e" + world.getName())) + .addLore(new Message("PLAIN_STRING", "§7Version: §e" + world.getVersion())) + .addLore(new Message("PLAIN_STRING", "§7Left: start, right: manage")), + world + )) + .toList(); + SWListInv inv = new SWListInv<>(sender, new Message("TEAM_WORLD_GUI_TITLE"), entries, (click, world) -> { + if (click.isRightClick() && sender.user().isLeader()) { + openTeamWorldManagement(sender, world); + } else { + startTeamWorld(sender, world); + } + }); + inv.open(); + } + + @Register("world") + public void world(@Validator("isInTeam") PlayerChatter sender, @Mapper("teamWorld") SteamwarWorld world) { + startTeamWorld(sender, world); + } + + @Register(value = "world create", description = "TEAM_WORLD_CREATE_USAGE") + public void createWorld(@Validator("isLeader") Chatter sender, String name, @ErrorMessage("BAU_WORLD_VERSION") ServerVersion version, @Mapper("templateWorld") SteamwarWorld template) { + Team team = Team.byId(sender.user().getTeam()); + if (template == null || template.getVersion() != version.getVersionSuffix()) { + sender.system("BAU_WORLD_UNKNOWN"); + return; + } + if (SteamwarWorld.getTeamWorld(team, name, version.getVersionSuffix()) != null) { + sender.system("TEAM_WORLD_EXISTS"); + return; + } + long count = SteamwarWorld.countTeamWorlds(team); + if (count > SteamwarWorld.TEAM_WORLD_LIMIT) { + sender.system("TEAM_WORLD_OVER_LIMIT_DELETE", SteamwarWorld.TEAM_WORLD_LIMIT); + return; + } + if (count >= SteamwarWorld.TEAM_WORLD_LIMIT) { + sender.system("TEAM_WORLD_LIMIT", SteamwarWorld.TEAM_WORLD_LIMIT); + return; + } + SteamwarWorld.getOrCreateTeamWorld(team, name, version.getVersionSuffix(), template.getStorageDirectory()); + sender.system("TEAM_WORLD_CREATED"); + } + + @Register(value = "world rename", description = "TEAM_WORLD_RENAME_USAGE") + public void renameWorld(@Validator("isLeader") Chatter sender, @Mapper("teamWorld") SteamwarWorld world, String newName) { + Team team = Team.byId(sender.user().getTeam()); + if (hasTooManyTeamWorlds(sender, team)) return; + if (SteamwarWorld.getTeamWorld(team, newName, world.getVersion()) != null) { + sender.system("TEAM_WORLD_EXISTS"); + return; + } + world.rename(newName); + sender.system("TEAM_WORLD_RENAMED"); + } + + @Register(value = "world upgrade", description = "TEAM_WORLD_UPGRADE_USAGE") + public void upgradeWorld(@Validator("isLeader") Chatter sender, @Mapper("teamWorld") SteamwarWorld world, @ErrorMessage("BAU_WORLD_VERSION") ServerVersion version) { + if (hasTooManyTeamWorlds(sender, Team.byId(sender.user().getTeam()))) return; + world.changeVersion(version.getVersionSuffix()); + sender.system("TEAM_WORLD_UPGRADED"); + } + + @Register(value = "world delete", description = "TEAM_WORLD_DELETE_USAGE") + public void deleteWorld(@Validator("isLeader") PlayerChatter sender, @Mapper("teamWorld") SteamwarWorld world) { + SWInventory inventory = new SWInventory(sender, 9, new Message("BAU_DELETE_GUI_NAME")); + inventory.addItem(0, new SWItem("LIME_DYE", new Message("BAU_DELETE_GUI_DELETE")), click -> { + VelocityCore.schedule(() -> { + Bauserver subserver = Bauserver.getByWorld(world.getUuid()); + if (subserver != null) { + subserver.stop(); + } + world.markDeleted(); + SubserverSystem.deleteFolder(VelocityCore.local, world.getStorageDirectory().getPath()); + sender.system("TEAM_WORLD_DELETED"); + }).schedule(); + inventory.close(); + }); + inventory.addItem(8, new SWItem("RED_DYE", new Message("BAU_DELETE_GUI_CANCEL")), click -> inventory.close()); + inventory.open(); + } + + private void startTeamWorld(PlayerChatter sender, SteamwarWorld world) { + new ServerStarter().build(ServerVersion.get(world.getVersion()), world).send(sender.getPlayer()).start(); + } + + private void openTeamWorldManagement(PlayerChatter sender, SteamwarWorld world) { + SWInventory inventory = new SWInventory(sender, 9, new Message("PLAIN_STRING", "§e" + world.getName())); + inventory.addItem(0, new SWItem("ENDER_PEARL", new Message("PLAIN_STRING", "§aStart")), click -> { + inventory.close(); + startTeamWorld(sender, world); + }); + inventory.addItem(3, new SWItem("EXPERIENCE_BOTTLE", new Message("PLAIN_STRING", "§eUpgrade to latest")), click -> { + if (hasTooManyTeamWorlds(sender, Team.byId(sender.user().getTeam()))) { + inventory.close(); + return; + } + world.changeVersion(ServerVersion.PAPER_21.getVersionSuffix()); + sender.system("TEAM_WORLD_UPGRADED"); + inventory.close(); + }); + inventory.addItem(8, new SWItem("BARRIER", new Message("PLAIN_STRING", "§cDelete")), click -> deleteWorld(sender, world)); + inventory.open(); + } + @Register("event") public void event(@Validator("isLeader") Chatter sender, Event event) { Team team = Team.byId(sender.user().getTeam()); @@ -544,6 +661,56 @@ public class TeamCommand extends SWCommand { }; } + @Mapper(value = "teamWorld", local = true) + public TypeMapper teamWorldMapper() { + return new TypeMapper<>() { + @Override + public SteamwarWorld map(Chatter sender, PreviousArguments previousArguments, String s) { + Team team = Team.byId(sender.user().getTeam()); + return SteamwarWorld.getTeamWorlds(team).stream() + .filter(world -> world.getName().equalsIgnoreCase(s) || world.getUuid().toString().equalsIgnoreCase(s)) + .findFirst() + .orElse(null); + } + + @Override + public Collection tabCompletes(Chatter sender, PreviousArguments previousArguments, String s) { + if (sender.user().getTeam() == 0) return List.of(); + return SteamwarWorld.getTeamWorlds(Team.byId(sender.user().getTeam())).stream() + .map(SteamwarWorld::getName) + .toList(); + } + }; + } + + @Mapper(value = "templateWorld", local = true) + public TypeMapper templateWorldMapper() { + return new TypeMapper<>() { + @Override + public SteamwarWorld map(Chatter sender, PreviousArguments previousArguments, String s) { + ServerVersion version = previousArguments.getAll(ServerVersion.class).stream().findFirst().orElse(null); + if (version == null) return null; + return SteamwarWorld.getTemplateWorld(s, version.getVersionSuffix()); + } + + @Override + public Collection tabCompletes(Chatter sender, PreviousArguments previousArguments, String s) { + ServerVersion version = previousArguments.getAll(ServerVersion.class).stream().findFirst().orElse(null); + if (version == null) return List.of(); + return SteamwarWorld.getTemplateWorlds(version.getVersionSuffix()).stream().map(SteamwarWorld::getName).toList(); + } + }; + } + + private boolean hasTooManyTeamWorlds(Chatter sender, Team team) { + if (SteamwarWorld.countTeamWorlds(team) <= SteamwarWorld.TEAM_WORLD_LIMIT) { + return false; + } + + sender.system("TEAM_WORLD_OVER_LIMIT_DELETE", SteamwarWorld.TEAM_WORLD_LIMIT); + return true; + } + private boolean checkTeamName(Chatter sender, Team team, String arg) { Team t = Team.get(arg); if (t != null && t.getTeamId() != team.getTeamId()) { diff --git a/VelocityCore/src/de/steamwar/velocitycore/commands/TpCommand.java b/VelocityCore/src/de/steamwar/velocitycore/commands/TpCommand.java index 39d269f1..e1ffc61a 100644 --- a/VelocityCore/src/de/steamwar/velocitycore/commands/TpCommand.java +++ b/VelocityCore/src/de/steamwar/velocitycore/commands/TpCommand.java @@ -120,17 +120,26 @@ public class TpCommand extends SWCommand { } } else if (Subserver.isBuild(subserver)) { Bauserver bauserver = (Bauserver) subserver; + SteamwarWorld world = SteamwarWorld.getWorld(bauserver.getWorld()); + if (world != null && world.getType() == WorldType.TEAM) { + if (sender.user().getTeam() != world.getTeam().getValue()) { + sender.system("JOIN_PLAYER_BLOCK"); + return; + } + SubserverSystem.sendPlayer(subserver, sender.getPlayer()); + return; + } Player checker = VelocityCore.getProxy().getPlayer(bauserver.getOwner()).orElse(null); if (checker != null && CheckCommand.isChecking(checker)) { if (!sender.user().hasPerm(UserPerm.CHECK) && CheckCommand.getCheckingSchem(checker).getOwner() != sender.user().getId()) { sender.system("JOIN_PLAYER_BLOCK"); return; } - } else if (BauLock.isLocked(SteamwarUser.get(bauserver.getOwner()), sender.user())) { + } else if (world != null && BauLock.isLocked(world, sender.user())) { sender.system("BAU_LOCKED_NOALLOWED"); Chatter.of(bauserver.getOwner()).system("BAU_LOCK_BLOCKED", sender); return; - } else if (!bauserver.getOwner().equals(sender.user().getUUID()) && BauweltMember.getBauMember(bauserver.getOwner(), sender.user().getUUID()) == null) { + } else if (!bauserver.getOwner().equals(sender.user().getUUID()) && BauweltMember.getBauMember(bauserver.getWorld(), sender.user().getUUID()) == null) { SubserverSystem.sendDeniedMessage(sender, bauserver.getOwner()); sender.system("JOIN_PLAYER_BLOCK"); return; diff --git a/VelocityCore/src/de/steamwar/velocitycore/util/BauLock.java b/VelocityCore/src/de/steamwar/velocitycore/util/BauLock.java index 2053d6cf..cb8ca084 100644 --- a/VelocityCore/src/de/steamwar/velocitycore/util/BauLock.java +++ b/VelocityCore/src/de/steamwar/velocitycore/util/BauLock.java @@ -25,37 +25,39 @@ import de.steamwar.network.packets.server.BaulockUpdatePacket; import de.steamwar.persistent.Bauserver; import de.steamwar.sql.BauweltMember; import de.steamwar.sql.SteamwarUser; -import de.steamwar.sql.UserConfig; +import de.steamwar.sql.SteamwarWorld; import de.steamwar.sql.UserPerm; +import de.steamwar.sql.WorldType; import de.steamwar.velocitycore.network.NetworkSender; import lombok.experimental.UtilityClass; @UtilityClass public class BauLock { - private static final String BAU_LOCK_CONFIG_NAME = "baulockstate"; - - public static void setLocked(Chatter owner, BauLockState state) { - UserConfig.updatePlayerConfig(owner.user().getId(), BAU_LOCK_CONFIG_NAME, state == BauLockState.OPEN ? null : state.name()); + public static void setLocked(Chatter owner, SteamwarWorld world, BauLockState state) { + world.changeLockState(state == BauLockState.OPEN ? null : state.name()); owner.system("BAU_LOCKED_" + state.name()); - Bauserver bauserver = Bauserver.get(owner.user().getUUID()); + Bauserver bauserver = Bauserver.getByWorld(world.getUuid()); if (bauserver != null) { bauserver.getRegisteredServer().getPlayersConnected().stream().findAny().ifPresent(player -> NetworkSender.send(player, new BaulockUpdatePacket())); } } - public static boolean isLocked(SteamwarUser owner, SteamwarUser target) { - if (owner.getId() == target.getId()) return false; + public static boolean isLocked(SteamwarWorld world, SteamwarUser target) { + if (world.getType() != WorldType.BAU || world.getOwner() == null) return false; + + SteamwarUser owner = SteamwarUser.byId(world.getOwner().getValue()); + if (owner == null || owner.getId() == target.getId()) return false; boolean locked; - String state = UserConfig.getConfig(owner.getId(), BAU_LOCK_CONFIG_NAME); + String state = world.getLockState(); switch (state == null ? BauLockState.OPEN : BauLockState.valueOf(state)) { case NOBODY: locked = true; break; case SUPERVISOR: - BauweltMember member = BauweltMember.getBauMember(owner.getId(), target.getId()); + BauweltMember member = BauweltMember.getBauMember(world.getUuid(), target.getId()); locked = member == null || !member.isSupervisor(); break; case SERVERTEAM: