Initial commit: LoParkour v1.1.0

This commit is contained in:
loki 2026-02-24 22:07:03 +01:00
commit aecb03ccf8
120 changed files with 9668 additions and 0 deletions

View file

@ -0,0 +1,76 @@
package dev.loki.loparkour.menu;
import dev.loki.loparkour.player.ParkourUser;
import dev.efnilite.vilib.inventory.Menu;
import dev.efnilite.vilib.inventory.item.MenuItem;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.function.BiFunction;
import java.util.function.Predicate;
/**
* A class for menus where items are dynamically displayed; that is, depending on provided conditions.
* These conditions are specified by the param {@code shouldDisplay} in the method {@link #registerMainItem(int, int, BiFunction, Predicate)}.
*/
public abstract class DynamicMenu {
protected final Map<Integer, List<ItemContainer>> registeredItems = new HashMap<>();
/**
* Registers an item that will be displayed in a specific slot if the specified condition is met.
* This will be used to create a context-aware main menu.
*
* @param row The row in which this item will be displayed. Starts from 0 and ends at 5.
* @param id The id of this item. This will be used to determine the positions of each item.
* Lower is more to the left. Can't be lower than 0.
* @param item The item
* @param shouldDisplay Whether this item should be displayed right now.
*/
public void registerMainItem(int row, int id, BiFunction<@NotNull Player, @Nullable ParkourUser, MenuItem> item, Predicate<Player> shouldDisplay) {
if (id < 0 || row < 0 || row > 4) {
return;
}
List<ItemContainer> existing = registeredItems.get(row);
if (existing == null) {
existing = new ArrayList<>();
}
existing.add(new ItemContainer(id, item, shouldDisplay));
registeredItems.put(row, existing);
}
/**
* Opens this dynamic menu to a player
*
* @param player The player
* @param menu The menu
*/
public void display(Player player, Menu menu) {
ParkourUser user = ParkourUser.getUser(player);
for (int row : registeredItems.keySet()) {
int actualSlot = row * 9; // 0, 9, 18, etc.
List<ItemContainer> containers = registeredItems.get(row); // sort by id first
containers.sort(Comparator.comparingInt(container -> container.id));
for (ItemContainer container : containers) {
if (container.predicate.test(player)) { // if item in id passes predicate, display it in the menu
menu.item(actualSlot, container.item.apply(player, user));
actualSlot++;
}
}
}
menu.open(player);
}
/**
* Data class for registered items
*/
protected record ItemContainer(int id, BiFunction<@NotNull Player, @Nullable ParkourUser, MenuItem> item,
Predicate<Player> predicate) {}
}

View file

@ -0,0 +1,24 @@
package dev.loki.loparkour.menu;
import dev.loki.loparkour.config.Config;
import dev.loki.loparkour.config.Locales;
import dev.loki.loparkour.player.ParkourPlayer;
import dev.loki.loparkour.player.ParkourUser;
import dev.efnilite.vilib.inventory.Menu;
import org.bukkit.entity.Player;
public class MainMenu extends DynamicMenu {
public MainMenu() {
registerMainItem(1, 0, (player, user) -> Locales.getItem(player, "play.item").click(event -> Menus.PLAY.open(event.getPlayer())), player -> ParkourOption.PLAY.mayPerform(player) && Config.CONFIG.getBoolean("joining"));
registerMainItem(1, 1, (player, user) -> Locales.getItem(player, "community.item").click(event -> Menus.COMMUNITY.open(event.getPlayer())), ParkourOption.COMMUNITY::mayPerform);
registerMainItem(1, 2, (player, user) -> Locales.getItem(player, "settings.item").click(event -> Menus.SETTINGS.open(event.getPlayer())), player -> ParkourOption.SETTINGS.mayPerform(player) && ParkourUser.isUser(player));
registerMainItem(1, 3, (player, user) -> Locales.getItem(player, "lobby.item").click(event -> Menus.LOBBY.open(event.getPlayer())), player -> ParkourOption.LOBBY.mayPerform(player) && ParkourUser.isUser(player));
registerMainItem(1, 4, (player, user) -> Locales.getItem(player, "other.quit").click(event -> ParkourPlayer.leave(player)), player -> ParkourOption.QUIT.mayPerform(player) && ParkourUser.isUser(player));
}
public void open(Player player) {
display(player, new Menu(3, Locales.getString(player, "main.name"))
.distributeRowsEvenly());
}
}

View file

@ -0,0 +1,39 @@
package dev.loki.loparkour.menu;
import dev.loki.loparkour.menu.community.CommunityMenu;
import dev.loki.loparkour.menu.community.LeaderboardsMenu;
import dev.loki.loparkour.menu.community.SingleLeaderboardMenu;
import dev.loki.loparkour.menu.lobby.LobbyMenu;
import dev.loki.loparkour.menu.lobby.PlayerManagementMenu;
import dev.loki.loparkour.menu.play.PlayMenu;
import dev.loki.loparkour.menu.play.SingleMenu;
import dev.loki.loparkour.menu.play.SpectatorMenu;
import dev.loki.loparkour.menu.settings.LangMenu;
import dev.loki.loparkour.menu.settings.ParkourSettingsMenu;
import dev.loki.loparkour.menu.settings.SettingsMenu;
public class Menus {
// main
public static MainMenu MAIN = new MainMenu();
// play
public static PlayMenu PLAY = new PlayMenu();
public static SingleMenu SINGLE = new SingleMenu();
public static SpectatorMenu SPECTATOR = new SpectatorMenu();
// community
public static CommunityMenu COMMUNITY = new CommunityMenu();
public static LeaderboardsMenu LEADERBOARDS = new LeaderboardsMenu();
public static SingleLeaderboardMenu SINGLE_LEADERBOARD = new SingleLeaderboardMenu();
// settings
public static SettingsMenu SETTINGS = new SettingsMenu();
public static LangMenu LANG = new LangMenu();
public static ParkourSettingsMenu PARKOUR_SETTINGS = new ParkourSettingsMenu();
// lobby
public static LobbyMenu LOBBY = new LobbyMenu();
public static PlayerManagementMenu PLAYER_MANAGEMENT = new PlayerManagementMenu();
}

View file

@ -0,0 +1,81 @@
package dev.loki.loparkour.menu;
import dev.loki.loparkour.config.Config;
import dev.loki.loparkour.config.Option;
import org.bukkit.permissions.Permissible;
/**
* An enum for all Parkour Menu Options
*/
public enum ParkourOption {
// main
MAIN("main", "LoParkour.main"),
// play
PLAY("play", "LoParkour.play"),
SINGLE("play.single", "LoParkour.play.single"),
SPECTATOR("play.spectator", "LoParkour.play.spectator"),
// community
COMMUNITY("community", "LoParkour.community"),
LEADERBOARDS("community.leaderboards", "LoParkour.community.leaderboards"),
// settings
SETTINGS("settings", "LoParkour.settings"),
PARKOUR_SETTINGS("settings.parkour_settings.item", "LoParkour.settings.parkour_settings"),
STYLES("settings.parkour_settings.items.styles", "LoParkour.settings.styles"),
LEADS("settings.parkour_settings.items.leads", "LoParkour.settings.leads"),
TIME("settings.parkour_settings.items.time", "LoParkour.settings.time"),
SCHEMATICS("settings.parkour_settings.items.schematics", "LoParkour.settings.schematics"),
SCOREBOARD("settings.parkour_settings.items.scoreboard", "LoParkour.settings.show_scoreboard"),
FALL_MESSAGE("settings.parkour_settings.items.fall_message", "LoParkour.settings.fall_message"),
PARTICLES("settings.parkour_settings.items.particles", "LoParkour.settings.particles"),
SOUND("settings.parkour_settings.items.sound", "LoParkour.settings.sound"),
SPECIAL_BLOCKS("settings.parkour_settings.items.special_blocks", "LoParkour.settings.special_blocks"),
LANG("settings.lang", "LoParkour.settings.lang"),
CHAT("settings.chat", "LoParkour.settings.chat"),
// lobby
LOBBY("lobby", "LoParkour.lobby"),
VISIBILITY("lobby.visibility", "LoParkour.lobby.visibility"),
PLAYER_MANAGEMENT("lobby.player_management", "LoParkour.lobby.player_management"),
// other
JOIN("join", "LoParkour.join"),
QUIT("quit", "LoParkour.quit"),
ADMIN("admin", "LoParkour.admin");
/**
* The path in config files for this option.
*/
public final String path;
/**
* The permission for this option.
*/
public final String permission;
ParkourOption(String path, String permission) {
this.path = path;
this.permission = permission;
}
/**
* @param permissible The player
* @return True if the player is allowed to view/perform this option, false if not.
*/
public boolean mayPerform(Permissible permissible) {
boolean value = Option.OPTIONS_ENABLED.getOrDefault(this, true);
if (value) {
if (Config.CONFIG.getBoolean("permissions.enabled")) {
return permissible.hasPermission(permission);
}
return true;
}
return false;
}
}

View file

@ -0,0 +1,24 @@
package dev.loki.loparkour.menu.community;
import dev.loki.loparkour.config.Locales;
import dev.loki.loparkour.menu.DynamicMenu;
import dev.loki.loparkour.menu.Menus;
import dev.loki.loparkour.menu.ParkourOption;
import dev.efnilite.vilib.inventory.Menu;
import org.bukkit.entity.Player;
/**
* The menu for all community-related things
*/
public class CommunityMenu extends DynamicMenu {
public CommunityMenu() {
registerMainItem(1, 1, (player, user) -> Locales.getItem(player, "community.leaderboards.item").click(event -> Menus.LEADERBOARDS.open(event.getPlayer())), ParkourOption.LEADERBOARDS::mayPerform);
registerMainItem(2, 10, (player, user) -> Locales.getItem(player, "other.close").click(event -> event.getPlayer().closeInventory()), player -> true);
}
public void open(Player player) {
display(player, new Menu(3, Locales.getString(player, "community.name"))
.distributeRowsEvenly());
}
}

View file

@ -0,0 +1,57 @@
package dev.loki.loparkour.menu.community;
import dev.loki.loparkour.api.Registry;
import dev.loki.loparkour.config.Locales;
import dev.loki.loparkour.config.Option;
import dev.loki.loparkour.leaderboard.Leaderboard;
import dev.loki.loparkour.menu.Menus;
import dev.loki.loparkour.menu.ParkourOption;
import dev.loki.loparkour.mode.Mode;
import dev.loki.loparkour.player.ParkourUser;
import dev.efnilite.vilib.inventory.PagedMenu;
import dev.efnilite.vilib.inventory.item.Item;
import dev.efnilite.vilib.inventory.item.MenuItem;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import java.util.ArrayList;
import java.util.List;
/**
* Leaderboards menu
*/
public class LeaderboardsMenu {
public void open(Player player) {
ParkourUser user = ParkourUser.getUser(player);
String locale = user == null ? Option.OPTIONS_DEFAULTS.get(ParkourOption.LANG) : user.locale;
PagedMenu menu = new PagedMenu(3, Locales.getString(player, "%s.name".formatted(ParkourOption.LEADERBOARDS.path)));
Mode latest = null;
List<MenuItem> items = new ArrayList<>();
for (Mode mode : Registry.getModes()) {
Leaderboard leaderboard = mode.getLeaderboard();
Item item = mode.getItem(locale);
if (leaderboard == null || item == null) {
continue;
}
items.add(item.clone().click(event -> Menus.SINGLE_LEADERBOARD.open(player, mode, leaderboard.sort)));
latest = mode;
}
if (items.size() == 1) {
Menus.SINGLE_LEADERBOARD.open(player, latest, Leaderboard.Sort.SCORE);
return;
}
menu.displayRows(0, 1)
.addToDisplay(items)
.nextPage(26, new Item(Material.LIME_DYE, "<#0DCB07><bold>»").click(event -> menu.page(1)))
.prevPage(18, new Item(Material.RED_DYE, "<#DE1F1F><bold>«").click(event -> menu.page(-1)))
.item(22, Locales.getItem(player, "other.close").click(event -> Menus.COMMUNITY.open(event.getPlayer())))
.open(player);
}
}

View file

@ -0,0 +1,137 @@
package dev.loki.loparkour.menu.community;
import dev.loki.loparkour.LoParkour;
import dev.loki.loparkour.api.Registry;
import dev.loki.loparkour.config.Locales;
import dev.loki.loparkour.config.Option;
import dev.loki.loparkour.leaderboard.Leaderboard;
import dev.loki.loparkour.leaderboard.Score;
import dev.loki.loparkour.menu.Menus;
import dev.loki.loparkour.menu.ParkourOption;
import dev.loki.loparkour.mode.Mode;
import dev.loki.loparkour.player.ParkourUser;
import dev.efnilite.vilib.inventory.PagedMenu;
import dev.efnilite.vilib.inventory.item.Item;
import dev.efnilite.vilib.inventory.item.MenuItem;
import dev.efnilite.vilib.util.SkullSetter;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.SkullMeta;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
/**
* Menu for a single leaderboard
*/
public class SingleLeaderboardMenu {
public void open(Player player, Mode mode, Leaderboard.Sort sort) {
Leaderboard leaderboard = mode.getLeaderboard();
if (leaderboard == null) {
return;
}
// init vars
var user = ParkourUser.getUser(player);
var locale = user == null ? Option.OPTIONS_DEFAULTS.get(ParkourOption.LANG) : user.locale;
var menu = new PagedMenu(3, Locales.getString(player, "%s.name".formatted(ParkourOption.LEADERBOARDS.path)));
var items = new ArrayList<MenuItem>();
var base = Locales.getItem(player, "%s.head".formatted(ParkourOption.LEADERBOARDS.path));
for (Map.Entry<UUID, Score> entry : leaderboard.sort(sort).entrySet()) {
int rank = items.size() + 1;
var uuid = entry.getKey();
var score = entry.getValue();
if (score == null) {
continue;
}
Item item = base.clone().material(Material.PLAYER_HEAD)
.modifyName(name -> name.replace("%r", Integer.toString(rank))
.replace("%s", Integer.toString(score.score()))
.replace("%p", score.name())
.replace("%t", score.time())
.replace("%d", score.difficulty()))
.modifyLore(line -> line.replace("%r", Integer.toString(rank))
.replace("%s", Integer.toString(score.score()))
.replace("%p", score.name())
.replace("%t", score.time())
.replace("%d", score.difficulty()));
// Player head gathering
ItemStack stack = item.build();
// if there are more than 36 players, don't show the heads to avoid server crashing
// and bedrock has no player skull support
if (rank <= 20 && !ParkourUser.isBedrockPlayer(player)) {
OfflinePlayer op = Bukkit.getOfflinePlayer(uuid);
if (op.getName() != null && !op.getName().startsWith(".")) { // bedrock players' names with geyser start with a .
SkullMeta meta = (SkullMeta) stack.getItemMeta();
if (meta != null) {
SkullSetter.setPlayerHead(op, meta);
item.meta(meta);
}
}
}
if (uuid.equals(player.getUniqueId())) {
menu.item(21, item.clone());
}
items.add(item);
}
List<String> values = Locales.getStringList(locale, "%s.sort.values".formatted(ParkourOption.LEADERBOARDS.path));
if (values.size() != 3) {
LoParkour.logging().stack("Error while trying to get locales for sort values: not enough sort values present",
"check your %s locale file".formatted(locale), new IllegalArgumentException());
}
String name = switch (sort) {
case SCORE -> values.get(0);
case TIME -> values.get(1);
case DIFFICULTY -> values.get(2);
};
// get next sorting type
var next = switch (sort) {
case SCORE -> Leaderboard.Sort.TIME;
case TIME -> Leaderboard.Sort.DIFFICULTY;
default -> Leaderboard.Sort.SCORE;
};
menu.displayRows(0, 1)
.addToDisplay(items)
.nextPage(26, new Item(Material.LIME_DYE, "<#0DCB07><bold>»").click(event -> menu.page(1)))
.prevPage(18, new Item(Material.RED_DYE, "<#DE1F1F><bold>«").click(event -> menu.page(-1)))
.item(22, Locales.getItem(player, ParkourOption.LEADERBOARDS.path + ".sort", name.toLowerCase()).click(event -> open(player, mode, next)))
.item(23, Locales.getItem(player, "other.close").click(event -> {
List<Mode> modes = Registry.getModes()
.stream()
.filter(m -> m.getLeaderboard() != null && m.getItem(locale) != null)
.toList();
if (modes.size() == 1) {
Menus.COMMUNITY.open(player);
return;
}
Menus.LEADERBOARDS.open(event.getPlayer());
}))
.open(player);
}
}

View file

@ -0,0 +1,79 @@
package dev.loki.loparkour.menu.lobby;
import dev.loki.loparkour.config.Locales;
import dev.loki.loparkour.menu.DynamicMenu;
import dev.loki.loparkour.menu.Menus;
import dev.loki.loparkour.menu.ParkourOption;
import dev.loki.loparkour.player.ParkourPlayer;
import dev.loki.loparkour.player.ParkourUser;
import dev.loki.loparkour.session.Session;
import dev.efnilite.vilib.inventory.Menu;
import dev.efnilite.vilib.inventory.item.SliderItem;
import org.bukkit.entity.Player;
import java.util.List;
public class LobbyMenu extends DynamicMenu {
public LobbyMenu() {
registerMainItem(1, 0, (player, user) -> Locales.getItem(player, "lobby.player_management.item").click(event -> Menus.PLAYER_MANAGEMENT.open(player)), player -> {
ParkourUser user = ParkourUser.getUser(player);
return ParkourOption.PLAYER_MANAGEMENT.mayPerform(player) && user instanceof ParkourPlayer && user.session.getPlayers().get(0) == user;
});
registerMainItem(1, 1, (player, user) -> {
if (user == null) {
return null;
}
List<String> values = Locales.getStringList(user.locale, "lobby.visibility.values");
return new SliderItem().initial(switch (user.session.getVisibility()) {
case PUBLIC -> 0;
case ID_ONLY -> 1;
case PRIVATE -> 2;
}).add(0, Locales.getItem(player, "lobby.visibility").modifyLore(lore -> lore.replace("%s", values.get(2))), event -> { // public
ParkourUser u = ParkourUser.getUser(event.getPlayer());
if (u != null) {
u.session.setVisibility(Session.Visibility.PUBLIC);
}
return true;
}).add(1, Locales.getItem(player, "lobby.visibility").modifyLore(lore -> lore.replace("%s", values.get(1))), event -> { // id only
ParkourUser u = ParkourUser.getUser(event.getPlayer());
if (u != null) {
u.session.setVisibility(Session.Visibility.ID_ONLY);
}
return true;
}).add(2, Locales.getItem(player, "lobby.visibility").modifyLore(lore -> lore.replace("%s", values.get(0))), event -> { // private
ParkourUser u = ParkourUser.getUser(event.getPlayer());
if (u != null) {
u.session.setVisibility(Session.Visibility.PRIVATE);
}
return true;
});
}, player -> {
ParkourUser user = ParkourUser.getUser(player);
return ParkourOption.VISIBILITY.mayPerform(player) && user instanceof ParkourPlayer && user.session.getPlayers().get(0) == user;
});
registerMainItem(2, 10, (player, user) -> Locales.getItem(player, "other.close").click(event -> event.getPlayer().closeInventory()), player -> true);
}
/**
* Opens the main menu.
*
* @param player The player to open the menu to
*/
public void open(Player player) {
display(player, new Menu(3, Locales.getString(player, "lobby.name"))
.distributeRowsEvenly());
}
}

View file

@ -0,0 +1,118 @@
package dev.loki.loparkour.menu.lobby;
import dev.loki.loparkour.config.Locales;
import dev.loki.loparkour.menu.Menus;
import dev.loki.loparkour.mode.Modes;
import dev.loki.loparkour.player.ParkourPlayer;
import dev.loki.loparkour.player.ParkourUser;
import dev.loki.loparkour.session.Session;
import dev.efnilite.vilib.inventory.PagedMenu;
import dev.efnilite.vilib.inventory.item.Item;
import dev.efnilite.vilib.util.SkullSetter;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.ClickType;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.SkullMeta;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* Menu for managing players
*/
public class PlayerManagementMenu {
public void open(Player p) {
if (p == null) {
return;
}
ParkourPlayer viewer = ParkourPlayer.getPlayer(p);
if (viewer == null) {
return;
}
Session session = viewer.session;
PagedMenu menu = new PagedMenu(3, Locales.getString(viewer.locale, "lobby.player_management.name"));
add(menu, viewer, session.getPlayers().stream().map(player -> (ParkourUser) player).toList());
add(menu, viewer, session.getSpectators().stream().map(player -> (ParkourUser) player).toList());
menu.displayRows(0, 1)
.prevPage(18, new Item(Material.RED_DYE, "<#DE1F1F><bold>«").click(event -> menu.page(-1)))
.nextPage(26, new Item(Material.LIME_DYE, "<#0DCB07><bold>»").click(event -> menu.page(1)))
.item(22, Locales.getItem(viewer.locale, "other.close").click(event -> Menus.LOBBY.open(event.getPlayer())))
.open(p);
}
private void add(PagedMenu menu, ParkourUser viewer, Collection<ParkourUser> users) {
Session session = viewer.session;
for (ParkourUser other : users) {
if (other == viewer) {
continue;
}
Item item = Locales.getItem(viewer.locale, "lobby.player_management.head", other.getName());
item.material(Material.PLAYER_HEAD);
boolean muted = session.isMuted(other);
List<String> lore = new ArrayList<>();
if (muted) {
// add top
lore.addAll(List.of(Locales.getString(viewer.locale, "lobby.player_management.head.top").split("\\|\\|")));
}
// add bottom
lore.addAll(List.of(Locales.getString(viewer.locale, "lobby.player_management.head.bottom").split("\\|\\|")));
// Player head gathering
item.material(Material.PLAYER_HEAD).lore(lore).click(event -> {
ClickType click = event.event().getClick();
switch (click) {
case LEFT -> {
Modes.DEFAULT.create(other.player);
other.sendTranslated("lobby.player_management.kicked");
viewer.sendTranslated("lobby.player_management.advice");
open(viewer.player);
}
case RIGHT -> {
session.toggleMute(other);
if (!muted) {
other.sendTranslated("lobby.player_management.muted");
} else {
other.sendTranslated("lobby.player_management.unmuted");
}
open(viewer.player);
}
}
});
ItemStack stack = item.build(); // Updating meta requires building
stack.setType(Material.PLAYER_HEAD);
// bedrock has no player skull support
if (menu.getTotalToDisplay().size() <= 36 && !ParkourUser.isBedrockPlayer(other.player)) {
if (other.getName() != null && !other.getName().startsWith(".")) { // bedrock players' names with geyser start with a .
SkullMeta meta = (SkullMeta) stack.getItemMeta();
if (meta != null) {
SkullSetter.setPlayerHead(other.player, meta);
item.meta(meta);
}
}
}
menu.addToDisplay(List.of(item));
}
}
}

View file

@ -0,0 +1,38 @@
package dev.loki.loparkour.menu.play;
import dev.loki.loparkour.config.Locales;
import dev.loki.loparkour.menu.DynamicMenu;
import dev.loki.loparkour.menu.Menus;
import dev.loki.loparkour.menu.ParkourOption;
import dev.efnilite.vilib.inventory.Menu;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
/**
* The menu where players can join modes
*/
public class PlayMenu extends DynamicMenu {
public PlayMenu() {
registerMainItem(1, 0, (player, user) -> Locales.getItem(player, "play.single.item")
.click(event -> Menus.SINGLE.open(event.getPlayer())),
ParkourOption.SINGLE::mayPerform);
registerMainItem(1, 1, (player, user) -> Locales.getItem(player, "other.iep")
.click(event -> event.getPlayer().performCommand("iep play")),
player -> Bukkit.getPluginManager().isPluginEnabled("IEP"));
registerMainItem(1, 6, (player, user) -> Locales.getItem(player, "play.spectator.item")
.click(event -> Menus.SPECTATOR.open(event.getPlayer())),
ParkourOption.SPECTATOR::mayPerform);
registerMainItem(2, 0, (player, user) -> Locales.getItem(player, "other.close")
.click(event -> event.getPlayer().closeInventory()),
player -> true);
}
public void open(Player player) {
display(player, new Menu(3, Locales.getString(player, "play.name"))
.distributeRowsEvenly());
}
}

View file

@ -0,0 +1,68 @@
package dev.loki.loparkour.menu.play;
import dev.loki.loparkour.api.Registry;
import dev.loki.loparkour.config.Config;
import dev.loki.loparkour.config.Locales;
import dev.loki.loparkour.config.Option;
import dev.loki.loparkour.menu.Menus;
import dev.loki.loparkour.menu.ParkourOption;
import dev.loki.loparkour.mode.Mode;
import dev.loki.loparkour.mode.MultiMode;
import dev.loki.loparkour.player.ParkourUser;
import dev.efnilite.vilib.inventory.PagedMenu;
import dev.efnilite.vilib.inventory.item.Item;
import dev.efnilite.vilib.inventory.item.MenuItem;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
/**
* The menu to select single player modes
*/
public class SingleMenu {
public void open(Player player) {
ParkourUser user = ParkourUser.getUser(player);
String locale = user == null ? Option.OPTIONS_DEFAULTS.get(ParkourOption.LANG) : user.locale;
List<Mode> modes = Registry.getModes();
List<MenuItem> items = new ArrayList<>();
List<Mode> modeSet = new ArrayList<>();
for (Mode mode : modes) {
boolean permissions = Config.CONFIG.getBoolean("permissions.enabled") && !player.hasPermission("LoParkour.gamemode." + mode.getName());
Item item = mode.getItem(locale);
if (permissions || mode instanceof MultiMode || item == null) {
continue;
}
modeSet.add(mode);
items.add(item.clone().click(event -> {
if (user == null || Duration.between(user.joined, Instant.now()).toSeconds() > 3) {
mode.create(player);
}
}));
}
if (modeSet.size() == 1) {
modeSet.get(0).create(player);
return;
}
PagedMenu mode = new PagedMenu(3, Locales.getString(player, "play.single.name"));
mode.displayRows(0, 1)
.addToDisplay(items)
.nextPage(26, new Item(Material.LIME_DYE, "<#0DCB07><bold>»").click(event -> mode.page(1)))
.prevPage(18, new Item(Material.RED_DYE, "<#DE1F1F><bold>«").click(event -> mode.page(-1)))
.item(22, Locales.getItem(player, "other.close").click(event -> Menus.PLAY.open(event.getPlayer())))
.open(player);
}
}

View file

@ -0,0 +1,84 @@
package dev.loki.loparkour.menu.play;
import dev.loki.loparkour.config.Locales;
import dev.loki.loparkour.config.Option;
import dev.loki.loparkour.menu.Menus;
import dev.loki.loparkour.menu.ParkourOption;
import dev.loki.loparkour.mode.Modes;
import dev.loki.loparkour.player.ParkourUser;
import dev.loki.loparkour.session.Session;
import dev.loki.loparkour.world.Divider;
import dev.efnilite.vilib.inventory.PagedMenu;
import dev.efnilite.vilib.inventory.item.Item;
import dev.efnilite.vilib.inventory.item.MenuItem;
import dev.efnilite.vilib.util.SkullSetter;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.SkullMeta;
import java.util.ArrayList;
import java.util.List;
/**
* The menu to select other players to spectate
*/
public class SpectatorMenu {
public void open(Player player) {
ParkourUser user = ParkourUser.getUser(player);
String locale = user == null ? Option.OPTIONS_DEFAULTS.get(ParkourOption.LANG) : user.locale;
PagedMenu spectator = new PagedMenu(3, Locales.getString(player, "play.spectator.name"));
List<MenuItem> display = new ArrayList<>();
for (Session session : Divider.sections.keySet()) {
if (!session.isAcceptingSpectators()) { // only showcase sessions with spectators enabled
continue;
}
if (user != null && session == user.session) { // don't let player join their own session
continue;
}
if (session.getPlayers().isEmpty()) { // weird but possible
continue;
}
var pp = session.getPlayers().get(0);
Item item = Locales.getItem(locale, "play.spectator.head", pp.getName());
// Player head gathering
item.material(Material.PLAYER_HEAD);
ItemStack stack = item.build(); // Updating meta requires building
stack.setType(Material.PLAYER_HEAD);
// bedrock has no player skull support
if (!ParkourUser.isBedrockPlayer(player)) {
if (pp.getName() != null && !pp.getName().startsWith(".")) { // bedrock players' names with geyser start with a .
SkullMeta meta = (SkullMeta) stack.getItemMeta();
if (meta != null) {
SkullSetter.setPlayerHead(pp.player, meta);
item.meta(meta);
}
}
}
item.click(event -> Modes.SPECTATOR.create(player, session));
display.add(item);
}
spectator.displayRows(0, 1)
.addToDisplay(display)
.nextPage(26, new Item(Material.LIME_DYE, "<#0DCB07><bold>»").click(event -> spectator.page(1)))
.prevPage(18, new Item(Material.RED_DYE, "<#DE1F1F><bold>«").click(event -> spectator.page(-1)))
.item(22, Locales.getItem(player, "other.close").click(event -> Menus.PLAY.open(event.getPlayer())))
.open(player);
}
}

View file

@ -0,0 +1,46 @@
package dev.loki.loparkour.menu.settings;
import dev.loki.loparkour.config.Locales;
import dev.loki.loparkour.menu.Menus;
import dev.loki.loparkour.player.ParkourPlayer;
import dev.efnilite.vilib.inventory.PagedMenu;
import dev.efnilite.vilib.inventory.item.Item;
import dev.efnilite.vilib.inventory.item.MenuItem;
import org.bukkit.Material;
import java.util.ArrayList;
import java.util.List;
public class LangMenu {
/**
* Opens the language menu
*
* @param user The ParkourPlayer instance
*/
public void open(ParkourPlayer user) {
if (user == null) {
return;
}
PagedMenu style = new PagedMenu(3, Locales.getString(user.locale, "settings.lang.name"));
List<MenuItem> items = new ArrayList<>();
for (String lang : Locales.locales.keySet()) {
Item item = new Item(Material.PAPER, "<#238681><bold>" + Locales.getString(lang, "name"));
items.add(item.click(event -> {
user.locale = lang;
user._locale = lang;
Menus.SETTINGS.open(event.getPlayer());
}));
}
style.displayRows(0, 1).addToDisplay(items)
.nextPage(26, new Item(Material.LIME_DYE, "<#0DCB07><bold>»").click(event -> style.page(1)))
.prevPage(18, new Item(Material.RED_DYE, "<#DE1F1F><bold>«").click(event -> style.page(-1)))
.item(22, Locales.getItem(user.locale, "other.close").click(event -> Menus.SETTINGS.open(event.getPlayer())))
.open(user.player);
}
}

View file

@ -0,0 +1,344 @@
package dev.loki.loparkour.menu.settings;
import dev.loki.loparkour.LoParkour;
import dev.loki.loparkour.api.Registry;
import dev.loki.loparkour.config.Config;
import dev.loki.loparkour.config.Locales;
import dev.loki.loparkour.config.Option;
import dev.loki.loparkour.menu.DynamicMenu;
import dev.loki.loparkour.menu.Menus;
import dev.loki.loparkour.menu.ParkourOption;
import dev.loki.loparkour.player.ParkourPlayer;
import dev.loki.loparkour.player.ParkourUser;
import dev.loki.loparkour.style.RandomStyle;
import dev.loki.loparkour.style.Style;
import dev.efnilite.vilib.fastboard.FastBoard;
import dev.efnilite.vilib.inventory.Menu;
import dev.efnilite.vilib.inventory.MenuClickEvent;
import dev.efnilite.vilib.inventory.PagedMenu;
import dev.efnilite.vilib.inventory.item.Item;
import dev.efnilite.vilib.inventory.item.MenuItem;
import dev.efnilite.vilib.inventory.item.SliderItem;
import dev.efnilite.vilib.inventory.item.TimedItem;
import org.bukkit.ChatColor;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Handles all menu-related activities
*
* @author loki
* @since v3.0.0
*/
public class ParkourSettingsMenu extends DynamicMenu {
public ParkourSettingsMenu(ParkourOption... disabled) {
// ---------- top row ----------
// styles
registerMainItem(1, 0, (p, user) -> Locales.getItem(p, ParkourOption.STYLES.path + ".item", user instanceof ParkourPlayer player ? player.style : null).click(event -> {
if (!(user instanceof ParkourPlayer player)) {
return;
}
openStyleMenu(player);
}), player -> checkOptions(player, ParkourOption.STYLES, disabled));
// leads
registerMainItem(1, 1, (p, user) -> {
Item displayItem = Locales.getItem(p, ParkourOption.LEADS.path);
if (!(user instanceof ParkourPlayer player)) {
return displayItem;
}
List<Integer> leads = Option.POSSIBLE_LEADS;
SliderItem item = new SliderItem().initial(leads.indexOf(player.blockLead)); // initial value of the player
int slot = 0;
for (int value : leads) {
item.add(slot, displayItem.clone()
.amount(value)
.modifyLore(line -> line.replace("%s", Integer.toString(value))),
event2 -> handleSettingChange(player, () -> player.blockLead = value));
slot++;
}
return item;
}, player -> checkOptions(player, ParkourOption.LEADS, disabled));
// schematics
registerMainItem(1, 8, (p, user) -> {
Item item = Locales.getItem(p, ParkourOption.SCHEMATICS.path);
if (!(user instanceof ParkourPlayer player)) {
return item;
}
List<Double> difficulties = List.of(0.0, 0.25, 0.5, 0.75, 1.0);
List<String> values = Locales.getStringList(user.locale, "%s.values".formatted(ParkourOption.SCHEMATICS.path));
if (!difficulties.contains(player.schematicDifficulty)) {
player.schematicDifficulty = difficulties.get(0);
}
return new SliderItem()
.initial(difficulties.indexOf(player.schematicDifficulty))
.add(0, item.clone()
.material(Material.RED_STAINED_GLASS_PANE)
.modifyLore(line -> line.replace("%s", values.get(0))),
event -> handleScoreSettingChange(player, event, () -> player.schematicDifficulty = 0.0))
.add(1, item.clone()
.material(Material.LIME_STAINED_GLASS_PANE)
.modifyLore(line -> line.replace("%s", values.get(1))),
event -> handleScoreSettingChange(player, event, () -> player.schematicDifficulty = 0.25))
.add(2, item.clone()
.material(Material.YELLOW_STAINED_GLASS_PANE)
.modifyLore(line -> line.replace("%s", values.get(2))),
event -> handleScoreSettingChange(player, event, () -> player.schematicDifficulty = 0.5))
.add(3, item.clone()
.material(Material.ORANGE_STAINED_GLASS_PANE)
.modifyLore(line -> line.replace("%s", values.get(3))),
event -> handleScoreSettingChange(player, event, () -> player.schematicDifficulty = 0.75))
.add(4, item.clone()
.material(Material.SKELETON_SKULL)
.modifyLore(line -> line.replace("%s", values.get(4))),
event -> handleScoreSettingChange(player, event, () -> player.schematicDifficulty = 1.0));
}, player -> checkOptions(player, ParkourOption.SCHEMATICS, disabled));
// time
registerMainItem(1, 9, (p, user) -> {
Item item = Locales.getItem(p, ParkourOption.TIME.path);
if (!(user instanceof ParkourPlayer player)) {
return item;
}
// Tick times start at 6:00 and total is 24.000.
// Source: https://minecraft.fandom.com/wiki/Daylight_cycle?file=Day_Night_Clock_24h.png
List<Integer> times = Arrays.asList(0, 6000, 12000, 18000); // 00:00 -> 6:00 -> 12:00 -> 18:00
var format = Config.CONFIG.getInt("options.time.format");
return new SliderItem()
.initial(times.indexOf(player.selectedTime))
.add(0, item.clone()
.modifyLore(line -> line.replace("%s", format == 12 ? "12:00 AM" : "00:00")),
event -> handleSettingChange(player, () -> player.selectedTime = 0))
.add(1, item.clone()
.modifyLore(line -> line.replace("%s", format == 12 ? "6:00 AM" : "6:00")),
event -> handleSettingChange(player, () -> player.selectedTime = 6000))
.add(2, item.clone()
.modifyLore(line -> line.replace("%s", format == 12 ? "12:00 PM" : "12:00")),
event -> handleSettingChange(player, () -> player.selectedTime = 12000))
.add(3, item.clone()
.modifyLore(line -> line.replace("%s", format == 12 ? "6:00 PM" : "18:00")),
event -> handleSettingChange(player, () -> player.selectedTime = 18000));
}, player -> checkOptions(player, ParkourOption.TIME, disabled));
// ---------- bottom row ----------
// show scoreboard
registerMainItem(2, 0, (p, user) -> {
Item item = Locales.getItem(p, ParkourOption.SCOREBOARD.path);
if (!(user instanceof ParkourPlayer player)) {
return item;
}
return new SliderItem()
.initial(player.showScoreboard ? 0 : 1)
.add(0, item.clone()
.material(Material.LIME_STAINED_GLASS_PANE)
.modifyName(name -> "<#0DCB07><bold>" + ChatColor.stripColor(name))
.modifyLore(line -> line.replace("%s", getBooleanSymbol(user, true))),
event -> handleSettingChange(player, () -> {
player.showScoreboard = true;
player.board = new FastBoard(p);
}))
.add(1, item.clone()
.material(Material.RED_STAINED_GLASS_PANE)
.modifyName(name -> "<red><bold>" + ChatColor.stripColor(name))
.modifyLore(line -> line.replace("%s", getBooleanSymbol(user, false))),
event -> handleSettingChange(player, () -> {
player.showScoreboard = false;
if (player.board != null && !player.board.isDeleted()) {
player.board.delete();
}
}));
}, player -> checkOptions(player, ParkourOption.SCOREBOARD, disabled) && Boolean.parseBoolean(Option.OPTIONS_DEFAULTS.get(ParkourOption.SCOREBOARD)));
// show fall message
registerMainItem(2, 1, (p, user) -> {
Item item = Locales.getItem(p, ParkourOption.FALL_MESSAGE.path + ".item");
if (!(user instanceof ParkourPlayer player)) {
return item;
}
return new SliderItem()
.initial(player.showFallMessage ? 0 : 1)
.add(0, item.clone()
.material(Material.LIME_STAINED_GLASS_PANE)
.modifyName(name -> "<#0DCB07><bold>" + ChatColor.stripColor(name))
.modifyLore(line -> line.replace("%s", getBooleanSymbol(user, true))),
event -> handleSettingChange(player, () -> player.showFallMessage = true))
.add(1, item.clone()
.material(Material.RED_STAINED_GLASS_PANE)
.modifyName(name -> "<red><bold>" + ChatColor.stripColor(name))
.modifyLore(line -> line.replace("%s", getBooleanSymbol(user, false))),
event -> handleSettingChange(player, () -> player.showFallMessage = false));
}, player -> checkOptions(player, ParkourOption.FALL_MESSAGE, disabled));
// show sound
registerMainItem(2, 2, (p, user) -> {
Item item = Locales.getItem(p, ParkourOption.PARTICLES.path);
if (!(user instanceof ParkourPlayer player)) {
return item;
}
return new SliderItem()
.initial(player.particles ? 0 : 1)
.add(0, item.clone().material(Material.LIME_STAINED_GLASS_PANE)
.modifyName(name -> "<#0DCB07><bold>" + ChatColor.stripColor(name))
.modifyLore(line -> line.replace("%s", getBooleanSymbol(user, true))),
event -> handleSettingChange(player, () -> player.particles = true))
.add(1, item.clone()
.material(Material.RED_STAINED_GLASS_PANE)
.modifyName(name -> "<red><bold>" + ChatColor.stripColor(name))
.modifyLore(line -> line.replace("%s", getBooleanSymbol(user, false))),
event -> handleSettingChange(player, () -> player.particles = false));
}, player -> checkOptions(player, ParkourOption.PARTICLES, disabled));
// show sound
registerMainItem(2, 3, (p, user) -> {
Item item = Locales.getItem(p, ParkourOption.SOUND.path);
if (!(user instanceof ParkourPlayer player)) {
return item;
}
return new SliderItem()
.initial(player.sound ? 0 : 1)
.add(0, item.clone()
.material(Material.LIME_STAINED_GLASS_PANE)
.modifyName(name -> "<#0DCB07><bold>" + ChatColor.stripColor(name))
.modifyLore(line -> line.replace("%s", getBooleanSymbol(user, true))),
event -> handleSettingChange(player, () -> player.sound = true))
.add(1, item.clone()
.material(Material.RED_STAINED_GLASS_PANE)
.modifyName(name -> "<red><bold>" + ChatColor.stripColor(name))
.modifyLore(line -> line.replace("%s", getBooleanSymbol(user, false))),
event -> handleSettingChange(player, () -> player.sound = false));
}, player -> checkOptions(player, ParkourOption.SOUND, disabled));
// show special blocks
registerMainItem(2, 4, (p, user) -> {
Item item = Locales.getItem(p, ParkourOption.SPECIAL_BLOCKS.path);
if (!(user instanceof ParkourPlayer player)) {
return item;
}
return new SliderItem()
.initial(player.useSpecialBlocks ? 0 : 1)
.add(0, item.clone()
.material(Material.LIME_STAINED_GLASS_PANE)
.modifyName(name -> "<#0DCB07><bold>" + ChatColor.stripColor(name))
.modifyLore(line -> line.replace("%s", getBooleanSymbol(user, true))),
event -> handleScoreSettingChange(player, event, () -> player.useSpecialBlocks = true))
.add(1, item.clone()
.material(Material.RED_STAINED_GLASS_PANE)
.modifyName(name -> "<red><bold>" + ChatColor.stripColor(name))
.modifyLore(line -> line.replace("%s", getBooleanSymbol(user, false))),
event -> handleScoreSettingChange(player, event, () -> player.useSpecialBlocks = false));
}, player -> checkOptions(player, ParkourOption.SPECIAL_BLOCKS, disabled));
// Always allow closing of the menu
registerMainItem(3, 10,
(player, user) -> Locales.getItem(player, "other.close")
.click(event -> Menus.SETTINGS.open(event.getPlayer())),
player -> true);
}
/**
* Shows the main menu to a valid ParkourPlayer instance
*
* @param user The ParkourPlayer
*/
public void open(ParkourPlayer user) {
display(user.player, new Menu(4, Locales.getString(user.locale, "settings.name"))
.distributeRowEvenly(0, 1, 2, 3)
.item(27, Locales.getItem(user.locale, "other.close").click(event -> Menus.SETTINGS.open(event.getPlayer()))));
}
/**
* Opens the style menu
*
* @param player The ParkourPlayer instance
*/
public void openStyleMenu(ParkourPlayer player) {
// init menu
PagedMenu menu = new PagedMenu(3, Locales.getString(player.locale, ParkourOption.STYLES.path + ".name"));
List<MenuItem> items = new ArrayList<>();
for (Style style : Registry.getStyles()) {
var name = style.getName();
var perm = "%s.%s".formatted(ParkourOption.STYLES.permission, name.toLowerCase());
if (Config.CONFIG.getBoolean("permissions.per-style") && !player.player.hasPermission(perm.replace(" ", "."))) {
continue;
}
var category = style instanceof RandomStyle ? "random" : "incremental";
items.add(Locales.getItem(player.player, ParkourOption.STYLES.path + ".style_item", name, category)
.material(style.getNext())
.click(event -> {
player.style = name;
player.updateGeneratorSettings(player.session.generator);
open(player);
}));
}
menu.displayRows(0, 1)
.addToDisplay(items)
.nextPage(26, new Item(Material.LIME_DYE, "<#0DCB07><bold>»").click(event -> menu.page(1)))
.prevPage(18, new Item(Material.RED_DYE, "<#DE1F1F><bold>«").click(event -> menu.page(-1)))
.item(22, Locales.getItem(player.locale, "other.close").click(event -> open(player)))
.open(player.player);
}
private boolean handleSettingChange(ParkourPlayer player, Runnable onAllowed) {
onAllowed.run();
player.updateGeneratorSettings(player.session.generator);
return true;
}
private boolean handleScoreSettingChange(ParkourPlayer player, MenuClickEvent event, Runnable onAllowed) {
if (player.session.generator.score == 0) {
return handleSettingChange(player, onAllowed);
}
event.menu().item(event.slot(), new TimedItem(Locales.getItem(player.locale, "settings.parkour_settings.items.no_change").click((event1) -> {}), event, LoParkour.getPlugin()).stay(5 * 20));
event.menu().updateItem(event.slot());
return false;
}
// replaces true/false with a checkmark and cross
private String getBooleanSymbol(ParkourUser user, boolean value) {
return value ? Locales.getString(user.player, "settings.parkour_settings.enabled") : Locales.getString(user.player, "settings.parkour_settings.disabled");
}
// check if option is allowed to be displayed
private boolean checkOptions(Player player, ParkourOption option, ParkourOption[] disabled) {
return !Arrays.asList(disabled).contains(option) && option.mayPerform(player);
}
}

View file

@ -0,0 +1,75 @@
package dev.loki.loparkour.menu.settings;
import dev.loki.loparkour.config.Locales;
import dev.loki.loparkour.menu.DynamicMenu;
import dev.loki.loparkour.menu.Menus;
import dev.loki.loparkour.menu.ParkourOption;
import dev.loki.loparkour.player.ParkourPlayer;
import dev.loki.loparkour.player.ParkourUser;
import dev.loki.loparkour.session.Session;
import dev.efnilite.vilib.inventory.Menu;
import dev.efnilite.vilib.inventory.item.SliderItem;
import org.bukkit.entity.Player;
import java.util.List;
/**
* The menu for settings
*/
public class SettingsMenu extends DynamicMenu {
public SettingsMenu() {
registerMainItem(1, 0, (player, user) -> Locales.getItem(player, "settings.parkour_settings.item").click(event -> {
ParkourPlayer pp = ParkourPlayer.getPlayer(event.getPlayer());
if (pp != null) {
pp.session.generator.menu(pp);
}
}), player -> ParkourOption.PARKOUR_SETTINGS.mayPerform(player) && ParkourPlayer.isPlayer(player));
registerMainItem(1, 1, (player, user) -> Locales.getItem(player, "settings.lang.item", user != null ? Locales.getString(user.locale, "name") : "?").click(event -> Menus.LANG.open(ParkourPlayer.getPlayer(event.getPlayer()))), player -> ParkourOption.LANG.mayPerform(player) && ParkourUser.isUser(player));
registerMainItem(1, 2, (player, user) -> {
// user has to be not-null to see this item
assert user != null;
List<String> values = Locales.getStringList(user.locale, "settings.chat.values");
return new SliderItem().initial(switch (user.chatType) {
case LOBBY_ONLY -> 0;
case PLAYERS_ONLY -> 1;
case PUBLIC -> 2;
}).add(0, Locales.getItem(player, "settings.chat").modifyLore(lore -> lore.replace("%s", values.get(0))), event -> { // lobby only
ParkourUser u = ParkourUser.getUser(event.getPlayer());
if (u != null) {
u.chatType = Session.ChatType.LOBBY_ONLY;
}
return true;
}).add(1, Locales.getItem(player, "settings.chat").modifyLore(lore -> lore.replace("%s", values.get(1))), event -> { // players only
ParkourUser u = ParkourUser.getUser(event.getPlayer());
if (u != null) {
u.chatType = Session.ChatType.PLAYERS_ONLY;
}
return true;
}).add(2, Locales.getItem(player, "settings.chat").modifyLore(lore -> lore.replace("%s", values.get(2))), event -> { // public
ParkourUser u = ParkourUser.getUser(event.getPlayer());
if (u != null) {
u.chatType = Session.ChatType.PUBLIC;
}
return true;
});
}, player -> ParkourOption.CHAT.mayPerform(player) && ParkourUser.isUser(player));
registerMainItem(2, 0, (player, user) -> Locales.getItem(player, "other.close").click(event -> event.getPlayer().closeInventory()), player -> true);
}
public void open(Player player) {
display(player, new Menu(3, Locales.getString(player, "settings.name")).distributeRowsEvenly());
}
}