Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -114,6 +117,7 @@ open class ChallengePluginPlugin : JavaPlugin() {
// Cleanup managers
challengeSettingsManager.cleanup()
challengeMenuManager.cleanup()
experienceBorderListener.cleanup()

logger.info(languageManager.getMessage("plugin.disabled"))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<String, WorldBorder>()

/**
* 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}")
}
}

/**
Expand All @@ -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
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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
}
}
Expand All @@ -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
}
}
Expand Down
16 changes: 6 additions & 10 deletions src/main/kotlin/li/angu/challengeplugin/models/Challenge.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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
}

/**
Expand Down