Skip to content

Commit fc17054

Browse files
authored
Merge pull request #1 from AnguliNetworks/feature/level-border
Fix level equal border challenge
2 parents 8a1fcd7 + 1b88b24 commit fc17054

File tree

5 files changed

+140
-74
lines changed

5 files changed

+140
-74
lines changed

build.gradle.kts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ repositories {
1616
maven("https://oss.sonatype.org/content/repositories/central")
1717
maven("https://jitpack.io")
1818
maven("https://repo.papermc.io/repository/maven-public/")
19+
maven {
20+
name = "eldonexus"
21+
url = uri("https://eldonexus.de/repository/maven-releases/")
22+
}
1923
}
2024

2125
dependencies {

src/main/kotlin/li/angu/challengeplugin/ChallengePluginPlugin.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ open class ChallengePluginPlugin : JavaPlugin() {
2727
open lateinit var lobbyManager: LobbyManager
2828
open lateinit var blockDropListener: BlockDropListener
2929
open lateinit var worldPreparationManager: WorldPreparationManager
30+
open lateinit var experienceBorderListener: ExperienceBorderListener
3031

3132
/**
3233
* Helper method to register a command with its executor and tab completer
@@ -88,7 +89,9 @@ open class ChallengePluginPlugin : JavaPlugin() {
8889
server.pluginManager.registerEvents(PortalListener(this), this)
8990
blockDropListener = BlockDropListener(this)
9091
server.pluginManager.registerEvents(blockDropListener, this)
91-
server.pluginManager.registerEvents(ExperienceBorderListener(this), this)
92+
93+
// Create and register the ExperienceBorderListener
94+
experienceBorderListener = ExperienceBorderListener(this)
9295

9396
// Start timer task for challenge duration display
9497
TimerTask.startTimer(this)
@@ -114,6 +117,7 @@ open class ChallengePluginPlugin : JavaPlugin() {
114117
// Cleanup managers
115118
challengeSettingsManager.cleanup()
116119
challengeMenuManager.cleanup()
120+
experienceBorderListener.cleanup()
117121

118122
logger.info(languageManager.getMessage("plugin.disabled"))
119123
}

src/main/kotlin/li/angu/challengeplugin/listeners/ExperienceBorderListener.kt

Lines changed: 110 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,84 @@ package li.angu.challengeplugin.listeners
33
import li.angu.challengeplugin.ChallengePluginPlugin
44
import org.bukkit.event.EventHandler
55
import org.bukkit.event.Listener
6-
import org.bukkit.event.player.PlayerExpChangeEvent
76
import org.bukkit.event.player.PlayerLevelChangeEvent
87
import org.bukkit.event.player.PlayerJoinEvent
98
import org.bukkit.event.player.PlayerChangedWorldEvent
9+
import org.bukkit.Bukkit
10+
import org.bukkit.Location
11+
import org.bukkit.WorldBorder
12+
import org.bukkit.entity.Player
13+
import org.bukkit.scheduler.BukkitTask
14+
import java.util.UUID
15+
import java.util.concurrent.ConcurrentHashMap
1016

1117
/**
1218
* This listener manages the world border adjustment based on player experience level
1319
* when the Level WorldBorder setting is enabled.
20+
*
21+
* This implementation simulates per-player borders by repeatedly updating the world border
22+
* for each player, making it appear as if they have individual borders.
1423
*/
1524
class ExperienceBorderListener(private val plugin: ChallengePluginPlugin) : Listener {
1625

26+
// Border update task
27+
private var borderUpdateTask: BukkitTask? = null
28+
1729
init {
1830
plugin.server.pluginManager.registerEvents(this, plugin)
31+
32+
// Start a repeating task to update borders for all players
33+
// We only need to update occasionally since we're storing per-player borders
34+
borderUpdateTask = Bukkit.getScheduler().runTaskTimer(plugin, Runnable {
35+
updateAllPlayerBorders()
36+
}, 10L, 20L) // Update every 20 ticks (1 second)
37+
}
38+
39+
/**
40+
* Update borders for all online players in challenges
41+
*/
42+
private fun updateAllPlayerBorders() {
43+
Bukkit.getOnlinePlayers().forEach { player ->
44+
val challenge = plugin.challengeManager.getPlayerChallenge(player) ?: return@forEach
45+
46+
if (!challenge.settings.levelWorldBorder) return@forEach
47+
48+
updatePlayerBorder(player, challenge.settings.borderSize, player.world.spawnLocation)
49+
}
50+
}
51+
52+
// Store a world border for each world to avoid recreating it
53+
private val worldBorders = mutableMapOf<String, WorldBorder>()
54+
55+
/**
56+
* Update a single player's border
57+
*/
58+
private fun updatePlayerBorder(player: Player, size: Double, center: Location) {
59+
try {
60+
val world = player.world
61+
62+
// Create a custom WorldBorder object for each world if it doesn't exist
63+
val worldBorder = worldBorders.computeIfAbsent(world.name) {
64+
// Create a new WorldBorder directly
65+
val border = Bukkit.createWorldBorder()
66+
// Set this to be very far away and very large by default
67+
border.center = Location(world, 0.0, 0.0, 0.0)
68+
border.size = 60000000.0 // Nearly unlimited
69+
border
70+
}
71+
72+
// Only update if necessary
73+
if (worldBorder.size != size || worldBorder.center != center) {
74+
worldBorder.size = size
75+
worldBorder.center = center
76+
}
77+
78+
// Send the border to the player without changing the world's actual border
79+
player.worldBorder = worldBorder
80+
} catch (e: Exception) {
81+
// Log any errors but don't crash the plugin
82+
plugin.logger.warning("Error updating border for player ${player.name}: ${e.message}")
83+
}
1984
}
2085

2186
/**
@@ -25,90 +90,73 @@ class ExperienceBorderListener(private val plugin: ChallengePluginPlugin) : List
2590
fun onPlayerLevelUp(event: PlayerLevelChangeEvent) {
2691
val player = event.player
2792
val challenge = plugin.challengeManager.getPlayerChallenge(player) ?: return
28-
93+
2994
// Check if the level world border setting is enabled for this challenge
3095
if (!challenge.settings.levelWorldBorder) return
31-
96+
3297
// Only apply when level increases (not when it decreases)
3398
if (event.newLevel <= event.oldLevel) return
34-
35-
// Get the player's current world
36-
val world = player.world
37-
38-
// Get current border size and add 2
39-
val currentSize = world.worldBorder.size
40-
val newSize = currentSize + 2.0
41-
42-
// Set the border
43-
world.worldBorder.size = newSize
44-
45-
// Send a message to players in the challenge
46-
val message = plugin.languageManager.getMessage(
47-
"challenge.border_expanded",
48-
player,
49-
"level" to event.newLevel.toString(),
50-
"size" to newSize.toInt().toString()
51-
)
52-
99+
100+
// Increase border by 1 block on each level up (+2 blocks total diameter)
101+
challenge.settings.borderSize += 2.0
102+
103+
// Save all challenges to persist the border size
104+
plugin.challengeManager.saveActiveChallenges()
105+
106+
// Update borders for all players in this challenge immediately
53107
challenge.players.forEach { playerId ->
54-
plugin.server.getPlayer(playerId)?.sendMessage(message)
108+
plugin.server.getPlayer(playerId)?.let { challengePlayer ->
109+
updatePlayerBorder(challengePlayer, challenge.settings.borderSize, challengePlayer.world.spawnLocation)
110+
}
55111
}
56112
}
57-
113+
58114
/**
59-
* When a player joins, don't modify the border
60-
* Border only expands when players level up
115+
* When a player joins, initialize their border
61116
*/
62117
@EventHandler
63118
fun onPlayerJoin(event: PlayerJoinEvent) {
64-
// No longer adjusting border on join - only increases on level up
119+
val player = event.player
120+
val challenge = plugin.challengeManager.getPlayerChallenge(player) ?: return
121+
122+
if (!challenge.settings.levelWorldBorder) return
123+
124+
// Immediately update the border for the player using the challenge's saved border size
125+
updatePlayerBorder(player, challenge.settings.borderSize, player.world.spawnLocation)
65126
}
66-
127+
67128
/**
68-
* When a player changes worlds, initialize the border if needed
129+
* When a player changes worlds, update their border center
69130
*/
70131
@EventHandler
71132
fun onPlayerChangeWorld(event: PlayerChangedWorldEvent) {
72133
val player = event.player
73134
val challenge = plugin.challengeManager.getPlayerChallenge(player) ?: return
74-
135+
75136
if (!challenge.settings.levelWorldBorder) return
76-
77-
// Only care about challenge worlds
78-
if (event.player.world.name == challenge.worldName ||
79-
event.player.world.name == "${challenge.worldName}_nether" ||
80-
event.player.world.name == "${challenge.worldName}_the_end") {
81-
82-
// Initialize the border in the new world if it's too small
83-
val world = event.player.world
84-
if (world.worldBorder.size < 3.0) {
85-
world.worldBorder.size = 3.0 // Initialize with minimum size
86-
}
87-
}
137+
138+
// Immediately update the border for the player in the new world
139+
updatePlayerBorder(player, challenge.settings.borderSize, player.world.spawnLocation)
88140
}
89-
141+
90142
/**
91-
* Initialize world border size for all worlds in a challenge
92-
* This method can be called when a challenge is created to set initial border
143+
* Initialize world borders for players in a challenge
144+
* This method should be called when a challenge is created or reset
93145
*/
94-
private fun initializeWorldBordersForChallenge(challenge: li.angu.challengeplugin.models.Challenge) {
146+
fun initializeWorldBordersForPlayers(challenge: li.angu.challengeplugin.models.Challenge) {
95147
if (!challenge.settings.levelWorldBorder) return
96148

97-
val initialSize = 3.0 // Starting with 3x3 border
98-
99-
// Initialize main world
100-
plugin.server.getWorld(challenge.worldName)?.let { world ->
101-
world.worldBorder.size = initialSize
102-
}
103-
104-
// Initialize nether
105-
challenge.getNetherWorld()?.let { world ->
106-
world.worldBorder.size = initialSize
107-
}
149+
// Initialize with the challenge's saved border size or the default
150+
// No need to calculate, as the border size is now a persistent property
108151

109-
// Initialize end
110-
challenge.getEndWorld()?.let { world ->
111-
world.worldBorder.size = initialSize
112-
}
152+
// The borders will be updated by the repeating task
153+
}
154+
155+
/**
156+
* Clean up tasks when plugin is disabled
157+
*/
158+
fun cleanup() {
159+
borderUpdateTask?.cancel()
160+
borderUpdateTask = null
113161
}
114-
}
162+
}

src/main/kotlin/li/angu/challengeplugin/managers/ChallengeManager.kt

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ class ChallengeManager(private val plugin: ChallengePluginPlugin) {
3939
naturalRegeneration = config.getBoolean("settings.naturalRegeneration", true),
4040
syncHearts = config.getBoolean("settings.syncHearts", false),
4141
blockRandomizer = config.getBoolean("settings.blockRandomizer", false),
42-
levelWorldBorder = config.getBoolean("settings.levelWorldBorder", false)
42+
levelWorldBorder = config.getBoolean("settings.levelWorldBorder", false),
43+
borderSize = config.getDouble("settings.borderSize", 3.0)
4344
)
4445

4546
val challenge = Challenge(
@@ -115,6 +116,7 @@ class ChallengeManager(private val plugin: ChallengePluginPlugin) {
115116
config.set("settings.syncHearts", challenge.settings.syncHearts)
116117
config.set("settings.blockRandomizer", challenge.settings.blockRandomizer)
117118
config.set("settings.levelWorldBorder", challenge.settings.levelWorldBorder)
119+
config.set("settings.borderSize", challenge.settings.borderSize)
118120

119121
config.save(file)
120122
}
@@ -328,6 +330,12 @@ class ChallengeManager(private val plugin: ChallengePluginPlugin) {
328330
// Add player to challenge without resetting
329331
if (challenge.addPlayer(player)) {
330332
playerChallengeMap[player.uniqueId] = challenge.id
333+
334+
// Initialize player's world border if the feature is enabled
335+
if (challenge.settings.levelWorldBorder) {
336+
plugin.experienceBorderListener.initializeWorldBordersForPlayers(challenge)
337+
}
338+
331339
return true
332340
}
333341
}
@@ -341,6 +349,12 @@ class ChallengeManager(private val plugin: ChallengePluginPlugin) {
341349
val world = Bukkit.getWorld(challenge.worldName) ?: loadWorld(challenge.worldName)
342350
if (world != null) {
343351
challenge.setupPlayerForChallenge(player, world)
352+
353+
// Initialize player's world border if the feature is enabled
354+
if (challenge.settings.levelWorldBorder) {
355+
plugin.experienceBorderListener.initializeWorldBordersForPlayers(challenge)
356+
}
357+
344358
return true
345359
}
346360
}

src/main/kotlin/li/angu/challengeplugin/models/Challenge.kt

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ data class ChallengeSettings(
3030
var syncHearts: Boolean = false,
3131
var blockRandomizer: Boolean = false,
3232
var starterKit: StarterKit = StarterKit.NONE,
33-
var levelWorldBorder: Boolean = false
33+
var levelWorldBorder: Boolean = false,
34+
var borderSize: Double = 3.0 // Initial border size of 3 blocks
3435
)
3536

3637
class Challenge(
@@ -277,15 +278,10 @@ class Challenge(
277278
world.setGameRule(GameRule.SPECTATORS_GENERATE_CHUNKS, false)
278279
world.difficulty = org.bukkit.Difficulty.HARD
279280

280-
// Apply Level WorldBorder setting if enabled
281-
if (settings.levelWorldBorder) {
282-
// Start with a small 3x3 border
283-
world.worldBorder.size = 3.0
284-
world.worldBorder.center = world.spawnLocation
285-
} else {
286-
// Use default world border (30,000,000 blocks)
287-
world.worldBorder.reset()
288-
}
281+
// For levelWorldBorder:
282+
// We don't modify the world's border directly anymore
283+
// Instead, individual player borders are managed by the ExperienceBorderListener
284+
// This allows mobs to spawn outside the visible border, fixing the issue
289285
}
290286

291287
/**

0 commit comments

Comments
 (0)