Skip to content
Open
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
25 changes: 15 additions & 10 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ paperweight.reobfArtifactConfiguration = io.papermc.paperweight.userdev.ReobfArt
group = "world.bentobox" // From <groupId>

// Base properties from <properties>
val buildVersion = "3.11.1"
val buildVersion = "3.11.2"
val buildNumberDefault = "-LOCAL" // Local build identifier
val snapshotSuffix = "-SNAPSHOT" // Indicates development/snapshot version

Expand Down Expand Up @@ -86,8 +86,7 @@ val mariadbVersion = "3.0.5"
val mysqlVersion = "8.0.27"
val postgresqlVersion = "42.2.18"
val hikaricpVersion = "5.0.1"
val spigotVersion = "1.21.10-R0.1-SNAPSHOT"
val paperVersion = "1.21.10-R0.1-SNAPSHOT"
val paperVersion = "1.21.11-R0.1-SNAPSHOT"
val bstatsVersion = "3.0.0"
val vaultVersion = "1.7.1"
val levelVersion = "2.21.3"
Expand Down Expand Up @@ -120,7 +119,6 @@ extra["mariadb.version"] = mariadbVersion
extra["mysql.version"] = mysqlVersion
extra["postgresql.version"] = postgresqlVersion
extra["hikaricp.version"] = hikaricpVersion
extra["spigot.version"] = spigotVersion
extra["paper.version"] = paperVersion
extra["bstats.version"] = bstatsVersion
extra["vault.version"] = vaultVersion
Expand All @@ -146,6 +144,19 @@ java {
tasks.withType<JavaCompile> {
// Ensure UTF-8 encoding for all source files
options.encoding = "UTF-8"
// Suppress all deprecation and removal warnings during compilation
options.compilerArgs.addAll(listOf("-Xlint:-deprecation", "-Xlint:-removal"))
// Set explicit Java release version for Eclipse compatibility
options.release.set(21)
}

tasks.compileTestJava {
// Ensure UTF-8 encoding for all source files
options.encoding = "UTF-8"
// Suppress all deprecation and removal warnings during compilation
options.compilerArgs.addAll(listOf("-Xlint:-deprecation", "-Xlint:-removal"))
// Set explicit Java release version for Eclipse compatibility
options.release.set(21)
}


Expand Down Expand Up @@ -202,9 +213,6 @@ dependencies {
testImplementation("commons-lang:commons-lang:$commonsLangVersion")

// --- Compile Only Dependencies: Provided by the server at runtime ---
compileOnly("org.spigotmc:spigot:$spigotVersion") {
exclude(group = "org.spigotmc", module = "spigot-api")
}
compileOnly("org.mongodb:mongodb-driver:$mongodbVersion")
compileOnly("com.zaxxer:HikariCP:$hikaricpVersion")
compileOnly("com.github.MilkBowl:VaultAPI:$vaultVersion")
Expand Down Expand Up @@ -253,9 +261,6 @@ dependencies {
paperweight {
addServerDependencyTo = configurations.named(JavaPlugin.COMPILE_ONLY_CONFIGURATION_NAME).map { setOf(it) }
javaLauncher = javaToolchains.launcherFor {
// Example scenario:
// Paper 1.17.1 was originally built with JDK 16 and the bundle
// has not been updated to work with 21+ (but we want to compile with a 25 toolchain)
// Use the project's configured Java version for paperweight tools (needs Java 21+)
languageVersion = JavaLanguageVersion.of(javaVersion)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,21 +101,26 @@ public boolean canExecute(User user, String label, List<String> args) {
*/
@Override
public boolean execute(User user, String label, List<String> args) {
// Get a map of potential names - this includes island names and home names
Map<String, IslandInfo> names = getNameIslandMap(user, getWorld());
// Check if the home is known
// Check if the name is known if one is given
if (!args.isEmpty()) {
// Assemble the arguments into one string
final String name = String.join(" ", args);
// If the name is not in the list
if (!names.containsKey(name)) {
// Failed home name check
user.sendMessage("commands.island.go.unknown-home");
user.sendMessage("commands.island.sethome.homes-are");
names.keySet().forEach(n -> user.sendMessage("commands.island.sethome.home-list-syntax", TextVariables.NAME, n));
return false;
} else {
// We know where this location is. Now work out if it's an island name or a home name
IslandInfo info = names.get(name);
getIslands().setPrimaryIsland(user.getUniqueId(), info.island);
if (!info.islandName) {
this.delayCommand(user, () -> getIslands().homeTeleportAsync(getWorld(), user.getPlayer(), name)
// This is a home name, not an island name
this.delayCommand(user, () -> getIslands().homeTeleportAsync(getWorld(), user.getPlayer(), name) // Teleport to the named home for this player
.thenAccept((r) -> {
if (r) {
// Success
Expand All @@ -127,8 +132,12 @@ public boolean execute(User user, String label, List<String> args) {
}));
return true;
}
// Not a home name, an island name, so teleport to the island
this.delayCommand(user, () -> getIslands().homeTeleportAsync(info.island(), user));
return true;
}
}

this.delayCommand(user, () -> getIslands().homeTeleportAsync(getWorld(), user.getPlayer()));
return true;
}
Expand All @@ -151,7 +160,7 @@ private boolean checkReserved(User user, List<Island> islands) {
}
return false;
}

@Override
public Optional<List<String>> tabComplete(User user, String alias, List<String> args) {
String lastArg = !args.isEmpty() ? args.getLast() : "";
Expand All @@ -164,7 +173,15 @@ public Optional<List<String>> tabComplete(User user, String alias, List<String>
* Record to store island information and whether the name refers to
* an island name or a home location.
*/
public record IslandInfo(Island island, boolean islandName) {
public record IslandInfo(
/**
* The island
*/
Island island,
/**
* True if this is an island name as opposed to a home name
*/
boolean islandName) {
}

/**
Expand Down Expand Up @@ -197,7 +214,7 @@ public static Map<String, IslandInfo> getNameIslandMap(User user, World world) {
}
// Add homes. Homes do not need an island specified
island.getHomes().keySet().stream().filter(n -> !n.isBlank())
.forEach(n -> islandMap.put(n, new IslandInfo(island, false)));
.forEach(n -> islandMap.put(n, new IslandInfo(island, false)));
}

return islandMap;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1398,13 +1398,11 @@ public void setSettingsFlag(Flag flag, boolean state, boolean doSubflags) {
* @param l - location
*/
public void setSpawnPoint(Environment islandType, Location l) {
spawnPoint.compute(islandType, (key, value) -> {
if (value == null || !value.equals(l)) {
setChanged(); // Call setChanged only if the value is updated.
return l;
}
return value;
});
if (spawnPoint.containsKey(islandType) && spawnPoint.get(islandType).equals(l)) {
return;
}
spawnPoint.put(islandType, l.clone());
setChanged();
}

/**
Expand Down
49 changes: 46 additions & 3 deletions src/main/java/world/bentobox/bentobox/managers/IslandsManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -462,7 +462,21 @@ public Set<Island> getOwnedIslands(@NonNull World world, @NonNull UUID uniqueId)
*/
@Nullable
public Island getIsland(@NonNull World world, @NonNull UUID uuid) {
return islandCache.getIsland(world, uuid);
// Check if player is online and get their current island location
Player player = Bukkit.getPlayer(uuid);
if (player != null && player.isOnline()) {
// This island must be in this world and the player must be on the team
Optional<Island> currentIsland = getIslandAt(player.getLocation())
.filter(is -> world.equals(is.getWorld()) && is.inTeam(uuid));

if (currentIsland.isPresent()) {
return currentIsland.get();
}
}
// Check cache for last island
Island cachedIsland = islandCache.getIsland(world, uuid);

return cachedIsland;
}

/**
Expand Down Expand Up @@ -1102,6 +1116,7 @@ private CompletableFuture<Boolean> homeTeleportAsync(@NonNull World world, @NonN
User user = User.getInstance(player);
user.sendMessage("commands.island.go.teleport");
goingHome.add(user.getUniqueId());

readyPlayer(player);
this.getAsyncSafeHomeLocation(world, user, name).thenAccept(home -> {
Island island = getIsland(world, user);
Expand Down Expand Up @@ -1802,14 +1817,42 @@ public void setPrimaryIsland(UUID uuid, Island i) {
}

/**
* Convenience method. See {@link IslandCache#getIsland(World, UUID)}
* Convenience method. See {@link #getIsland(World, UUID)}
*
* @param world world
* @param uuid player's UUID
* @return Island of player or null if there isn't one
*/
public Island getPrimaryIsland(World world, UUID uuid) {
return this.getIslandCache().getIsland(world, uuid);
return getIsland(world, uuid);
}

public CompletableFuture<Void> homeTeleportAsync(Island island, User user) {
return homeTeleportAsync(island, user, false);
}

/**
* Teleport the user home
* @param island island
* @param user user
* @param newIsland true if this is a new island first time teleport
* @return future when it is done
*/
public CompletableFuture<Void> homeTeleportAsync(Island island, User user, boolean newIsland) {
Location loc = island.getHome("");
user.sendMessage("commands.island.go.teleport");
goingHome.add(user.getUniqueId());
readyPlayer(user.getPlayer());
return Util.teleportAsync(Objects.requireNonNull(user.getPlayer()), loc).thenAccept(b -> {
// Only run the commands if the player is successfully teleported
Comment on lines +1841 to +1847
Copy link

Copilot AI Jan 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

homeTeleportAsync(Island, User, boolean) calls island.getHome("") directly and passes the result to Util.teleportAsync(...) without any null check or fallback. For islands that do not yet have a default home (e.g., no-paste creations or legacy islands without homes), island.getHome("") can be null, which will lead to a null Location being used for teleportation and likely a runtime exception. To keep behavior consistent with the existing world-based homeTeleportAsync overload, this method should resolve a safe home location (e.g., via getHomeLocation(island) or equivalent safe-location logic) and handle the case where no home exists before attempting teleport.

Copilot uses AI. Check for mistakes.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot open a new pull request to apply changes based on this feedback

if (Boolean.TRUE.equals(b)) {
teleported(island.getWorld(), user, "", newIsland, island);
this.setPrimaryIsland(user.getUniqueId(), island);
} else {
// Remove from mid-teleport set
goingHome.remove(user.getUniqueId());
}
});
Comment on lines +1830 to +1855
Copy link

Copilot AI Jan 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new overload homeTeleportAsync(Island, User, boolean) in IslandsManager is not covered by existing tests, whereas other teleport-related behaviors (e.g., the world-based homeTeleportAsync overloads and listeners using them) do have tests. Consider adding unit tests for this method, particularly for cases where the island has and does not have a default home, to ensure it behaves correctly and stays aligned with the existing safe-teleport logic.

Copilot uses AI. Check for mistakes.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot open a new pull request to apply changes based on this feedback

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,8 @@ public void newIsland(Island oldIsland) throws IOException {
private void postCreationTask(Island oldIsland) {
// Set initial spawn point if one exists
if (island.getSpawnPoint(Environment.NORMAL) != null) {
plugin.getIslands().setHomeLocation(user, island.getSpawnPoint(Environment.NORMAL));
plugin.getIslands().setHomeLocation(island, island.getSpawnPoint(Environment.NORMAL), "");
plugin.getIslands().setPrimaryIsland(user.getUniqueId(), island);
}
// If player is online, handle teleportation and movement
if (user.isOnline()) {
Expand All @@ -277,7 +278,7 @@ private void postCreationTask(Island oldIsland) {
user.getPlayer().setVelocity(new Vector(0, 0, 0));
user.getPlayer().setFallDistance(0F);
// Teleport player after island is built, then tidy up
plugin.getIslands().homeTeleportAsync(world, user.getPlayer(), true).thenRun(() -> tidyUp(oldIsland));
plugin.getIslands().homeTeleportAsync(island, user, true).thenRun(() -> tidyUp(oldIsland));
return;
} else {
// Notify player they can teleport to their island
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,7 @@ public void testExecuteUserStringListOfStringHasIsland() {
assertTrue(iec.execute(user, "", Collections.singletonList("tasty")));
verify(user).sendMessage("commands.island.expel.success", TextVariables.NAME, "target",
TextVariables.DISPLAY_NAME, "&Ctarget");
verify(im).homeTeleportAsync(any(), any());
verify(im).homeTeleportAsync(any(), any(Player.class));
}

/**
Expand Down