Duplicate import of {@code dev.lolib.scheduler.Scheduler} — removed.
- *
Duplicate field declaration {@code dev.lolib.scheduler.ScheduledTask task}
- * shadowed the already-imported type — removed the fully-qualified duplicate.
+ *
Duplicate scheduler import — removed.
+ *
Duplicate {@code ScheduledTask task} field shadowed the already-imported
+ * type — removed the fully-qualified duplicate.
*
*/
public class GhostPlayer {
diff --git a/src/main/java/dev/loki/loparkour/ghost/service/GhostReplay.java b/src/main/java/dev/loki/loparkour/ghost/service/GhostReplay.java
index 5fdbc6f..8768fb0 100644
--- a/src/main/java/dev/loki/loparkour/ghost/service/GhostReplay.java
+++ b/src/main/java/dev/loki/loparkour/ghost/service/GhostReplay.java
@@ -5,8 +5,8 @@ import dev.loki.loparkour.ghost.model.GhostData;
import dev.loki.loparkour.ghost.model.GhostFrame;
import dev.loki.loparkour.ghost.replay.PlayerGhost;
import dev.loki.loparkour.player.core.ParkourUser;
-import dev.lolib.scheduler.ScheduledTask;
-import dev.lolib.scheduler.Scheduler;
+import dev.loki.loparkour.scheduler.ScheduledTask;
+import dev.loki.loparkour.scheduler.Scheduler;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.World;
diff --git a/src/main/java/dev/loki/loparkour/leaderboard/persistence/LeaderboardStorage.java b/src/main/java/dev/loki/loparkour/leaderboard/persistence/LeaderboardStorage.java
index e6fb551..ac392c5 100644
--- a/src/main/java/dev/loki/loparkour/leaderboard/persistence/LeaderboardStorage.java
+++ b/src/main/java/dev/loki/loparkour/leaderboard/persistence/LeaderboardStorage.java
@@ -6,7 +6,7 @@ import dev.loki.loparkour.leaderboard.core.LeaderboardSorter;
import dev.loki.loparkour.LoParkour;
import dev.loki.loparkour.config.core.Config;
import dev.loki.loparkour.storage.Storage;
-import dev.lolib.scheduler.Scheduler;
+import dev.loki.loparkour.scheduler.Scheduler;
import org.jetbrains.annotations.NotNull;
import java.util.Map;
diff --git a/src/main/java/dev/loki/loparkour/listener/gameplay/ParkourRestrictionListener.java b/src/main/java/dev/loki/loparkour/listener/gameplay/ParkourRestrictionListener.java
index aa7c4c3..3ae8a2e 100644
--- a/src/main/java/dev/loki/loparkour/listener/gameplay/ParkourRestrictionListener.java
+++ b/src/main/java/dev/loki/loparkour/listener/gameplay/ParkourRestrictionListener.java
@@ -142,7 +142,7 @@ public class ParkourRestrictionListener implements Listener {
return;
}
if (event.getInventory().getType() == InventoryType.CHEST) {
- return; // LoLib GUI
+ return; // menu GUI
}
if (event.getInventory().getType() == InventoryType.CRAFTING) {
return; // own inv
diff --git a/src/main/java/dev/loki/loparkour/menu/community/LeaderboardsMenu.java b/src/main/java/dev/loki/loparkour/menu/community/LeaderboardsMenu.java
index 93a386f..9bae19b 100644
--- a/src/main/java/dev/loki/loparkour/menu/community/LeaderboardsMenu.java
+++ b/src/main/java/dev/loki/loparkour/menu/community/LeaderboardsMenu.java
@@ -7,7 +7,7 @@ import dev.loki.loparkour.menu.core.LPMenu;
import dev.loki.loparkour.menu.core.Menus;
import dev.loki.loparkour.menu.core.screen.ParkourOption;
import dev.loki.loparkour.mode.base.Mode;
-import dev.lolib.gui.InventoryGUI;
+import dev.loki.loparkour.menu.gui.inventory.InventoryGui;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
@@ -58,7 +58,7 @@ public class LeaderboardsMenu extends LPMenu {
final int startIndex = currentPage * ITEMS_PER_PAGE;
final int endIndex = Math.min(startIndex + ITEMS_PER_PAGE, items.size());
- InventoryGUI gui = baseGui(title, ROWS);
+ InventoryGui gui = baseGui(title, ROWS);
// Add items for current page
for (int i = startIndex; i < endIndex; i++) {
diff --git a/src/main/java/dev/loki/loparkour/menu/community/SingleLeaderboardMenu.java b/src/main/java/dev/loki/loparkour/menu/community/SingleLeaderboardMenu.java
index 06a47c2..1d9d177 100644
--- a/src/main/java/dev/loki/loparkour/menu/community/SingleLeaderboardMenu.java
+++ b/src/main/java/dev/loki/loparkour/menu/community/SingleLeaderboardMenu.java
@@ -7,7 +7,7 @@ import dev.loki.loparkour.menu.core.LPMenu;
import dev.loki.loparkour.menu.core.screen.ParkourOption;
import dev.loki.loparkour.mode.base.Mode;
import dev.loki.loparkour.player.core.ParkourUser;
-import dev.lolib.gui.InventoryGUI;
+import dev.loki.loparkour.menu.gui.inventory.InventoryGui;
import org.bukkit.Material;
import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player;
@@ -49,7 +49,7 @@ public class SingleLeaderboardMenu extends LPMenu {
final int startIndex = currentPage * ITEMS_PER_PAGE;
final int endIndex = Math.min(startIndex + ITEMS_PER_PAGE, items.size());
- InventoryGUI gui = baseGui(title, ROWS);
+ InventoryGui gui = baseGui(title, ROWS);
for (int i = startIndex; i < endIndex; i++) {
final int slot = FIRST_SLOT + (i - startIndex); // Slots 10-16
diff --git a/src/main/java/dev/loki/loparkour/menu/core/LPMenu.java b/src/main/java/dev/loki/loparkour/menu/core/LPMenu.java
index 68e92ff..f6d02a9 100644
--- a/src/main/java/dev/loki/loparkour/menu/core/LPMenu.java
+++ b/src/main/java/dev/loki/loparkour/menu/core/LPMenu.java
@@ -3,7 +3,7 @@ package dev.loki.loparkour.menu.core;
import dev.loki.loparkour.config.locale.Locales;
import dev.loki.loparkour.config.options.Option;
import dev.loki.loparkour.util.text.ColorUtil;
-import dev.lolib.gui.InventoryGUI;
+import dev.loki.loparkour.menu.gui.inventory.InventoryGui;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
@@ -14,13 +14,11 @@ import org.jetbrains.annotations.NotNull;
/**
* Base class for all LoParkour menus.
*
- * Real LoLib API:
- * InventoryGUI.create().title(String).rows(n).fill(item).setItem(slot, item, Consumer<InventoryClickEvent>).open(player)
- * ScrollableGUI.create().title(String).rows(n).addItem(item, Consumer<InventoryClickEvent>)
- * .scrollUpButton(item,slot).scrollDownButton(item,slot).open(player)
+ * Menu API ({@link InventoryGui}):
+ * InventoryGui.create().title(String).rows(n).fill(item)
+ * .setItem(slot, item, Consumer<InventoryClickEvent>).open(player)
*
- * cancelAllClicks = true by default — items cannot be taken out automatically.
- * Handlers receive Consumer<InventoryClickEvent>, event is already cancelled before handler runs.
+ * Every click is cancelled before the handler runs — items cannot be taken out.
*/
public abstract class LPMenu {
@@ -74,8 +72,8 @@ public abstract class LPMenu {
* Create a base GUI filled with filler items.
* Uses .fill() — only fills empty slots, so setItem calls after this override correctly.
*/
- protected InventoryGUI baseGui(String title, int rows) {
- return InventoryGUI.create()
+ protected InventoryGui baseGui(String title, int rows) {
+ return InventoryGui.create()
.title(ColorUtil.color(title))
.rows(rows)
.fill(filler());
diff --git a/src/main/java/dev/loki/loparkour/menu/core/MenuStub.java b/src/main/java/dev/loki/loparkour/menu/core/MenuStub.java
index a61b8e3..665740c 100644
--- a/src/main/java/dev/loki/loparkour/menu/core/MenuStub.java
+++ b/src/main/java/dev/loki/loparkour/menu/core/MenuStub.java
@@ -1,7 +1,7 @@
package dev.loki.loparkour.menu.core;
/**
- * Placeholder during menu migration; real flows use {@link Menus} / LoLib GUI.
+ * Placeholder during menu migration; real flows use {@link Menus} / InventoryGui.
*/
public final class MenuStub {
private MenuStub() {}
diff --git a/src/main/java/dev/loki/loparkour/menu/core/screen/DynamicMenu.java b/src/main/java/dev/loki/loparkour/menu/core/screen/DynamicMenu.java
index 5e82849..db24984 100644
--- a/src/main/java/dev/loki/loparkour/menu/core/screen/DynamicMenu.java
+++ b/src/main/java/dev/loki/loparkour/menu/core/screen/DynamicMenu.java
@@ -29,7 +29,7 @@ public abstract class DynamicMenu {
* @param item The item
* @param shouldDisplay Whether this item should be displayed right now.
*/
- // Stub: migrate to LoLib GUI when dynamic rows are wired up.
+ // Stub: migrate to InventoryGui when dynamic rows are wired up.
/*
public void registerMainItem(
int row, int id,
@@ -55,7 +55,7 @@ public abstract class DynamicMenu {
* @param player The player
* @param menu The menu
*/
- // Stub: migrate to LoLib GUI when dynamic rows are wired up.
+ // Stub: migrate to InventoryGui when dynamic rows are wired up.
/*
public void display(Player player, Menu menu) {
ParkourUser user = ParkourUser.getUser(player);
diff --git a/src/main/java/dev/loki/loparkour/menu/core/screen/MainMenu.java b/src/main/java/dev/loki/loparkour/menu/core/screen/MainMenu.java
index 18ff89a..6372588 100644
--- a/src/main/java/dev/loki/loparkour/menu/core/screen/MainMenu.java
+++ b/src/main/java/dev/loki/loparkour/menu/core/screen/MainMenu.java
@@ -5,7 +5,7 @@ import dev.loki.loparkour.config.locale.Locales;
import dev.loki.loparkour.menu.core.LPMenu;
import dev.loki.loparkour.menu.core.Menus;
import dev.loki.loparkour.player.core.ParkourUser;
-import dev.lolib.gui.InventoryGUI;
+import dev.loki.loparkour.menu.gui.inventory.InventoryGui;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
@@ -51,7 +51,7 @@ public class MainMenu extends LPMenu {
final int[] slots = distributeEvenly(count, SLOT_START, SLOT_END);
- InventoryGUI gui = baseGui(title, ROWS);
+ InventoryGui gui = baseGui(title, ROWS);
for (int i = 0; i < count; i++) {
final int idx = i;
diff --git a/src/main/java/dev/loki/loparkour/menu/gui/Menu.java b/src/main/java/dev/loki/loparkour/menu/gui/Menu.java
deleted file mode 100644
index df26abc..0000000
--- a/src/main/java/dev/loki/loparkour/menu/gui/Menu.java
+++ /dev/null
@@ -1,30 +0,0 @@
-package dev.loki.loparkour.menu.gui;
-
-import dev.loki.loparkour.config.locale.Locales;
-import dev.loki.loparkour.util.item.Item;
-import org.bukkit.entity.Player;
-
-/**
- * Legacy menu stub — real menus use LoLib {@link dev.lolib.gui.InventoryGUI}.
- */
-public class Menu {
- private final int rows;
- private final String title;
-
- public Menu(int rows, String title) {
- this.rows = rows;
- this.title = title;
- }
-
- public Menu item(int slot, Item item) {
- return this;
- }
-
- public Menu distributeRowsEvenly() {
- return this;
- }
-
- public void open(Player player) {
- Locales.send(player, "other.menu_unavailable", title, rows);
- }
-}
diff --git a/src/main/java/dev/loki/loparkour/menu/gui/PagedMenu.java b/src/main/java/dev/loki/loparkour/menu/gui/PagedMenu.java
index 326b3b5..ccc7fc5 100644
--- a/src/main/java/dev/loki/loparkour/menu/gui/PagedMenu.java
+++ b/src/main/java/dev/loki/loparkour/menu/gui/PagedMenu.java
@@ -7,7 +7,7 @@ import org.bukkit.entity.Player;
import java.util.List;
/**
- * Legacy paged menu stub — real menus use LoLib GUI.
+ * Legacy paged menu stub — real menus use InventoryGui.
*/
public class PagedMenu {
private final int rows;
diff --git a/src/main/java/dev/loki/loparkour/menu/gui/inventory/GuiManager.java b/src/main/java/dev/loki/loparkour/menu/gui/inventory/GuiManager.java
new file mode 100644
index 0000000..e67dcd1
--- /dev/null
+++ b/src/main/java/dev/loki/loparkour/menu/gui/inventory/GuiManager.java
@@ -0,0 +1,72 @@
+package dev.loki.loparkour.menu.gui.inventory;
+
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+import org.bukkit.event.inventory.InventoryClickEvent;
+import org.bukkit.event.inventory.InventoryCloseEvent;
+import org.bukkit.event.player.PlayerQuitEvent;
+import org.bukkit.inventory.Inventory;
+import org.bukkit.plugin.Plugin;
+
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Routes inventory clicks and closes to the {@link InventoryGui} opened for a player.
+ * One GUI per player UUID; entries are dropped on close and on quit.
+ */
+public final class GuiManager implements Listener {
+
+ private static final Map ACTIVE = new ConcurrentHashMap<>();
+
+ private GuiManager() {
+ }
+
+ /**
+ * Registers the click/close listener. Safe to call once during plugin enable.
+ *
+ * @param plugin the owning plugin
+ */
+ public static void init(Plugin plugin) {
+ plugin.getServer().getPluginManager().registerEvents(new GuiManager(), plugin);
+ }
+
+ static void register(Player player, InventoryGui gui, Inventory inventory) {
+ ACTIVE.put(player.getUniqueId(), new OpenGui(gui, inventory));
+ }
+
+ @EventHandler
+ public void onInventoryClick(InventoryClickEvent event) {
+ if (!(event.getWhoClicked() instanceof Player player)) {
+ return;
+ }
+ final OpenGui open = ACTIVE.get(player.getUniqueId());
+ if (open == null || !open.inventory().equals(event.getInventory())) {
+ return;
+ }
+ open.gui().handleClick(event);
+ }
+
+ @EventHandler
+ public void onInventoryClose(InventoryCloseEvent event) {
+ if (!(event.getPlayer() instanceof Player player)) {
+ return;
+ }
+ final OpenGui open = ACTIVE.get(player.getUniqueId());
+ if (open == null || !open.inventory().equals(event.getInventory())) {
+ return;
+ }
+ ACTIVE.remove(player.getUniqueId());
+ }
+
+ @EventHandler
+ public void onPlayerQuit(PlayerQuitEvent event) {
+ ACTIVE.remove(event.getPlayer().getUniqueId());
+ }
+
+ /** The GUI a player currently has open, together with its inventory. */
+ private record OpenGui(InventoryGui gui, Inventory inventory) {
+ }
+}
diff --git a/src/main/java/dev/loki/loparkour/menu/gui/inventory/InventoryGui.java b/src/main/java/dev/loki/loparkour/menu/gui/inventory/InventoryGui.java
new file mode 100644
index 0000000..8772268
--- /dev/null
+++ b/src/main/java/dev/loki/loparkour/menu/gui/inventory/InventoryGui.java
@@ -0,0 +1,135 @@
+package dev.loki.loparkour.menu.gui.inventory;
+
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Player;
+import org.bukkit.event.inventory.InventoryClickEvent;
+import org.bukkit.inventory.Inventory;
+import org.bukkit.inventory.ItemStack;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Consumer;
+
+/**
+ * Inventory menu built fluently: fill background, place items with click handlers,
+ * then open it for a player. Clicks are always cancelled, so menu items cannot be
+ * taken out; only the registered handlers run.
+ *
+ *
Replaces the LoLib {@code InventoryGUI} (TODO, Задача 1). Handler dispatch lives
+ * in {@link GuiManager}, which tracks the inventory opened for each player.
+ */
+public final class InventoryGui {
+
+ private static final int SLOTS_PER_ROW = 9;
+ private static final int MIN_ROWS = 1;
+ private static final int MAX_ROWS = 6;
+ private static final String DEFAULT_TITLE = "GUI";
+
+ private final Map items = new HashMap<>();
+
+ private String title = DEFAULT_TITLE;
+ private int rows = MIN_ROWS;
+
+ private InventoryGui() {
+ }
+
+ /**
+ * @return a new empty menu
+ */
+ public static InventoryGui create() {
+ return new InventoryGui();
+ }
+
+ /**
+ * @param newTitle the (already colored) inventory title
+ * @return this menu
+ */
+ public InventoryGui title(String newTitle) {
+ this.title = newTitle;
+ return this;
+ }
+
+ /**
+ * @param newRows inventory rows, 1..6
+ * @return this menu
+ */
+ public InventoryGui rows(int newRows) {
+ if (newRows < MIN_ROWS || newRows > MAX_ROWS) {
+ throw new IllegalArgumentException("Rows must be between " + MIN_ROWS + " and " + MAX_ROWS);
+ }
+ this.rows = newRows;
+ return this;
+ }
+
+ /**
+ * Puts the item into every slot that does not have one yet.
+ *
+ * @param item background item
+ * @return this menu
+ */
+ public InventoryGui fill(ItemStack item) {
+ for (int slot = 0; slot < rows * SLOTS_PER_ROW; slot++) {
+ items.putIfAbsent(slot, new PlacedItem(item, null));
+ }
+ return this;
+ }
+
+ /**
+ * @param slot inventory slot
+ * @param item item to show
+ * @return this menu
+ */
+ public InventoryGui setItem(int slot, ItemStack item) {
+ items.put(slot, new PlacedItem(item, null));
+ return this;
+ }
+
+ /**
+ * @param slot inventory slot
+ * @param item item to show
+ * @param onClick click handler, invoked after the event was cancelled
+ * @return this menu
+ */
+ public InventoryGui setItem(int slot, ItemStack item, Consumer onClick) {
+ items.put(slot, new PlacedItem(item, onClick));
+ return this;
+ }
+
+ /**
+ * Builds the inventory, registers it for the player and opens it.
+ *
+ * @param player the player to show this menu to
+ */
+ public void open(Player player) {
+ final Inventory inventory = Bukkit.createInventory(null, rows * SLOTS_PER_ROW, title);
+ items.forEach((slot, placed) -> {
+ if (slot < inventory.getSize() && placed.item() != null) {
+ inventory.setItem(slot, placed.item());
+ }
+ });
+ GuiManager.register(player, this, inventory);
+ player.openInventory(inventory);
+ }
+
+ /**
+ * Cancels the click and runs the handler registered for the clicked slot.
+ *
+ * @param event the click event
+ */
+ void handleClick(InventoryClickEvent event) {
+ event.setCancelled(true);
+ final PlacedItem placed = items.get(event.getRawSlot());
+ if (placed != null && placed.onClick() != null) {
+ placed.onClick().accept(event);
+ }
+ }
+
+ /** Snapshot of the placed items — package-private seam for tests. */
+ Map items() {
+ return Map.copyOf(items);
+ }
+
+ /** An item and its optional click handler. */
+ record PlacedItem(ItemStack item, Consumer onClick) {
+ }
+}
diff --git a/src/main/java/dev/loki/loparkour/menu/lobby/DuelsMenu.java b/src/main/java/dev/loki/loparkour/menu/lobby/DuelsMenu.java
index 4258250..3e1b091 100644
--- a/src/main/java/dev/loki/loparkour/menu/lobby/DuelsMenu.java
+++ b/src/main/java/dev/loki/loparkour/menu/lobby/DuelsMenu.java
@@ -8,7 +8,7 @@ import dev.loki.loparkour.player.core.ParkourUser;
import dev.loki.loparkour.session.core.Session;
import dev.loki.loparkour.util.text.ColorUtil;
import dev.loki.loparkour.world.core.Divider;
-import dev.lolib.gui.InventoryGUI;
+import dev.loki.loparkour.menu.gui.inventory.InventoryGui;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
@@ -50,7 +50,7 @@ public class DuelsMenu extends LPMenu {
final int startIndex = currentPage * ITEMS_PER_PAGE;
final int endIndex = Math.min(startIndex + ITEMS_PER_PAGE, sessions.size());
- InventoryGUI gui = baseGui(title, ROWS);
+ InventoryGui gui = baseGui(title, ROWS);
if (sessions.isEmpty()) {
gui = gui.setItem(SLOT_EMPTY, localeItem(player, "play.multi.duels.menu_empty"));
diff --git a/src/main/java/dev/loki/loparkour/menu/lobby/LobbyMenu.java b/src/main/java/dev/loki/loparkour/menu/lobby/LobbyMenu.java
index 11cd630..9f8c3d1 100644
--- a/src/main/java/dev/loki/loparkour/menu/lobby/LobbyMenu.java
+++ b/src/main/java/dev/loki/loparkour/menu/lobby/LobbyMenu.java
@@ -7,7 +7,7 @@ import dev.loki.loparkour.menu.core.screen.ParkourOption;
import dev.loki.loparkour.player.core.ParkourUser;
import dev.loki.loparkour.session.core.Session;
import dev.loki.loparkour.util.text.ColorUtil;
-import dev.lolib.gui.InventoryGUI;
+import dev.loki.loparkour.menu.gui.inventory.InventoryGui;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
@@ -36,7 +36,7 @@ public class LobbyMenu extends LPMenu {
final boolean isHost = !user.session.getPlayers().isEmpty()
&& user.session.getPlayers().get(0) == user;
- InventoryGUI gui = baseGui(title, ROWS);
+ InventoryGui gui = baseGui(title, ROWS);
if (ParkourOption.PLAYER_MANAGEMENT.mayPerform(player) && isHost) {
gui = gui.setItem(SLOT_PLAYER_MANAGEMENT, localeItem(player, "lobby.player_management.item"),
diff --git a/src/main/java/dev/loki/loparkour/menu/lobby/PlayerManagementMenu.java b/src/main/java/dev/loki/loparkour/menu/lobby/PlayerManagementMenu.java
index a7d4443..4707b99 100644
--- a/src/main/java/dev/loki/loparkour/menu/lobby/PlayerManagementMenu.java
+++ b/src/main/java/dev/loki/loparkour/menu/lobby/PlayerManagementMenu.java
@@ -8,7 +8,7 @@ import dev.loki.loparkour.player.core.ParkourPlayer;
import dev.loki.loparkour.player.core.ParkourUser;
import dev.loki.loparkour.session.core.Session;
import dev.loki.loparkour.util.text.ColorUtil;
-import dev.lolib.gui.InventoryGUI;
+import dev.loki.loparkour.menu.gui.inventory.InventoryGui;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.ClickType;
@@ -42,7 +42,7 @@ public class PlayerManagementMenu extends LPMenu {
others.addAll(session.getPlayers().stream().filter(p -> p != viewer).toList());
others.addAll(session.getSpectators());
- InventoryGUI gui = baseGui(title, ROWS)
+ InventoryGui gui = baseGui(title, ROWS)
.setItem(SLOT_CLOSE, closeButton(player), e -> Menus.LOBBY.open(player));
for (int i = 0; i < Math.min(others.size(), MAX_PLAYERS); i++) {
diff --git a/src/main/java/dev/loki/loparkour/menu/play/SingleMenu.java b/src/main/java/dev/loki/loparkour/menu/play/SingleMenu.java
index ed6cde9..67d08e0 100644
--- a/src/main/java/dev/loki/loparkour/menu/play/SingleMenu.java
+++ b/src/main/java/dev/loki/loparkour/menu/play/SingleMenu.java
@@ -6,7 +6,7 @@ import dev.loki.loparkour.config.locale.Locales;
import dev.loki.loparkour.menu.core.LPMenu;
import dev.loki.loparkour.mode.base.Mode;
import dev.loki.loparkour.player.core.ParkourUser;
-import dev.lolib.gui.InventoryGUI;
+import dev.loki.loparkour.menu.gui.inventory.InventoryGui;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
@@ -46,7 +46,7 @@ public class SingleMenu extends LPMenu {
final int startIndex = currentPage * ITEMS_PER_PAGE;
final int endIndex = Math.min(startIndex + ITEMS_PER_PAGE, items.size());
- InventoryGUI gui = baseGui(title, ROWS);
+ InventoryGui gui = baseGui(title, ROWS);
for (int i = startIndex; i < endIndex; i++) {
final Mode mode = availableModes.get(i);
diff --git a/src/main/java/dev/loki/loparkour/menu/play/SpectatorMenu.java b/src/main/java/dev/loki/loparkour/menu/play/SpectatorMenu.java
index 9cfbe12..9436e84 100644
--- a/src/main/java/dev/loki/loparkour/menu/play/SpectatorMenu.java
+++ b/src/main/java/dev/loki/loparkour/menu/play/SpectatorMenu.java
@@ -6,7 +6,7 @@ import dev.loki.loparkour.mode.base.Modes;
import dev.loki.loparkour.player.core.ParkourUser;
import dev.loki.loparkour.session.core.Session;
import dev.loki.loparkour.world.core.Divider;
-import dev.lolib.gui.InventoryGUI;
+import dev.loki.loparkour.menu.gui.inventory.InventoryGui;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
@@ -43,7 +43,7 @@ public class SpectatorMenu extends LPMenu {
final int startIndex = currentPage * ITEMS_PER_PAGE;
final int endIndex = Math.min(startIndex + ITEMS_PER_PAGE, items.size());
- InventoryGUI gui = baseGui(title, ROWS);
+ InventoryGui gui = baseGui(title, ROWS);
for (int i = startIndex; i < endIndex; i++) {
final Session session = sessions.get(i);
diff --git a/src/main/java/dev/loki/loparkour/menu/settings/LangMenu.java b/src/main/java/dev/loki/loparkour/menu/settings/LangMenu.java
index daabd68..9e1a073 100644
--- a/src/main/java/dev/loki/loparkour/menu/settings/LangMenu.java
+++ b/src/main/java/dev/loki/loparkour/menu/settings/LangMenu.java
@@ -5,7 +5,7 @@ import dev.loki.loparkour.menu.core.LPMenu;
import dev.loki.loparkour.menu.core.Menus;
import dev.loki.loparkour.player.core.ParkourPlayer;
import dev.loki.loparkour.util.text.ColorUtil;
-import dev.lolib.gui.InventoryGUI;
+import dev.loki.loparkour.menu.gui.inventory.InventoryGui;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
@@ -40,7 +40,7 @@ public class LangMenu extends LPMenu {
final List langs = new ArrayList<>(Locales.getLocaleKeys());
- InventoryGUI gui = baseGui(title, ROWS);
+ InventoryGui gui = baseGui(title, ROWS);
for (int i = 0; i < Math.min(langs.size(), MAX_LANGS); i++) {
final String lang = langs.get(i);
diff --git a/src/main/java/dev/loki/loparkour/menu/settings/ParkourSettingsMenu.java b/src/main/java/dev/loki/loparkour/menu/settings/ParkourSettingsMenu.java
index 23591e2..85a2d66 100644
--- a/src/main/java/dev/loki/loparkour/menu/settings/ParkourSettingsMenu.java
+++ b/src/main/java/dev/loki/loparkour/menu/settings/ParkourSettingsMenu.java
@@ -9,7 +9,7 @@ import dev.loki.loparkour.menu.core.screen.ParkourOption;
import dev.loki.loparkour.player.core.ParkourPlayer;
import dev.loki.loparkour.style.core.Style;
import dev.loki.loparkour.util.text.ColorUtil;
-import dev.lolib.gui.InventoryGUI;
+import dev.loki.loparkour.menu.gui.inventory.InventoryGui;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
@@ -168,7 +168,7 @@ public class ParkourSettingsMenu extends LPMenu {
final String locale = pp.locale;
final String title = Locales.getString(locale, "settings.parkour_settings.styles.name");
- InventoryGUI gui = baseGui(title, ROWS_STYLE);
+ InventoryGui gui = baseGui(title, ROWS_STYLE);
int slot = FIRST_STYLE_SLOT;
for (final Style style : Registry.getStyles()) {
diff --git a/src/main/java/dev/loki/loparkour/menu/settings/SettingsMenu.java b/src/main/java/dev/loki/loparkour/menu/settings/SettingsMenu.java
index 9fb5f5d..338c610 100644
--- a/src/main/java/dev/loki/loparkour/menu/settings/SettingsMenu.java
+++ b/src/main/java/dev/loki/loparkour/menu/settings/SettingsMenu.java
@@ -6,7 +6,7 @@ import dev.loki.loparkour.menu.core.Menus;
import dev.loki.loparkour.menu.core.screen.ParkourOption;
import dev.loki.loparkour.player.core.ParkourPlayer;
import dev.loki.loparkour.player.core.ParkourUser;
-import dev.lolib.gui.InventoryGUI;
+import dev.loki.loparkour.menu.gui.inventory.InventoryGui;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
@@ -22,7 +22,7 @@ public class SettingsMenu extends LPMenu {
final String locale = locale(player);
final String title = Locales.getString(locale, "settings.name");
- InventoryGUI gui = baseGui(title, ROWS);
+ InventoryGui gui = baseGui(title, ROWS);
if (ParkourOption.PARKOUR_SETTINGS.mayPerform(player) && ParkourPlayer.isPlayer(player)) {
gui = gui.setItem(SLOT_PARKOUR_SETTINGS, localeItem(player, "settings.parkour_settings.item"), e -> {
diff --git a/src/main/java/dev/loki/loparkour/mode/impl/multi/RaceMode.java b/src/main/java/dev/loki/loparkour/mode/impl/multi/RaceMode.java
index 6292a5b..7e25e02 100644
--- a/src/main/java/dev/loki/loparkour/mode/impl/multi/RaceMode.java
+++ b/src/main/java/dev/loki/loparkour/mode/impl/multi/RaceMode.java
@@ -146,7 +146,7 @@ public class RaceMode implements Mode {
registerScore(time, "1.0", targetScore);
// Return to lobby after 5 seconds — guarded against null/closed session
- dev.lolib.scheduler.Scheduler.get(LoParkour.getPlugin())
+ dev.loki.loparkour.scheduler.Scheduler.get(LoParkour.getPlugin())
.runLater(() -> {
if (session != null && !state.stopped) {
reset(false);
diff --git a/src/main/java/dev/loki/loparkour/mode/impl/solo/SpeedrunMode.java b/src/main/java/dev/loki/loparkour/mode/impl/solo/SpeedrunMode.java
index 0b81820..062d714 100644
--- a/src/main/java/dev/loki/loparkour/mode/impl/solo/SpeedrunMode.java
+++ b/src/main/java/dev/loki/loparkour/mode/impl/solo/SpeedrunMode.java
@@ -5,8 +5,8 @@ import dev.loki.loparkour.mode.base.Mode;
import dev.loki.loparkour.mode.base.Modes;
import dev.loki.loparkour.util.item.Item;
-import dev.lolib.scheduler.Scheduler;
-import dev.lolib.scheduler.ScheduledTask;
+import dev.loki.loparkour.scheduler.Scheduler;
+import dev.loki.loparkour.scheduler.ScheduledTask;
import dev.loki.loparkour.LoParkour;
import dev.loki.loparkour.config.core.Config;
import dev.loki.loparkour.config.locale.Locales;
diff --git a/src/main/java/dev/loki/loparkour/player/core/PlayerPersistence.java b/src/main/java/dev/loki/loparkour/player/core/PlayerPersistence.java
index 7e32ec6..4c25fc5 100644
--- a/src/main/java/dev/loki/loparkour/player/core/PlayerPersistence.java
+++ b/src/main/java/dev/loki/loparkour/player/core/PlayerPersistence.java
@@ -2,7 +2,7 @@ package dev.loki.loparkour.player.core;
import dev.loki.loparkour.LoParkour;
import dev.loki.loparkour.storage.Storage;
-import dev.lolib.scheduler.Scheduler;
+import dev.loki.loparkour.scheduler.Scheduler;
/**
* Handles persistence of player data to the configured storage backend.
diff --git a/src/main/java/dev/loki/loparkour/player/core/PlayerSpawnSetup.java b/src/main/java/dev/loki/loparkour/player/core/PlayerSpawnSetup.java
index 5fa4eee..8c0f97e 100644
--- a/src/main/java/dev/loki/loparkour/player/core/PlayerSpawnSetup.java
+++ b/src/main/java/dev/loki/loparkour/player/core/PlayerSpawnSetup.java
@@ -3,7 +3,7 @@ package dev.loki.loparkour.player.core;
import dev.loki.loparkour.LoParkour;
import dev.loki.loparkour.config.core.Config;
import dev.loki.loparkour.player.service.ui.ParkourHotbar;
-import dev.lolib.scheduler.Scheduler;
+import dev.loki.loparkour.scheduler.Scheduler;
import org.bukkit.GameMode;
import org.bukkit.Location;
diff --git a/src/main/java/dev/loki/loparkour/player/data/InventoryData.java b/src/main/java/dev/loki/loparkour/player/data/InventoryData.java
index 5d72052..5499caf 100644
--- a/src/main/java/dev/loki/loparkour/player/data/InventoryData.java
+++ b/src/main/java/dev/loki/loparkour/player/data/InventoryData.java
@@ -2,7 +2,7 @@ package dev.loki.loparkour.player.data;
import dev.loki.loparkour.LoParkour;
import dev.loki.loparkour.config.core.Config;
-import dev.lolib.scheduler.Scheduler;
+import dev.loki.loparkour.scheduler.Scheduler;
import org.bukkit.Bukkit;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Player;
diff --git a/src/main/java/dev/loki/loparkour/player/spectator/ParkourSpectator.java b/src/main/java/dev/loki/loparkour/player/spectator/ParkourSpectator.java
index 7a5f26d..6127508 100644
--- a/src/main/java/dev/loki/loparkour/player/spectator/ParkourSpectator.java
+++ b/src/main/java/dev/loki/loparkour/player/spectator/ParkourSpectator.java
@@ -9,8 +9,8 @@ import dev.loki.loparkour.config.locale.Locales;
import dev.loki.loparkour.player.data.PreviousData;
import dev.loki.loparkour.session.core.Session;
import dev.loki.loparkour.util.text.ColorUtil;
-import dev.lolib.scheduler.Scheduler;
-import dev.lolib.scheduler.ScheduledTask;
+import dev.loki.loparkour.scheduler.Scheduler;
+import dev.loki.loparkour.scheduler.ScheduledTask;
import net.md_5.bungee.api.ChatMessageType;
import net.md_5.bungee.api.chat.TextComponent;
import org.bukkit.GameMode;
diff --git a/src/main/java/dev/loki/loparkour/scheduler/BukkitBackend.java b/src/main/java/dev/loki/loparkour/scheduler/BukkitBackend.java
new file mode 100644
index 0000000..8230d98
--- /dev/null
+++ b/src/main/java/dev/loki/loparkour/scheduler/BukkitBackend.java
@@ -0,0 +1,36 @@
+package dev.loki.loparkour.scheduler;
+
+import org.bukkit.Bukkit;
+import org.bukkit.plugin.Plugin;
+
+/**
+ * Scheduler backend for Bukkit/Paper servers.
+ */
+final class BukkitBackend implements Scheduler.Backend {
+
+ private final Plugin plugin;
+
+ BukkitBackend(Plugin plugin) {
+ this.plugin = plugin;
+ }
+
+ @Override
+ public ScheduledTask run(Runnable task) {
+ return Bukkit.getScheduler().runTask(plugin, task)::cancel;
+ }
+
+ @Override
+ public ScheduledTask runAsync(Runnable task) {
+ return Bukkit.getScheduler().runTaskAsynchronously(plugin, task)::cancel;
+ }
+
+ @Override
+ public ScheduledTask runLater(Runnable task, long delayTicks) {
+ return Bukkit.getScheduler().runTaskLater(plugin, task, delayTicks)::cancel;
+ }
+
+ @Override
+ public ScheduledTask runTimer(Runnable task, long delayTicks, long periodTicks) {
+ return Bukkit.getScheduler().runTaskTimer(plugin, task, delayTicks, periodTicks)::cancel;
+ }
+}
diff --git a/src/main/java/dev/loki/loparkour/scheduler/FoliaBackend.java b/src/main/java/dev/loki/loparkour/scheduler/FoliaBackend.java
new file mode 100644
index 0000000..5495159
--- /dev/null
+++ b/src/main/java/dev/loki/loparkour/scheduler/FoliaBackend.java
@@ -0,0 +1,92 @@
+package dev.loki.loparkour.scheduler;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Objects;
+import java.util.function.Consumer;
+import org.bukkit.Bukkit;
+import org.bukkit.plugin.Plugin;
+
+/**
+ * Scheduler backend for Folia servers: the global region scheduler drives sync tasks
+ * and the async scheduler drives worker tasks, mirroring the Bukkit semantics above.
+ *
+ *
The Folia API is resolved reflectively because the build matrix compiles against
+ * Spigot/Paper API artifacts that do not ship it (see TODO, Задача 1 / п.143).
+ */
+final class FoliaBackend implements Scheduler.Backend {
+
+ private static final String SCHEDULER_PACKAGE = "io.papermc.paper.threadedregions.scheduler.";
+
+ private static final String GLOBAL_SCHEDULER_CLASS = "GlobalRegionScheduler";
+ private static final String ASYNC_SCHEDULER_CLASS = "AsyncScheduler";
+
+ private final Plugin plugin;
+ private final Object globalScheduler;
+ private final Object asyncScheduler;
+ private final Method globalRun;
+ private final Method globalRunDelayed;
+ private final Method globalRunAtFixedRate;
+ private final Method asyncRunNow;
+ private final Method taskCancel;
+
+ FoliaBackend(Plugin plugin) throws ReflectiveOperationException {
+ this.plugin = plugin;
+ this.globalScheduler = scheduler("getGlobalRegionScheduler");
+ this.asyncScheduler = scheduler("getAsyncScheduler");
+ this.globalRun = method(GLOBAL_SCHEDULER_CLASS, "run", Plugin.class, Consumer.class);
+ this.globalRunDelayed = method(GLOBAL_SCHEDULER_CLASS, "runDelayed",
+ Plugin.class, Consumer.class, long.class);
+ this.globalRunAtFixedRate = method(GLOBAL_SCHEDULER_CLASS, "runAtFixedRate",
+ Plugin.class, Consumer.class, long.class, long.class);
+ this.asyncRunNow = method(ASYNC_SCHEDULER_CLASS, "runNow", Plugin.class, Consumer.class);
+ this.taskCancel = Class.forName(SCHEDULER_PACKAGE + "ScheduledTask").getMethod("cancel");
+ }
+
+ @Override
+ public ScheduledTask run(Runnable task) {
+ return wrap(invoke(globalRun, globalScheduler, plugin, consumer(task)));
+ }
+
+ @Override
+ public ScheduledTask runAsync(Runnable task) {
+ return wrap(invoke(asyncRunNow, asyncScheduler, plugin, consumer(task)));
+ }
+
+ @Override
+ public ScheduledTask runLater(Runnable task, long delayTicks) {
+ return wrap(invoke(globalRunDelayed, globalScheduler, plugin, consumer(task), delayTicks));
+ }
+
+ @Override
+ public ScheduledTask runTimer(Runnable task, long delayTicks, long periodTicks) {
+ return wrap(invoke(globalRunAtFixedRate, globalScheduler, plugin,
+ consumer(task), delayTicks, periodTicks));
+ }
+
+ private static Object scheduler(String bukkitGetter) throws ReflectiveOperationException {
+ return Bukkit.class.getMethod(bukkitGetter).invoke(null);
+ }
+
+ private static Method method(String owner, String name, Class>... params)
+ throws ReflectiveOperationException {
+ return Class.forName(SCHEDULER_PACKAGE + owner).getMethod(name, params);
+ }
+
+ private static Consumer