diff --git a/build.gradle.kts b/build.gradle.kts index 43f5a76..cc91b1b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -16,6 +16,10 @@ repositories { maven("https://oss.sonatype.org/content/repositories/central") maven("https://jitpack.io") maven("https://repo.papermc.io/repository/maven-public/") + maven { + name = "eldonexus" + url = uri("https://eldonexus.de/repository/maven-releases/") + } } dependencies { diff --git a/src/main/kotlin/li/angu/challengeplugin/ChallengePluginPlugin.kt b/src/main/kotlin/li/angu/challengeplugin/ChallengePluginPlugin.kt index d4fd89f..912e617 100644 --- a/src/main/kotlin/li/angu/challengeplugin/ChallengePluginPlugin.kt +++ b/src/main/kotlin/li/angu/challengeplugin/ChallengePluginPlugin.kt @@ -27,6 +27,7 @@ open class ChallengePluginPlugin : JavaPlugin() { open lateinit var lobbyManager: LobbyManager open lateinit var blockDropListener: BlockDropListener open lateinit var worldPreparationManager: WorldPreparationManager + open lateinit var experienceBorderListener: ExperienceBorderListener /** * Helper method to register a command with its executor and tab completer @@ -88,7 +89,9 @@ open class ChallengePluginPlugin : JavaPlugin() { server.pluginManager.registerEvents(PortalListener(this), this) blockDropListener = BlockDropListener(this) server.pluginManager.registerEvents(blockDropListener, this) - server.pluginManager.registerEvents(ExperienceBorderListener(this), this) + + // Create and register the ExperienceBorderListener + experienceBorderListener = ExperienceBorderListener(this) // Start timer task for challenge duration display TimerTask.startTimer(this) @@ -114,6 +117,7 @@ open class ChallengePluginPlugin : JavaPlugin() { // Cleanup managers challengeSettingsManager.cleanup() challengeMenuManager.cleanup() + experienceBorderListener.cleanup() logger.info(languageManager.getMessage("plugin.disabled")) } diff --git a/src/main/kotlin/li/angu/challengeplugin/listeners/ExperienceBorderListener.kt b/src/main/kotlin/li/angu/challengeplugin/listeners/ExperienceBorderListener.kt index 3e518d6..55db731 100644 --- a/src/main/kotlin/li/angu/challengeplugin/listeners/ExperienceBorderListener.kt +++ b/src/main/kotlin/li/angu/challengeplugin/listeners/ExperienceBorderListener.kt @@ -3,19 +3,84 @@ package li.angu.challengeplugin.listeners import li.angu.challengeplugin.ChallengePluginPlugin import org.bukkit.event.EventHandler import org.bukkit.event.Listener -import org.bukkit.event.player.PlayerExpChangeEvent import org.bukkit.event.player.PlayerLevelChangeEvent import org.bukkit.event.player.PlayerJoinEvent import org.bukkit.event.player.PlayerChangedWorldEvent +import org.bukkit.Bukkit +import org.bukkit.Location +import org.bukkit.WorldBorder +import org.bukkit.entity.Player +import org.bukkit.scheduler.BukkitTask +import java.util.UUID +import java.util.concurrent.ConcurrentHashMap /** * This listener manages the world border adjustment based on player experience level * when the Level WorldBorder setting is enabled. + * + * This implementation simulates per-player borders by repeatedly updating the world border + * for each player, making it appear as if they have individual borders. */ class ExperienceBorderListener(private val plugin: ChallengePluginPlugin) : Listener { + // Border update task + private var borderUpdateTask: BukkitTask? = null + init { plugin.server.pluginManager.registerEvents(this, plugin) + + // Start a repeating task to update borders for all players + // We only need to update occasionally since we're storing per-player borders + borderUpdateTask = Bukkit.getScheduler().runTaskTimer(plugin, Runnable { + updateAllPlayerBorders() + }, 10L, 20L) // Update every 20 ticks (1 second) + } + + /** + * Update borders for all online players in challenges + */ + private fun updateAllPlayerBorders() { + Bukkit.getOnlinePlayers().forEach { player -> + val challenge = plugin.challengeManager.getPlayerChallenge(player) ?: return@forEach + + if (!challenge.settings.levelWorldBorder) return@forEach + + updatePlayerBorder(player, challenge.settings.borderSize, player.world.spawnLocation) + } + } + + // Store a world border for each world to avoid recreating it + private val worldBorders = mutableMapOf() + + /** + * Update a single player's border + */ + private fun updatePlayerBorder(player: Player, size: Double, center: Location) { + try { + val world = player.world + + // Create a custom WorldBorder object for each world if it doesn't exist + val worldBorder = worldBorders.computeIfAbsent(world.name) { + // Create a new WorldBorder directly + val border = Bukkit.createWorldBorder() + // Set this to be very far away and very large by default + border.center = Location(world, 0.0, 0.0, 0.0) + border.size = 60000000.0 // Nearly unlimited + border + } + + // Only update if necessary + if (worldBorder.size != size || worldBorder.center != center) { + worldBorder.size = size + worldBorder.center = center + } + + // Send the border to the player without changing the world's actual border + player.worldBorder = worldBorder + } catch (e: Exception) { + // Log any errors but don't crash the plugin + plugin.logger.warning("Error updating border for player ${player.name}: ${e.message}") + } } /** @@ -25,90 +90,73 @@ class ExperienceBorderListener(private val plugin: ChallengePluginPlugin) : List fun onPlayerLevelUp(event: PlayerLevelChangeEvent) { val player = event.player val challenge = plugin.challengeManager.getPlayerChallenge(player) ?: return - + // Check if the level world border setting is enabled for this challenge if (!challenge.settings.levelWorldBorder) return - + // Only apply when level increases (not when it decreases) if (event.newLevel <= event.oldLevel) return - - // Get the player's current world - val world = player.world - - // Get current border size and add 2 - val currentSize = world.worldBorder.size - val newSize = currentSize + 2.0 - - // Set the border - world.worldBorder.size = newSize - - // Send a message to players in the challenge - val message = plugin.languageManager.getMessage( - "challenge.border_expanded", - player, - "level" to event.newLevel.toString(), - "size" to newSize.toInt().toString() - ) - + + // Increase border by 1 block on each level up (+2 blocks total diameter) + challenge.settings.borderSize += 2.0 + + // Save all challenges to persist the border size + plugin.challengeManager.saveActiveChallenges() + + // Update borders for all players in this challenge immediately challenge.players.forEach { playerId -> - plugin.server.getPlayer(playerId)?.sendMessage(message) + plugin.server.getPlayer(playerId)?.let { challengePlayer -> + updatePlayerBorder(challengePlayer, challenge.settings.borderSize, challengePlayer.world.spawnLocation) + } } } - + /** - * When a player joins, don't modify the border - * Border only expands when players level up + * When a player joins, initialize their border */ @EventHandler fun onPlayerJoin(event: PlayerJoinEvent) { - // No longer adjusting border on join - only increases on level up + val player = event.player + val challenge = plugin.challengeManager.getPlayerChallenge(player) ?: return + + if (!challenge.settings.levelWorldBorder) return + + // Immediately update the border for the player using the challenge's saved border size + updatePlayerBorder(player, challenge.settings.borderSize, player.world.spawnLocation) } - + /** - * When a player changes worlds, initialize the border if needed + * When a player changes worlds, update their border center */ @EventHandler fun onPlayerChangeWorld(event: PlayerChangedWorldEvent) { val player = event.player val challenge = plugin.challengeManager.getPlayerChallenge(player) ?: return - + if (!challenge.settings.levelWorldBorder) return - - // Only care about challenge worlds - if (event.player.world.name == challenge.worldName || - event.player.world.name == "${challenge.worldName}_nether" || - event.player.world.name == "${challenge.worldName}_the_end") { - - // Initialize the border in the new world if it's too small - val world = event.player.world - if (world.worldBorder.size < 3.0) { - world.worldBorder.size = 3.0 // Initialize with minimum size - } - } + + // Immediately update the border for the player in the new world + updatePlayerBorder(player, challenge.settings.borderSize, player.world.spawnLocation) } - + /** - * Initialize world border size for all worlds in a challenge - * This method can be called when a challenge is created to set initial border + * Initialize world borders for players in a challenge + * This method should be called when a challenge is created or reset */ - private fun initializeWorldBordersForChallenge(challenge: li.angu.challengeplugin.models.Challenge) { + fun initializeWorldBordersForPlayers(challenge: li.angu.challengeplugin.models.Challenge) { if (!challenge.settings.levelWorldBorder) return - val initialSize = 3.0 // Starting with 3x3 border - - // Initialize main world - plugin.server.getWorld(challenge.worldName)?.let { world -> - world.worldBorder.size = initialSize - } - - // Initialize nether - challenge.getNetherWorld()?.let { world -> - world.worldBorder.size = initialSize - } + // Initialize with the challenge's saved border size or the default + // No need to calculate, as the border size is now a persistent property - // Initialize end - challenge.getEndWorld()?.let { world -> - world.worldBorder.size = initialSize - } + // The borders will be updated by the repeating task + } + + /** + * Clean up tasks when plugin is disabled + */ + fun cleanup() { + borderUpdateTask?.cancel() + borderUpdateTask = null } -} \ No newline at end of file +} diff --git a/src/main/kotlin/li/angu/challengeplugin/managers/ChallengeManager.kt b/src/main/kotlin/li/angu/challengeplugin/managers/ChallengeManager.kt index 284a2ca..4539c34 100644 --- a/src/main/kotlin/li/angu/challengeplugin/managers/ChallengeManager.kt +++ b/src/main/kotlin/li/angu/challengeplugin/managers/ChallengeManager.kt @@ -39,7 +39,8 @@ class ChallengeManager(private val plugin: ChallengePluginPlugin) { naturalRegeneration = config.getBoolean("settings.naturalRegeneration", true), syncHearts = config.getBoolean("settings.syncHearts", false), blockRandomizer = config.getBoolean("settings.blockRandomizer", false), - levelWorldBorder = config.getBoolean("settings.levelWorldBorder", false) + levelWorldBorder = config.getBoolean("settings.levelWorldBorder", false), + borderSize = config.getDouble("settings.borderSize", 3.0) ) val challenge = Challenge( @@ -115,6 +116,7 @@ class ChallengeManager(private val plugin: ChallengePluginPlugin) { config.set("settings.syncHearts", challenge.settings.syncHearts) config.set("settings.blockRandomizer", challenge.settings.blockRandomizer) config.set("settings.levelWorldBorder", challenge.settings.levelWorldBorder) + config.set("settings.borderSize", challenge.settings.borderSize) config.save(file) } @@ -328,6 +330,12 @@ class ChallengeManager(private val plugin: ChallengePluginPlugin) { // Add player to challenge without resetting if (challenge.addPlayer(player)) { playerChallengeMap[player.uniqueId] = challenge.id + + // Initialize player's world border if the feature is enabled + if (challenge.settings.levelWorldBorder) { + plugin.experienceBorderListener.initializeWorldBordersForPlayers(challenge) + } + return true } } @@ -341,6 +349,12 @@ class ChallengeManager(private val plugin: ChallengePluginPlugin) { val world = Bukkit.getWorld(challenge.worldName) ?: loadWorld(challenge.worldName) if (world != null) { challenge.setupPlayerForChallenge(player, world) + + // Initialize player's world border if the feature is enabled + if (challenge.settings.levelWorldBorder) { + plugin.experienceBorderListener.initializeWorldBordersForPlayers(challenge) + } + return true } } diff --git a/src/main/kotlin/li/angu/challengeplugin/models/Challenge.kt b/src/main/kotlin/li/angu/challengeplugin/models/Challenge.kt index d3de5ab..e69d0c3 100644 --- a/src/main/kotlin/li/angu/challengeplugin/models/Challenge.kt +++ b/src/main/kotlin/li/angu/challengeplugin/models/Challenge.kt @@ -30,7 +30,8 @@ data class ChallengeSettings( var syncHearts: Boolean = false, var blockRandomizer: Boolean = false, var starterKit: StarterKit = StarterKit.NONE, - var levelWorldBorder: Boolean = false + var levelWorldBorder: Boolean = false, + var borderSize: Double = 3.0 // Initial border size of 3 blocks ) class Challenge( @@ -277,15 +278,10 @@ class Challenge( world.setGameRule(GameRule.SPECTATORS_GENERATE_CHUNKS, false) world.difficulty = org.bukkit.Difficulty.HARD - // Apply Level WorldBorder setting if enabled - if (settings.levelWorldBorder) { - // Start with a small 3x3 border - world.worldBorder.size = 3.0 - world.worldBorder.center = world.spawnLocation - } else { - // Use default world border (30,000,000 blocks) - world.worldBorder.reset() - } + // For levelWorldBorder: + // We don't modify the world's border directly anymore + // Instead, individual player borders are managed by the ExperienceBorderListener + // This allows mobs to spawn outside the visible border, fixing the issue } /**