diff --git a/API/src/main/java/fr/maxlego08/essentials/api/utils/Warp.java b/API/src/main/java/fr/maxlego08/essentials/api/utils/Warp.java index 5b94596d..4f4fb78e 100644 --- a/API/src/main/java/fr/maxlego08/essentials/api/utils/Warp.java +++ b/API/src/main/java/fr/maxlego08/essentials/api/utils/Warp.java @@ -1,9 +1,11 @@ package fr.maxlego08.essentials.api.utils; import fr.maxlego08.essentials.api.commands.Permission; -import org.bukkit.Location; import org.bukkit.permissions.Permissible; +import java.util.Collection; +import java.util.Locale; + /** * Represents a warp location. * This record encapsulates data related to a warp, including its name and location. @@ -17,6 +19,24 @@ public record Warp(String name, SafeLocation location) { * @return true if the permissible entity has permission, false otherwise. */ public boolean hasPermission(Permissible permissible) { - return permissible.hasPermission(Permission.ESSENTIALS_WARP_.asPermission(this.name)); + if (permissible.hasPermission(Permission.ESSENTIALS_WARP.asPermission())) { + return true; + } + return permissible.hasPermission(Permission.ESSENTIALS_WARP_.asPermission(this.name.toLowerCase(Locale.ROOT))); + } + + /** + * Checks if the permissible can use at least one warp or the global warp permission. + */ + public static boolean canAccessAnyWarp(Permissible permissible, Collection warps) { + if (permissible.hasPermission(Permission.ESSENTIALS_WARP.asPermission())) { + return true; + } + for (Warp warp : warps) { + if (warp.hasPermission(permissible)) { + return true; + } + } + return false; } } diff --git a/src/main/java/fr/maxlego08/essentials/buttons/ButtonWarp.java b/src/main/java/fr/maxlego08/essentials/buttons/ButtonWarp.java index 1a6d7fa4..a81e181a 100644 --- a/src/main/java/fr/maxlego08/essentials/buttons/ButtonWarp.java +++ b/src/main/java/fr/maxlego08/essentials/buttons/ButtonWarp.java @@ -46,6 +46,9 @@ public boolean hasPermission() { @Override public boolean checkPermission(Player player, InventoryEngine inventory, Placeholders placeholders) { Optional optional = plugin.getWarp(this.warpName); - return super.checkPermission(player, inventory, placeholders) && optional.map(warp -> warp.hasPermission(player)).orElse(false); + if (optional.isEmpty() || !optional.get().hasPermission(player)) { + return false; + } + return super.checkPermission(player, inventory, placeholders); } } diff --git a/src/main/java/fr/maxlego08/essentials/buttons/kit/ButtonKitCooldown.java b/src/main/java/fr/maxlego08/essentials/buttons/kit/ButtonKitCooldown.java index 60d41760..03a543db 100644 --- a/src/main/java/fr/maxlego08/essentials/buttons/kit/ButtonKitCooldown.java +++ b/src/main/java/fr/maxlego08/essentials/buttons/kit/ButtonKitCooldown.java @@ -80,6 +80,9 @@ public boolean checkPermission(Player player, InventoryEngine inventory, Placeho User user = this.plugin.getUser(player.getUniqueId()); if (user == null) return false; Optional optional = this.plugin.getKit(this.kitName); - return optional.filter(kit -> super.checkPermission(player, inventory, placeholders) && user.isKitCooldown(kit)).isPresent(); + if (optional.isEmpty() || !optional.get().hasPermission(player) || !user.isKitCooldown(optional.get())) { + return false; + } + return super.checkPermission(player, inventory, placeholders); } } diff --git a/src/main/java/fr/maxlego08/essentials/buttons/kit/ButtonKitGet.java b/src/main/java/fr/maxlego08/essentials/buttons/kit/ButtonKitGet.java index 4c994370..dbd9ab2e 100644 --- a/src/main/java/fr/maxlego08/essentials/buttons/kit/ButtonKitGet.java +++ b/src/main/java/fr/maxlego08/essentials/buttons/kit/ButtonKitGet.java @@ -66,6 +66,9 @@ public boolean checkPermission(Player player, InventoryEngine inventory, Placeho User user = this.plugin.getUser(player.getUniqueId()); if (user == null) return false; Optional optional = this.plugin.getKit(this.kitName); - return optional.filter(kit -> super.checkPermission(player, inventory, placeholders) && kit.hasPermission(player)).isPresent(); + if (optional.isEmpty() || !optional.get().hasPermission(player)) { + return false; + } + return super.checkPermission(player, inventory, placeholders); } } diff --git a/src/main/java/fr/maxlego08/essentials/commands/commands/warp/CommandWarp.java b/src/main/java/fr/maxlego08/essentials/commands/commands/warp/CommandWarp.java index a712a9e7..7ac74ca7 100644 --- a/src/main/java/fr/maxlego08/essentials/commands/commands/warp/CommandWarp.java +++ b/src/main/java/fr/maxlego08/essentials/commands/commands/warp/CommandWarp.java @@ -2,12 +2,12 @@ import fr.maxlego08.essentials.api.EssentialsPlugin; import fr.maxlego08.essentials.api.commands.CommandResultType; -import fr.maxlego08.essentials.api.commands.Permission; import fr.maxlego08.essentials.api.messages.Message; import fr.maxlego08.essentials.api.utils.Warp; import fr.maxlego08.essentials.module.modules.WarpModule; import fr.maxlego08.essentials.zutils.utils.commands.VCommand; import org.apache.logging.log4j.util.Strings; +import org.bukkit.command.CommandSender; import java.util.List; @@ -16,7 +16,6 @@ public class CommandWarp extends VCommand { public CommandWarp(EssentialsPlugin plugin) { super(plugin); this.setModule(WarpModule.class); - this.setPermission(Permission.ESSENTIALS_WARP); this.setDescription(Message.DESCRIPTION_WARP_USE); this.addOptionalArg("name", (sender, args) -> { List warps = plugin.getWarps(); @@ -25,6 +24,14 @@ public CommandWarp(EssentialsPlugin plugin) { this.onlyPlayers(); } + @Override + public CommandResultType prePerform(EssentialsPlugin plugin, CommandSender commandSender, String[] args) { + if (!Warp.canAccessAnyWarp(commandSender, plugin.getWarps())) { + return CommandResultType.NO_PERMISSION; + } + return super.prePerform(plugin, commandSender, args); + } + @Override protected CommandResultType perform(EssentialsPlugin plugin) { diff --git a/src/main/java/fr/maxlego08/essentials/listener/PlayerListener.java b/src/main/java/fr/maxlego08/essentials/listener/PlayerListener.java index c64dd527..19f030a8 100644 --- a/src/main/java/fr/maxlego08/essentials/listener/PlayerListener.java +++ b/src/main/java/fr/maxlego08/essentials/listener/PlayerListener.java @@ -8,7 +8,7 @@ import fr.maxlego08.essentials.api.user.Option; import fr.maxlego08.essentials.api.user.User; import fr.maxlego08.essentials.api.utils.DynamicCooldown; -import fr.maxlego08.essentials.storage.ConfigStorage; +import fr.maxlego08.essentials.zutils.utils.FoliaJoinHelper; import fr.maxlego08.essentials.zutils.utils.TimerBuilder; import fr.maxlego08.essentials.zutils.utils.ZUtils; import org.bukkit.Material; @@ -190,11 +190,7 @@ public void onJoin(PlayerJoinEvent event) { if (user != null) user.startCurrentSessionPlayTime(); if (user != null && user.isFirstJoin()) { - if (ConfigStorage.firstSpawnLocation != null && ConfigStorage.firstSpawnLocation.isValid()) { - this.plugin.getScheduler().teleportAsync(player, ConfigStorage.firstSpawnLocation.getLocation()); - } else if (ConfigStorage.spawnLocation != null && ConfigStorage.spawnLocation.isValid()) { - this.plugin.getScheduler().teleportAsync(player, ConfigStorage.spawnLocation.getLocation()); - } + FoliaJoinHelper.teleportFirstSpawnAfterJoin(this.plugin, player); } if (user != null && user.getOption(Option.VANISH)) { @@ -225,7 +221,7 @@ public void onJoin(PlayerJoinEvent event) { player.setFlySpeed(0.1f); player.setWalkSpeed(0.2f); } - }, 1); + }, FoliaJoinHelper.SAFE_LOGIN_DELAY_TICKS); } @EventHandler(priority = EventPriority.LOWEST) diff --git a/src/main/java/fr/maxlego08/essentials/module/modules/SanctionModule.java b/src/main/java/fr/maxlego08/essentials/module/modules/SanctionModule.java index 78fa21cf..50605db5 100644 --- a/src/main/java/fr/maxlego08/essentials/module/modules/SanctionModule.java +++ b/src/main/java/fr/maxlego08/essentials/module/modules/SanctionModule.java @@ -17,6 +17,7 @@ import fr.maxlego08.essentials.listener.paper.ChatListener; import fr.maxlego08.essentials.module.ZModule; import fr.maxlego08.essentials.user.ZUser; +import fr.maxlego08.essentials.zutils.utils.FoliaJoinHelper; import fr.maxlego08.essentials.zutils.utils.TimerBuilder; import org.bukkit.Bukkit; import org.bukkit.Material; @@ -446,7 +447,7 @@ public void onJoin(PlayerJoinEvent event) { player.setAllowFlight(true); player.setFlying(true); player.setFlySpeed(0f); - this.plugin.getScheduler().teleportAsync(player, player.getLocation().add(0, 0.1, 0)); + FoliaJoinHelper.teleportAfterJoin(this.plugin, player, player.getLocation().add(0, 0.1, 0)); } this.plugin.getEssentialsServer().sendMessage(user.getUniqueId(), Message.MESSAGE_FREEZE); } diff --git a/src/main/java/fr/maxlego08/essentials/module/modules/SpawnModule.java b/src/main/java/fr/maxlego08/essentials/module/modules/SpawnModule.java index 7b8d0775..c3a482a6 100644 --- a/src/main/java/fr/maxlego08/essentials/module/modules/SpawnModule.java +++ b/src/main/java/fr/maxlego08/essentials/module/modules/SpawnModule.java @@ -5,6 +5,7 @@ import fr.maxlego08.essentials.api.user.User; import fr.maxlego08.essentials.module.ZModule; import fr.maxlego08.essentials.storage.ConfigStorage; +import fr.maxlego08.essentials.zutils.utils.FoliaJoinHelper; import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.entity.Player; @@ -69,7 +70,9 @@ public void loadConfiguration() { var player = playerJoinEvent.getPlayer(); if (ConfigStorage.spawnLocation != null && ConfigStorage.spawnLocation.isValid()) { Location spawnLoc = ConfigStorage.spawnLocation.getLocation(); - if (spawnLoc != null) player.teleport(spawnLoc); + if (spawnLoc != null) { + FoliaJoinHelper.teleportAfterJoin(spawnModule.plugin, player, spawnLoc, true); + } } } }, this.plugin); @@ -135,14 +138,4 @@ private void onRespawn(PlayerRespawnEvent event, Player player) { event.setRespawnLocation(ConfigStorage.spawnLocation.getLocation()); } - public void onPlayerFirstJoin(Player player) { - - if (!this.isEnable) return; - - if (ConfigStorage.firstSpawnLocation != null && ConfigStorage.firstSpawnLocation.isValid()) { - player.teleport(ConfigStorage.firstSpawnLocation.getLocation()); - } else if (ConfigStorage.spawnLocation != null && ConfigStorage.spawnLocation.isValid()) { - player.teleport(ConfigStorage.spawnLocation.getLocation()); - } - } } diff --git a/src/main/java/fr/maxlego08/essentials/module/modules/kit/KitModule.java b/src/main/java/fr/maxlego08/essentials/module/modules/kit/KitModule.java index 6384e3fc..dc3a961f 100644 --- a/src/main/java/fr/maxlego08/essentials/module/modules/kit/KitModule.java +++ b/src/main/java/fr/maxlego08/essentials/module/modules/kit/KitModule.java @@ -34,6 +34,7 @@ import java.util.Arrays; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Optional; import java.util.concurrent.atomic.AtomicInteger; @@ -116,7 +117,7 @@ private void loadKit(File file) { String name = configuration.getString("name"); String displayName = configuration.getString("display-name", name); - String permission = configuration.getString("permission", Permission.ESSENTIALS_KIT_.asPermission(name)); + String permission = configuration.getString("permission", Permission.ESSENTIALS_KIT_.asPermission(name.toLowerCase(Locale.ROOT))); String category = configuration.getString("category", null); String subCategory = configuration.getString("sub-category", null); diff --git a/src/main/java/fr/maxlego08/essentials/module/modules/kit/ZKit.java b/src/main/java/fr/maxlego08/essentials/module/modules/kit/ZKit.java index ea2c7f58..2549aba8 100644 --- a/src/main/java/fr/maxlego08/essentials/module/modules/kit/ZKit.java +++ b/src/main/java/fr/maxlego08/essentials/module/modules/kit/ZKit.java @@ -1,6 +1,7 @@ package fr.maxlego08.essentials.module.modules.kit; import fr.maxlego08.essentials.api.EssentialsPlugin; +import fr.maxlego08.essentials.api.commands.Permission; import fr.maxlego08.essentials.api.kit.Kit; import fr.maxlego08.essentials.zutils.utils.ZUtils; import fr.maxlego08.menu.api.MenuItemStack; @@ -12,6 +13,7 @@ import java.io.File; import java.util.List; +import java.util.Locale; import java.util.Map; public class ZKit extends ZUtils implements Kit { @@ -125,7 +127,16 @@ public List getActions() { @Override public boolean hasPermission(Permissible permissible) { - return permissible.hasPermission(this.permission); + if (this.permission == null || this.permission.isEmpty()) { + return true; + } + if (permissible.hasPermission(Permission.ESSENTIALS_KIT.asPermission())) { + return true; + } + if (permissible.hasPermission(this.permission)) { + return true; + } + return permissible.hasPermission(Permission.ESSENTIALS_KIT_.asPermission(this.name.toLowerCase(Locale.ROOT))); } @Override diff --git a/src/main/java/fr/maxlego08/essentials/storage/ZStorageManager.java b/src/main/java/fr/maxlego08/essentials/storage/ZStorageManager.java index 63155876..907a1347 100644 --- a/src/main/java/fr/maxlego08/essentials/storage/ZStorageManager.java +++ b/src/main/java/fr/maxlego08/essentials/storage/ZStorageManager.java @@ -8,7 +8,6 @@ import fr.maxlego08.essentials.api.storage.IStorage; import fr.maxlego08.essentials.api.storage.StorageManager; import fr.maxlego08.essentials.api.storage.StorageType; -import fr.maxlego08.essentials.module.modules.SpawnModule; import fr.maxlego08.essentials.storage.storages.JsonStorage; import fr.maxlego08.essentials.storage.storages.SqlStorage; import fr.maxlego08.essentials.zutils.utils.TimerBuilder; @@ -88,10 +87,6 @@ public void onLogin(PlayerLoginEvent event) { var user = this.iStorage.createOrLoad(playerUuid, playerName); user.setAddress(event.getAddress().getHostAddress()); - if (user.isFirstJoin()){ - this.plugin.getModuleManager().getModule(SpawnModule.class).onPlayerFirstJoin(event.getPlayer()); - } - var userEvent = new UserJoinEvent(user); this.plugin.getScheduler().runNextTick(wrappedTask -> userEvent.callEvent()); } diff --git a/src/main/java/fr/maxlego08/essentials/user/ZTeleportHereRequest.java b/src/main/java/fr/maxlego08/essentials/user/ZTeleportHereRequest.java index 3f5a142f..314f953b 100644 --- a/src/main/java/fr/maxlego08/essentials/user/ZTeleportHereRequest.java +++ b/src/main/java/fr/maxlego08/essentials/user/ZTeleportHereRequest.java @@ -96,6 +96,9 @@ public void accept() { private void teleport(TeleportationModule teleportationModule) { Location playerLocation = fromUser.getPlayer().getLocation(); Location location = toUser.getPlayer().isFlying() ? playerLocation : teleportationModule.isTeleportSafety() ? toSafeLocation(playerLocation) : playerLocation; + if (location == null) { + location = playerLocation; + } if (teleportationModule.isTeleportToCenter()) { location = location.getBlock().getLocation().add(0.5, 0, 0.5); diff --git a/src/main/java/fr/maxlego08/essentials/user/ZTeleportRequest.java b/src/main/java/fr/maxlego08/essentials/user/ZTeleportRequest.java index c38f84ad..c80b67bd 100644 --- a/src/main/java/fr/maxlego08/essentials/user/ZTeleportRequest.java +++ b/src/main/java/fr/maxlego08/essentials/user/ZTeleportRequest.java @@ -96,6 +96,9 @@ public void accept() { private void teleport(TeleportationModule teleportationModule) { Location playerLocation = toUser.getPlayer().getLocation(); Location location = fromUser.getPlayer().isFlying() ? playerLocation : teleportationModule.isTeleportSafety() ? toSafeLocation(playerLocation) : playerLocation; + if (location == null) { + location = playerLocation; + } if (teleportationModule.isTeleportToCenter()) { location = location.getBlock().getLocation().add(0.5, 0, 0.5); diff --git a/src/main/java/fr/maxlego08/essentials/user/ZUser.java b/src/main/java/fr/maxlego08/essentials/user/ZUser.java index 3a0dea4c..eef04cfe 100644 --- a/src/main/java/fr/maxlego08/essentials/user/ZUser.java +++ b/src/main/java/fr/maxlego08/essentials/user/ZUser.java @@ -395,6 +395,9 @@ private void teleport(TeleportationModule teleportationModule, Location toLocati if (player == null) return; Location location = player.isFlying() ? toLocation : teleportationModule.isTeleportSafety() ? toSafeLocation(toLocation) : toLocation; + if (location == null) { + location = toLocation; + } if (teleportationModule.isTeleportToCenter()) { location = location.getBlock().getLocation().add(0.5, 0, 0.5); diff --git a/src/main/java/fr/maxlego08/essentials/zutils/utils/FoliaJoinHelper.java b/src/main/java/fr/maxlego08/essentials/zutils/utils/FoliaJoinHelper.java new file mode 100644 index 00000000..052c4bfe --- /dev/null +++ b/src/main/java/fr/maxlego08/essentials/zutils/utils/FoliaJoinHelper.java @@ -0,0 +1,63 @@ +package fr.maxlego08.essentials.zutils.utils; + +import fr.maxlego08.essentials.api.EssentialsPlugin; +import fr.maxlego08.essentials.storage.ConfigStorage; +import org.bukkit.Location; +import org.bukkit.entity.Player; + +/** + * Folia-safe helpers for player join flows. + *

+ * Teleports must not run during {@link org.bukkit.event.player.PlayerJoinEvent} + * or {@link org.bukkit.event.player.PlayerLoginEvent}; the player is still being placed in the world. + */ +public final class FoliaJoinHelper { + + private static final double SPAWN_TOLERANCE_SQUARED = 1.0; + private static final long JOIN_TELEPORT_DELAY_TICKS = 1L; + public static final long SAFE_LOGIN_DELAY_TICKS = 5L; + + private FoliaJoinHelper() { + } + + public static Location resolveFirstSpawnLocation() { + if (ConfigStorage.firstSpawnLocation != null && ConfigStorage.firstSpawnLocation.isValid()) { + return ConfigStorage.firstSpawnLocation.getLocation(); + } + if (ConfigStorage.spawnLocation != null && ConfigStorage.spawnLocation.isValid()) { + return ConfigStorage.spawnLocation.getLocation(); + } + return null; + } + + public static void teleportFirstSpawnAfterJoin(EssentialsPlugin plugin, Player player) { + teleportAfterJoin(plugin, player, resolveFirstSpawnLocation(), true); + } + + public static void teleportAfterJoin(EssentialsPlugin plugin, Player player, Location target) { + teleportAfterJoin(plugin, player, target, false); + } + + public static void teleportAfterJoin(EssentialsPlugin plugin, Player player, Location target, boolean skipWhenAlreadyThere) { + if (player == null || target == null || target.getWorld() == null) { + return; + } + + Location destination = target; + plugin.getScheduler().runAtLocationLater(player.getLocation(), () -> { + if (!player.isOnline()) { + return; + } + + if (skipWhenAlreadyThere) { + Location current = player.getLocation(); + if (current.getWorld().equals(destination.getWorld()) + && current.distanceSquared(destination) < SPAWN_TOLERANCE_SQUARED) { + return; + } + } + + plugin.getScheduler().teleportAsync(player, destination); + }, JOIN_TELEPORT_DELAY_TICKS); + } +} diff --git a/src/main/java/fr/maxlego08/essentials/zutils/utils/ZUtils.java b/src/main/java/fr/maxlego08/essentials/zutils/utils/ZUtils.java index c357c381..ccc2f62c 100644 --- a/src/main/java/fr/maxlego08/essentials/zutils/utils/ZUtils.java +++ b/src/main/java/fr/maxlego08/essentials/zutils/utils/ZUtils.java @@ -173,36 +173,56 @@ protected boolean same(Location l1, Location l2) { } protected Location toSafeLocation(Location location) { - - Location defaultLocation = location.clone(); - - if (isValid(defaultLocation)) { - return defaultLocation; + if (location == null || location.getWorld() == null) { + return location; } - location = findMeSafeLocation(defaultLocation, BlockFace.UP, 1); - - return location; - } - - protected Location findMeSafeLocation(Location location, BlockFace blockFace, int distance) { + Location base = location.clone(); + if (isValid(base)) { + return base; + } - if (distance > location.getWorld().getMaxHeight() * 2) { - return null; + World world = base.getWorld(); + int x = base.getBlockX(); + int z = base.getBlockZ(); + float yaw = base.getYaw(); + float pitch = base.getPitch(); + int startY = base.getBlockY(); + int maxY = world.getMaxHeight() - 2; + int minY = world.getMinHeight() + 1; + + for (int y = startY; y <= maxY; y++) { + Location candidate = new Location(world, x + 0.5, y, z + 0.5, yaw, pitch); + if (isValid(candidate)) { + return candidate; + } } - Location location2 = relative(location, blockFace, distance); - if (isValid(location2)) { - return location2; + for (int y = startY - 1; y >= minY; y--) { + Location candidate = new Location(world, x + 0.5, y, z + 0.5, yaw, pitch); + if (isValid(candidate)) { + return candidate; + } } - return findMeSafeLocation(location2, blockFace.equals(BlockFace.UP) ? BlockFace.DOWN : BlockFace.UP, distance + 1); + return base; } protected boolean isValid(Location location) { - if (location == null) return false; - if (location.getWorld() == null) return false; - return !location.getBlock().getType().isSolid() && !relative(location, BlockFace.UP).getBlock().getType().isSolid() && relative(location, BlockFace.DOWN).getBlock().getType().isSolid(); + if (location == null || location.getWorld() == null) { + return false; + } + + Material below = relative(location, BlockFace.DOWN).getBlock().getType(); + Material at = location.getBlock().getType(); + Material above = relative(location, BlockFace.UP).getBlock().getType(); + + return below.isSolid() + && below != Material.WATER && below != Material.LAVA + && !at.isSolid() + && at != Material.WATER && at != Material.LAVA + && !above.isSolid() + && above != Material.WATER && above != Material.LAVA; } protected Location relative(Location location, BlockFace face) {