fix and migrate 70%
This commit is contained in:
parent
8705cad578
commit
d1dcf6ab57
138 changed files with 3736 additions and 4735 deletions
|
|
@ -1,645 +1,7 @@
|
|||
package dev.loki.loparkour;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import dev.loki.loparkour.util.Item;
|
||||
import dev.loki.loparkour.util.ParticleData;
|
||||
|
||||
import dev.loki.loparkour.api.Registry;
|
||||
import dev.loki.loparkour.config.Config;
|
||||
import dev.loki.loparkour.config.Locales;
|
||||
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.mode.Modes;
|
||||
import dev.loki.loparkour.mode.MultiMode;
|
||||
import dev.loki.loparkour.player.ParkourPlayer;
|
||||
import dev.loki.loparkour.player.ParkourUser;
|
||||
import dev.loki.loparkour.player.data.InventoryData;
|
||||
import dev.loki.loparkour.schematic.lpschem.LPSchematic;
|
||||
import dev.loki.loparkour.schematic.lpschem.LPSchematicBuilder;
|
||||
import dev.loki.loparkour.schematic.lpschem.SchematicConverter;
|
||||
import dev.loki.loparkour.session.Session;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.TabCompleter;
|
||||
|
||||
import dev.loki.loparkour.util.ParticleUtil;
|
||||
|
||||
import dev.loki.loparkour.util.Locations;
|
||||
import dev.loki.loparkour.util.ColorUtil;
|
||||
import org.bukkit.*;
|
||||
import org.bukkit.command.BlockCommandSender;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.util.BoundingBox;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public class Command implements CommandExecutor, TabCompleter {
|
||||
|
||||
public static final HashMap<Player, Location[]> selections = new HashMap<>();
|
||||
|
||||
private static final ItemStack WAND = new Item(Material.GOLDEN_AXE, "<red><bold>LPSchematic Wand")
|
||||
.lore("<gray>Left click: first position", "<gray>Right click: second position")
|
||||
.build();
|
||||
|
||||
@Override
|
||||
public boolean onCommand(CommandSender sender, org.bukkit.command.Command command, String label, String[] args) {
|
||||
Player player = null;
|
||||
if (sender instanceof Player) {
|
||||
player = (Player) sender;
|
||||
}
|
||||
|
||||
switch (args.length) {
|
||||
case 0 -> handle0Args(sender, player);
|
||||
case 1 -> handle1Args(args[0], sender, player);
|
||||
case 2 -> handle2Args(args[0], args[1], sender, player);
|
||||
case 3 -> handle3Args(args[0], args[1], args[2], sender, player);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> onTabComplete(CommandSender sender, org.bukkit.command.Command command, String alias, String[] args) {
|
||||
List<String> completions = new ArrayList<>();
|
||||
switch (args.length) {
|
||||
case 1 -> {
|
||||
if (ParkourOption.JOIN.mayPerform(sender)) {
|
||||
completions.add("join");
|
||||
completions.add("leave");
|
||||
}
|
||||
if (ParkourOption.MAIN.mayPerform(sender)) {
|
||||
completions.add("menu");
|
||||
}
|
||||
if (ParkourOption.PLAY.mayPerform(sender)) {
|
||||
completions.add("play");
|
||||
}
|
||||
if (ParkourOption.LEADERBOARDS.mayPerform(sender)) {
|
||||
completions.add("leaderboard");
|
||||
}
|
||||
if (sender.hasPermission(ParkourOption.ADMIN.permission)) {
|
||||
completions.add("schematic");
|
||||
completions.add("reload");
|
||||
completions.add("forcejoin");
|
||||
completions.add("forceleave");
|
||||
completions.add("reset");
|
||||
completions.add("recoverinventory");
|
||||
}
|
||||
return completions(args[0], completions);
|
||||
}
|
||||
case 2 -> {
|
||||
if (args[0].equalsIgnoreCase("reset") && sender.hasPermission(ParkourOption.ADMIN.permission)) {
|
||||
completions.add("everyone");
|
||||
for (ParkourPlayer pp : ParkourPlayer.getPlayers()) {
|
||||
completions.add(pp.getName());
|
||||
}
|
||||
} else if (args[0].equalsIgnoreCase("join") && sender.hasPermission(ParkourOption.JOIN.permission)) {
|
||||
for (ParkourPlayer pp : ParkourPlayer.getPlayers()) {
|
||||
completions.add(pp.getName());
|
||||
}
|
||||
} else if (args[0].equalsIgnoreCase("schematic") && sender.hasPermission(ParkourOption.ADMIN.permission)) {
|
||||
completions.addAll(Arrays.asList("wand", "pos1", "pos2", "save", "paste"));
|
||||
} else if (args[0].equalsIgnoreCase("forcejoin") && sender.hasPermission(ParkourOption.ADMIN.permission)) {
|
||||
completions.add("nearest");
|
||||
completions.add("everyone");
|
||||
|
||||
for (Player pl : Bukkit.getOnlinePlayers()) {
|
||||
completions.add(pl.getName());
|
||||
}
|
||||
} else if (args[0].equalsIgnoreCase("forceleave") && sender.hasPermission(ParkourOption.ADMIN.permission)) {
|
||||
completions.add("everyone");
|
||||
|
||||
for (Player pl : Bukkit.getOnlinePlayers()) {
|
||||
completions.add(pl.getName());
|
||||
}
|
||||
} else if (args[0].equalsIgnoreCase("recoverinventory") && sender.hasPermission(ParkourOption.ADMIN.permission)) {
|
||||
for (Player pl : Bukkit.getOnlinePlayers()) {
|
||||
completions.add(pl.getName());
|
||||
}
|
||||
}
|
||||
return completions(args[1], completions);
|
||||
}
|
||||
default -> {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handle0Args(@NotNull CommandSender sender, @Nullable Player player) {
|
||||
if (player != null && ParkourOption.MAIN.mayPerform(player)) {
|
||||
// TODO: Migrate to LoLib GUI
|
||||
// Menus.*.open(player);
|
||||
player.sendMessage("§cМеню временно недоступно во время миграции");
|
||||
return;
|
||||
}
|
||||
sendHelpMessages(sender);
|
||||
}
|
||||
|
||||
private void sendHelpMessages(CommandSender sender) {
|
||||
send(sender, "");
|
||||
send(sender, "<dark_gray><strikethrough>---------------<reset> %s <dark_gray><strikethrough>---------------<reset>".formatted(LoParkour.NAME));
|
||||
send(sender, "");
|
||||
send(sender, "<gray>/parkour <dark_gray>- Main command");
|
||||
if (sender.hasPermission(ParkourOption.JOIN.permission)) {
|
||||
send(sender, "<gray>/parkour join [mode/player] <dark_gray>- Join the default mode or specify one.");
|
||||
send(sender, "<gray>/parkour leave <dark_gray>- Leave the game on this server");
|
||||
}
|
||||
if (sender.hasPermission(ParkourOption.MAIN.permission)) {
|
||||
send(sender, "<gray>/parkour menu <dark_gray>- Open the menu");
|
||||
}
|
||||
if (sender.hasPermission(ParkourOption.PLAY.permission)) {
|
||||
send(sender, "<gray>/parkour play <dark_gray>- Mode selection menu");
|
||||
}
|
||||
if (sender.hasPermission(ParkourOption.LEADERBOARDS.permission)) {
|
||||
send(sender, "<gray>/parkour leaderboard [type]<dark_gray>- Open the leaderboard of a mode");
|
||||
}
|
||||
if (sender.hasPermission(ParkourOption.ADMIN.permission)) {
|
||||
send(sender, "<gray>/LoParkour schematic <dark_gray>- Create a schematic");
|
||||
send(sender, "<gray>/LoParkour reload <dark_gray>- Reloads the messages-v3.yml file");
|
||||
send(sender, "<gray>/LoParkour reset <everyone/player> <dark_gray>- Resets all high scores. <red>This can't be recovered!");
|
||||
send(sender, "<gray>/LoParkour forcejoin <everyone/nearest/player> <dark_gray>- Forces a specific player, the nearest or everyone to join");
|
||||
send(sender, "<gray>/LoParkour forceleave <everyone/nearest/player> <dark_gray>- Forces a specific player, the nearest or everyone to leave");
|
||||
send(sender, "<gray>/LoParkour recoverinventory <player> <dark_gray>- Recover a player's saved inventory. <red>Useful for recovering data after server crashes or errors when leaving.");
|
||||
}
|
||||
send(sender, "");
|
||||
}
|
||||
|
||||
private void handle1Args(@NotNull String arg, @NotNull CommandSender sender, @Nullable Player player) {
|
||||
switch (arg.toLowerCase()) {
|
||||
case "help" -> sendHelpMessages(sender);
|
||||
case "reload" -> {
|
||||
if (!cooldown(sender, "reload", 2500)) {
|
||||
return;
|
||||
}
|
||||
if (!sender.hasPermission(ParkourOption.ADMIN.permission)) {
|
||||
send(sender, Locales.getString(player, "other.no_do"));
|
||||
return;
|
||||
}
|
||||
|
||||
Config.reload(false);
|
||||
|
||||
send(sender, "%sReloaded config files.".formatted(LoParkour.PREFIX));
|
||||
}
|
||||
}
|
||||
|
||||
if (player == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (arg.toLowerCase()) {
|
||||
case "join" -> {
|
||||
if (!cooldown(sender, "join", 2500)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ParkourOption.JOIN.mayPerform(player)) {
|
||||
send(sender, Locales.getString(player, "other.no_do"));
|
||||
return;
|
||||
}
|
||||
|
||||
ParkourUser user = ParkourUser.getUser(player);
|
||||
if (user != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Modes.DEFAULT.create(player);
|
||||
}
|
||||
case "play" -> {
|
||||
if (ParkourOption.PLAY.mayPerform(player)) {
|
||||
// TODO: Migrate to LoLib GUI
|
||||
// Menus.*.open(player);
|
||||
player.sendMessage("§cМеню временно недоступно во время миграции");
|
||||
}
|
||||
}
|
||||
case "leave" -> {
|
||||
if (!cooldown(sender, "leave", 2500)) {
|
||||
return;
|
||||
}
|
||||
ParkourUser.leave(player);
|
||||
}
|
||||
case "menu", "main" -> {
|
||||
if (!ParkourOption.MAIN.mayPerform(player)) {
|
||||
// TODO: Migrate to LoLib GUI
|
||||
// Menus.*.open(player);
|
||||
player.sendMessage("§cМеню временно недоступно во время миграции");
|
||||
}
|
||||
}
|
||||
case "leaderboard" -> player.performCommand("LoParkour leaderboard invalid");
|
||||
case "schematic" -> {
|
||||
if (!player.hasPermission(ParkourOption.ADMIN.permission)) { // default players shouldn't have access even if perms are disabled
|
||||
send(sender, Locales.getString(player, "other.no_do"));
|
||||
return;
|
||||
}
|
||||
|
||||
send(player, "");
|
||||
send(player, "<red>/LoParkour schematic wand <dark_gray>- <gray>Get the schematic wand");
|
||||
send(player, "<red>/LoParkour schematic pos1 <dark_gray>- <gray>Set the first position of your selection");
|
||||
send(player, "<red>/LoParkour schematic pos2 <dark_gray>- <gray>Set the second position of your selection");
|
||||
send(player, "<red>/LoParkour schematic save <name> <dark_gray>- <gray>Save selection to .lpschem format");
|
||||
send(player, "<red>/LoParkour schematic paste <name> <dark_gray>- <gray>Paste a .lpschem schematic");
|
||||
send(player, "<red>/LoParkour schematic list <dark_gray>- <gray>List all loaded schematics");
|
||||
send(player, "<red>/LoParkour schematic reload <dark_gray>- <gray>Reload all schematics");
|
||||
send(player, "<red>/LoParkour schematic convert <dark_gray>- <gray>Convert old schematics to .lpschem");
|
||||
send(player, "");
|
||||
send(player, "<dark_gray><underlined>Have any questions or need help? Join the Discord.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handle2Args(@NotNull String arg1, @NotNull String arg2, @NotNull CommandSender sender, @Nullable Player player) {
|
||||
switch (arg1.toLowerCase()) {
|
||||
case "forcejoin" -> {
|
||||
if (!sender.hasPermission(ParkourOption.ADMIN.permission)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (arg2.equalsIgnoreCase("everyone")) {
|
||||
Bukkit.getOnlinePlayers().forEach(other -> Modes.DEFAULT.create(other));
|
||||
send(sender, LoParkour.PREFIX + "Successfully force joined everyone!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (arg2.equalsIgnoreCase("nearest")) {
|
||||
Player closest = null;
|
||||
double distance = Double.MAX_VALUE;
|
||||
|
||||
// if player is found get location from player
|
||||
// if no player is found, get location from command block
|
||||
// if no command block is found, return null
|
||||
Location from = sender instanceof Player ? ((Player) sender).getLocation() : (sender instanceof BlockCommandSender ? ((BlockCommandSender) sender).getBlock().getLocation() : null);
|
||||
|
||||
if (from == null || from.getWorld() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// get the closest player
|
||||
for (Player p : from.getWorld().getPlayers()) {
|
||||
double d = p.getLocation().distance(from);
|
||||
|
||||
if (d < distance) {
|
||||
distance = d;
|
||||
closest = p;
|
||||
}
|
||||
}
|
||||
|
||||
// no closest player found
|
||||
if (closest == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
send(sender, LoParkour.PREFIX + "Successfully force joined " + closest.getName() + "!");
|
||||
Modes.DEFAULT.create(closest);
|
||||
return;
|
||||
}
|
||||
|
||||
Player other = Bukkit.getPlayer(arg2);
|
||||
if (other == null) {
|
||||
send(sender, LoParkour.PREFIX + "That player isn't online!");
|
||||
return;
|
||||
}
|
||||
|
||||
Modes.DEFAULT.create(other);
|
||||
}
|
||||
case "forceleave" -> {
|
||||
if (!sender.hasPermission(ParkourOption.ADMIN.permission)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (arg2.equalsIgnoreCase("everyone")) {
|
||||
ParkourPlayer.getPlayers().forEach(ParkourUser::leave);
|
||||
send(sender, LoParkour.PREFIX + "Successfully force kicked everyone!");
|
||||
return;
|
||||
}
|
||||
|
||||
Player other = Bukkit.getPlayer(arg2);
|
||||
if (other == null) {
|
||||
send(sender, LoParkour.PREFIX + "That player isn't online!");
|
||||
return;
|
||||
}
|
||||
|
||||
ParkourUser user = ParkourUser.getUser(other);
|
||||
if (user == null) {
|
||||
send(sender, LoParkour.PREFIX + "That player isn't currently playing!");
|
||||
return;
|
||||
}
|
||||
|
||||
ParkourUser.leave(user);
|
||||
}
|
||||
case "reset" -> {
|
||||
if (!sender.hasPermission(ParkourOption.ADMIN.permission) || !cooldown(sender, "reset", 2500)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (arg2.equalsIgnoreCase("everyone")) {
|
||||
for (Mode mode : Registry.getModes()) {
|
||||
Leaderboard leaderboard = mode.getLeaderboard();
|
||||
|
||||
if (leaderboard == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
leaderboard.resetAll();
|
||||
leaderboard.write(true);
|
||||
}
|
||||
|
||||
send(sender, LoParkour.PREFIX + "Successfully reset all high scores in memory and the files.");
|
||||
return;
|
||||
}
|
||||
String name = null;
|
||||
UUID uuid = null;
|
||||
|
||||
// Check online players
|
||||
Player online = Bukkit.getPlayerExact(arg2);
|
||||
if (online != null) {
|
||||
name = online.getName();
|
||||
uuid = online.getUniqueId();
|
||||
}
|
||||
|
||||
// Check uuid
|
||||
if (arg2.contains("-")) {
|
||||
uuid = UUID.fromString(arg2);
|
||||
}
|
||||
|
||||
// Check offline player
|
||||
if (uuid == null) {
|
||||
OfflinePlayer offline = Bukkit.getOfflinePlayer(arg2);
|
||||
name = offline.getName();
|
||||
uuid = offline.getUniqueId();
|
||||
}
|
||||
|
||||
UUID finalUuid = uuid;
|
||||
String finalName = name;
|
||||
|
||||
for (Mode mode : Registry.getModes()) {
|
||||
Leaderboard leaderboard = mode.getLeaderboard();
|
||||
|
||||
if (leaderboard == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
leaderboard.remove(finalUuid);
|
||||
leaderboard.write(true);
|
||||
}
|
||||
|
||||
send(sender, LoParkour.PREFIX + "Successfully reset the high score of " + finalName + " in memory and the files.");
|
||||
}
|
||||
case "recoverinventory" -> {
|
||||
if (!cooldown(sender, "recoverinventory", 2500) || !sender.hasPermission(ParkourOption.ADMIN.permission)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Player other = Bukkit.getPlayer(arg2);
|
||||
if (other == null) {
|
||||
send(sender, LoParkour.PREFIX + "That player isn't online!");
|
||||
return;
|
||||
}
|
||||
|
||||
new InventoryData(other).load(result -> {
|
||||
if (result != null) {
|
||||
send(sender, "%sSuccessfully recovered the inventory of %s from their file".formatted(LoParkour.PREFIX, other.getName()));
|
||||
send(sender, "%sGiving %s their items now...".formatted(LoParkour.PREFIX, other.getName()));
|
||||
} else {
|
||||
send(sender, "%s<red>There was an error recovering the inventory of %s from their file".formatted(LoParkour.PREFIX, other.getName()));
|
||||
send(sender, "%s%s has no saved inventory or there was an error. Check the console.".formatted(LoParkour.PREFIX, other.getName()));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (player == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (arg1) {
|
||||
case "join" -> {
|
||||
if (!cooldown(sender, "join", 2500) || !ParkourOption.JOIN.mayPerform(player)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Mode mode = Registry.getMode(arg2);
|
||||
|
||||
if (mode != null) {
|
||||
mode.create(player);
|
||||
return;
|
||||
}
|
||||
|
||||
Player other = Bukkit.getPlayer(arg2);
|
||||
|
||||
if (other == null) {
|
||||
send(sender, "%sUnknown player! Try typing the name again.".formatted(LoParkour.PREFIX)); // could not find, so go to default
|
||||
return;
|
||||
}
|
||||
|
||||
ParkourPlayer parkourPlayer = ParkourPlayer.getPlayer(other);
|
||||
|
||||
if (parkourPlayer == null) {
|
||||
send(sender, "%sUnknown player! Try typing the name again.".formatted(LoParkour.PREFIX)); // could not find, so go to default
|
||||
return;
|
||||
}
|
||||
|
||||
ParkourUser user = ParkourUser.getUser(player);
|
||||
Session session = parkourPlayer.session;
|
||||
if (user != null && user.session == session) { // already in same session
|
||||
return;
|
||||
}
|
||||
|
||||
if (session.isAcceptingPlayers()) {
|
||||
((MultiMode) session.generator.getMode()).join(player, session);
|
||||
} else {
|
||||
Modes.SPECTATOR.create(player, session);
|
||||
}
|
||||
}
|
||||
case "leaderboard" -> {
|
||||
if (!ParkourOption.LEADERBOARDS.mayPerform(player)) {
|
||||
send(sender, Locales.getString(player, "other.no_do"));
|
||||
return;
|
||||
}
|
||||
|
||||
Mode mode = Registry.getMode(arg2.toLowerCase());
|
||||
|
||||
// if found gamemode is null, return to default
|
||||
if (mode == null) {
|
||||
// TODO: Migrate to LoLib GUI
|
||||
// Menus.*.open(player);
|
||||
player.sendMessage("§cМеню временно недоступно во время миграции");
|
||||
} else {
|
||||
// TODO: Migrate to LoLib GUI
|
||||
// Menus.SINGLE_LEADERBOARD.open(player, mode, Leaderboard.Sort.SCORE);
|
||||
player.sendMessage("§cМеню временно недоступно во время миграции");
|
||||
}
|
||||
}
|
||||
case "schematic" -> {
|
||||
if (!sender.hasPermission(ParkourOption.ADMIN.permission)) {
|
||||
send(sender, Locales.getString(player, "other.no_do"));
|
||||
return;
|
||||
}
|
||||
|
||||
Location playerLocation = player.getLocation();
|
||||
Location[] existingSelection = selections.get(player);
|
||||
|
||||
switch (arg2.toLowerCase()) {
|
||||
case "wand" -> {
|
||||
player.getInventory().addItem(WAND);
|
||||
|
||||
send(player, "<dark_gray>----------- <dark_red><bold>Schematics <reset><dark_gray>-----------");
|
||||
send(player, "<gray><red>Left click<gray> -> set first position | <red>Right click<gray> -> set second position");
|
||||
send(player, "<gray>If you can't place a block and need to set a position mid-air, use <dark_gray>/LoParkour schematic pos1/pos2 <gray>instead.");
|
||||
}
|
||||
case "pos1" -> {
|
||||
send(player, "%sPosition 1 was set to %s".formatted(LoParkour.PREFIX, Locations.toString(playerLocation, true)));
|
||||
|
||||
if (existingSelection == null) {
|
||||
selections.put(player, new Location[]{playerLocation, null});
|
||||
return;
|
||||
}
|
||||
|
||||
selections.put(player, new Location[]{playerLocation, existingSelection[1]});
|
||||
|
||||
ParticleUtil.box(BoundingBox.of(playerLocation, existingSelection[1]), player.getWorld(), Particle.END_ROD, player, 0.2);
|
||||
}
|
||||
case "pos2" -> {
|
||||
send(player, "%sPosition 2 was set to %s".formatted(LoParkour.PREFIX, Locations.toString(playerLocation, true)));
|
||||
|
||||
if (existingSelection == null) {
|
||||
selections.put(player, new Location[]{null, playerLocation});
|
||||
return;
|
||||
}
|
||||
|
||||
selections.put(player, new Location[]{existingSelection[0], playerLocation});
|
||||
|
||||
ParticleUtil.box(BoundingBox.of(existingSelection[0], playerLocation), player.getWorld(), Particle.END_ROD, player, 0.2);
|
||||
}
|
||||
case "save" -> {
|
||||
if (!cooldown(sender, "LoParkour save schematic", 2500)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (existingSelection == null || existingSelection[0] == null || existingSelection[1] == null) {
|
||||
send(player, "<dark_red><bold>Schematics <reset><gray>Your schematic isn't complete yet. Make sure you've set the first and second position.");
|
||||
return;
|
||||
}
|
||||
|
||||
String code = UUID.randomUUID().toString().split("-")[0];
|
||||
|
||||
send(player, ("<dark_red><bold>Schematics <reset><gray>Your schematic is being saved. It will use code <red>'%s'<gray>. " + "You can change the code to whatever you like. " + "Don't forget to add this schematic to <dark_gray>schematics.yml<gray>.").formatted(code));
|
||||
|
||||
// TODO: LPSchematic.save(file, pos1, pos2);
|
||||
}
|
||||
case "list" -> {
|
||||
send(player, "<dark_red><bold>Schematics <reset><gray>Loaded schematics:");
|
||||
LoParkour.getSchematicManager().getAllSchematics().forEach((name, schem) -> {
|
||||
send(player, "<gray>- <red>" + name + " <dark_gray>(" + schem.getMetadata().getDifficulty() + ")");
|
||||
});
|
||||
}
|
||||
case "reload" -> {
|
||||
if (!cooldown(sender, "LoParkour reload schematics", 2500)) {
|
||||
return;
|
||||
}
|
||||
LoParkour.getSchematicManager().reload();
|
||||
send(player, "<dark_red><bold>Schematics <reset><gray>Reloaded all schematics.");
|
||||
}
|
||||
case "convert" -> {
|
||||
if (!cooldown(sender, "LoParkour convert schematics", 5000)) {
|
||||
return;
|
||||
}
|
||||
send(player, "<dark_red><bold>Schematics <reset><gray>Converting old schematics to .lpschem format...");
|
||||
SchematicConverter.convertAll();
|
||||
send(player, "<dark_red><bold>Schematics <reset><gray>Conversion complete! Check console for details.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handle3Args(@NotNull String arg1, @NotNull String arg2, @NotNull String arg3, @NotNull CommandSender sender, @Nullable Player player) {
|
||||
if (player == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (arg1.equalsIgnoreCase("schematic")) {
|
||||
if (!player.hasPermission(ParkourOption.ADMIN.permission)) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (arg2.toLowerCase()) {
|
||||
case "save" -> {
|
||||
if (!cooldown(sender, "LoParkour save schematic", 2500)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Location[] selection = selections.get(player);
|
||||
if (selection == null || selection[0] == null || selection[1] == null) {
|
||||
send(player, "<dark_red><bold>Schematics <reset><gray>Your schematic isn't complete yet. Make sure you've set the first and second position.");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
int width = Math.abs(selection[1].getBlockX() - selection[0].getBlockX()) + 1;
|
||||
int height = Math.abs(selection[1].getBlockY() - selection[0].getBlockY()) + 1;
|
||||
int length = Math.abs(selection[1].getBlockZ() - selection[0].getBlockZ()) + 1;
|
||||
|
||||
LPSchematic schematic = new LPSchematicBuilder(arg3, player.getName(), 0.5, width, height, length)
|
||||
.fromWorld(player.getWorld(), selection[0], selection[1])
|
||||
.build();
|
||||
|
||||
LoParkour.getSchematicManager().saveSchematic(schematic);
|
||||
send(player, "<dark_red><bold>Schematics <reset><gray>Saved schematic as <red>" + arg3 + "<gray>!");
|
||||
} catch (Exception e) {
|
||||
send(player, "<dark_red><bold>Schematics <reset><gray>Failed to save schematic: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
case "paste" -> {
|
||||
LPSchematic lpSchematic = LoParkour.getSchematicManager().getSchematic(arg3);
|
||||
if (lpSchematic != null) {
|
||||
lpSchematic.paste(player.getLocation(), player.getWorld());
|
||||
send(sender, "%sPasted .lpschem schematic %s".formatted(LoParkour.PREFIX, arg3));
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Implement schematic loading with LoLib
|
||||
LPSchematic oldSchematic = null;
|
||||
if (oldSchematic == null) {
|
||||
send(sender, "%sCouldn't find %s".formatted(LoParkour.PREFIX, arg3));
|
||||
return;
|
||||
}
|
||||
|
||||
// oldSchematic.paste(player.getLocation(), player.getWorld());
|
||||
send(sender, "%sPasted old schematic %s".formatted(LoParkour.PREFIX, arg3));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void send(CommandSender sender, String message) {
|
||||
sender.sendMessage(ColorUtil.color(message));
|
||||
}
|
||||
|
||||
private final Map<String, Long> cooldowns = new HashMap<>();
|
||||
|
||||
private boolean cooldown(CommandSender sender, String key, long millis) {
|
||||
String fullKey = sender.getName() + ":" + key;
|
||||
long now = System.currentTimeMillis();
|
||||
Long last = cooldowns.get(fullKey);
|
||||
|
||||
if (last != null && now - last < millis) {
|
||||
return false;
|
||||
}
|
||||
|
||||
cooldowns.put(fullKey, now);
|
||||
return true;
|
||||
}
|
||||
|
||||
private List<String> completions(String arg, List<String> completions) {
|
||||
return completions.stream()
|
||||
.filter(s -> s.toLowerCase().startsWith(arg.toLowerCase()))
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @deprecated Replaced by {@link LoParkourCommand}. Delete this file.
|
||||
*/
|
||||
@Deprecated
|
||||
class Command extends LoParkourCommand {}
|
||||
|
|
|
|||
|
|
@ -1,328 +1,12 @@
|
|||
package dev.loki.loparkour;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import dev.loki.loparkour.util.ParticleData;
|
||||
|
||||
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.Modes;
|
||||
import dev.loki.loparkour.player.ParkourPlayer;
|
||||
import dev.loki.loparkour.player.ParkourUser;
|
||||
import dev.loki.loparkour.session.Session;
|
||||
import dev.loki.loparkour.world.World;
|
||||
import org.bukkit.event.Listener;
|
||||
|
||||
import dev.loki.loparkour.util.ParticleUtil;
|
||||
import dev.loki.loparkour.util.Locations;
|
||||
import dev.loki.loparkour.util.ColorUtil;
|
||||
import io.papermc.lib.PaperLib;
|
||||
import org.bukkit.*;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.Cancellable;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.block.Action;
|
||||
import org.bukkit.event.block.BlockBreakEvent;
|
||||
import org.bukkit.event.block.BlockPlaceEvent;
|
||||
import org.bukkit.event.entity.EntityDamageEvent;
|
||||
import org.bukkit.event.inventory.InventoryClickEvent;
|
||||
import org.bukkit.event.inventory.InventoryType;
|
||||
import org.bukkit.event.player.*;
|
||||
import org.bukkit.inventory.EquipmentSlot;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.PlayerInventory;
|
||||
import org.bukkit.util.BoundingBox;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
/**
|
||||
* Internal event handler
|
||||
* @deprecated Split into:
|
||||
* - {@link dev.loki.loparkour.listener.PlayerConnectionListener}
|
||||
* - {@link dev.loki.loparkour.listener.ParkourRestrictionListener}
|
||||
* - {@link dev.loki.loparkour.listener.SchematicWandListener}
|
||||
*
|
||||
* Delete this file after verifying compilation.
|
||||
*/
|
||||
@ApiStatus.Internal
|
||||
public class Events implements Listener {
|
||||
|
||||
@EventHandler
|
||||
public void chat(AsyncPlayerChatEvent event) {
|
||||
if (!Option.OPTIONS_ENABLED.get(ParkourOption.CHAT)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Player player = event.getPlayer();
|
||||
ParkourUser user = ParkourUser.getUser(player);
|
||||
|
||||
if (user == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Session session = user.session;
|
||||
|
||||
if (session.isMuted(user)) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.setCancelled(true);
|
||||
switch (user.chatType) {
|
||||
case LOBBY_ONLY -> session.getUsers().forEach(other -> other.sendTranslated("settings.chat.formats.lobby", player.getName(), event.getMessage()));
|
||||
case PLAYERS_ONLY -> session.getPlayers().forEach(other -> other.sendTranslated("settings.chat.formats.players", player.getName(), event.getMessage()));
|
||||
default -> event.setCancelled(false);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
public void join(PlayerJoinEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
|
||||
if (Config.CONFIG.getBoolean("bungeecord.enabled")) {
|
||||
Modes.DEFAULT.create(player);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!player.getWorld().equals(World.getWorld())) {
|
||||
return;
|
||||
}
|
||||
|
||||
org.bukkit.World fallback = Bukkit.getWorld(Config.CONFIG.getString("world.fall-back"));
|
||||
|
||||
if (fallback != null) {
|
||||
PaperLib.teleportAsync(player, fallback.getSpawnLocation(), PlayerTeleportEvent.TeleportCause.PLUGIN);
|
||||
return;
|
||||
}
|
||||
|
||||
PaperLib.teleportAsync(player, Bukkit.getWorlds().stream()
|
||||
.filter(world -> !world.equals(World.getWorld()))
|
||||
.findAny()
|
||||
.orElseThrow(() -> new NoSuchElementException("No fallback world was found!"))
|
||||
.getSpawnLocation());
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void leave(PlayerQuitEvent event) {
|
||||
ParkourUser user = ParkourUser.getUser(event.getPlayer());
|
||||
|
||||
if (user == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
ParkourUser.unregister(user, true, false, true);
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGHEST)
|
||||
public void command(PlayerCommandPreprocessEvent event) {
|
||||
if (!Config.CONFIG.getBoolean("focus-mode.enabled")) {
|
||||
return;
|
||||
}
|
||||
|
||||
ParkourUser user = ParkourUser.getUser(event.getPlayer());
|
||||
|
||||
if (user == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
String command = event.getMessage().toLowerCase();
|
||||
if (Config.CONFIG.getStringList("focus-mode.whitelist").stream().anyMatch(c -> command.contains(c.toLowerCase()))) {
|
||||
return;
|
||||
}
|
||||
|
||||
user.sendTranslated("other.no_do");
|
||||
event.setCancelled(true);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void interactWand(PlayerInteractEvent event) {
|
||||
Action action = event.getAction();
|
||||
Player player = event.getPlayer();
|
||||
ItemStack item = player.getInventory().getItemInMainHand();
|
||||
|
||||
if (!player.hasPermission("LoParkour.admin") || item.getItemMeta() == null || !item.getItemMeta().getDisplayName().contains("LPSchematic Wand") || event.getClickedBlock() == null || event.getHand() != EquipmentSlot.HAND) {
|
||||
return;
|
||||
}
|
||||
|
||||
Location location = event.getClickedBlock().getLocation();
|
||||
Location[] existingSelection = Command.selections.get(player);
|
||||
|
||||
event.setCancelled(true);
|
||||
|
||||
switch (action) {
|
||||
case LEFT_CLICK_BLOCK -> {
|
||||
send(player, LoParkour.PREFIX + "Position 1 was set to " + Locations.toString(location, true));
|
||||
|
||||
if (existingSelection == null) {
|
||||
Command.selections.put(player, new Location[]{location, null});
|
||||
return;
|
||||
}
|
||||
|
||||
Command.selections.put(player, new Location[]{location, existingSelection[1]});
|
||||
|
||||
ParticleUtil.box(BoundingBox.of(location, existingSelection[1]), player.getWorld(), Particle.END_ROD, player, 0.2);
|
||||
}
|
||||
case RIGHT_CLICK_BLOCK -> {
|
||||
send(player, LoParkour.PREFIX + "Position 2 was set to " + Locations.toString(location, true));
|
||||
|
||||
if (existingSelection == null) {
|
||||
Command.selections.put(player, new Location[]{null, location});
|
||||
return;
|
||||
}
|
||||
|
||||
Command.selections.put(player, new Location[]{existingSelection[0], location});
|
||||
|
||||
ParticleUtil.box(BoundingBox.of(existingSelection[0], location), player.getWorld(), Particle.END_ROD, player, 0.2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGHEST)
|
||||
public void interact(PlayerInteractEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
ParkourPlayer pp = ParkourPlayer.getPlayer(player);
|
||||
|
||||
if (pp == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean type = event.getClickedBlock() != null && (event.getClickedBlock().getType() == Material.DISPENSER || event.getClickedBlock().getType() == Material.DROPPER || event.getClickedBlock().getType() == Material.HOPPER);
|
||||
|
||||
if (event.getAction() == Action.RIGHT_CLICK_BLOCK && type && event.getHand() == EquipmentSlot.HAND) {
|
||||
event.setCancelled(true);
|
||||
player.closeInventory();
|
||||
return;
|
||||
}
|
||||
|
||||
boolean action = (event.getAction() == Action.RIGHT_CLICK_AIR || event.getAction() == Action.RIGHT_CLICK_BLOCK) && event.getHand() == EquipmentSlot.HAND;
|
||||
|
||||
if (!action) {
|
||||
return;
|
||||
}
|
||||
|
||||
Material held = getHeldItem(player).getType();
|
||||
|
||||
Material play = Locales.getItem(player, "play.item").build().getType();
|
||||
Material community = Locales.getItem(player, "community.item").build().getType();
|
||||
Material settings = Locales.getItem(player, "settings.item").build().getType();
|
||||
Material lobby = Locales.getItem(player, "lobby.item").build().getType();
|
||||
Material quit = Locales.getItem(player, "other.quit").build().getType();
|
||||
|
||||
event.setCancelled(true);
|
||||
|
||||
if (held == play) {
|
||||
// TODO: Migrate to LoLib GUI
|
||||
// Menus.*.open(player);
|
||||
player.sendMessage("§cМеню временно недоступно во время миграции");
|
||||
} else if (held == community) {
|
||||
// TODO: Migrate to LoLib GUI
|
||||
// Menus.*.open(player);
|
||||
player.sendMessage("§cМеню временно недоступно во время миграции");
|
||||
} else if (held == settings) {
|
||||
// TODO: Migrate to LoLib GUI
|
||||
// Menus.*.open(player);
|
||||
player.sendMessage("§cМеню временно недоступно во время миграции");
|
||||
} else if (held == lobby) {
|
||||
// TODO: Migrate to LoLib GUI
|
||||
// Menus.*.open(player);
|
||||
player.sendMessage("§cМеню временно недоступно во время миграции");
|
||||
} else if (held == quit) {
|
||||
ParkourUser.leave(player);
|
||||
} else {
|
||||
if (!Config.CONFIG.getBoolean("options.disable-inventory-blocks")) {
|
||||
event.setCancelled(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ItemStack getHeldItem(Player player) {
|
||||
PlayerInventory inventory = player.getInventory();
|
||||
return inventory.getItemInMainHand().getType() == Material.AIR ? inventory.getItemInOffHand() : inventory.getItemInMainHand();
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void switchWorld(PlayerChangedWorldEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
ParkourUser user = ParkourUser.getUser(player);
|
||||
org.bukkit.World parkour = World.getWorld();
|
||||
|
||||
boolean isAdmin = Config.CONFIG.getBoolean("permissions.enabled") ? ParkourOption.ADMIN.mayPerform(player) : player.isOp();
|
||||
|
||||
if (player.getWorld() == parkour && user == null && !isAdmin && player.getTicksLived() > 20) {
|
||||
Bukkit.getWorlds().stream()
|
||||
.filter(world -> !world.equals(parkour))
|
||||
.findAny()
|
||||
.ifPresent(world -> PaperLib.teleportAsync(player, world.getSpawnLocation()));
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.getFrom() == parkour && user != null && Duration.between(user.joined, Instant.now()).toMillis() > 100) {
|
||||
ParkourUser.unregister(user, true, false, false);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onDrop(PlayerDropItemEvent event) {
|
||||
handleRestriction(event.getPlayer(), event);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPlace(BlockPlaceEvent event) {
|
||||
handleRestriction(event.getPlayer(), event);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onBreak(BlockBreakEvent event) {
|
||||
handleRestriction(event.getPlayer(), event);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void damage(EntityDamageEvent event) {
|
||||
if (!(event.getEntity() instanceof Player player)) {
|
||||
return;
|
||||
}
|
||||
|
||||
handleRestriction(player, event);
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGHEST)
|
||||
public void inventory(InventoryClickEvent event) {
|
||||
if (!(event.getWhoClicked() instanceof Player player) || event.getInventory().getType() == InventoryType.CRAFTING) {
|
||||
return;
|
||||
}
|
||||
|
||||
handleRestriction(player, event);
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGHEST)
|
||||
public void spectate(PlayerTeleportEvent event) {
|
||||
if (event.getCause() != PlayerTeleportEvent.TeleportCause.SPECTATE) {
|
||||
return;
|
||||
}
|
||||
|
||||
handleRestriction(event.getPlayer(), event);
|
||||
}
|
||||
|
||||
// @EventHandler(priority = EventPriority.HIGHEST)
|
||||
// public void mount(EntityMountEvent event) {
|
||||
// if (!(event.getEntity() instanceof Player player)) {
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// handleRestriction(player, event);
|
||||
// }
|
||||
|
||||
private void handleRestriction(Player player, Cancellable event) {
|
||||
if (!ParkourUser.isUser(player)) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.setCancelled(true);
|
||||
}
|
||||
|
||||
private void send(CommandSender sender, String message) {
|
||||
sender.sendMessage(ColorUtil.color(message));
|
||||
}
|
||||
}
|
||||
@Deprecated
|
||||
class Events {}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import dev.loki.loparkour.schematic.lpschem.LPSchematicManager;
|
|||
import dev.loki.loparkour.storage.Storage;
|
||||
import dev.loki.loparkour.world.World;
|
||||
import dev.lolib.core.LoPlugin;
|
||||
import dev.lolib.scheduler.Scheduler;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import org.bstats.bukkit.Metrics;
|
||||
|
|
@ -28,6 +29,8 @@ import org.bstats.charts.SingleLineChart;
|
|||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Main class of LoParkour
|
||||
|
|
@ -39,7 +42,10 @@ public final class LoParkour extends LoPlugin {
|
|||
public static final String NAME = "&#FF6464<bold>LoParkour<reset>";
|
||||
public static final String PREFIX = NAME + " 񢩈» &#A0A0A0";
|
||||
|
||||
private static final Gson gson = new GsonBuilder().setPrettyPrinting().create();
|
||||
private static final Gson gson = new GsonBuilder()
|
||||
.setPrettyPrinting()
|
||||
.excludeFieldsWithoutExposeAnnotation()
|
||||
.create();
|
||||
|
||||
public static Gson getGson() {
|
||||
return gson;
|
||||
|
|
@ -87,14 +93,9 @@ public final class LoParkour extends LoPlugin {
|
|||
|
||||
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
instance = this;
|
||||
super.onEnable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enable() {
|
||||
instance = this; // must be first — Config enum accesses instance inside reload()
|
||||
loadConfigs();
|
||||
loadSchematics();
|
||||
registerModes();
|
||||
|
|
@ -110,8 +111,77 @@ public final class LoParkour extends LoPlugin {
|
|||
|
||||
private void loadSchematics() {
|
||||
schematicManager = new LPSchematicManager();
|
||||
|
||||
// Create default island schematic if it doesn't exist
|
||||
File schematicsFolder = getInFolder("schematics-new");
|
||||
File islandFile = new File(schematicsFolder, "island.lpschem");
|
||||
|
||||
if (!islandFile.exists()) {
|
||||
try {
|
||||
createDefaultIslandSchematic(islandFile);
|
||||
getLogger().info("Created default island schematic");
|
||||
} catch (Exception ex) {
|
||||
getLogger().severe("Failed to create default island schematic: " + ex.getMessage());
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
schematicManager.loadAll();
|
||||
}
|
||||
|
||||
private void createDefaultIslandSchematic(File file) throws Exception {
|
||||
// Create a larger 9x5x9 island for better visibility and safety
|
||||
List<String> palette = new ArrayList<>();
|
||||
palette.add("minecraft:air");
|
||||
palette.add("minecraft:stone");
|
||||
palette.add("minecraft:diamond_block");
|
||||
palette.add("minecraft:emerald_block");
|
||||
|
||||
int width = 9, height = 5, length = 9;
|
||||
int[] blocks = new int[width * height * length];
|
||||
|
||||
// Fill everything with air first
|
||||
for (int i = 0; i < blocks.length; i++) {
|
||||
blocks[i] = 0;
|
||||
}
|
||||
|
||||
// Create a solid stone platform at y=1 (7x7 platform)
|
||||
for (int z = 1; z < 8; z++) {
|
||||
for (int x = 1; x < 8; x++) {
|
||||
int index = x + (z * width) + (1 * width * length);
|
||||
blocks[index] = 1; // stone
|
||||
}
|
||||
}
|
||||
|
||||
// Place diamond block (player spawn) at center
|
||||
int centerX = 4, centerZ = 4;
|
||||
int diamondIndex = centerX + (centerZ * width) + (1 * width * length);
|
||||
blocks[diamondIndex] = 2; // diamond_block - player spawns here
|
||||
|
||||
// Place emerald block (parkour start) to the right of spawn
|
||||
int emeraldIndex = (centerX + 1) + (centerZ * width) + (1 * width * length);
|
||||
blocks[emeraldIndex] = 3; // emerald_block - parkour starts here
|
||||
|
||||
dev.loki.loparkour.schematic.lpschem.SchematicMetadata metadata =
|
||||
new dev.loki.loparkour.schematic.lpschem.SchematicMetadata("island", "LoParkour", 0.0);
|
||||
metadata.addTag("spawn");
|
||||
metadata.addTag("island");
|
||||
|
||||
dev.loki.loparkour.schematic.lpschem.SchematicDimensions dimensions =
|
||||
new dev.loki.loparkour.schematic.lpschem.SchematicDimensions(width, height, length);
|
||||
|
||||
// Markers point to the blocks where player spawns and parkour begins
|
||||
dev.loki.loparkour.schematic.lpschem.SchematicMarkers markers =
|
||||
new dev.loki.loparkour.schematic.lpschem.SchematicMarkers(
|
||||
new dev.loki.loparkour.schematic.lpschem.SchematicMarkers.Vector3i(centerX, 1, centerZ),
|
||||
new dev.loki.loparkour.schematic.lpschem.SchematicMarkers.Vector3i(centerX + 1, 1, centerZ)
|
||||
);
|
||||
|
||||
dev.loki.loparkour.schematic.lpschem.LPSchematic schematic =
|
||||
new dev.loki.loparkour.schematic.lpschem.LPSchematic(metadata, dimensions, palette, blocks, markers);
|
||||
|
||||
schematic.save(file);
|
||||
}
|
||||
|
||||
private void registerModes() {
|
||||
Registry.register(new DefaultMode());
|
||||
|
|
@ -140,14 +210,31 @@ public final class LoParkour extends LoPlugin {
|
|||
}
|
||||
|
||||
private void setupWorld() {
|
||||
if (Config.CONFIG.getBoolean("joining")) {
|
||||
if (!Config.CONFIG.getBoolean("joining")) {
|
||||
return;
|
||||
}
|
||||
|
||||
// World creation MUST happen on the main thread (Bukkit API requirement)
|
||||
try {
|
||||
loLogger().info("Creating parkour world...");
|
||||
World.create();
|
||||
loLogger().info("Parkour world created successfully!");
|
||||
} catch (Exception ex) {
|
||||
loLogger().error("Failed to create parkour world", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void registerEventsAndCommands() {
|
||||
getServer().getPluginManager().registerEvents(new Events(), this);
|
||||
getCommand("LoParkour").setExecutor(new Command());
|
||||
dev.lolib.gui.GUIManager.init(this); // required for LoLib GUI click handling
|
||||
|
||||
var pm = getServer().getPluginManager();
|
||||
pm.registerEvents(new dev.loki.loparkour.listener.PlayerConnectionListener(), this);
|
||||
pm.registerEvents(new dev.loki.loparkour.listener.ParkourRestrictionListener(), this);
|
||||
pm.registerEvents(new dev.loki.loparkour.listener.SchematicWandListener(), this);
|
||||
|
||||
LoParkourCommand cmd = new LoParkourCommand();
|
||||
getCommand("LoParkour").setExecutor(cmd);
|
||||
getCommand("LoParkour").setTabCompleter(cmd);
|
||||
}
|
||||
|
||||
private void setupMetrics() {
|
||||
|
|
|
|||
|
|
@ -1,11 +1,9 @@
|
|||
package dev.loki.loparkour;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import dev.loki.loparkour.api.Registry;
|
||||
import dev.loki.loparkour.config.Config;
|
||||
import dev.loki.loparkour.command.AdminCommandHandler;
|
||||
import dev.loki.loparkour.command.PlayerCommandHandler;
|
||||
import dev.loki.loparkour.command.SchematicCommandHandler;
|
||||
import dev.loki.loparkour.config.Locales;
|
||||
import dev.loki.loparkour.leaderboard.Leaderboard;
|
||||
import dev.loki.loparkour.menu.Menus;
|
||||
|
|
@ -15,613 +13,170 @@ import dev.loki.loparkour.mode.Modes;
|
|||
import dev.loki.loparkour.mode.MultiMode;
|
||||
import dev.loki.loparkour.player.ParkourPlayer;
|
||||
import dev.loki.loparkour.player.ParkourUser;
|
||||
import dev.loki.loparkour.player.data.InventoryData;
|
||||
import dev.loki.loparkour.schematic.lpschem.LPSchematic;
|
||||
import dev.loki.loparkour.schematic.lpschem.LPSchematicBuilder;
|
||||
import dev.loki.loparkour.session.Session;
|
||||
import dev.loki.loparkour.util.ColorUtil;
|
||||
import dev.loki.loparkour.util.ParticleUtil;
|
||||
import dev.loki.loparkour.util.Locations;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.TabCompleter;
|
||||
import org.bukkit.*;
|
||||
import org.bukkit.command.BlockCommandSender;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.command.TabCompleter;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.util.BoundingBox;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Main command router for /LoParkour and /parkour.
|
||||
* Delegates to {@link PlayerCommandHandler}, {@link AdminCommandHandler},
|
||||
* and {@link SchematicCommandHandler}.
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public class LoParkourCommand implements CommandExecutor, TabCompleter {
|
||||
|
||||
public static final HashMap<Player, Location[]> selections = new HashMap<>();
|
||||
|
||||
private static final ItemStack WAND = createWand();
|
||||
|
||||
private static ItemStack createWand() {
|
||||
ItemStack wand = new ItemStack(Material.GOLDEN_AXE);
|
||||
var meta = wand.getItemMeta();
|
||||
if (meta != null) {
|
||||
meta.setDisplayName(ColorUtil.color("<red><bold>LPSchematic Wand"));
|
||||
meta.setLore(List.of(
|
||||
ColorUtil.color("<gray>Left click: first position"),
|
||||
ColorUtil.color("<gray>Right click: second position")
|
||||
));
|
||||
wand.setItemMeta(meta);
|
||||
}
|
||||
return wand;
|
||||
}
|
||||
private final PlayerCommandHandler player = new PlayerCommandHandler();
|
||||
private final AdminCommandHandler admin = new AdminCommandHandler(player);
|
||||
|
||||
@Override
|
||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
Player player = null;
|
||||
if (sender instanceof Player) {
|
||||
player = (Player) sender;
|
||||
}
|
||||
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command,
|
||||
@NotNull String label, @NotNull String[] args) {
|
||||
Player p = sender instanceof Player ? (Player) sender : null;
|
||||
|
||||
switch (args.length) {
|
||||
case 0 -> handle0Args(sender, player);
|
||||
case 1 -> handle1Args(args[0], sender, player);
|
||||
case 2 -> handle2Args(args[0], args[1], sender, player);
|
||||
case 3 -> handle3Args(args[0], args[1], args[2], sender, player);
|
||||
case 0 -> player.handleNoArgs(sender, p);
|
||||
case 1 -> player.handle(args[0], sender, p);
|
||||
case 2 -> handle2(args[0], args[1], sender, p);
|
||||
case 3 -> handle3(args[0], args[1], args[2], sender, p);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
|
||||
List<String> completions = new ArrayList<>();
|
||||
switch (args.length) {
|
||||
case 1 -> {
|
||||
if (ParkourOption.JOIN.mayPerform(sender)) {
|
||||
completions.add("join");
|
||||
completions.add("leave");
|
||||
}
|
||||
if (ParkourOption.MAIN.mayPerform(sender)) {
|
||||
completions.add("menu");
|
||||
}
|
||||
if (ParkourOption.PLAY.mayPerform(sender)) {
|
||||
completions.add("play");
|
||||
}
|
||||
if (ParkourOption.LEADERBOARDS.mayPerform(sender)) {
|
||||
completions.add("leaderboard");
|
||||
}
|
||||
if (sender.hasPermission(ParkourOption.ADMIN.permission)) {
|
||||
completions.add("schematic");
|
||||
completions.add("reload");
|
||||
completions.add("forcejoin");
|
||||
completions.add("forceleave");
|
||||
completions.add("reset");
|
||||
completions.add("recoverinventory");
|
||||
}
|
||||
return completions(args[0], completions);
|
||||
}
|
||||
case 2 -> {
|
||||
if (args[0].equalsIgnoreCase("reset") && sender.hasPermission(ParkourOption.ADMIN.permission)) {
|
||||
completions.add("everyone");
|
||||
for (ParkourPlayer pp : ParkourPlayer.getPlayers()) {
|
||||
completions.add(pp.getName());
|
||||
}
|
||||
} else if (args[0].equalsIgnoreCase("join") && sender.hasPermission(ParkourOption.JOIN.permission)) {
|
||||
for (ParkourPlayer pp : ParkourPlayer.getPlayers()) {
|
||||
completions.add(pp.getName());
|
||||
}
|
||||
} else if (args[0].equalsIgnoreCase("schematic") && sender.hasPermission(ParkourOption.ADMIN.permission)) {
|
||||
completions.addAll(Arrays.asList("wand", "pos1", "pos2", "save", "paste"));
|
||||
} else if (args[0].equalsIgnoreCase("forcejoin") && sender.hasPermission(ParkourOption.ADMIN.permission)) {
|
||||
completions.add("nearest");
|
||||
completions.add("everyone");
|
||||
// ── 2-arg routing ──────────────────────────────────────────────────────────
|
||||
|
||||
for (Player pl : Bukkit.getOnlinePlayers()) {
|
||||
completions.add(pl.getName());
|
||||
}
|
||||
} else if (args[0].equalsIgnoreCase("forceleave") && sender.hasPermission(ParkourOption.ADMIN.permission)) {
|
||||
completions.add("everyone");
|
||||
|
||||
for (Player pl : Bukkit.getOnlinePlayers()) {
|
||||
completions.add(pl.getName());
|
||||
}
|
||||
} else if (args[0].equalsIgnoreCase("recoverinventory") && sender.hasPermission(ParkourOption.ADMIN.permission)) {
|
||||
for (Player pl : Bukkit.getOnlinePlayers()) {
|
||||
completions.add(pl.getName());
|
||||
}
|
||||
}
|
||||
return completions(args[1], completions);
|
||||
}
|
||||
default -> {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handle0Args(@NotNull CommandSender sender, @Nullable Player player) {
|
||||
if (player != null && ParkourOption.MAIN.mayPerform(player)) {
|
||||
// TODO: Migrate to LoLib GUI
|
||||
// Menus.MAIN.open(player);
|
||||
player.sendMessage("§cМеню временно недоступно во время миграции");
|
||||
return;
|
||||
}
|
||||
sendHelpMessages(sender);
|
||||
}
|
||||
|
||||
private void sendHelpMessages(CommandSender sender) {
|
||||
send(sender, "");
|
||||
send(sender, "<dark_gray><strikethrough>---------------<reset> %s <dark_gray><strikethrough>---------------<reset>".formatted(LoParkour.NAME));
|
||||
send(sender, "");
|
||||
send(sender, "<gray>/parkour <dark_gray>- Main command");
|
||||
if (sender.hasPermission(ParkourOption.JOIN.permission)) {
|
||||
send(sender, "<gray>/parkour join [mode/player] <dark_gray>- Join the default mode or specify one.");
|
||||
send(sender, "<gray>/parkour leave <dark_gray>- Leave the game on this server");
|
||||
}
|
||||
if (sender.hasPermission(ParkourOption.MAIN.permission)) {
|
||||
send(sender, "<gray>/parkour menu <dark_gray>- Open the menu");
|
||||
}
|
||||
if (sender.hasPermission(ParkourOption.PLAY.permission)) {
|
||||
send(sender, "<gray>/parkour play <dark_gray>- Mode selection menu");
|
||||
}
|
||||
if (sender.hasPermission(ParkourOption.LEADERBOARDS.permission)) {
|
||||
send(sender, "<gray>/parkour leaderboard [type]<dark_gray>- Open the leaderboard of a mode");
|
||||
}
|
||||
if (sender.hasPermission(ParkourOption.ADMIN.permission)) {
|
||||
send(sender, "<gray>/LoParkour schematic <dark_gray>- Create a schematic");
|
||||
send(sender, "<gray>/LoParkour reload <dark_gray>- Reloads the messages-v3.yml file");
|
||||
send(sender, "<gray>/LoParkour reset <everyone/player> <dark_gray>- Resets all high scores. <red>This can't be recovered!");
|
||||
send(sender, "<gray>/LoParkour forcejoin <everyone/nearest/player> <dark_gray>- Forces a specific player, the nearest or everyone to join");
|
||||
send(sender, "<gray>/LoParkour forceleave <everyone/nearest/player> <dark_gray>- Forces a specific player, the nearest or everyone to leave");
|
||||
send(sender, "<gray>/LoParkour recoverinventory <player> <dark_gray>- Recover a player's saved inventory. <red>Useful for recovering data after server crashes or errors when leaving.");
|
||||
}
|
||||
send(sender, "");
|
||||
}
|
||||
|
||||
private void handle1Args(@NotNull String arg, @NotNull CommandSender sender, @Nullable Player player) {
|
||||
switch (arg.toLowerCase()) {
|
||||
case "help" -> sendHelpMessages(sender);
|
||||
case "reload" -> {
|
||||
if (!cooldown(sender, "reload", 2500)) {
|
||||
return;
|
||||
}
|
||||
if (!sender.hasPermission(ParkourOption.ADMIN.permission)) {
|
||||
send(sender, Locales.getString(player, "other.no_do"));
|
||||
return;
|
||||
}
|
||||
|
||||
Config.reload(false);
|
||||
|
||||
send(sender, "%sReloaded config files.".formatted(LoParkour.PREFIX));
|
||||
}
|
||||
}
|
||||
|
||||
if (player == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (arg.toLowerCase()) {
|
||||
case "join" -> {
|
||||
if (!cooldown(sender, "join", 2500)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ParkourOption.JOIN.mayPerform(player)) {
|
||||
send(sender, Locales.getString(player, "other.no_do"));
|
||||
return;
|
||||
}
|
||||
|
||||
ParkourUser user = ParkourUser.getUser(player);
|
||||
if (user != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Modes.DEFAULT.create(player);
|
||||
}
|
||||
case "play" -> {
|
||||
if (ParkourOption.PLAY.mayPerform(player)) {
|
||||
// TODO: Migrate to LoLib GUI
|
||||
// Menus.PLAY.open(player);
|
||||
player.sendMessage("§cМеню временно недоступно во время миграции");
|
||||
}
|
||||
}
|
||||
case "leave" -> {
|
||||
if (!cooldown(sender, "leave", 2500)) {
|
||||
return;
|
||||
}
|
||||
ParkourUser.leave(player);
|
||||
}
|
||||
case "menu", "main" -> {
|
||||
if (!ParkourOption.MAIN.mayPerform(player)) {
|
||||
// TODO: Migrate to LoLib GUI
|
||||
// Menus.MAIN.open(player);
|
||||
player.sendMessage("§cМеню временно недоступно во время миграции");
|
||||
}
|
||||
}
|
||||
case "leaderboard" -> player.performCommand("LoParkour leaderboard invalid");
|
||||
case "schematic" -> {
|
||||
if (!player.hasPermission(ParkourOption.ADMIN.permission)) { // default players shouldn't have access even if perms are disabled
|
||||
send(sender, Locales.getString(player, "other.no_do"));
|
||||
return;
|
||||
}
|
||||
|
||||
send(player, "");
|
||||
send(player, "<red>/LoParkour schematic wand <dark_gray>- <gray>Get the schematic wand");
|
||||
send(player, "<red>/LoParkour schematic pos1 <dark_gray>- <gray>Set the first position of your selection");
|
||||
send(player, "<red>/LoParkour schematic pos2 <dark_gray>- <gray>Set the second position of your selection");
|
||||
send(player, "<red>/LoParkour schematic save <name> <dark_gray>- <gray>Save selection to .lpschem format");
|
||||
send(player, "<red>/LoParkour schematic paste <name> <dark_gray>- <gray>Paste a .lpschem schematic");
|
||||
send(player, "<red>/LoParkour schematic list <dark_gray>- <gray>List all loaded schematics");
|
||||
send(player, "<red>/LoParkour schematic reload <dark_gray>- <gray>Reload all schematics");
|
||||
send(player, "");
|
||||
send(player, "<dark_gray><underlined>Have any questions or need help? Join the Discord.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handle2Args(@NotNull String arg1, @NotNull String arg2, @NotNull CommandSender sender, @Nullable Player player) {
|
||||
switch (arg1.toLowerCase()) {
|
||||
case "forcejoin" -> {
|
||||
if (!sender.hasPermission(ParkourOption.ADMIN.permission)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (arg2.equalsIgnoreCase("everyone")) {
|
||||
Bukkit.getOnlinePlayers().forEach(other -> Modes.DEFAULT.create(other));
|
||||
send(sender, LoParkour.PREFIX + "Successfully force joined everyone!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (arg2.equalsIgnoreCase("nearest")) {
|
||||
Player closest = null;
|
||||
double distance = Double.MAX_VALUE;
|
||||
|
||||
// if player is found get location from player
|
||||
// if no player is found, get location from command block
|
||||
// if no command block is found, return null
|
||||
Location from = sender instanceof Player ? ((Player) sender).getLocation() : (sender instanceof BlockCommandSender ? ((BlockCommandSender) sender).getBlock().getLocation() : null);
|
||||
|
||||
if (from == null || from.getWorld() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// get the closest player
|
||||
for (Player p : from.getWorld().getPlayers()) {
|
||||
double d = p.getLocation().distance(from);
|
||||
|
||||
if (d < distance) {
|
||||
distance = d;
|
||||
closest = p;
|
||||
}
|
||||
}
|
||||
|
||||
// no closest player found
|
||||
if (closest == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
send(sender, LoParkour.PREFIX + "Successfully force joined " + closest.getName() + "!");
|
||||
Modes.DEFAULT.create(closest);
|
||||
return;
|
||||
}
|
||||
|
||||
Player other = Bukkit.getPlayer(arg2);
|
||||
if (other == null) {
|
||||
send(sender, LoParkour.PREFIX + "That player isn't online!");
|
||||
return;
|
||||
}
|
||||
|
||||
Modes.DEFAULT.create(other);
|
||||
}
|
||||
case "forceleave" -> {
|
||||
if (!sender.hasPermission(ParkourOption.ADMIN.permission)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (arg2.equalsIgnoreCase("everyone")) {
|
||||
ParkourPlayer.getPlayers().forEach(ParkourUser::leave);
|
||||
send(sender, LoParkour.PREFIX + "Successfully force kicked everyone!");
|
||||
return;
|
||||
}
|
||||
|
||||
Player other = Bukkit.getPlayer(arg2);
|
||||
if (other == null) {
|
||||
send(sender, LoParkour.PREFIX + "That player isn't online!");
|
||||
return;
|
||||
}
|
||||
|
||||
ParkourUser user = ParkourUser.getUser(other);
|
||||
if (user == null) {
|
||||
send(sender, LoParkour.PREFIX + "That player isn't currently playing!");
|
||||
return;
|
||||
}
|
||||
|
||||
ParkourUser.leave(user);
|
||||
}
|
||||
case "reset" -> {
|
||||
if (!sender.hasPermission(ParkourOption.ADMIN.permission) || !cooldown(sender, "reset", 2500)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (arg2.equalsIgnoreCase("everyone")) {
|
||||
for (Mode mode : Registry.getModes()) {
|
||||
Leaderboard leaderboard = mode.getLeaderboard();
|
||||
|
||||
if (leaderboard == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
leaderboard.resetAll();
|
||||
leaderboard.write(true);
|
||||
}
|
||||
|
||||
send(sender, LoParkour.PREFIX + "Successfully reset all high scores in memory and the files.");
|
||||
return;
|
||||
}
|
||||
String name = null;
|
||||
UUID uuid = null;
|
||||
|
||||
// Check online players
|
||||
Player online = Bukkit.getPlayerExact(arg2);
|
||||
if (online != null) {
|
||||
name = online.getName();
|
||||
uuid = online.getUniqueId();
|
||||
}
|
||||
|
||||
// Check uuid
|
||||
if (arg2.contains("-")) {
|
||||
uuid = UUID.fromString(arg2);
|
||||
}
|
||||
|
||||
// Check offline player
|
||||
if (uuid == null) {
|
||||
OfflinePlayer offline = Bukkit.getOfflinePlayer(arg2);
|
||||
name = offline.getName();
|
||||
uuid = offline.getUniqueId();
|
||||
}
|
||||
|
||||
UUID finalUuid = uuid;
|
||||
String finalName = name;
|
||||
|
||||
for (Mode mode : Registry.getModes()) {
|
||||
Leaderboard leaderboard = mode.getLeaderboard();
|
||||
|
||||
if (leaderboard == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
leaderboard.remove(finalUuid);
|
||||
leaderboard.write(true);
|
||||
}
|
||||
|
||||
send(sender, LoParkour.PREFIX + "Successfully reset the high score of " + finalName + " in memory and the files.");
|
||||
}
|
||||
case "recoverinventory" -> {
|
||||
if (!cooldown(sender, "recoverinventory", 2500) || !sender.hasPermission(ParkourOption.ADMIN.permission)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Player other = Bukkit.getPlayer(arg2);
|
||||
if (other == null) {
|
||||
send(sender, LoParkour.PREFIX + "That player isn't online!");
|
||||
return;
|
||||
}
|
||||
|
||||
new InventoryData(other).load(result -> {
|
||||
if (result != null) {
|
||||
send(sender, "%sSuccessfully recovered the inventory of %s from their file".formatted(LoParkour.PREFIX, other.getName()));
|
||||
send(sender, "%sGiving %s their items now...".formatted(LoParkour.PREFIX, other.getName()));
|
||||
} else {
|
||||
send(sender, "%s<red>There was an error recovering the inventory of %s from their file".formatted(LoParkour.PREFIX, other.getName()));
|
||||
send(sender, "%s%s has no saved inventory or there was an error. Check the console.".formatted(LoParkour.PREFIX, other.getName()));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (player == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (arg1) {
|
||||
case "join" -> {
|
||||
if (!cooldown(sender, "join", 2500) || !ParkourOption.JOIN.mayPerform(player)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Mode mode = Registry.getMode(arg2);
|
||||
|
||||
if (mode != null) {
|
||||
mode.create(player);
|
||||
return;
|
||||
}
|
||||
|
||||
Player other = Bukkit.getPlayer(arg2);
|
||||
|
||||
if (other == null) {
|
||||
send(sender, "%sUnknown player! Try typing the name again.".formatted(LoParkour.PREFIX)); // could not find, so go to default
|
||||
return;
|
||||
}
|
||||
|
||||
ParkourPlayer parkourPlayer = ParkourPlayer.getPlayer(other);
|
||||
|
||||
if (parkourPlayer == null) {
|
||||
send(sender, "%sUnknown player! Try typing the name again.".formatted(LoParkour.PREFIX)); // could not find, so go to default
|
||||
return;
|
||||
}
|
||||
|
||||
ParkourUser user = ParkourUser.getUser(player);
|
||||
Session session = parkourPlayer.session;
|
||||
if (user != null && user.session == session) { // already in same session
|
||||
return;
|
||||
}
|
||||
|
||||
if (session.isAcceptingPlayers()) {
|
||||
((MultiMode) session.generator.getMode()).join(player, session);
|
||||
} else {
|
||||
Modes.SPECTATOR.create(player, session);
|
||||
}
|
||||
}
|
||||
case "leaderboard" -> {
|
||||
if (!ParkourOption.LEADERBOARDS.mayPerform(player)) {
|
||||
send(sender, Locales.getString(player, "other.no_do"));
|
||||
return;
|
||||
}
|
||||
|
||||
Mode mode = Registry.getMode(arg2.toLowerCase());
|
||||
|
||||
// if found gamemode is null, return to default
|
||||
if (mode == null) {
|
||||
// TODO: Migrate to LoLib GUI
|
||||
// Menus.LEADERBOARDS.open(player);
|
||||
player.sendMessage("§cМеню временно недоступно во время миграции");
|
||||
} else {
|
||||
// TODO: Migrate to LoLib GUI
|
||||
// Menus.SINGLE_LEADERBOARD.open(player, mode, Leaderboard.Sort.SCORE);
|
||||
player.sendMessage("§cМеню временно недоступно во время миграции");
|
||||
}
|
||||
}
|
||||
case "schematic" -> {
|
||||
if (!sender.hasPermission(ParkourOption.ADMIN.permission)) {
|
||||
send(sender, Locales.getString(player, "other.no_do"));
|
||||
return;
|
||||
}
|
||||
|
||||
Location playerLocation = player.getLocation();
|
||||
Location[] existingSelection = selections.get(player);
|
||||
|
||||
switch (arg2.toLowerCase()) {
|
||||
case "wand" -> {
|
||||
player.getInventory().addItem(WAND);
|
||||
|
||||
send(player, "<dark_gray>----------- <dark_red><bold>Schematics <reset><dark_gray>-----------");
|
||||
send(player, "<gray><red>Left click<gray> -> set first position | <red>Right click<gray> -> set second position");
|
||||
send(player, "<gray>If you can't place a block and need to set a position mid-air, use <dark_gray>/LoParkour schematic pos1/pos2 <gray>instead.");
|
||||
}
|
||||
case "pos1" -> {
|
||||
send(player, "%sPosition 1 was set to %s".formatted(LoParkour.PREFIX, Locations.toString(playerLocation, true)));
|
||||
|
||||
if (existingSelection == null) {
|
||||
selections.put(player, new Location[]{playerLocation, null});
|
||||
return;
|
||||
}
|
||||
|
||||
selections.put(player, new Location[]{playerLocation, existingSelection[1]});
|
||||
|
||||
ParticleUtil.box(BoundingBox.of(playerLocation, existingSelection[1]), player.getWorld(), Particle.END_ROD, player, 0.2);
|
||||
}
|
||||
case "pos2" -> {
|
||||
send(player, "%sPosition 2 was set to %s".formatted(LoParkour.PREFIX, Locations.toString(playerLocation, true)));
|
||||
|
||||
if (existingSelection == null) {
|
||||
selections.put(player, new Location[]{null, playerLocation});
|
||||
return;
|
||||
}
|
||||
|
||||
selections.put(player, new Location[]{existingSelection[0], playerLocation});
|
||||
|
||||
ParticleUtil.box(BoundingBox.of(existingSelection[0], playerLocation), player.getWorld(), Particle.END_ROD, player, 0.2);
|
||||
}
|
||||
case "save" -> {
|
||||
if (!cooldown(sender, "LoParkour save schematic", 2500)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (existingSelection == null || existingSelection[0] == null || existingSelection[1] == null) {
|
||||
send(player, "<dark_red><bold>Schematics <reset><gray>Your schematic isn't complete yet. Make sure you've set the first and second position.");
|
||||
return;
|
||||
}
|
||||
|
||||
String code = UUID.randomUUID().toString().split("-")[0];
|
||||
|
||||
send(player, ("<dark_red><bold>Schematics <reset><gray>Your schematic is being saved. It will use code <red>'%s'<gray>. " + "You can change the code to whatever you like. " + "Don't forget to add this schematic to <dark_gray>schematics.yml<gray>.").formatted(code));
|
||||
|
||||
// Old vilib schematic save - deprecated, use .lpschem format instead
|
||||
send(player, "<red>Warning: Old schematic format is deprecated. Use '/loparkour schematic save <name>' for .lpschem format.");
|
||||
}
|
||||
case "list" -> {
|
||||
send(player, "<dark_red><bold>Schematics <reset><gray>Loaded schematics:");
|
||||
LoParkour.getSchematicManager().getAllSchematics().forEach((name, schem) -> {
|
||||
send(player, "<gray>- <red>" + name + " <dark_gray>(" + schem.getMetadata().getDifficulty() + ")");
|
||||
});
|
||||
}
|
||||
case "reload" -> {
|
||||
if (!cooldown(sender, "LoParkour reload schematics", 2500)) {
|
||||
return;
|
||||
}
|
||||
LoParkour.getSchematicManager().reload();
|
||||
send(player, "<dark_red><bold>Schematics <reset><gray>Reloaded all schematics.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handle3Args(@NotNull String arg1, @NotNull String arg2, @NotNull String arg3, @NotNull CommandSender sender, @Nullable Player player) {
|
||||
if (player == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (arg1.equalsIgnoreCase("schematic")) {
|
||||
if (!player.hasPermission(ParkourOption.ADMIN.permission)) {
|
||||
private void handle2(String a1, String a2, CommandSender sender, @Nullable Player p) {
|
||||
// Admin commands that don't need a player
|
||||
switch (a1.toLowerCase()) {
|
||||
case "forcejoin", "forceleave", "reset", "recoverinventory" -> {
|
||||
admin.handle(a1, a2, sender, p);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
switch (arg2.toLowerCase()) {
|
||||
case "save" -> {
|
||||
if (!cooldown(sender, "LoParkour save schematic", 2500)) {
|
||||
return;
|
||||
}
|
||||
if (p == null) return;
|
||||
|
||||
Location[] selection = selections.get(player);
|
||||
if (selection == null || selection[0] == null || selection[1] == null) {
|
||||
send(player, "<dark_red><bold>Schematics <reset><gray>Your schematic isn't complete yet. Make sure you've set the first and second position.");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
int width = Math.abs(selection[1].getBlockX() - selection[0].getBlockX()) + 1;
|
||||
int height = Math.abs(selection[1].getBlockY() - selection[0].getBlockY()) + 1;
|
||||
int length = Math.abs(selection[1].getBlockZ() - selection[0].getBlockZ()) + 1;
|
||||
|
||||
LPSchematic schematic = new LPSchematicBuilder(arg3, player.getName(), 0.5, width, height, length)
|
||||
.fromWorld(player.getWorld(), selection[0], selection[1])
|
||||
.build();
|
||||
|
||||
LoParkour.getSchematicManager().saveSchematic(schematic);
|
||||
send(player, "<dark_red><bold>Schematics <reset><gray>Saved schematic as <red>" + arg3 + "<gray>!");
|
||||
} catch (Exception e) {
|
||||
send(player, "<dark_red><bold>Schematics <reset><gray>Failed to save schematic: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
case "paste" -> {
|
||||
LPSchematic lpSchematic = LoParkour.getSchematicManager().getSchematic(arg3);
|
||||
if (lpSchematic != null) {
|
||||
lpSchematic.paste(player.getLocation(), player.getWorld());
|
||||
send(sender, "%sPasted .lpschem schematic %s".formatted(LoParkour.PREFIX, arg3));
|
||||
return;
|
||||
}
|
||||
|
||||
send(sender, "%sCouldn't find %s. Only .lpschem format is supported.".formatted(LoParkour.PREFIX, arg3));
|
||||
switch (a1.toLowerCase()) {
|
||||
case "join" -> handleJoin(a2, sender, p);
|
||||
case "leaderboard" -> handleLeaderboard(a2, sender, p);
|
||||
case "schematic" -> {
|
||||
if (!p.hasPermission(ParkourOption.ADMIN.permission)) {
|
||||
send(sender, Locales.getString(p, "other.no_do"));
|
||||
return;
|
||||
}
|
||||
SchematicCommandHandler.handleSubcommand(a2, sender, p, player);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void send(CommandSender sender, String message) {
|
||||
sender.sendMessage(ColorUtil.color(message));
|
||||
// ── 3-arg routing ──────────────────────────────────────────────────────────
|
||||
|
||||
private void handle3(String a1, String a2, String a3, CommandSender sender, @Nullable Player p) {
|
||||
if (p == null) return;
|
||||
if (!a1.equalsIgnoreCase("schematic")) return;
|
||||
if (!p.hasPermission(ParkourOption.ADMIN.permission)) return;
|
||||
SchematicCommandHandler.handleSubcommandWithName(a2, a3, sender, p, player);
|
||||
}
|
||||
|
||||
private List<String> completions(String arg, List<String> options) {
|
||||
String lower = arg.toLowerCase();
|
||||
return options.stream()
|
||||
.filter(option -> option.toLowerCase().startsWith(lower))
|
||||
.toList();
|
||||
// ── join logic ─────────────────────────────────────────────────────────────
|
||||
|
||||
private void handleJoin(String arg, CommandSender sender, Player p) {
|
||||
if (!player.cooldown(sender, "join", 2500) || !ParkourOption.JOIN.mayPerform(p)) return;
|
||||
|
||||
Mode mode = Registry.getMode(arg);
|
||||
if (mode != null) { mode.create(p); return; }
|
||||
|
||||
Player other = Bukkit.getPlayer(arg);
|
||||
if (other == null) { send(sender, LoParkour.PREFIX + "Unknown player."); return; }
|
||||
|
||||
ParkourPlayer pp = ParkourPlayer.getPlayer(other);
|
||||
if (pp == null) { send(sender, LoParkour.PREFIX + "That player isn't playing."); return; }
|
||||
|
||||
ParkourUser user = ParkourUser.getUser(p);
|
||||
Session session = pp.session;
|
||||
if (user != null && user.session == session) return;
|
||||
|
||||
if (session.isAcceptingPlayers()) {
|
||||
((MultiMode) session.generator.getMode()).join(p, session);
|
||||
} else {
|
||||
Modes.SPECTATOR.create(p, session);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean cooldown(CommandSender sender, String key, long millis) {
|
||||
// Simple cooldown implementation - can be enhanced later
|
||||
return true;
|
||||
// ── leaderboard logic ──────────────────────────────────────────────────────
|
||||
|
||||
private void handleLeaderboard(String arg, CommandSender sender, Player p) {
|
||||
if (!ParkourOption.LEADERBOARDS.mayPerform(p)) {
|
||||
send(sender, Locales.getString(p, "other.no_do"));
|
||||
return;
|
||||
}
|
||||
Mode mode = Registry.getMode(arg.toLowerCase());
|
||||
if (mode == null) {
|
||||
Menus.LEADERBOARDS.open(p);
|
||||
} else {
|
||||
Menus.SINGLE_LEADERBOARD.open(p, mode, Leaderboard.Sort.SCORE);
|
||||
}
|
||||
}
|
||||
|
||||
// ── tab complete ───────────────────────────────────────────────────────────
|
||||
|
||||
@Override
|
||||
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command,
|
||||
@NotNull String alias, @NotNull String[] args) {
|
||||
List<String> completions = new ArrayList<>();
|
||||
|
||||
if (args.length == 1) {
|
||||
if (ParkourOption.JOIN.mayPerform(sender)) { completions.add("join"); completions.add("leave"); }
|
||||
if (ParkourOption.MAIN.mayPerform(sender)) completions.add("menu");
|
||||
if (ParkourOption.PLAY.mayPerform(sender)) completions.add("play");
|
||||
if (ParkourOption.LEADERBOARDS.mayPerform(sender)) completions.add("leaderboard");
|
||||
if (sender.hasPermission(ParkourOption.ADMIN.permission)) {
|
||||
completions.addAll(List.of("schematic", "reload", "forcejoin", "forceleave", "reset", "recoverinventory"));
|
||||
}
|
||||
return filter(args[0], completions);
|
||||
}
|
||||
|
||||
if (args.length == 2) {
|
||||
String a1 = args[0].toLowerCase();
|
||||
if (a1.equals("reset") && sender.hasPermission(ParkourOption.ADMIN.permission)) {
|
||||
completions.add("everyone");
|
||||
ParkourPlayer.getPlayers().forEach(pp -> completions.add(pp.getName()));
|
||||
} else if (a1.equals("join") && ParkourOption.JOIN.mayPerform(sender)) {
|
||||
ParkourPlayer.getPlayers().forEach(pp -> completions.add(pp.getName()));
|
||||
} else if (a1.equals("schematic") && sender.hasPermission(ParkourOption.ADMIN.permission)) {
|
||||
completions.addAll(Arrays.asList("wand", "pos1", "pos2", "save", "paste", "list", "reload"));
|
||||
} else if ((a1.equals("forcejoin") || a1.equals("forceleave")) && sender.hasPermission(ParkourOption.ADMIN.permission)) {
|
||||
completions.add("everyone");
|
||||
if (a1.equals("forcejoin")) completions.add("nearest");
|
||||
Bukkit.getOnlinePlayers().forEach(pl -> completions.add(pl.getName()));
|
||||
} else if (a1.equals("recoverinventory") && sender.hasPermission(ParkourOption.ADMIN.permission)) {
|
||||
Bukkit.getOnlinePlayers().forEach(pl -> completions.add(pl.getName()));
|
||||
}
|
||||
return filter(args[1], completions);
|
||||
}
|
||||
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
// ── util ───────────────────────────────────────────────────────────────────
|
||||
|
||||
private List<String> filter(String input, List<String> options) {
|
||||
String lower = input.toLowerCase();
|
||||
return options.stream().filter(s -> s.toLowerCase().startsWith(lower)).toList();
|
||||
}
|
||||
|
||||
private void send(CommandSender sender, String msg) {
|
||||
sender.sendMessage(ColorUtil.color(msg));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,14 +27,10 @@ public final class Registry {
|
|||
*/
|
||||
public static void register(@NotNull Mode mode) {
|
||||
modes.add(mode);
|
||||
|
||||
LoParkour.log("Registered mode %s".formatted(mode.getName()));
|
||||
}
|
||||
|
||||
public static void register(@NotNull Style style) {
|
||||
styles.add(style);
|
||||
|
||||
LoParkour.log("Registered style %s".formatted(style.getName()));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -0,0 +1,162 @@
|
|||
package dev.loki.loparkour.command;
|
||||
|
||||
import dev.loki.loparkour.LoParkour;
|
||||
import dev.loki.loparkour.api.Registry;
|
||||
import dev.loki.loparkour.config.Locales;
|
||||
import dev.loki.loparkour.leaderboard.Leaderboard;
|
||||
import dev.loki.loparkour.menu.ParkourOption;
|
||||
import dev.loki.loparkour.mode.Mode;
|
||||
import dev.loki.loparkour.mode.Modes;
|
||||
import dev.loki.loparkour.player.ParkourPlayer;
|
||||
import dev.loki.loparkour.player.ParkourUser;
|
||||
import dev.loki.loparkour.player.data.InventoryData;
|
||||
import dev.loki.loparkour.util.ColorUtil;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.bukkit.command.BlockCommandSender;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Handles admin-only /LoParkour sub-commands:
|
||||
* forcejoin, forceleave, reset, recoverinventory.
|
||||
*/
|
||||
public class AdminCommandHandler {
|
||||
|
||||
private final PlayerCommandHandler base;
|
||||
|
||||
public AdminCommandHandler(PlayerCommandHandler base) {
|
||||
this.base = base;
|
||||
}
|
||||
|
||||
public void handle(@NotNull String arg1, @NotNull String arg2,
|
||||
@NotNull CommandSender sender, @Nullable Player player) {
|
||||
|
||||
if (!sender.hasPermission(ParkourOption.ADMIN.permission)) return;
|
||||
|
||||
switch (arg1.toLowerCase()) {
|
||||
case "forcejoin" -> handleForceJoin(arg2, sender);
|
||||
case "forceleave" -> handleForceLeave(arg2, sender);
|
||||
case "reset" -> handleReset(arg2, sender);
|
||||
case "recoverinventory" -> handleRecoverInventory(arg2, sender);
|
||||
}
|
||||
|
||||
if (player == null) return;
|
||||
|
||||
if (arg1.equals("schematic")) {
|
||||
SchematicCommandHandler.handleSubcommand(arg2, sender, player, base);
|
||||
}
|
||||
}
|
||||
|
||||
// ── forcejoin ──────────────────────────────────────────────────────────────
|
||||
|
||||
private void handleForceJoin(String target, CommandSender sender) {
|
||||
if (target.equalsIgnoreCase("everyone")) {
|
||||
Bukkit.getOnlinePlayers().forEach(p -> Modes.DEFAULT.create(p));
|
||||
send(sender, LoParkour.PREFIX + "Force joined everyone!");
|
||||
return;
|
||||
}
|
||||
if (target.equalsIgnoreCase("nearest")) {
|
||||
Player closest = findNearest(sender);
|
||||
if (closest == null) return;
|
||||
Modes.DEFAULT.create(closest);
|
||||
send(sender, LoParkour.PREFIX + "Force joined " + closest.getName() + "!");
|
||||
return;
|
||||
}
|
||||
Player other = Bukkit.getPlayer(target);
|
||||
if (other == null) { send(sender, LoParkour.PREFIX + "Player not online!"); return; }
|
||||
Modes.DEFAULT.create(other);
|
||||
}
|
||||
|
||||
// ── forceleave ─────────────────────────────────────────────────────────────
|
||||
|
||||
private void handleForceLeave(String target, CommandSender sender) {
|
||||
if (target.equalsIgnoreCase("everyone")) {
|
||||
ParkourPlayer.getPlayers().forEach(ParkourUser::leave);
|
||||
send(sender, LoParkour.PREFIX + "Force kicked everyone!");
|
||||
return;
|
||||
}
|
||||
Player other = Bukkit.getPlayer(target);
|
||||
if (other == null) { send(sender, LoParkour.PREFIX + "Player not online!"); return; }
|
||||
ParkourUser user = ParkourUser.getUser(other);
|
||||
if (user == null) { send(sender, LoParkour.PREFIX + "Player isn't playing!"); return; }
|
||||
ParkourUser.leave(user);
|
||||
}
|
||||
|
||||
// ── reset ──────────────────────────────────────────────────────────────────
|
||||
|
||||
private void handleReset(String target, CommandSender sender) {
|
||||
if (!base.cooldown(sender, "reset", 2500)) return;
|
||||
|
||||
if (target.equalsIgnoreCase("everyone")) {
|
||||
Registry.getModes().stream()
|
||||
.map(Mode::getLeaderboard)
|
||||
.filter(lb -> lb != null)
|
||||
.forEach(lb -> { lb.resetAll(); lb.write(true); });
|
||||
send(sender, LoParkour.PREFIX + "Reset all high scores.");
|
||||
return;
|
||||
}
|
||||
|
||||
UUID uuid = resolveUUID(target);
|
||||
String name = resolvePlayerName(target, uuid);
|
||||
|
||||
Registry.getModes().stream()
|
||||
.map(Mode::getLeaderboard)
|
||||
.filter(lb -> lb != null)
|
||||
.forEach(lb -> { lb.remove(uuid); lb.write(true); });
|
||||
|
||||
send(sender, LoParkour.PREFIX + "Reset high score of " + name + ".");
|
||||
}
|
||||
|
||||
// ── recoverinventory ───────────────────────────────────────────────────────
|
||||
|
||||
private void handleRecoverInventory(String target, CommandSender sender) {
|
||||
if (!base.cooldown(sender, "recoverinventory", 2500)) return;
|
||||
Player other = Bukkit.getPlayer(target);
|
||||
if (other == null) { send(sender, LoParkour.PREFIX + "Player not online!"); return; }
|
||||
|
||||
new InventoryData(other).load(result -> {
|
||||
if (result != null) {
|
||||
send(sender, LoParkour.PREFIX + "Recovered inventory of " + other.getName() + ".");
|
||||
} else {
|
||||
send(sender, LoParkour.PREFIX + "<red>No saved inventory found for " + other.getName() + ".");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ── helpers ────────────────────────────────────────────────────────────────
|
||||
|
||||
private @Nullable Player findNearest(CommandSender sender) {
|
||||
Location from = sender instanceof Player p ? p.getLocation()
|
||||
: sender instanceof BlockCommandSender b ? b.getBlock().getLocation()
|
||||
: null;
|
||||
if (from == null || from.getWorld() == null) return null;
|
||||
|
||||
return from.getWorld().getPlayers().stream()
|
||||
.min((a, b) -> Double.compare(a.getLocation().distance(from), b.getLocation().distance(from)))
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
private UUID resolveUUID(String input) {
|
||||
Player online = Bukkit.getPlayerExact(input);
|
||||
if (online != null) return online.getUniqueId();
|
||||
if (input.contains("-")) return UUID.fromString(input);
|
||||
return Bukkit.getOfflinePlayer(input).getUniqueId();
|
||||
}
|
||||
|
||||
private String resolvePlayerName(String input, UUID uuid) {
|
||||
Player online = Bukkit.getPlayerExact(input);
|
||||
if (online != null) return online.getName();
|
||||
OfflinePlayer op = Bukkit.getOfflinePlayer(uuid);
|
||||
return op.getName() != null ? op.getName() : input;
|
||||
}
|
||||
|
||||
private void send(CommandSender sender, String msg) {
|
||||
sender.sendMessage(ColorUtil.color(msg));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,121 @@
|
|||
package dev.loki.loparkour.command;
|
||||
|
||||
import dev.loki.loparkour.LoParkour;
|
||||
import dev.loki.loparkour.config.Config;
|
||||
import dev.loki.loparkour.config.Locales;
|
||||
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.util.ColorUtil;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Handles all player-facing /parkour sub-commands.
|
||||
* Admin commands are in {@link AdminCommandHandler}.
|
||||
*/
|
||||
public class PlayerCommandHandler {
|
||||
|
||||
private final Map<String, Long> cooldowns = new HashMap<>();
|
||||
|
||||
public void handleNoArgs(@NotNull CommandSender sender, @Nullable Player player) {
|
||||
if (player != null && ParkourOption.MAIN.mayPerform(player)) {
|
||||
Menus.MAIN.open(player);
|
||||
} else {
|
||||
sendHelp(sender);
|
||||
}
|
||||
}
|
||||
|
||||
public void handle(@NotNull String arg, @NotNull CommandSender sender, @Nullable Player player) {
|
||||
switch (arg.toLowerCase()) {
|
||||
case "help" -> sendHelp(sender);
|
||||
case "reload" -> handleReload(sender, player);
|
||||
}
|
||||
|
||||
if (player == null) return;
|
||||
|
||||
switch (arg.toLowerCase()) {
|
||||
case "join" -> {
|
||||
if (!cooldown(sender, "join", 2500)) return;
|
||||
if (!ParkourOption.JOIN.mayPerform(player)) {
|
||||
send(sender, Locales.getString(player, "other.no_do"));
|
||||
return;
|
||||
}
|
||||
if (ParkourUser.getUser(player) != null) return;
|
||||
Modes.DEFAULT.create(player);
|
||||
}
|
||||
case "leave" -> {
|
||||
if (!cooldown(sender, "leave", 2500)) return;
|
||||
ParkourUser.leave(player);
|
||||
}
|
||||
case "play" -> {
|
||||
if (ParkourOption.PLAY.mayPerform(player)) Menus.PLAY.open(player);
|
||||
}
|
||||
case "menu", "main" -> {
|
||||
if (ParkourOption.MAIN.mayPerform(player)) Menus.MAIN.open(player);
|
||||
}
|
||||
case "leaderboard" -> player.performCommand("LoParkour leaderboard invalid");
|
||||
case "schematic" -> {
|
||||
if (!player.hasPermission(ParkourOption.ADMIN.permission)) {
|
||||
send(sender, Locales.getString(player, "other.no_do"));
|
||||
return;
|
||||
}
|
||||
SchematicCommandHandler.sendHelp(player);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handleReload(@NotNull CommandSender sender, @Nullable Player player) {
|
||||
if (!cooldown(sender, "reload", 2500)) return;
|
||||
if (!sender.hasPermission(ParkourOption.ADMIN.permission)) {
|
||||
send(sender, Locales.getString(player, "other.no_do"));
|
||||
return;
|
||||
}
|
||||
Config.reload(false);
|
||||
send(sender, LoParkour.PREFIX + "Reloaded config files.");
|
||||
}
|
||||
|
||||
public void sendHelp(@NotNull CommandSender sender) {
|
||||
send(sender, "");
|
||||
send(sender, "<dark_gray><strikethrough>---------------<reset> " + LoParkour.NAME + " <dark_gray><strikethrough>---------------<reset>");
|
||||
send(sender, "");
|
||||
send(sender, "<gray>/parkour <dark_gray>- Main command");
|
||||
if (sender.hasPermission(ParkourOption.JOIN.permission)) {
|
||||
send(sender, "<gray>/parkour join [mode/player] <dark_gray>- Join a mode");
|
||||
send(sender, "<gray>/parkour leave <dark_gray>- Leave parkour");
|
||||
}
|
||||
if (sender.hasPermission(ParkourOption.MAIN.permission))
|
||||
send(sender, "<gray>/parkour menu <dark_gray>- Open the menu");
|
||||
if (sender.hasPermission(ParkourOption.PLAY.permission))
|
||||
send(sender, "<gray>/parkour play <dark_gray>- Mode selection menu");
|
||||
if (sender.hasPermission(ParkourOption.LEADERBOARDS.permission))
|
||||
send(sender, "<gray>/parkour leaderboard [type] <dark_gray>- Leaderboard");
|
||||
if (sender.hasPermission(ParkourOption.ADMIN.permission)) {
|
||||
send(sender, "<gray>/LoParkour schematic <dark_gray>- Schematic tools");
|
||||
send(sender, "<gray>/LoParkour reload <dark_gray>- Reload configs");
|
||||
send(sender, "<gray>/LoParkour reset <everyone/player> <dark_gray>- Reset scores");
|
||||
send(sender, "<gray>/LoParkour forcejoin/forceleave <target> <dark_gray>- Force join/leave");
|
||||
send(sender, "<gray>/LoParkour recoverinventory <player> <dark_gray>- Recover inventory");
|
||||
}
|
||||
send(sender, "");
|
||||
}
|
||||
|
||||
public boolean cooldown(CommandSender sender, String key, long millis) {
|
||||
String fullKey = sender.getName() + ":" + key;
|
||||
long now = System.currentTimeMillis();
|
||||
Long last = cooldowns.get(fullKey);
|
||||
if (last != null && now - last < millis) return false;
|
||||
cooldowns.put(fullKey, now);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void send(CommandSender sender, String message) {
|
||||
sender.sendMessage(ColorUtil.color(message));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,149 @@
|
|||
package dev.loki.loparkour.command;
|
||||
|
||||
import dev.loki.loparkour.LoParkour;
|
||||
import dev.loki.loparkour.schematic.lpschem.LPSchematic;
|
||||
import dev.loki.loparkour.schematic.lpschem.LPSchematicBuilder;
|
||||
import dev.loki.loparkour.util.ColorUtil;
|
||||
import dev.loki.loparkour.util.Locations;
|
||||
import dev.loki.loparkour.util.ParticleUtil;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Particle;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.util.BoundingBox;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Handles /LoParkour schematic sub-commands (wand, pos1, pos2, save, paste, list, reload).
|
||||
*/
|
||||
public class SchematicCommandHandler {
|
||||
|
||||
public static final Map<Player, Location[]> selections = new HashMap<>();
|
||||
|
||||
private static final ItemStack WAND = buildWand();
|
||||
|
||||
private static ItemStack buildWand() {
|
||||
ItemStack item = new ItemStack(Material.GOLDEN_AXE);
|
||||
ItemMeta meta = item.getItemMeta();
|
||||
if (meta != null) {
|
||||
meta.setDisplayName(ColorUtil.color("<red><bold>LPSchematic Wand"));
|
||||
meta.setLore(List.of(
|
||||
ColorUtil.color("<gray>Left click: first position"),
|
||||
ColorUtil.color("<gray>Right click: second position")
|
||||
));
|
||||
item.setItemMeta(meta);
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
public static ItemStack getWand() {
|
||||
return WAND.clone();
|
||||
}
|
||||
|
||||
public static void sendHelp(Player player) {
|
||||
send(player, "");
|
||||
send(player, "<dark_red><bold>Schematics <reset><dark_gray>-----------");
|
||||
send(player, "<red>/LoParkour schematic wand <dark_gray>- <gray>Get the wand");
|
||||
send(player, "<red>/LoParkour schematic pos1 <dark_gray>- <gray>Set position 1");
|
||||
send(player, "<red>/LoParkour schematic pos2 <dark_gray>- <gray>Set position 2");
|
||||
send(player, "<red>/LoParkour schematic save <n> <dark_gray>- <gray>Save selection");
|
||||
send(player, "<red>/LoParkour schematic paste <n> <dark_gray>- <gray>Paste schematic");
|
||||
send(player, "<red>/LoParkour schematic list <dark_gray>- <gray>List loaded schematics");
|
||||
send(player, "<red>/LoParkour schematic reload <dark_gray>- <gray>Reload schematics");
|
||||
send(player, "");
|
||||
}
|
||||
|
||||
/** Called for 2-arg schematic commands (wand/pos1/pos2/list/reload) */
|
||||
public static void handleSubcommand(String sub, CommandSender sender, Player player, PlayerCommandHandler cooldowns) {
|
||||
Location playerLoc = player.getLocation();
|
||||
Location[] sel = selections.get(player);
|
||||
|
||||
switch (sub.toLowerCase()) {
|
||||
case "wand" -> {
|
||||
player.getInventory().addItem(WAND.clone());
|
||||
send(player, "<dark_red><bold>Schematics <reset><gray>Wand given. Left/Right click to set positions.");
|
||||
}
|
||||
case "pos1" -> setPos(player, sel, playerLoc, 0);
|
||||
case "pos2" -> setPos(player, sel, playerLoc, 1);
|
||||
case "list" -> {
|
||||
send(player, "<dark_red><bold>Schematics <reset><gray>Loaded schematics:");
|
||||
LoParkour.getSchematicManager().getAllSchematics().forEach((name, s) ->
|
||||
send(player, "<gray>- <red>" + name + " <dark_gray>(" + s.getMetadata().getDifficulty() + ")"));
|
||||
}
|
||||
case "reload" -> {
|
||||
if (!cooldowns.cooldown(sender, "schematic-reload", 2500)) return;
|
||||
LoParkour.getSchematicManager().reload();
|
||||
send(player, "<dark_red><bold>Schematics <reset><gray>Reloaded.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Called for 3-arg schematic commands (save <n>, paste <n>) */
|
||||
public static void handleSubcommandWithName(String sub, String name, CommandSender sender,
|
||||
Player player, PlayerCommandHandler cooldowns) {
|
||||
switch (sub.toLowerCase()) {
|
||||
case "save" -> handleSave(name, sender, player, cooldowns);
|
||||
case "paste" -> handlePaste(name, sender, player);
|
||||
}
|
||||
}
|
||||
|
||||
// ── internal ───────────────────────────────────────────────────────────────
|
||||
|
||||
private static void setPos(Player player, Location[] existing, Location loc, int index) {
|
||||
String label = index == 0 ? "1" : "2";
|
||||
send(player, LoParkour.PREFIX + "Position " + label + " set to " + Locations.toString(loc, true));
|
||||
|
||||
Location[] updated = existing != null ? existing.clone() : new Location[]{null, null};
|
||||
updated[index] = loc;
|
||||
selections.put(player, updated);
|
||||
|
||||
if (updated[0] != null && updated[1] != null) {
|
||||
ParticleUtil.box(BoundingBox.of(updated[0], updated[1]), player.getWorld(), Particle.END_ROD, player, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
private static void handleSave(String name, CommandSender sender, Player player, PlayerCommandHandler cooldowns) {
|
||||
if (!cooldowns.cooldown(sender, "schematic-save", 2500)) return;
|
||||
|
||||
Location[] sel = selections.get(player);
|
||||
if (sel == null || sel[0] == null || sel[1] == null) {
|
||||
send(player, "<dark_red><bold>Schematics <reset><red>Selection incomplete. Set both positions first.");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
int w = Math.abs(sel[1].getBlockX() - sel[0].getBlockX()) + 1;
|
||||
int h = Math.abs(sel[1].getBlockY() - sel[0].getBlockY()) + 1;
|
||||
int l = Math.abs(sel[1].getBlockZ() - sel[0].getBlockZ()) + 1;
|
||||
|
||||
LPSchematic schematic = new LPSchematicBuilder(name, player.getName(), 0.5, w, h, l)
|
||||
.fromWorld(player.getWorld(), sel[0], sel[1])
|
||||
.build();
|
||||
|
||||
LoParkour.getSchematicManager().saveSchematic(schematic);
|
||||
send(player, "<dark_red><bold>Schematics <reset><gray>Saved as <red>" + name + "<gray>!");
|
||||
} catch (Exception e) {
|
||||
send(player, "<dark_red><bold>Schematics <reset><red>Failed: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private static void handlePaste(String name, CommandSender sender, Player player) {
|
||||
LPSchematic s = LoParkour.getSchematicManager().getSchematic(name);
|
||||
if (s == null) {
|
||||
send(sender, LoParkour.PREFIX + "Schematic '" + name + "' not found.");
|
||||
return;
|
||||
}
|
||||
s.paste(player.getLocation(), player.getWorld());
|
||||
send(sender, LoParkour.PREFIX + "Pasted " + name + ".");
|
||||
}
|
||||
|
||||
private static void send(CommandSender sender, String msg) {
|
||||
sender.sendMessage(ColorUtil.color(msg));
|
||||
}
|
||||
}
|
||||
|
|
@ -75,8 +75,6 @@ public enum Config {
|
|||
Locales.init();
|
||||
Schematics.init();
|
||||
Option.init(initialLoad);
|
||||
|
||||
LoParkour.log("Loaded all config files");
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
115
src/main/java/dev/loki/loparkour/config/GenerationOptions.java
Normal file
115
src/main/java/dev/loki/loparkour/config/GenerationOptions.java
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
package dev.loki.loparkour.config;
|
||||
|
||||
import dev.loki.loparkour.LoParkour;
|
||||
|
||||
/**
|
||||
* Generation and jump-validation settings. Populated by {@link Option#init}.
|
||||
*/
|
||||
public final class GenerationOptions {
|
||||
|
||||
// Block type chances
|
||||
public static double TYPE_NORMAL;
|
||||
public static double TYPE_SPECIAL;
|
||||
public static double TYPE_SCHEMATICS;
|
||||
|
||||
// Special block chances
|
||||
public static double SPECIAL_ICE;
|
||||
public static double SPECIAL_SLAB;
|
||||
public static double SPECIAL_PANE;
|
||||
public static double SPECIAL_FENCE;
|
||||
|
||||
// Distance weights
|
||||
public static double NORMAL_DISTANCE_1;
|
||||
public static double NORMAL_DISTANCE_2;
|
||||
public static double NORMAL_DISTANCE_3;
|
||||
public static double NORMAL_DISTANCE_4;
|
||||
|
||||
// Height weights
|
||||
public static double NORMAL_HEIGHT_1;
|
||||
public static double NORMAL_HEIGHT_0;
|
||||
public static double NORMAL_HEIGHT_NEG1;
|
||||
public static double NORMAL_HEIGHT_NEG2;
|
||||
|
||||
// World bounds
|
||||
public static int MAX_Y;
|
||||
public static int MIN_Y;
|
||||
|
||||
// Jump validation
|
||||
public static boolean JUMP_VALIDATION_ENABLED;
|
||||
public static double MAX_JUMP_DISTANCE;
|
||||
public static double MAX_HORIZONTAL_DISTANCE;
|
||||
public static double MAX_VERTICAL_UP;
|
||||
public static double MAX_VERTICAL_DOWN;
|
||||
|
||||
// Jump types
|
||||
public static boolean JUMP_TYPES_ENABLED;
|
||||
public static java.util.Map<String, Boolean> JUMP_TYPE_ENABLED;
|
||||
public static java.util.Map<String, Double> JUMP_TYPE_CHANCE;
|
||||
|
||||
// Memory optimisation
|
||||
public static int BLOCK_CLEANUP_DISTANCE;
|
||||
public static int CLEANUP_INTERVAL;
|
||||
|
||||
// Ghost mode
|
||||
public static boolean GHOST_MODE_ENABLED;
|
||||
public static int GHOST_SHOW_TOP;
|
||||
public static double GHOST_TRANSPARENCY;
|
||||
|
||||
static void init() {
|
||||
// Jump validation
|
||||
JUMP_VALIDATION_ENABLED = Config.CONFIG.getBoolean("jump-validation.enabled");
|
||||
MAX_JUMP_DISTANCE = Config.CONFIG.getDouble("jump-validation.max-distance");
|
||||
MAX_HORIZONTAL_DISTANCE = Config.CONFIG.getDouble("jump-validation.max-horizontal");
|
||||
MAX_VERTICAL_UP = Config.CONFIG.getDouble("jump-validation.max-vertical-up");
|
||||
MAX_VERTICAL_DOWN = Config.CONFIG.getDouble("jump-validation.max-vertical-down");
|
||||
|
||||
// Jump types
|
||||
JUMP_TYPES_ENABLED = Config.CONFIG.getBoolean("jump-types.enabled");
|
||||
JUMP_TYPE_ENABLED = new java.util.HashMap<>();
|
||||
JUMP_TYPE_CHANCE = new java.util.HashMap<>();
|
||||
for (String type : Config.CONFIG.getChildren("jump-types.types")) {
|
||||
String path = "jump-types.types." + type;
|
||||
JUMP_TYPE_ENABLED.put(type, Config.CONFIG.getBoolean(path + ".enabled"));
|
||||
JUMP_TYPE_CHANCE.put(type, Config.CONFIG.getDouble(path + ".chance"));
|
||||
}
|
||||
|
||||
// Memory
|
||||
BLOCK_CLEANUP_DISTANCE = Config.CONFIG.getInt("memory.block-cleanup-distance");
|
||||
CLEANUP_INTERVAL = Config.CONFIG.getInt("memory.cleanup-interval");
|
||||
|
||||
// Ghost
|
||||
GHOST_MODE_ENABLED = Config.CONFIG.getBoolean("ghost-mode.enabled");
|
||||
GHOST_SHOW_TOP = Config.CONFIG.getInt("ghost-mode.show-top");
|
||||
GHOST_TRANSPARENCY = Config.CONFIG.getDouble("ghost-mode.transparency");
|
||||
|
||||
// Generation chances
|
||||
TYPE_NORMAL = Config.GENERATION.getInt("generation.type.normal") / 100.0;
|
||||
TYPE_SPECIAL = Config.GENERATION.getInt("generation.type.special") / 100.0;
|
||||
TYPE_SCHEMATICS = Config.GENERATION.getInt("generation.type.schematic") / 100.0;
|
||||
|
||||
SPECIAL_ICE = Config.GENERATION.getInt("generation.special.ice") / 100.0;
|
||||
SPECIAL_SLAB = Config.GENERATION.getInt("generation.special.slab") / 100.0;
|
||||
SPECIAL_PANE = Config.GENERATION.getInt("generation.special.pane") / 100.0;
|
||||
SPECIAL_FENCE = Config.GENERATION.getInt("generation.special.fence") / 100.0;
|
||||
|
||||
NORMAL_DISTANCE_1 = Config.GENERATION.getInt("generation.normal.distance.1") / 100.0;
|
||||
NORMAL_DISTANCE_2 = Config.GENERATION.getInt("generation.normal.distance.2") / 100.0;
|
||||
NORMAL_DISTANCE_3 = Config.GENERATION.getInt("generation.normal.distance.3") / 100.0;
|
||||
NORMAL_DISTANCE_4 = Config.GENERATION.getInt("generation.normal.distance.4") / 100.0;
|
||||
|
||||
NORMAL_HEIGHT_1 = Config.GENERATION.getInt("generation.normal.height.1") / 100.0;
|
||||
NORMAL_HEIGHT_0 = Config.GENERATION.getInt("generation.normal.height.0") / 100.0;
|
||||
NORMAL_HEIGHT_NEG1 = Config.GENERATION.getInt("generation.normal.height.-1") / 100.0;
|
||||
NORMAL_HEIGHT_NEG2 = Config.GENERATION.getInt("generation.normal.height.-2") / 100.0;
|
||||
|
||||
MAX_Y = Config.GENERATION.getInt("generation.settings.max-y");
|
||||
MIN_Y = Config.GENERATION.getInt("generation.settings.min-y");
|
||||
|
||||
if (MIN_Y >= MAX_Y) {
|
||||
MIN_Y = 100; MAX_Y = 200;
|
||||
LoParkour.getPlugin().getLogger().severe("min-y >= max-y in generation.yml — using defaults 100/200.");
|
||||
}
|
||||
}
|
||||
|
||||
private GenerationOptions() {}
|
||||
}
|
||||
|
|
@ -69,9 +69,6 @@ public class Locales {
|
|||
if (files != null && files.length == 0) {
|
||||
plugin.saveResource("locales/en.yml", false);
|
||||
plugin.saveResource("locales/ru.yml", false);
|
||||
plugin.saveResource("locales/nl.yml", false);
|
||||
plugin.saveResource("locales/fr.yml", false);
|
||||
plugin.saveResource("locales/zh_cn.yml", false);
|
||||
}
|
||||
|
||||
// get all files in locales folder
|
||||
|
|
@ -241,7 +238,13 @@ public class Locales {
|
|||
idx++;
|
||||
}
|
||||
|
||||
Item item = new Item(Material.getMaterial(material.toUpperCase()), name);
|
||||
Material mat = Material.getMaterial(material.toUpperCase());
|
||||
if (mat == null) {
|
||||
LoParkour.getPlugin().getLogger().warning("Invalid material '%s' for locale path '%s', using STONE as fallback".formatted(material, path));
|
||||
mat = Material.STONE;
|
||||
}
|
||||
|
||||
Item item = new Item(mat, name);
|
||||
|
||||
if (!lore.isEmpty()) {
|
||||
item.lore(lore.split("\\|\\|"));
|
||||
|
|
|
|||
|
|
@ -1,17 +1,11 @@
|
|||
package dev.loki.loparkour.config;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import dev.loki.loparkour.util.ParticleData;
|
||||
|
||||
import dev.loki.loparkour.LoParkour;
|
||||
import dev.loki.loparkour.api.Registry;
|
||||
import dev.loki.loparkour.menu.ParkourOption;
|
||||
import dev.loki.loparkour.style.RandomStyle;
|
||||
import dev.loki.loparkour.style.Style;
|
||||
|
||||
import dev.loki.loparkour.util.ParticleData;
|
||||
import org.bukkit.*;
|
||||
import org.bukkit.block.BlockFace;
|
||||
import org.bukkit.configuration.file.FileConfiguration;
|
||||
|
|
@ -21,303 +15,198 @@ import java.util.*;
|
|||
import java.util.function.BiFunction;
|
||||
|
||||
/**
|
||||
* Class for variables required in generating without accessing the file a lot (constants)
|
||||
* Top-level config constants used throughout the plugin.
|
||||
*
|
||||
* Heavy sub-sections are delegated to focused classes:
|
||||
* <ul>
|
||||
* <li>{@link SqlOptions} – SQL connection settings</li>
|
||||
* <li>{@link ParticleOptions} – particle & sound settings</li>
|
||||
* <li>{@link GenerationOptions} – generation & jump-validation settings</li>
|
||||
* </ul>
|
||||
*
|
||||
* Those classes expose their own static fields. Option re-exports the most
|
||||
* commonly accessed ones via forwarding fields so existing call-sites compile
|
||||
* without changes.
|
||||
*/
|
||||
public class Option {
|
||||
|
||||
// ── General ───────────────────────────────────────────────────────────────
|
||||
|
||||
public static double BORDER_SIZE;
|
||||
public static List<Integer> POSSIBLE_LEADS;
|
||||
|
||||
// Advanced settings
|
||||
public static BlockFace HEADING;
|
||||
|
||||
public static Map<ParkourOption, Boolean> OPTIONS_ENABLED;
|
||||
public static Map<ParkourOption, String> OPTIONS_DEFAULTS;
|
||||
public static Map<ParkourOption, String> OPTIONS_DEFAULTS;
|
||||
|
||||
public static Location GO_BACK_LOC;
|
||||
|
||||
public static void init(boolean firstLoad) {
|
||||
initSql();
|
||||
initEnums();
|
||||
initGeneration();
|
||||
initStyles("styles.list", Config.CONFIG.fileConfiguration, RandomStyle::new)
|
||||
.forEach(Registry::register);
|
||||
// ── Forwarded from sub-classes (backwards compat) ─────────────────────────
|
||||
|
||||
/** @see SqlOptions#SQL */
|
||||
public static boolean SQL;
|
||||
|
||||
/** @see ParticleOptions#SHAPE */
|
||||
public static ParticleShape PARTICLE_SHAPE;
|
||||
/** @see ParticleOptions#SOUND_TYPE */
|
||||
public static Sound SOUND_TYPE;
|
||||
/** @see ParticleOptions#SOUND_PITCH */
|
||||
public static int SOUND_PITCH;
|
||||
/** @see ParticleOptions#SOUND_VOLUME */
|
||||
public static int SOUND_VOLUME;
|
||||
/** @see ParticleOptions#PARTICLE_TYPE */
|
||||
public static Particle PARTICLE_TYPE;
|
||||
/** @see ParticleOptions#PARTICLE_DATA */
|
||||
public static ParticleData<?> PARTICLE_DATA;
|
||||
|
||||
/** @see GenerationOptions */
|
||||
public static double TYPE_NORMAL, TYPE_SPECIAL, TYPE_SCHEMATICS;
|
||||
public static double SPECIAL_ICE, SPECIAL_SLAB, SPECIAL_PANE, SPECIAL_FENCE;
|
||||
public static double NORMAL_DISTANCE_1, NORMAL_DISTANCE_2, NORMAL_DISTANCE_3, NORMAL_DISTANCE_4;
|
||||
public static double NORMAL_HEIGHT_1, NORMAL_HEIGHT_0, NORMAL_HEIGHT_NEG1, NORMAL_HEIGHT_NEG2;
|
||||
public static int MAX_Y, MIN_Y;
|
||||
public static int BLOCK_CLEANUP_DISTANCE, CLEANUP_INTERVAL;
|
||||
public static boolean GHOST_MODE_ENABLED;
|
||||
public static int GHOST_SHOW_TOP;
|
||||
public static double GHOST_TRANSPARENCY;
|
||||
|
||||
// ── init ──────────────────────────────────────────────────────────────────
|
||||
|
||||
public static void init(boolean firstLoad) {
|
||||
SqlOptions.init();
|
||||
ParticleOptions.init();
|
||||
GenerationOptions.init();
|
||||
|
||||
syncForwardedFields();
|
||||
initGeneral(firstLoad);
|
||||
initOptions();
|
||||
|
||||
initStyles("styles.list", Config.CONFIG.fileConfiguration, RandomStyle::new)
|
||||
.forEach(Registry::register);
|
||||
}
|
||||
|
||||
/** Copy sub-class fields to this class so existing code still compiles. */
|
||||
private static void syncForwardedFields() {
|
||||
SQL = SqlOptions.SQL;
|
||||
|
||||
PARTICLE_SHAPE = ParticleOptions.SHAPE;
|
||||
SOUND_TYPE = ParticleOptions.SOUND_TYPE;
|
||||
SOUND_PITCH = ParticleOptions.SOUND_PITCH;
|
||||
SOUND_VOLUME = ParticleOptions.SOUND_VOLUME;
|
||||
PARTICLE_TYPE = ParticleOptions.PARTICLE_TYPE;
|
||||
PARTICLE_DATA = ParticleOptions.PARTICLE_DATA;
|
||||
|
||||
TYPE_NORMAL = GenerationOptions.TYPE_NORMAL;
|
||||
TYPE_SPECIAL = GenerationOptions.TYPE_SPECIAL;
|
||||
TYPE_SCHEMATICS = GenerationOptions.TYPE_SCHEMATICS;
|
||||
|
||||
SPECIAL_ICE = GenerationOptions.SPECIAL_ICE;
|
||||
SPECIAL_SLAB = GenerationOptions.SPECIAL_SLAB;
|
||||
SPECIAL_PANE = GenerationOptions.SPECIAL_PANE;
|
||||
SPECIAL_FENCE = GenerationOptions.SPECIAL_FENCE;
|
||||
|
||||
NORMAL_DISTANCE_1 = GenerationOptions.NORMAL_DISTANCE_1;
|
||||
NORMAL_DISTANCE_2 = GenerationOptions.NORMAL_DISTANCE_2;
|
||||
NORMAL_DISTANCE_3 = GenerationOptions.NORMAL_DISTANCE_3;
|
||||
NORMAL_DISTANCE_4 = GenerationOptions.NORMAL_DISTANCE_4;
|
||||
|
||||
NORMAL_HEIGHT_1 = GenerationOptions.NORMAL_HEIGHT_1;
|
||||
NORMAL_HEIGHT_0 = GenerationOptions.NORMAL_HEIGHT_0;
|
||||
NORMAL_HEIGHT_NEG1 = GenerationOptions.NORMAL_HEIGHT_NEG1;
|
||||
NORMAL_HEIGHT_NEG2 = GenerationOptions.NORMAL_HEIGHT_NEG2;
|
||||
|
||||
MAX_Y = GenerationOptions.MAX_Y;
|
||||
MIN_Y = GenerationOptions.MIN_Y;
|
||||
|
||||
BLOCK_CLEANUP_DISTANCE = GenerationOptions.BLOCK_CLEANUP_DISTANCE;
|
||||
CLEANUP_INTERVAL = GenerationOptions.CLEANUP_INTERVAL;
|
||||
|
||||
GHOST_MODE_ENABLED = GenerationOptions.GHOST_MODE_ENABLED;
|
||||
GHOST_SHOW_TOP = GenerationOptions.GHOST_SHOW_TOP;
|
||||
GHOST_TRANSPARENCY = GenerationOptions.GHOST_TRANSPARENCY;
|
||||
}
|
||||
|
||||
// ── General init ──────────────────────────────────────────────────────────
|
||||
|
||||
private static void initGeneral(boolean firstLoad) {
|
||||
GO_BACK_LOC = parseLocation(Config.CONFIG.getString("bungeecord.go-back"));
|
||||
String[] axes = Config.CONFIG.getString("bungeecord.go-back-axes").split(",");
|
||||
GO_BACK_LOC.setPitch(Float.parseFloat(axes[0]));
|
||||
GO_BACK_LOC.setYaw(Float.parseFloat(axes[1]));
|
||||
|
||||
// General settings
|
||||
|
||||
// Options
|
||||
|
||||
List<ParkourOption> options = new ArrayList<>(Arrays.asList(ParkourOption.values()));
|
||||
|
||||
// exceptions
|
||||
options.remove(ParkourOption.JOIN);
|
||||
options.remove(ParkourOption.ADMIN);
|
||||
|
||||
// =====================================
|
||||
|
||||
OPTIONS_DEFAULTS = new HashMap<>();
|
||||
OPTIONS_ENABLED = new HashMap<>();
|
||||
|
||||
String prefix = "default-values";
|
||||
for (ParkourOption option : options) {
|
||||
String parent = "%s.%s".formatted(prefix, option.path);
|
||||
|
||||
// register enabled value
|
||||
OPTIONS_ENABLED.put(option, Config.CONFIG.getBoolean("%s.enabled".formatted(parent)));
|
||||
|
||||
// register default value
|
||||
if (!Config.CONFIG.isPath("%s.default".formatted(parent))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Object value = Config.CONFIG.get("%s.default".formatted(parent));
|
||||
|
||||
if (value != null) {
|
||||
OPTIONS_DEFAULTS.put(option, String.valueOf(value));
|
||||
}
|
||||
}
|
||||
|
||||
// =====================================
|
||||
|
||||
// Config stuff
|
||||
|
||||
POSSIBLE_LEADS = Config.CONFIG.getIntList("options.leads.amount");
|
||||
for (int lead : new ArrayList<>(POSSIBLE_LEADS)) {
|
||||
if (lead < 1 || lead > 128) {
|
||||
LoParkour.getPlugin().getLogger().severe("Invalid lead: %d. Should be above 1 and below 128.".formatted(lead));
|
||||
LoParkour.getPlugin().getLogger().severe("Invalid lead: %d. Must be 1–128.".formatted(lead));
|
||||
POSSIBLE_LEADS.remove((Object) lead);
|
||||
}
|
||||
}
|
||||
|
||||
// Generation
|
||||
String heading = Config.GENERATION.getString("advanced.island.parkour.heading");
|
||||
|
||||
switch (heading.toLowerCase()) {
|
||||
case "north" -> HEADING = BlockFace.NORTH;
|
||||
case "south" -> HEADING = BlockFace.SOUTH;
|
||||
case "west" -> HEADING = BlockFace.WEST;
|
||||
case "east" -> HEADING = BlockFace.EAST;
|
||||
default -> LoParkour.getPlugin().getLogger().severe("Invalid heading: %s".formatted(heading));
|
||||
}
|
||||
|
||||
// Scoring
|
||||
HEADING = switch (heading.toLowerCase()) {
|
||||
case "north" -> BlockFace.NORTH;
|
||||
case "south" -> BlockFace.SOUTH;
|
||||
case "west" -> BlockFace.WEST;
|
||||
case "east" -> BlockFace.EAST;
|
||||
default -> {
|
||||
LoParkour.getPlugin().getLogger().severe("Invalid heading: " + heading);
|
||||
yield BlockFace.NORTH;
|
||||
}
|
||||
};
|
||||
|
||||
if (firstLoad) {
|
||||
BORDER_SIZE = Config.GENERATION.getDouble("advanced.border-size");
|
||||
SQL = Config.CONFIG.getBoolean("sql.enabled");
|
||||
}
|
||||
}
|
||||
|
||||
private static Vector stringToVector(String direction) {
|
||||
return switch (direction.toLowerCase()) {
|
||||
case "north" -> new Vector(0, 0, -1);
|
||||
case "south" -> new Vector(0, 0, 1);
|
||||
case "west" -> new Vector(-1, 0, 0);
|
||||
default -> new Vector(1, 0, 0); // east
|
||||
};
|
||||
private static void initOptions() {
|
||||
List<ParkourOption> options = new ArrayList<>(Arrays.asList(ParkourOption.values()));
|
||||
options.remove(ParkourOption.JOIN);
|
||||
options.remove(ParkourOption.ADMIN);
|
||||
|
||||
OPTIONS_DEFAULTS = new HashMap<>();
|
||||
OPTIONS_ENABLED = new HashMap<>();
|
||||
|
||||
for (ParkourOption option : options) {
|
||||
String parent = "default-values." + option.path;
|
||||
OPTIONS_ENABLED.put(option, Config.CONFIG.getBoolean(parent + ".enabled"));
|
||||
|
||||
if (Config.CONFIG.isPath(parent + ".default")) {
|
||||
Object value = Config.CONFIG.get(parent + ".default");
|
||||
if (value != null) OPTIONS_DEFAULTS.put(option, String.valueOf(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Helpers ───────────────────────────────────────────────────────────────
|
||||
|
||||
private static Location parseLocation(String location) {
|
||||
String[] values = location.replaceAll("[()]", "").replaceAll("[, ]", " ").split(" ");
|
||||
|
||||
World world = Bukkit.getWorld(values[3]);
|
||||
|
||||
if (world == null) {
|
||||
world = Bukkit.getWorlds().get(0);
|
||||
}
|
||||
|
||||
return new Location(world, Double.parseDouble(values[0]), Double.parseDouble(values[1]), Double.parseDouble(values[2]));
|
||||
if (world == null) world = Bukkit.getWorlds().get(0);
|
||||
return new Location(world, Double.parseDouble(values[0]),
|
||||
Double.parseDouble(values[1]), Double.parseDouble(values[2]));
|
||||
}
|
||||
|
||||
public static ParticleShape PARTICLE_SHAPE;
|
||||
public static Sound SOUND_TYPE;
|
||||
public static int SOUND_PITCH;
|
||||
public static int SOUND_VOLUME;
|
||||
public static Particle PARTICLE_TYPE;
|
||||
public static ParticleData<?> PARTICLE_DATA;
|
||||
|
||||
private static void initEnums() {
|
||||
String value = Config.CONFIG.getString("particles.sound-type").toUpperCase();
|
||||
|
||||
try {
|
||||
SOUND_TYPE = Sound.valueOf(value);
|
||||
} catch (IllegalArgumentException ex) {
|
||||
SOUND_TYPE = Sound.valueOf("BLOCK_NOTE_PLING");
|
||||
LoParkour.getPlugin().getLogger().severe("Invalid sound: %s".formatted(value));
|
||||
}
|
||||
|
||||
value = Config.CONFIG.getString("particles.particle-type");
|
||||
try {
|
||||
PARTICLE_TYPE = Particle.valueOf(value);
|
||||
} catch (IllegalArgumentException ex) {
|
||||
PARTICLE_TYPE = Particle.valueOf("SPELL_INSTANT");
|
||||
LoParkour.getPlugin().getLogger().severe("Invalid particle type: %s".formatted(value));
|
||||
}
|
||||
|
||||
SOUND_PITCH = Config.CONFIG.getInt("particles.sound-pitch");
|
||||
SOUND_VOLUME = Config.CONFIG.getInt("particles.sound-volume");
|
||||
PARTICLE_SHAPE = ParticleShape.valueOf(Config.CONFIG.getString("particles.particle-shape").toUpperCase());
|
||||
PARTICLE_DATA = new ParticleData<>(PARTICLE_TYPE, null, 10);
|
||||
}
|
||||
|
||||
public enum ParticleShape {
|
||||
DOT, CIRCLE, BOX
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------
|
||||
// MySQL
|
||||
public static boolean SQL;
|
||||
public static int SQL_PORT;
|
||||
public static String SQL_URL;
|
||||
public static String SQL_DB;
|
||||
public static String SQL_USERNAME;
|
||||
public static String SQL_PASSWORD;
|
||||
public static String SQL_PREFIX;
|
||||
|
||||
private static void initSql() {
|
||||
SQL_PORT = Config.CONFIG.getInt("sql.port");
|
||||
SQL_DB = Config.CONFIG.getString("sql.database");
|
||||
SQL_URL = Config.CONFIG.getString("sql.url");
|
||||
SQL_USERNAME = Config.CONFIG.getString("sql.username");
|
||||
SQL_PASSWORD = Config.CONFIG.getString("sql.password");
|
||||
SQL_PREFIX = Config.CONFIG.getString("sql.prefix");
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------
|
||||
// Jump Validation
|
||||
|
||||
public static boolean JUMP_VALIDATION_ENABLED;
|
||||
public static double MAX_JUMP_DISTANCE;
|
||||
public static double MAX_HORIZONTAL_DISTANCE;
|
||||
public static double MAX_VERTICAL_UP;
|
||||
public static double MAX_VERTICAL_DOWN;
|
||||
|
||||
// Jump Types
|
||||
|
||||
public static boolean JUMP_TYPES_ENABLED;
|
||||
public static Map<String, Boolean> JUMP_TYPE_ENABLED;
|
||||
public static Map<String, Double> JUMP_TYPE_CHANCE;
|
||||
|
||||
// Memory Optimization
|
||||
|
||||
public static int BLOCK_CLEANUP_DISTANCE;
|
||||
public static int CLEANUP_INTERVAL;
|
||||
|
||||
// Ghost Mode
|
||||
|
||||
public static boolean GHOST_MODE_ENABLED;
|
||||
public static int GHOST_SHOW_TOP;
|
||||
public static double GHOST_TRANSPARENCY;
|
||||
|
||||
// --------------------------------------------------------------
|
||||
// Generation
|
||||
|
||||
public static double TYPE_NORMAL;
|
||||
public static double TYPE_SPECIAL;
|
||||
public static double TYPE_SCHEMATICS;
|
||||
|
||||
public static double SPECIAL_ICE;
|
||||
public static double SPECIAL_SLAB;
|
||||
public static double SPECIAL_PANE;
|
||||
public static double SPECIAL_FENCE;
|
||||
|
||||
public static double NORMAL_DISTANCE_1;
|
||||
public static double NORMAL_DISTANCE_2;
|
||||
public static double NORMAL_DISTANCE_3;
|
||||
public static double NORMAL_DISTANCE_4;
|
||||
|
||||
public static double NORMAL_HEIGHT_1;
|
||||
public static double NORMAL_HEIGHT_0;
|
||||
public static double NORMAL_HEIGHT_NEG1;
|
||||
public static double NORMAL_HEIGHT_NEG2;
|
||||
|
||||
public static int MAX_Y;
|
||||
public static int MIN_Y;
|
||||
|
||||
private static void initGeneration() {
|
||||
// Jump validation
|
||||
JUMP_VALIDATION_ENABLED = Config.CONFIG.getBoolean("jump-validation.enabled");
|
||||
MAX_JUMP_DISTANCE = Config.CONFIG.getDouble("jump-validation.max-distance");
|
||||
MAX_HORIZONTAL_DISTANCE = Config.CONFIG.getDouble("jump-validation.max-horizontal");
|
||||
MAX_VERTICAL_UP = Config.CONFIG.getDouble("jump-validation.max-vertical-up");
|
||||
MAX_VERTICAL_DOWN = Config.CONFIG.getDouble("jump-validation.max-vertical-down");
|
||||
|
||||
// Jump types
|
||||
JUMP_TYPES_ENABLED = Config.CONFIG.getBoolean("jump-types.enabled");
|
||||
JUMP_TYPE_ENABLED = new HashMap<>();
|
||||
JUMP_TYPE_CHANCE = new HashMap<>();
|
||||
|
||||
for (String type : Config.CONFIG.getChildren("jump-types.types")) {
|
||||
String path = "jump-types.types." + type;
|
||||
JUMP_TYPE_ENABLED.put(type, Config.CONFIG.getBoolean(path + ".enabled"));
|
||||
JUMP_TYPE_CHANCE.put(type, Config.CONFIG.getDouble(path + ".chance"));
|
||||
}
|
||||
|
||||
// Memory optimization
|
||||
BLOCK_CLEANUP_DISTANCE = Config.CONFIG.getInt("memory.block-cleanup-distance");
|
||||
CLEANUP_INTERVAL = Config.CONFIG.getInt("memory.cleanup-interval");
|
||||
|
||||
// Ghost mode
|
||||
GHOST_MODE_ENABLED = Config.CONFIG.getBoolean("ghost-mode.enabled");
|
||||
GHOST_SHOW_TOP = Config.CONFIG.getInt("ghost-mode.show-top");
|
||||
GHOST_TRANSPARENCY = Config.CONFIG.getDouble("ghost-mode.transparency");
|
||||
|
||||
TYPE_NORMAL = Config.GENERATION.getInt("generation.type.normal") / 100.0;
|
||||
TYPE_SPECIAL = Config.GENERATION.getInt("generation.type.schematic") / 100.0;
|
||||
TYPE_SCHEMATICS = Config.GENERATION.getInt("generation.type.special") / 100.0;
|
||||
|
||||
SPECIAL_ICE = Config.GENERATION.getInt("generation.special.ice") / 100.0;
|
||||
SPECIAL_SLAB = Config.GENERATION.getInt("generation.special.slab") / 100.0;
|
||||
SPECIAL_PANE = Config.GENERATION.getInt("generation.special.pane") / 100.0;
|
||||
SPECIAL_FENCE = Config.GENERATION.getInt("generation.special.fence") / 100.0;
|
||||
|
||||
NORMAL_DISTANCE_1 = Config.GENERATION.getInt("generation.normal.distance.1") / 100.0;
|
||||
NORMAL_DISTANCE_2 = Config.GENERATION.getInt("generation.normal.distance.2") / 100.0;
|
||||
NORMAL_DISTANCE_3 = Config.GENERATION.getInt("generation.normal.distance.3") / 100.0;
|
||||
NORMAL_DISTANCE_4 = Config.GENERATION.getInt("generation.normal.distance.4") / 100.0;
|
||||
|
||||
NORMAL_HEIGHT_1 = Config.GENERATION.getInt("generation.normal.height.1") / 100.0;
|
||||
NORMAL_HEIGHT_0 = Config.GENERATION.getInt("generation.normal.height.0") / 100.0;
|
||||
NORMAL_HEIGHT_NEG1 = Config.GENERATION.getInt("generation.normal.height.-1") / 100.0;
|
||||
NORMAL_HEIGHT_NEG2 = Config.GENERATION.getInt("generation.normal.height.-2") / 100.0;
|
||||
|
||||
MAX_Y = Config.GENERATION.getInt("generation.settings.max-y");
|
||||
MIN_Y = Config.GENERATION.getInt("generation.settings.min-y");
|
||||
|
||||
if (MIN_Y >= MAX_Y) {
|
||||
MIN_Y = 100;
|
||||
MAX_Y = 200;
|
||||
|
||||
LoParkour.getPlugin().getLogger().severe("Provided minimum y is the same or larger than maximum y! - check your generation.yml file");
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------
|
||||
|
||||
public static Set<Style> initStyles(String path, FileConfiguration config, BiFunction<String, List<Material>, Style> fn) {
|
||||
var styles = new HashSet<Style>();
|
||||
|
||||
for (String style : Locales.getChildren(config, path, false)) {
|
||||
styles.add(fn.apply(style,
|
||||
config.getStringList("%s.%s".formatted(path, style)).stream()
|
||||
.map(name -> {
|
||||
var material = Material.getMaterial(name.toUpperCase());
|
||||
|
||||
if (material == null) {
|
||||
LoParkour.getPlugin().getLogger().severe("Invalid material %s in style %s".formatted(name, style));
|
||||
return Material.STONE;
|
||||
}
|
||||
|
||||
return material;
|
||||
})
|
||||
.toList()));
|
||||
config.getStringList("%s.%s".formatted(path, style)).stream()
|
||||
.map(name -> {
|
||||
Material m = Material.getMaterial(name.toUpperCase());
|
||||
if (m == null) {
|
||||
LoParkour.getPlugin().getLogger().severe("Invalid material %s in style %s".formatted(name, style));
|
||||
return Material.STONE;
|
||||
}
|
||||
return m;
|
||||
})
|
||||
.toList()));
|
||||
}
|
||||
|
||||
return styles;
|
||||
}
|
||||
|
||||
// ── Inner types ───────────────────────────────────────────────────────────
|
||||
|
||||
public enum ParticleShape { DOT, CIRCLE, BOX }
|
||||
}
|
||||
|
|
|
|||
44
src/main/java/dev/loki/loparkour/config/ParticleOptions.java
Normal file
44
src/main/java/dev/loki/loparkour/config/ParticleOptions.java
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
package dev.loki.loparkour.config;
|
||||
|
||||
import dev.loki.loparkour.LoParkour;
|
||||
import dev.loki.loparkour.util.ParticleData;
|
||||
import org.bukkit.Particle;
|
||||
import org.bukkit.Sound;
|
||||
|
||||
/**
|
||||
* Particle and sound settings. Populated by {@link Option#init}.
|
||||
*/
|
||||
public final class ParticleOptions {
|
||||
|
||||
public static Option.ParticleShape SHAPE;
|
||||
public static Particle PARTICLE_TYPE;
|
||||
public static ParticleData<?> PARTICLE_DATA;
|
||||
public static Sound SOUND_TYPE;
|
||||
public static int SOUND_PITCH;
|
||||
public static int SOUND_VOLUME;
|
||||
|
||||
static void init() {
|
||||
String sound = Config.CONFIG.getString("particles.sound-type").toUpperCase();
|
||||
try {
|
||||
SOUND_TYPE = Sound.valueOf(sound);
|
||||
} catch (IllegalArgumentException ex) {
|
||||
SOUND_TYPE = Sound.valueOf("BLOCK_NOTE_PLING");
|
||||
LoParkour.getPlugin().getLogger().severe("Invalid sound: " + sound);
|
||||
}
|
||||
|
||||
String particle = Config.CONFIG.getString("particles.particle-type");
|
||||
try {
|
||||
PARTICLE_TYPE = Particle.valueOf(particle);
|
||||
} catch (IllegalArgumentException ex) {
|
||||
PARTICLE_TYPE = Particle.valueOf("SPELL_INSTANT");
|
||||
LoParkour.getPlugin().getLogger().severe("Invalid particle: " + particle);
|
||||
}
|
||||
|
||||
SOUND_PITCH = Config.CONFIG.getInt("particles.sound-pitch");
|
||||
SOUND_VOLUME = Config.CONFIG.getInt("particles.sound-volume");
|
||||
SHAPE = Option.ParticleShape.valueOf(Config.CONFIG.getString("particles.particle-shape").toUpperCase());
|
||||
PARTICLE_DATA = new ParticleData<>(PARTICLE_TYPE, null, 10);
|
||||
}
|
||||
|
||||
private ParticleOptions() {}
|
||||
}
|
||||
27
src/main/java/dev/loki/loparkour/config/SqlOptions.java
Normal file
27
src/main/java/dev/loki/loparkour/config/SqlOptions.java
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
package dev.loki.loparkour.config;
|
||||
|
||||
/**
|
||||
* SQL connection settings. Populated by {@link Option#init}.
|
||||
*/
|
||||
public final class SqlOptions {
|
||||
|
||||
public static boolean SQL;
|
||||
public static int PORT;
|
||||
public static String URL;
|
||||
public static String DB;
|
||||
public static String USERNAME;
|
||||
public static String PASSWORD;
|
||||
public static String PREFIX;
|
||||
|
||||
static void init() {
|
||||
SQL = Config.CONFIG.getBoolean("sql.enabled");
|
||||
PORT = Config.CONFIG.getInt("sql.port");
|
||||
DB = Config.CONFIG.getString("sql.database");
|
||||
URL = Config.CONFIG.getString("sql.url");
|
||||
USERNAME = Config.CONFIG.getString("sql.username");
|
||||
PASSWORD = Config.CONFIG.getString("sql.password");
|
||||
PREFIX = Config.CONFIG.getString("sql.prefix");
|
||||
}
|
||||
|
||||
private SqlOptions() {}
|
||||
}
|
||||
202
src/main/java/dev/loki/loparkour/generator/BlockPlacer.java
Normal file
202
src/main/java/dev/loki/loparkour/generator/BlockPlacer.java
Normal file
|
|
@ -0,0 +1,202 @@
|
|||
package dev.loki.loparkour.generator;
|
||||
|
||||
import dev.loki.loparkour.LoParkour;
|
||||
import dev.loki.loparkour.api.event.ParkourBlockGenerateEvent;
|
||||
import dev.loki.loparkour.api.event.ParkourSchematicGenerateEvent;
|
||||
import dev.loki.loparkour.api.Registry;
|
||||
import dev.loki.loparkour.config.Config;
|
||||
import dev.loki.loparkour.config.Option;
|
||||
import dev.loki.loparkour.style.Style;
|
||||
import dev.loki.loparkour.util.Probs;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.block.data.type.Fence;
|
||||
import org.bukkit.block.data.type.GlassPane;
|
||||
import org.bukkit.util.BoundingBox;
|
||||
import org.bukkit.util.Vector;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Level;
|
||||
|
||||
/**
|
||||
* Handles block selection, placement, and schematic generation.
|
||||
* Extracted from {@link ParkourGenerator}.
|
||||
*/
|
||||
public class BlockPlacer {
|
||||
|
||||
private final ParkourGenerator g;
|
||||
|
||||
public BlockPlacer(ParkourGenerator generator) {
|
||||
this.g = generator;
|
||||
}
|
||||
|
||||
// ── Public entry points ────────────────────────────────────────────────────
|
||||
|
||||
/** Generates one block or schematic ahead. */
|
||||
public void generate() {
|
||||
if (g.waitForSchematicCompletion) return;
|
||||
|
||||
Map<ParkourGenerator.BlockGenerationType, Double> chances = new HashMap<>(g.defaultChances);
|
||||
if (g.schematicCooldown > 0
|
||||
|| g.generatorOptions.contains(GeneratorOption.DISABLE_SCHEMATICS)
|
||||
|| g.profile.get("schematicDifficulty").asDouble() == 0.0
|
||||
|| !g.schematicBlocks.isEmpty()) {
|
||||
chances.remove(ParkourGenerator.BlockGenerationType.SCHEMATIC);
|
||||
}
|
||||
if (!g.profile.get("useSpecialBlocks").asBoolean()) {
|
||||
chances.remove(ParkourGenerator.BlockGenerationType.SPECIAL);
|
||||
}
|
||||
if (chances.isEmpty()) chances.put(ParkourGenerator.BlockGenerationType.DEFAULT, 1.0);
|
||||
|
||||
ParkourGenerator.BlockGenerationType jump = Probs.random(chances);
|
||||
if (jump == ParkourGenerator.BlockGenerationType.SCHEMATIC && tryGenerateSchematic()) return;
|
||||
|
||||
placeNormalBlock(jump);
|
||||
}
|
||||
|
||||
/** Generates {@code amount+1} blocks. */
|
||||
public void generate(int amount) {
|
||||
for (int i = 0; i <= amount; i++) generate();
|
||||
}
|
||||
|
||||
/** Seeds the history and generates the initial lead. */
|
||||
public void generateFirst(Location spawn, Location blockSpawn) {
|
||||
g.playerSpawn = spawn;
|
||||
g.lastStandingPlayerLocation = spawn;
|
||||
g.blockSpawn = blockSpawn;
|
||||
g.history.add(blockSpawn.getBlock());
|
||||
generate(g.profile.get("blockLead").asInt());
|
||||
}
|
||||
|
||||
/** Clears schematic blocks and resets cooldown. */
|
||||
public void deleteSchematic() {
|
||||
if (!g.deleteSchematic) return;
|
||||
g.schematicBlocks.forEach(b -> b.setType(Material.AIR));
|
||||
g.schematicBlocks.clear();
|
||||
g.deleteSchematic = false;
|
||||
g.schematicCooldown = Config.GENERATION.getInt("advanced.schematic-cooldown");
|
||||
}
|
||||
|
||||
// ── Block selection ────────────────────────────────────────────────────────
|
||||
|
||||
protected List<Block> selectBlocks() {
|
||||
int height = Probs.random(g.heightChances);
|
||||
int distance = Probs.random(g.distanceChances);
|
||||
return List.of(selectNext(g.getLatest(), distance, height));
|
||||
}
|
||||
|
||||
protected Block selectNext(Block current, int distance, int height) {
|
||||
JumpDirector director = new JumpDirector(
|
||||
BoundingBox.of(g.zone[0], g.zone[1]), g.getLatest().getLocation().toVector());
|
||||
|
||||
g.heading = director.getRecommendedHeading(g.heading);
|
||||
height = director.getRecommendedHeight(height);
|
||||
|
||||
switch (g.getLatest().getType()) {
|
||||
case SMOOTH_QUARTZ_SLAB -> height = Math.min(height, 0);
|
||||
case GLASS_PANE -> distance = Math.min(distance, 3);
|
||||
}
|
||||
if (height > 0) distance = Math.max(distance - height, 1);
|
||||
|
||||
double sd = g.generatorOptions.contains(GeneratorOption.REDUCE_RANDOM_BLOCK_SELECTION_ANGLE) ? 0.5 : 1;
|
||||
int randomOffset = new JumpOffsetGenerator(height, distance).getRandomOffset(0, sd);
|
||||
|
||||
Vector offset = g.heading.clone().multiply(distance + 1).setY(height);
|
||||
if (offset.getX() == 0) offset.setX(randomOffset);
|
||||
else offset.setZ(randomOffset);
|
||||
|
||||
offset.rotateAroundY(angleInY(g.heading, Option.HEADING.getDirection()));
|
||||
return current.getLocation().add(offset).getBlock();
|
||||
}
|
||||
|
||||
protected BlockData selectBlockData() {
|
||||
Style style = Registry.getStyle(g.profile.get("style").value());
|
||||
if (style == null) {
|
||||
g.profile.set("style", Registry.getStyles().stream().findFirst().orElseThrow().getName());
|
||||
return selectBlockData();
|
||||
}
|
||||
return style.getNext().createBlockData();
|
||||
}
|
||||
|
||||
// ── Internal ───────────────────────────────────────────────────────────────
|
||||
|
||||
private void placeNormalBlock(ParkourGenerator.BlockGenerationType jump) {
|
||||
List<Block> blocks = selectBlocks();
|
||||
if (blocks.isEmpty()) {
|
||||
LoParkour.getPlugin().getLogger().severe("No blocks to generate!");
|
||||
return;
|
||||
}
|
||||
|
||||
List<Block> placed = new ArrayList<>();
|
||||
for (Block block : blocks) {
|
||||
BlockData data = (jump == ParkourGenerator.BlockGenerationType.SPECIAL
|
||||
&& !g.generatorOptions.contains(GeneratorOption.DISABLE_SPECIAL))
|
||||
? Probs.random(g.specialChances)
|
||||
: selectBlockData();
|
||||
|
||||
if (data instanceof Fence) block = block.getLocation().subtract(0, 1, 0).getBlock();
|
||||
block.setBlockData(data, data instanceof Fence || data instanceof GlassPane);
|
||||
placed.add(block);
|
||||
}
|
||||
|
||||
new ParkourBlockGenerateEvent(placed, g, g.player).call();
|
||||
g.effects.particles(placed);
|
||||
g.effects.sound(placed);
|
||||
g.history.addAll(placed);
|
||||
g.schematicCooldown--;
|
||||
}
|
||||
|
||||
private boolean tryGenerateSchematic() {
|
||||
var manager = LoParkour.getSchematicManager();
|
||||
if (manager == null) return false;
|
||||
|
||||
var all = manager.getAllSchematics();
|
||||
if (all.isEmpty()) return false;
|
||||
|
||||
double diff = g.profile.get("schematicDifficulty").asDouble();
|
||||
var candidates = all.values().stream()
|
||||
.filter(s -> s.getMetadata() != null && Math.abs(s.getMetadata().getDifficulty() - diff) <= 0.26)
|
||||
.toList();
|
||||
var pool = candidates.isEmpty() ? new ArrayList<>(all.values()) : candidates;
|
||||
var schematic = pool.get(new java.util.Random().nextInt(pool.size()));
|
||||
|
||||
Location origin = g.getLatest().getLocation().add(g.heading.clone().multiply(2));
|
||||
List<Block> placed = rotatedPaste(schematic, origin);
|
||||
if (placed.isEmpty()) return false;
|
||||
|
||||
g.schematicBlocks.addAll(placed);
|
||||
g.history.addAll(placed);
|
||||
g.waitForSchematicCompletion = true;
|
||||
g.deleteSchematic = false;
|
||||
|
||||
new ParkourSchematicGenerateEvent(schematic, g, g.player).call();
|
||||
return true;
|
||||
}
|
||||
|
||||
private @NotNull List<Block> rotatedPaste(
|
||||
dev.loki.loparkour.schematic.lpschem.LPSchematic schematic, Location location) {
|
||||
int[] rotations = {0, 90, 180, 270};
|
||||
int rotation = rotations[new java.util.Random().nextInt(rotations.length)];
|
||||
org.bukkit.World world = location.getWorld();
|
||||
if (world == null) return List.of();
|
||||
|
||||
if (rotation == 0) return schematic.paste(location, world);
|
||||
try {
|
||||
var t = new dev.loki.loparkour.schematic.lpschem.SchematicTransformer(schematic);
|
||||
return t.rotate(rotation).paste(location, world);
|
||||
} catch (Exception ex) {
|
||||
LoParkour.getPlugin().getLogger().log(Level.WARNING, "Could not rotate schematic", ex);
|
||||
return schematic.paste(location, world);
|
||||
}
|
||||
}
|
||||
|
||||
private double angleInY(Vector a, Vector b) {
|
||||
return Math.atan2(a.getX() * b.getZ() - a.getZ() * b.getX(), a.dot(b));
|
||||
}
|
||||
}
|
||||
120
src/main/java/dev/loki/loparkour/generator/BlockSelector.java
Normal file
120
src/main/java/dev/loki/loparkour/generator/BlockSelector.java
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
package dev.loki.loparkour.generator;
|
||||
|
||||
import dev.loki.loparkour.api.Registry;
|
||||
import dev.loki.loparkour.config.Option;
|
||||
import dev.loki.loparkour.style.Style;
|
||||
import dev.loki.loparkour.util.Probs;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.util.BoundingBox;
|
||||
import org.bukkit.util.Vector;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Selects next parkour blocks (SRP - Single Responsibility)
|
||||
*/
|
||||
public class BlockSelector {
|
||||
|
||||
private final Profile profile;
|
||||
private final Location[] zone;
|
||||
private final Vector initialHeading;
|
||||
private final Map<Integer, Double> heightChances = new HashMap<>();
|
||||
private final Map<Integer, Double> distanceChances = new HashMap<>();
|
||||
|
||||
private Vector heading;
|
||||
|
||||
public BlockSelector(Profile profile, Location[] zone, Vector initialHeading) {
|
||||
this.profile = profile;
|
||||
this.zone = zone;
|
||||
this.heading = initialHeading.clone();
|
||||
this.initialHeading = initialHeading.clone();
|
||||
|
||||
calculateChances();
|
||||
}
|
||||
|
||||
private void calculateChances() {
|
||||
heightChances.put(1, Option.NORMAL_HEIGHT_1);
|
||||
heightChances.put(0, Option.NORMAL_HEIGHT_0);
|
||||
heightChances.put(-1, Option.NORMAL_HEIGHT_NEG1);
|
||||
heightChances.put(-2, Option.NORMAL_HEIGHT_NEG2);
|
||||
|
||||
distanceChances.put(1, Option.NORMAL_DISTANCE_1);
|
||||
distanceChances.put(2, Option.NORMAL_DISTANCE_2);
|
||||
distanceChances.put(3, Option.NORMAL_DISTANCE_3);
|
||||
distanceChances.put(4, Option.NORMAL_DISTANCE_4);
|
||||
}
|
||||
|
||||
public List<Block> selectBlocks(Block latest) {
|
||||
int height = Probs.random(heightChances);
|
||||
int distance = Probs.random(distanceChances);
|
||||
return List.of(selectNext(latest, distance, height));
|
||||
}
|
||||
|
||||
private Block selectNext(Block current, int distance, int height) {
|
||||
JumpDirector director = new JumpDirector(
|
||||
BoundingBox.of(zone[0], zone[1]),
|
||||
current.getLocation().toVector()
|
||||
);
|
||||
|
||||
heading = director.getRecommendedHeading(heading);
|
||||
height = director.getRecommendedHeight(height);
|
||||
|
||||
// Ensure special blocks are possible
|
||||
switch (current.getType()) {
|
||||
case SMOOTH_QUARTZ_SLAB -> height = Math.min(height, 0);
|
||||
case GLASS_PANE -> distance = Math.min(distance, 3);
|
||||
}
|
||||
|
||||
if (height > 0) {
|
||||
distance = Math.max(distance - height, 1);
|
||||
}
|
||||
|
||||
int randomOffset = new JumpOffsetGenerator(height, distance).getRandomOffset(0, 1);
|
||||
|
||||
Vector offset = heading.clone()
|
||||
.multiply(distance + 1)
|
||||
.setY(height);
|
||||
|
||||
if (offset.getX() == 0) {
|
||||
offset.setX(randomOffset);
|
||||
} else {
|
||||
offset.setZ(randomOffset);
|
||||
}
|
||||
|
||||
offset.rotateAroundY(angleInY(heading, initialHeading));
|
||||
|
||||
return current.getLocation().add(offset).getBlock();
|
||||
}
|
||||
|
||||
public BlockData selectBlockData() {
|
||||
Style style = Registry.getStyle(profile.get("style").value());
|
||||
|
||||
if (style == null) {
|
||||
profile.set("style", Registry.getStyles().stream()
|
||||
.findFirst()
|
||||
.orElseThrow()
|
||||
.getName());
|
||||
return selectBlockData();
|
||||
}
|
||||
|
||||
return style.getNext().createBlockData();
|
||||
}
|
||||
|
||||
private double angleInY(Vector a, Vector b) {
|
||||
double det = a.getX() * b.getZ() - a.getZ() * b.getX();
|
||||
return Math.atan2(det, a.dot(b));
|
||||
}
|
||||
|
||||
public Vector getHeading() {
|
||||
return heading;
|
||||
}
|
||||
|
||||
public void resetHeading() {
|
||||
heading = initialHeading.clone();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
package dev.loki.loparkour.generator;
|
||||
|
||||
import dev.loki.loparkour.config.Option;
|
||||
import dev.loki.loparkour.player.ParkourPlayer;
|
||||
import dev.loki.loparkour.player.ParkourSpectator;
|
||||
import dev.loki.loparkour.util.ParticleUtil;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.block.Block;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Handles particle and sound effects for the parkour generator.
|
||||
* Extracted from ParkourGenerator to keep it focused on generation logic.
|
||||
*/
|
||||
public class EffectManager {
|
||||
|
||||
private final ParkourGenerator generator;
|
||||
|
||||
public EffectManager(ParkourGenerator generator) {
|
||||
this.generator = generator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Spawns particles around the given blocks based on the configured shape.
|
||||
*/
|
||||
public void particles(List<Block> blocks) {
|
||||
if (!generator.profile.get("particles").asBoolean()) return;
|
||||
|
||||
List<Location> locations = blocks.stream().map(Block::getLocation).toList();
|
||||
Location max = locations.stream().reduce((a, b) -> {
|
||||
double x = Math.max(a.getX(), b.getX());
|
||||
double y = Math.max(a.getY(), b.getY());
|
||||
double z = Math.max(a.getZ(), b.getZ());
|
||||
return new Location(a.getWorld(), x, y, z);
|
||||
}).orElse(null);
|
||||
Location min = locations.stream().reduce((a, b) -> {
|
||||
double x = Math.min(a.getX(), b.getX());
|
||||
double y = Math.min(a.getY(), b.getY());
|
||||
double z = Math.min(a.getZ(), b.getZ());
|
||||
return new Location(a.getWorld(), x, y, z);
|
||||
}).orElse(null);
|
||||
|
||||
if (max == null || min == null || max.getWorld() == null) return;
|
||||
|
||||
List<ParkourPlayer> viewers = generator.getPlayers();
|
||||
|
||||
switch (Option.PARTICLE_SHAPE) {
|
||||
case DOT -> {
|
||||
Location center = min.clone().add(
|
||||
(max.getX() - min.getX()) / 2,
|
||||
(max.getY() - min.getY()) / 2,
|
||||
(max.getZ() - min.getZ()) / 2
|
||||
);
|
||||
viewers.forEach(p -> p.player.spawnParticle(
|
||||
Option.PARTICLE_TYPE, center, 5, 0.1, 0.1, 0.1, 0,
|
||||
Option.PARTICLE_DATA != null ? Option.PARTICLE_DATA.data() : null
|
||||
));
|
||||
}
|
||||
case CIRCLE -> ParticleUtil.circle(
|
||||
min.clone().add(0.5, 0.5, 0.5), max.getWorld(), Option.PARTICLE_TYPE,
|
||||
viewers.stream().map(p -> p.player).toList(), 0.5, 8
|
||||
);
|
||||
case BOX -> ParticleUtil.box(
|
||||
org.bukkit.util.BoundingBox.of(min, max), max.getWorld(), Option.PARTICLE_TYPE,
|
||||
viewers.isEmpty() ? null : viewers.get(0).player, 0.3
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Plays the configured sound at the first block's location.
|
||||
*/
|
||||
public void sound(List<Block> blocks) {
|
||||
if (!generator.profile.get("sound").asBoolean() || blocks.isEmpty()) return;
|
||||
|
||||
Location loc = blocks.get(0).getLocation();
|
||||
generator.getPlayers().forEach(p ->
|
||||
p.player.playSound(loc, Option.SOUND_TYPE, Option.SOUND_VOLUME, Option.SOUND_PITCH));
|
||||
generator.getSpectators().forEach(s ->
|
||||
s.player.playSound(loc, Option.SOUND_TYPE, Option.SOUND_VOLUME, Option.SOUND_PITCH));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,182 @@
|
|||
package dev.loki.loparkour.generator;
|
||||
|
||||
import dev.lolib.scheduler.Scheduler;
|
||||
import dev.loki.loparkour.LoParkour;
|
||||
import dev.loki.loparkour.api.event.ParkourFallEvent;
|
||||
import dev.loki.loparkour.api.event.ParkourScoreEvent;
|
||||
import dev.loki.loparkour.config.Config;
|
||||
import dev.loki.loparkour.config.Option;
|
||||
import dev.loki.loparkour.leaderboard.Leaderboard;
|
||||
import dev.loki.loparkour.leaderboard.Score;
|
||||
import dev.loki.loparkour.mode.Modes;
|
||||
import dev.loki.loparkour.player.ParkourPlayer;
|
||||
import dev.loki.loparkour.player.ParkourSpectator;
|
||||
import dev.loki.loparkour.reward.Rewards;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.block.Block;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* Handles parkour lifecycle: tick, fall, reset, cleanup.
|
||||
*/
|
||||
public class GeneratorLifecycle {
|
||||
|
||||
private final ParkourGenerator generator;
|
||||
private dev.lolib.scheduler.ScheduledTask cleanupTask;
|
||||
|
||||
public GeneratorLifecycle(ParkourGenerator generator) {
|
||||
this.generator = generator;
|
||||
}
|
||||
|
||||
public void startTick() {
|
||||
generator.task = Scheduler.get(LoParkour.getPlugin()).runTimer(this::tick, 0, 1);
|
||||
cleanupTask = Scheduler.get(LoParkour.getPlugin()).runTimer(this::cleanupDistantBlocks, 0, Option.CLEANUP_INTERVAL);
|
||||
}
|
||||
|
||||
public void tick() {
|
||||
if (generator.state.stopped) {
|
||||
generator.task.cancel();
|
||||
if (cleanupTask != null) cleanupTask.cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
generator.getPlayers().forEach(p -> {
|
||||
updateVisualTime(p, p.selectedTime);
|
||||
p.updateScoreboard(generator);
|
||||
p.player.setSaturation(20);
|
||||
});
|
||||
generator.getSpectators().forEach(ParkourSpectator::update);
|
||||
|
||||
if (generator.player.getLocation().getWorld() != generator.state.lastStandingPlayerLocation.getWorld()) return;
|
||||
if (generator.player.getLocation().subtract(generator.state.lastStandingPlayerLocation).getY() < -10) {
|
||||
fall();
|
||||
return;
|
||||
}
|
||||
|
||||
Block below = blockBelow();
|
||||
if (below == null) return;
|
||||
|
||||
handleSchematicEndBlock(below);
|
||||
if (!generator.state.history.contains(below)) return;
|
||||
|
||||
int idx = generator.state.history.indexOf(below);
|
||||
int delta = idx - generator.state.lastPositionIndexPlayer;
|
||||
if (delta <= 0) return;
|
||||
|
||||
generator.state.lastStandingPlayerLocation = generator.player.getLocation();
|
||||
|
||||
int lead = generator.profile.get("blockLead").asInt();
|
||||
if (generator.state.history.size() - idx <= lead) {
|
||||
generator.generate(lead - (generator.state.history.size() - idx));
|
||||
}
|
||||
generator.state.lastPositionIndexPlayer = idx;
|
||||
|
||||
removeTrailBlocks(idx);
|
||||
cleanupDistantBlocks();
|
||||
generator.placer.deleteSchematic();
|
||||
|
||||
int pts = Config.CONFIG.getBoolean("scoring.all-points") ? delta : 1;
|
||||
for (int i = 0; i < pts; i++) score();
|
||||
|
||||
if (generator.state.start == null) generator.state.start = Instant.now();
|
||||
}
|
||||
|
||||
private void handleSchematicEndBlock(Block below) {
|
||||
if (generator.state.schematicBlocks.contains(below)
|
||||
&& below.getType() == Material.RED_WOOL
|
||||
&& !generator.state.deleteSchematic) {
|
||||
|
||||
for (int i = 0; i < generator.profile.get("schematicDifficulty").asDouble() * 15; i++) {
|
||||
score();
|
||||
}
|
||||
generator.state.waitForSchematicCompletion = false;
|
||||
generator.state.schematicCooldown = Config.GENERATION.getInt("advanced.schematic-cooldown");
|
||||
generator.generate(generator.profile.get("blockLead").asInt());
|
||||
generator.state.deleteSchematic = true;
|
||||
}
|
||||
}
|
||||
|
||||
private @Nullable Block blockBelow() {
|
||||
Location loc = generator.player.getLocation().subtract(0, 1, 0);
|
||||
Block b = loc.getBlock();
|
||||
if (b.getType() == Material.AIR) {
|
||||
if (loc.subtract(0, 0.5, 0).getBlock().getType() == Material.AIR) return null;
|
||||
b = loc.getBlock();
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
private void removeTrailBlocks(int idx) {
|
||||
for (int i = idx - ParkourGenerator.BLOCK_TRAIL - 1; i >= idx - 4 * ParkourGenerator.BLOCK_TRAIL; i--) {
|
||||
if (i > 0) generator.state.history.get(i).setType(Material.AIR);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateVisualTime(ParkourPlayer p, int selectedTime) {
|
||||
int t = 18000 + selectedTime;
|
||||
if (t >= 24000) t -= 24000;
|
||||
p.player.setPlayerTime(t, false);
|
||||
}
|
||||
|
||||
protected void score() {
|
||||
generator.state.score++;
|
||||
generator.state.totalScore++;
|
||||
checkRewards();
|
||||
new ParkourScoreEvent(generator.player).call();
|
||||
}
|
||||
|
||||
private void checkRewards() {
|
||||
if (!Rewards.REWARDS_ENABLED || generator.state.score == 0) return;
|
||||
|
||||
if (Rewards.SCORE_REWARDS.containsKey(generator.state.score)) {
|
||||
Rewards.SCORE_REWARDS.get(generator.state.score).forEach(r -> r.execute(generator.player, generator.getMode()));
|
||||
}
|
||||
|
||||
int intervalScore = Config.CONFIG.getBoolean("scoring.rewards-use-total-score")
|
||||
? generator.state.totalScore
|
||||
: generator.state.score;
|
||||
|
||||
for (int interval : Rewards.INTERVAL_REWARDS.keySet()) {
|
||||
if (intervalScore % interval == 0) {
|
||||
Rewards.INTERVAL_REWARDS.get(interval).forEach(r -> r.execute(generator.player, generator.getMode()));
|
||||
}
|
||||
}
|
||||
|
||||
String key = Integer.toString(generator.state.score);
|
||||
if (Rewards.ONE_TIME_REWARDS.containsKey(generator.state.score)
|
||||
&& !generator.player.collectedRewards.contains(key)) {
|
||||
Rewards.ONE_TIME_REWARDS.get(generator.state.score).forEach(r -> r.execute(generator.player, generator.getMode()));
|
||||
generator.player.collectedRewards.add(key);
|
||||
}
|
||||
}
|
||||
|
||||
protected void fall() {
|
||||
new ParkourFallEvent(generator.player).call();
|
||||
generator.reset(true);
|
||||
}
|
||||
|
||||
protected void cleanupDistantBlocks() {
|
||||
if (generator.state.history.size() < Option.BLOCK_CLEANUP_DISTANCE * 2) return;
|
||||
|
||||
Location loc = generator.player.getLocation();
|
||||
int removed = 0;
|
||||
Iterator<Block> it = generator.state.history.iterator();
|
||||
|
||||
while (it.hasNext()) {
|
||||
Block b = it.next();
|
||||
if (b.getLocation().distance(loc) > Option.BLOCK_CLEANUP_DISTANCE) {
|
||||
b.setType(Material.AIR, false);
|
||||
it.remove();
|
||||
removed++;
|
||||
} else break;
|
||||
}
|
||||
|
||||
if (removed > 0) {
|
||||
generator.state.lastPositionIndexPlayer = Math.max(0, generator.state.lastPositionIndexPlayer - removed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
package dev.loki.loparkour.generator;
|
||||
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.util.Vector;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Holds all state data for parkour generation.
|
||||
* No logic - just data and getters/setters.
|
||||
*/
|
||||
public class GeneratorState {
|
||||
|
||||
// Score state
|
||||
public int score = 0;
|
||||
public int totalScore = 0;
|
||||
public int schematicCooldown;
|
||||
public boolean stopped = false;
|
||||
|
||||
// Location state
|
||||
public Location blockSpawn;
|
||||
public Location playerSpawn;
|
||||
public Location lastStandingPlayerLocation;
|
||||
public Vector heading;
|
||||
|
||||
// Time state
|
||||
public Instant start;
|
||||
|
||||
// Block history
|
||||
public List<Block> history = new LinkedList<>();
|
||||
public int lastPositionIndexPlayer = -1;
|
||||
|
||||
// Schematic state
|
||||
public boolean deleteSchematic = false;
|
||||
public boolean waitForSchematicCompletion = false;
|
||||
public List<Block> schematicBlocks = new ArrayList<>();
|
||||
|
||||
// Chance maps
|
||||
public final Map<Integer, Double> distanceChances = new HashMap<>();
|
||||
public final Map<Integer, Double> heightChances = new HashMap<>();
|
||||
public final Map<BlockData, Double> specialChances = new HashMap<>();
|
||||
public final Map<ParkourGenerator.BlockGenerationType, Double> defaultChances = new HashMap<>();
|
||||
|
||||
public GeneratorState() {}
|
||||
|
||||
public void resetScore() {
|
||||
score = 0;
|
||||
start = null;
|
||||
}
|
||||
|
||||
public void clearHistory() {
|
||||
history.clear();
|
||||
lastPositionIndexPlayer = 0;
|
||||
}
|
||||
|
||||
public void resetSchematicState() {
|
||||
waitForSchematicCompletion = false;
|
||||
deleteSchematic = true;
|
||||
schematicBlocks.clear();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package dev.loki.loparkour.generator;
|
||||
|
||||
/**
|
||||
* @deprecated Tick logic was merged back into {@link ParkourGenerator#tick()}.
|
||||
* Delete this file.
|
||||
*/
|
||||
@Deprecated
|
||||
class GeneratorTick {}
|
||||
|
|
@ -47,11 +47,11 @@ public final class Island {
|
|||
*/
|
||||
public void build(Location location) {
|
||||
if (schematic == null) {
|
||||
LoParkour.getPlugin().getLogger().warning("Cannot build island - schematic is null!");
|
||||
return;
|
||||
}
|
||||
LoParkour.log("Building island");
|
||||
|
||||
blocks = new ArrayList<>(); // TODO: schematic.paste(location, world)
|
||||
blocks = schematic.paste(location, location.getWorld());
|
||||
|
||||
Material playerMaterial = Material.getMaterial(Config.GENERATION.getString("advanced.island.spawn.player-block").toUpperCase());
|
||||
Material parkourMaterial = Material.getMaterial(Config.GENERATION.getString("advanced.island.parkour.begin-block").toUpperCase());
|
||||
|
|
@ -60,14 +60,18 @@ public final class Island {
|
|||
Block player = blocks.stream().filter(block -> block.getType() == playerMaterial).findAny().orElseThrow();
|
||||
Block parkour = blocks.stream().filter(block -> block.getType() == parkourMaterial).findAny().orElseThrow();
|
||||
|
||||
player.setType(Material.AIR);
|
||||
parkour.setType(Material.AIR);
|
||||
player.setType(Material.STONE);
|
||||
parkour.setType(Material.STONE);
|
||||
|
||||
Location ps = player.getLocation().add(0.5, 0, 0.5);
|
||||
Location ps = player.getLocation().add(0.5, 1.0, 0.5);
|
||||
ps.setYaw(Config.GENERATION.getInt("advanced.island.spawn.yaw"));
|
||||
ps.setPitch(Config.GENERATION.getInt("advanced.island.spawn.pitch"));
|
||||
|
||||
session.generator.generateFirst(ps, parkour.getLocation().subtract(session.generator.heading).subtract(0, 1, 0));
|
||||
// First parkour block should be generated FROM the parkour start block
|
||||
// in the direction of heading, not AT the parkour start block
|
||||
Location parkourStart = parkour.getLocation().add(session.generator.heading);
|
||||
|
||||
session.generator.generateFirst(ps, parkourStart);
|
||||
session.generator.startTick();
|
||||
session.getPlayers().forEach(pp -> pp.setup(ps));
|
||||
} catch (NoSuchElementException ex) {
|
||||
|
|
|
|||
|
|
@ -1,16 +1,9 @@
|
|||
package dev.loki.loparkour.generator;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import dev.loki.loparkour.util.ParticleData;
|
||||
|
||||
import dev.lolib.scheduler.Scheduler;
|
||||
|
||||
import dev.loki.loparkour.LoParkour;
|
||||
import dev.loki.loparkour.api.Registry;
|
||||
import dev.loki.loparkour.api.event.ParkourBlockGenerateEvent;
|
||||
import dev.loki.loparkour.api.event.ParkourFallEvent;
|
||||
import dev.loki.loparkour.api.event.ParkourSchematicGenerateEvent;
|
||||
import dev.loki.loparkour.api.event.ParkourScoreEvent;
|
||||
import dev.loki.loparkour.config.Config;
|
||||
import dev.loki.loparkour.config.Option;
|
||||
|
|
@ -22,23 +15,13 @@ import dev.loki.loparkour.mode.Modes;
|
|||
import dev.loki.loparkour.player.ParkourPlayer;
|
||||
import dev.loki.loparkour.player.ParkourSpectator;
|
||||
import dev.loki.loparkour.reward.Rewards;
|
||||
import dev.loki.loparkour.schematic.lpschem.LPSchematic;
|
||||
import dev.loki.loparkour.session.Session;
|
||||
import dev.loki.loparkour.style.Style;
|
||||
import dev.loki.loparkour.world.Divider;
|
||||
|
||||
import dev.loki.loparkour.util.ParticleUtil;
|
||||
|
||||
import dev.loki.loparkour.util.Locations;
|
||||
import dev.loki.loparkour.util.Probs;
|
||||
import dev.lolib.scheduler.Scheduler;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.block.data.type.Fence;
|
||||
import org.bukkit.block.data.type.GlassPane;
|
||||
import dev.lolib.scheduler.ScheduledTask;
|
||||
import org.bukkit.util.BoundingBox;
|
||||
import org.bukkit.util.Vector;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
|
@ -49,98 +32,102 @@ import java.time.format.DateTimeFormatter;
|
|||
import java.util.*;
|
||||
|
||||
/**
|
||||
* The class that generates the parkour, which each {@link ParkourPlayer} has.
|
||||
* Coordinates parkour generation, ticking, scoring, and reset.
|
||||
*
|
||||
* @author loki
|
||||
* @since 5.0.0
|
||||
* Heavy lifting is delegated to:
|
||||
* <ul>
|
||||
* <li>{@link BlockPlacer} – block selection & placement</li>
|
||||
* <li>{@link EffectManager} – particles & sounds</li>
|
||||
* </ul>
|
||||
*/
|
||||
public class ParkourGenerator {
|
||||
|
||||
public static final int BLOCK_TRAIL = 2;
|
||||
|
||||
// ── State ──────────────────────────────────────────────────────────────────
|
||||
public int score = 0;
|
||||
public int totalScore = 0;
|
||||
public int schematicCooldown = Config.GENERATION.getInt("advanced.schematic-cooldown");
|
||||
public int schematicCooldown;
|
||||
public boolean stopped = false;
|
||||
|
||||
public Location[] zone;
|
||||
public ParkourPlayer player;
|
||||
public dev.lolib.scheduler.ScheduledTask task;
|
||||
|
||||
private dev.lolib.scheduler.ScheduledTask cleanupTask;
|
||||
|
||||
public Location blockSpawn;
|
||||
|
||||
/**
|
||||
* Where the player spawns on reset
|
||||
*/
|
||||
public Location playerSpawn;
|
||||
|
||||
/**
|
||||
* Instant when run started.
|
||||
*/
|
||||
public Instant start;
|
||||
|
||||
/**
|
||||
* The direction of the parkour
|
||||
*/
|
||||
public Vector heading = Option.HEADING.getDirection();
|
||||
|
||||
/**
|
||||
* Generator options
|
||||
*/
|
||||
public final List<GeneratorOption> generatorOptions;
|
||||
|
||||
/**
|
||||
* The {@link Session} associated with this Generator.
|
||||
*/
|
||||
public final Session session;
|
||||
|
||||
/**
|
||||
* This Generator's {@link Profile}.
|
||||
*/
|
||||
public final Profile profile = new Profile();
|
||||
|
||||
public final Island island;
|
||||
public final Map<Integer, Double> distanceChances = new HashMap<>();
|
||||
public final Map<Integer, Double> heightChances = new HashMap<>();
|
||||
public final Map<BlockData, Double> specialChances = new HashMap<>();
|
||||
public final Map<BlockGenerationType, Double> defaultChances = new HashMap<>();
|
||||
|
||||
protected boolean deleteSchematic = false;
|
||||
protected boolean waitForSchematicCompletion = false;
|
||||
protected Location lastStandingPlayerLocation;
|
||||
protected List<Block> schematicBlocks = new ArrayList<>();
|
||||
protected int lastPositionIndexPlayer = -1;
|
||||
protected List<Block> history = new LinkedList<>();
|
||||
// Chance maps – populated by calculateChances()
|
||||
public final Map<Integer, Double> distanceChances = new HashMap<>();
|
||||
public final Map<Integer, Double> heightChances = new HashMap<>();
|
||||
public final Map<BlockData, Double> specialChances = new HashMap<>();
|
||||
public final Map<BlockGenerationType, Double> defaultChances = new HashMap<>();
|
||||
|
||||
public ParkourGenerator(@NotNull Session session, @Nullable dev.loki.loparkour.schematic.lpschem.LPSchematic schematic, GeneratorOption... generatorOptions) {
|
||||
// Schematic state
|
||||
public boolean deleteSchematic = false;
|
||||
public boolean waitForSchematicCompletion = false;
|
||||
public List<Block> schematicBlocks = new ArrayList<>();
|
||||
|
||||
// History
|
||||
public List<Block> history = new LinkedList<>();
|
||||
public int lastPositionIndexPlayer = -1;
|
||||
public Location lastStandingPlayerLocation;
|
||||
|
||||
// Delegates
|
||||
public final EffectManager effects;
|
||||
public final BlockPlacer placer;
|
||||
|
||||
// ── Constructors ───────────────────────────────────────────────────────────
|
||||
|
||||
public ParkourGenerator(@NotNull Session session, @Nullable LPSchematic schematic,
|
||||
GeneratorOption... generatorOptions) {
|
||||
this.session = session;
|
||||
this.generatorOptions = Arrays.asList(generatorOptions);
|
||||
|
||||
player = session.getPlayers().get(0);
|
||||
island = new Island(session, schematic);
|
||||
zone = Divider.toSelection(session);
|
||||
|
||||
this.player = session.getPlayers().get(0);
|
||||
this.island = new Island(session, schematic);
|
||||
this.zone = Divider.toSelection(session);
|
||||
this.schematicCooldown = Config.GENERATION.getInt("advanced.schematic-cooldown");
|
||||
this.effects = new EffectManager(this);
|
||||
this.placer = new BlockPlacer(this);
|
||||
calculateChances();
|
||||
}
|
||||
|
||||
public ParkourGenerator(@NotNull Session session, GeneratorOption... generatorOptions) {
|
||||
// TODO: Implement schematic loading with LoLib
|
||||
this(session, null, generatorOptions);
|
||||
this(session, loadIslandSchematic(), generatorOptions);
|
||||
}
|
||||
|
||||
public void overrideProfile() { }
|
||||
private static LPSchematic loadIslandSchematic() {
|
||||
String name = Config.GENERATION.getString("advanced.island.schematic-name");
|
||||
if (name == null || name.isEmpty()) return null;
|
||||
var manager = LoParkour.getSchematicManager();
|
||||
if (manager == null) return null;
|
||||
LPSchematic s = manager.getSchematic(name);
|
||||
if (s == null) LoParkour.getPlugin().getLogger()
|
||||
.warning("Island schematic '%s' not found!".formatted(name));
|
||||
return s;
|
||||
}
|
||||
|
||||
public void overrideProfile() {}
|
||||
|
||||
// ── Chances ────────────────────────────────────────────────────────────────
|
||||
|
||||
protected void calculateChances() {
|
||||
defaultChances.clear();
|
||||
defaultChances.put(BlockGenerationType.DEFAULT, Option.TYPE_NORMAL);
|
||||
defaultChances.put(BlockGenerationType.DEFAULT, Option.TYPE_NORMAL);
|
||||
defaultChances.put(BlockGenerationType.SCHEMATIC, Option.TYPE_SCHEMATICS);
|
||||
defaultChances.put(BlockGenerationType.SPECIAL, Option.TYPE_SPECIAL);
|
||||
defaultChances.put(BlockGenerationType.SPECIAL, Option.TYPE_SPECIAL);
|
||||
|
||||
heightChances.clear();
|
||||
heightChances.put(1, Option.NORMAL_HEIGHT_1);
|
||||
heightChances.put(0, Option.NORMAL_HEIGHT_0);
|
||||
heightChances.put( 1, Option.NORMAL_HEIGHT_1);
|
||||
heightChances.put( 0, Option.NORMAL_HEIGHT_0);
|
||||
heightChances.put(-1, Option.NORMAL_HEIGHT_NEG1);
|
||||
heightChances.put(-2, Option.NORMAL_HEIGHT_NEG2);
|
||||
|
||||
|
|
@ -151,208 +138,47 @@ public class ParkourGenerator {
|
|||
distanceChances.put(4, Option.NORMAL_DISTANCE_4);
|
||||
|
||||
specialChances.clear();
|
||||
specialChances.put(Material.PACKED_ICE.createBlockData(), Option.SPECIAL_ICE);
|
||||
specialChances.put(Material.PACKED_ICE.createBlockData(), Option.SPECIAL_ICE);
|
||||
specialChances.put(Material.SMOOTH_QUARTZ_SLAB.createBlockData("[type=bottom]"), Option.SPECIAL_SLAB);
|
||||
specialChances.put(Material.GLASS_PANE.createBlockData(), Option.SPECIAL_PANE);
|
||||
specialChances.put(Material.OAK_FENCE.createBlockData(), Option.SPECIAL_FENCE);
|
||||
specialChances.put(Material.GLASS_PANE.createBlockData(), Option.SPECIAL_PANE);
|
||||
specialChances.put(Material.OAK_FENCE.createBlockData(), Option.SPECIAL_FENCE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates particles around blocks.
|
||||
*
|
||||
* @param blocks The blocks.
|
||||
*/
|
||||
protected void particles(List<Block> blocks) {
|
||||
if (!profile.get("particles").asBoolean()) {
|
||||
return;
|
||||
}
|
||||
// ── Generation (delegates) ─────────────────────────────────────────────────
|
||||
|
||||
ParticleData<?> data = Option.PARTICLE_DATA;
|
||||
List<Location> locations = blocks.stream().map(Block::getLocation).toList();
|
||||
Location max = locations.stream().reduce(Locations::max).orElseThrow();
|
||||
Location min = locations.stream().reduce(Locations::min).orElseThrow();
|
||||
Location center = min.clone().add(max.clone().subtract(min));
|
||||
public void generate() { placer.generate(); }
|
||||
public void generate(int amount) { placer.generate(amount); }
|
||||
|
||||
// display particle
|
||||
switch (Option.PARTICLE_SHAPE) {
|
||||
case DOT -> {} // TODO: ParticleUtil.draw
|
||||
case CIRCLE -> {} // TODO: ParticleUtil.circle
|
||||
case BOX -> {} // TODO: ParticleUtil.box
|
||||
}
|
||||
public void generateFirst(Location spawn, Location block) {
|
||||
placer.generateFirst(spawn, block);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates sound around blocks.
|
||||
*
|
||||
* @param blocks The blocks.
|
||||
*/
|
||||
protected void sound(List<Block> blocks) {
|
||||
if (!profile.get("sound").asBoolean()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// play sound
|
||||
getPlayers().forEach(viewer -> viewer.player.playSound(blocks.get(0).getLocation(), Option.SOUND_TYPE, Option.SOUND_VOLUME, Option.SOUND_PITCH));
|
||||
getSpectators().forEach(viewer -> viewer.player.playSound(blocks.get(0).getLocation(), Option.SOUND_TYPE, Option.SOUND_VOLUME, Option.SOUND_PITCH));
|
||||
}
|
||||
|
||||
protected BlockData selectBlockData() {
|
||||
Style style = Registry.getStyle(profile.get("style").value());
|
||||
|
||||
if (style == null) {
|
||||
profile.set("style", Registry.getStyles().stream()
|
||||
.findFirst()
|
||||
.orElseThrow()
|
||||
.getName());
|
||||
return selectBlockData();
|
||||
}
|
||||
|
||||
return style.getNext().createBlockData();
|
||||
}
|
||||
|
||||
protected List<Block> selectBlocks() {
|
||||
int height = Probs.random(heightChances);
|
||||
int distance = Probs.random(distanceChances);
|
||||
|
||||
return List.of(selectNext(getLatest(), distance, height));
|
||||
}
|
||||
|
||||
// Selects the next block that will continue the parkour.
|
||||
// This is done by choosing a random value for the sideways movement.
|
||||
// Based on this sideways movement, a value for forward movement will be chosen.
|
||||
// This is done to ensure players are able to complete the jump.
|
||||
protected Block selectNext(Block current, int distance, int height) {
|
||||
JumpDirector director = new JumpDirector(BoundingBox.of(zone[0], zone[1]), getLatest().getLocation().toVector());
|
||||
|
||||
heading = director.getRecommendedHeading(heading);
|
||||
height = director.getRecommendedHeight(height);
|
||||
|
||||
// ensure special is possible
|
||||
switch (getLatest().getType()) {
|
||||
case SMOOTH_QUARTZ_SLAB -> height = Math.min(height, 0);
|
||||
case GLASS_PANE -> distance = Math.min(distance, 3);
|
||||
}
|
||||
|
||||
if (height > 0) {
|
||||
distance = Math.max(distance - height, 1);
|
||||
}
|
||||
|
||||
double mean = 0;
|
||||
double standardDeviation = generatorOptions.contains(GeneratorOption.REDUCE_RANDOM_BLOCK_SELECTION_ANGLE) ? 0.5 : 1;
|
||||
|
||||
int randomOffset = new JumpOffsetGenerator(height, distance).getRandomOffset(mean, standardDeviation);
|
||||
|
||||
Vector offset = heading.clone()
|
||||
.multiply(distance + 1)
|
||||
.setY(height);
|
||||
if (offset.getX() == 0) {
|
||||
offset.setX(randomOffset);
|
||||
} else {
|
||||
offset.setZ(randomOffset);
|
||||
}
|
||||
|
||||
// rotate offset to match heading
|
||||
offset.rotateAroundY(angleInY(heading, Option.HEADING.getDirection()));
|
||||
|
||||
return current.getLocation().add(offset).getBlock();
|
||||
}
|
||||
|
||||
protected void score() {
|
||||
score++;
|
||||
totalScore++;
|
||||
|
||||
checkRewards();
|
||||
new ParkourScoreEvent(player).call();
|
||||
}
|
||||
|
||||
private void checkRewards() {
|
||||
if (!Rewards.REWARDS_ENABLED || score == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// check generic score rewards
|
||||
if (Rewards.SCORE_REWARDS.containsKey(score)) {
|
||||
Rewards.SCORE_REWARDS.get(score).forEach(s -> s.execute(player, getMode()));
|
||||
}
|
||||
|
||||
// gets the correct type of score to check based on the config option
|
||||
int intervalScore = Config.CONFIG.getBoolean("scoring.rewards-use-total-score") ? totalScore : score;
|
||||
for (int interval : Rewards.INTERVAL_REWARDS.keySet()) {
|
||||
if (intervalScore % interval != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Rewards.INTERVAL_REWARDS.get(interval).forEach(s -> s.execute(player, getMode()));
|
||||
}
|
||||
|
||||
if (Rewards.ONE_TIME_REWARDS.containsKey(score) && !player.collectedRewards.contains(Integer.toString(score))) {
|
||||
Rewards.ONE_TIME_REWARDS.get(score).forEach(s -> s.execute(player, getMode()));
|
||||
player.collectedRewards.add(Integer.toString(score));
|
||||
}
|
||||
}
|
||||
|
||||
protected void fall() {
|
||||
new ParkourFallEvent(player).call();
|
||||
reset(true);
|
||||
}
|
||||
|
||||
public void menu(ParkourPlayer player) {
|
||||
// TODO: Migrate to LoLib GUI system
|
||||
// Menus.PARKOUR_SETTINGS.open(player);
|
||||
player.sendTranslated("other.menu-unavailable");
|
||||
}
|
||||
// ── Tick ───────────────────────────────────────────────────────────────────
|
||||
|
||||
public void startTick() {
|
||||
task = Scheduler.get(LoParkour.getPlugin()).runTimer(this::tick, 0, 1);
|
||||
task = Scheduler.get(LoParkour.getPlugin()).runTimer(this::tick, 0, 1);
|
||||
cleanupTask = Scheduler.get(LoParkour.getPlugin()).runTimer(this::cleanupDistantBlocks, 0, Option.CLEANUP_INTERVAL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the check
|
||||
*/
|
||||
public void tick() {
|
||||
if (stopped) {
|
||||
task.cancel();
|
||||
if (cleanupTask != null) {
|
||||
cleanupTask.cancel();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (stopped) { task.cancel(); if (cleanupTask != null) cleanupTask.cancel(); return; }
|
||||
|
||||
getPlayers().forEach(other -> {
|
||||
updateVisualTime(other, other.selectedTime);
|
||||
other.updateScoreboard(this);
|
||||
other.player.setSaturation(20);
|
||||
getPlayers().forEach(p -> {
|
||||
updateVisualTime(p, p.selectedTime);
|
||||
p.updateScoreboard(this);
|
||||
p.player.setSaturation(20);
|
||||
});
|
||||
|
||||
getSpectators().forEach(ParkourSpectator::update);
|
||||
|
||||
if (player.getLocation().getWorld() != lastStandingPlayerLocation.getWorld()) {
|
||||
return;
|
||||
}
|
||||
if (player.getLocation().getWorld() != lastStandingPlayerLocation.getWorld()) return;
|
||||
if (player.getLocation().subtract(lastStandingPlayerLocation).getY() < -10) { fall(); return; }
|
||||
|
||||
if (player.getLocation().subtract(lastStandingPlayerLocation).getY() < -10) { // fall check
|
||||
LoParkour.log("Player %s is falling".formatted(player.getName()));
|
||||
|
||||
fall();
|
||||
return;
|
||||
}
|
||||
|
||||
Location belowPlayer = player.getLocation().subtract(0, 1, 0);
|
||||
Block blockBelowPlayer = belowPlayer.getBlock(); // Get the block below
|
||||
|
||||
if (blockBelowPlayer.getType() == Material.AIR) {
|
||||
if (belowPlayer.subtract(0, 0.5, 0).getBlock().getType() == Material.AIR) {
|
||||
return;
|
||||
}
|
||||
blockBelowPlayer = belowPlayer.getBlock();
|
||||
}
|
||||
|
||||
if (schematicBlocks.contains(blockBelowPlayer) && blockBelowPlayer.getType() == Material.RED_WOOL && !deleteSchematic) { // Structure deletion check
|
||||
for (int i = 0; i < profile.get("schematicDifficulty").asDouble() * 15; i++) {
|
||||
score();
|
||||
}
|
||||
Block below = blockBelow();
|
||||
if (below == null) return;
|
||||
|
||||
// Schematic end block
|
||||
if (schematicBlocks.contains(below) && below.getType() == Material.RED_WOOL && !deleteSchematic) {
|
||||
for (int i = 0; i < profile.get("schematicDifficulty").asDouble() * 15; i++) score();
|
||||
waitForSchematicCompletion = false;
|
||||
schematicCooldown = Config.GENERATION.getInt("advanced.schematic-cooldown");
|
||||
generate(profile.get("blockLead").asInt());
|
||||
|
|
@ -360,365 +186,194 @@ public class ParkourGenerator {
|
|||
return;
|
||||
}
|
||||
|
||||
if (!history.contains(blockBelowPlayer)) {
|
||||
return; // player is on an unknown block
|
||||
}
|
||||
if (!history.contains(below)) return;
|
||||
|
||||
int currentIndex = history.indexOf(blockBelowPlayer); // current index of the player
|
||||
int deltaFromLast = currentIndex - lastPositionIndexPlayer;
|
||||
|
||||
if (deltaFromLast <= 0) { // the player is actually making progress and not going backwards (current index is higher than the previous)
|
||||
return;
|
||||
}
|
||||
int idx = history.indexOf(below);
|
||||
int delta = idx - lastPositionIndexPlayer;
|
||||
if (delta <= 0) return;
|
||||
|
||||
lastStandingPlayerLocation = player.getLocation();
|
||||
|
||||
int blockLead = profile.get("blockLead").asInt();
|
||||
int lead = profile.get("blockLead").asInt();
|
||||
if (history.size() - idx <= lead) generate(lead - (history.size() - idx));
|
||||
lastPositionIndexPlayer = idx;
|
||||
|
||||
int deltaCurrentTotal = history.size() - currentIndex; // delta between current index and total
|
||||
if (deltaCurrentTotal <= blockLead) {
|
||||
generate(blockLead - deltaCurrentTotal); // generate the remaining amount so it will match
|
||||
}
|
||||
lastPositionIndexPlayer = currentIndex;
|
||||
|
||||
for (int i = currentIndex - BLOCK_TRAIL - 1; i >= currentIndex - 4 * BLOCK_TRAIL; i--) {
|
||||
if (i <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
history.get(i).setType(Material.AIR);
|
||||
// Remove trail blocks behind player
|
||||
for (int i = idx - BLOCK_TRAIL - 1; i >= idx - 4 * BLOCK_TRAIL; i--) {
|
||||
if (i > 0) history.get(i).setType(Material.AIR);
|
||||
}
|
||||
|
||||
cleanupDistantBlocks();
|
||||
placer.deleteSchematic();
|
||||
|
||||
deleteSchematic();
|
||||
int pts = Config.CONFIG.getBoolean("scoring.all-points") ? delta : 1;
|
||||
for (int i = 0; i < pts; i++) score();
|
||||
|
||||
for (int i = 0; i < (Config.CONFIG.getBoolean("scoring.all-points") ? deltaFromLast : 1); i++) { // score the difference
|
||||
score();
|
||||
if (start == null) start = Instant.now();
|
||||
}
|
||||
|
||||
private @Nullable Block blockBelow() {
|
||||
Location loc = player.getLocation().subtract(0, 1, 0);
|
||||
Block b = loc.getBlock();
|
||||
if (b.getType() == Material.AIR) {
|
||||
if (loc.subtract(0, 0.5, 0).getBlock().getType() == Material.AIR) return null;
|
||||
b = loc.getBlock();
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
private void updateVisualTime(ParkourPlayer p, int selectedTime) {
|
||||
int t = 18000 + selectedTime;
|
||||
if (t >= 24000) t -= 24000;
|
||||
p.player.setPlayerTime(t, false);
|
||||
}
|
||||
|
||||
// ── Score ──────────────────────────────────────────────────────────────────
|
||||
|
||||
protected void score() {
|
||||
score++;
|
||||
totalScore++;
|
||||
checkRewards();
|
||||
new ParkourScoreEvent(player).call();
|
||||
}
|
||||
|
||||
private void checkRewards() {
|
||||
if (!Rewards.REWARDS_ENABLED || score == 0) return;
|
||||
|
||||
if (Rewards.SCORE_REWARDS.containsKey(score))
|
||||
Rewards.SCORE_REWARDS.get(score).forEach(r -> r.execute(player, getMode()));
|
||||
|
||||
int intervalScore = Config.CONFIG.getBoolean("scoring.rewards-use-total-score") ? totalScore : score;
|
||||
for (int interval : Rewards.INTERVAL_REWARDS.keySet()) {
|
||||
if (intervalScore % interval == 0)
|
||||
Rewards.INTERVAL_REWARDS.get(interval).forEach(r -> r.execute(player, getMode()));
|
||||
}
|
||||
|
||||
if (start == null) { // start stopwatch when first point is achieved
|
||||
start = Instant.now();
|
||||
String key = Integer.toString(score);
|
||||
if (Rewards.ONE_TIME_REWARDS.containsKey(score) && !player.collectedRewards.contains(key)) {
|
||||
Rewards.ONE_TIME_REWARDS.get(score).forEach(r -> r.execute(player, getMode()));
|
||||
player.collectedRewards.add(key);
|
||||
}
|
||||
}
|
||||
|
||||
// updates the player time
|
||||
protected void updateVisualTime(ParkourPlayer player, int selectedTime) {
|
||||
int newTime = 18000 + selectedTime;
|
||||
if (newTime >= 24000) {
|
||||
newTime -= 24000;
|
||||
}
|
||||
// ── Fall / Reset ───────────────────────────────────────────────────────────
|
||||
|
||||
player.player.setPlayerTime(newTime, false);
|
||||
protected void fall() {
|
||||
new ParkourFallEvent(player).call();
|
||||
reset(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the parkour. If regenerate is false, this generator is stopped and the island is destroyed.
|
||||
*
|
||||
* @param regenerate True if parkour should regenerate, false if not.
|
||||
*/
|
||||
public void reset(boolean regenerate) {
|
||||
LoParkour.log("Resetting generator, regenerate = %s".formatted(regenerate));
|
||||
|
||||
stopped = !regenerate;
|
||||
|
||||
if (!regenerate && task == null) {
|
||||
LoParkour.getPlugin().getLogger().warning("## Incomplete joining setup.");
|
||||
LoParkour.getPlugin().getLogger().warning("## There has probably been an error somewhere. Please report this error!");
|
||||
LoParkour.getPlugin().getLogger().warning("## You don't have to report this warning.");
|
||||
}
|
||||
if (!regenerate && task == null)
|
||||
LoParkour.getPlugin().getLogger().warning("## Incomplete joining setup — report this!");
|
||||
|
||||
lastPositionIndexPlayer = 0;
|
||||
if (!history.isEmpty()) {
|
||||
history.remove(0);
|
||||
history.forEach(block -> block.setType(Material.AIR, false));
|
||||
history.forEach(b -> b.setType(Material.AIR, false));
|
||||
history.clear();
|
||||
}
|
||||
|
||||
waitForSchematicCompletion = false;
|
||||
deleteSchematic = true;
|
||||
deleteSchematic();
|
||||
placer.deleteSchematic();
|
||||
|
||||
Leaderboard leaderboard = getMode().getLeaderboard();
|
||||
int record = leaderboard != null ? leaderboard.get(player.getUUID()).score() : 0;
|
||||
Leaderboard lb = getMode().getLeaderboard();
|
||||
int record = lb != null ? lb.get(player.getUUID()).score() : 0;
|
||||
|
||||
if (profile.get("showFallMessage").asBoolean()) {
|
||||
String message;
|
||||
int number = 0;
|
||||
|
||||
if (score == record) {
|
||||
message = "settings.parkour_settings.items.fall_message.formats.tied";
|
||||
} else if (score > record) {
|
||||
number = score - record;
|
||||
message = "settings.parkour_settings.items.fall_message.formats.beat";
|
||||
} else {
|
||||
number = record - score;
|
||||
message = "settings.parkour_settings.items.fall_message.formats.miss";
|
||||
}
|
||||
|
||||
for (ParkourPlayer players : getPlayers()) {
|
||||
players.sendTranslated("settings.parkour_settings.items.fall_message.divider");
|
||||
players.sendTranslated("settings.parkour_settings.items.fall_message.score", Integer.toString(score));
|
||||
players.sendTranslated("settings.parkour_settings.items.fall_message.time", getFormattedTime());
|
||||
players.sendTranslated("settings.parkour_settings.items.fall_message.high_score", Integer.toString(record));
|
||||
players.sendTranslated(message, Integer.toString(number));
|
||||
players.sendTranslated("settings.parkour_settings.items.fall_message.divider");
|
||||
}
|
||||
}
|
||||
|
||||
if (leaderboard != null && score > record) {
|
||||
if (profile.get("showFallMessage").asBoolean()) sendFallMessage(record);
|
||||
if (lb != null && score > record)
|
||||
registerScore(getDetailedTime(), Double.toString(getDifficultyScore()).substring(0, 3), score);
|
||||
}
|
||||
|
||||
score = 0;
|
||||
start = null;
|
||||
heading = Option.HEADING.getDirection();
|
||||
|
||||
if (regenerate) { // generate back the blocks
|
||||
if (regenerate) {
|
||||
player.teleport(playerSpawn);
|
||||
generateFirst(playerSpawn, blockSpawn);
|
||||
return;
|
||||
}
|
||||
|
||||
island.destroy();
|
||||
if (getPlayers().isEmpty())
|
||||
getSpectators().forEach(s -> Modes.DEFAULT.create(s.player));
|
||||
}
|
||||
|
||||
if (getPlayers().isEmpty()) {
|
||||
getSpectators().forEach(spectator -> Modes.DEFAULT.create(spectator.player));
|
||||
private void sendFallMessage(int record) {
|
||||
String key;
|
||||
int number = 0;
|
||||
if (score == record) key = "settings.parkour_settings.items.fall_message.formats.tied";
|
||||
else if (score > record) { key = "settings.parkour_settings.items.fall_message.formats.beat"; number = score - record; }
|
||||
else { key = "settings.parkour_settings.items.fall_message.formats.miss"; number = record - score; }
|
||||
|
||||
for (ParkourPlayer p : getPlayers()) {
|
||||
p.sendTranslated("settings.parkour_settings.items.fall_message.divider");
|
||||
p.sendTranslated("settings.parkour_settings.items.fall_message.score", Integer.toString(score));
|
||||
p.sendTranslated("settings.parkour_settings.items.fall_message.time", getFormattedTime());
|
||||
p.sendTranslated("settings.parkour_settings.items.fall_message.high_score", Integer.toString(record));
|
||||
p.sendTranslated(key, Integer.toString(number));
|
||||
p.sendTranslated("settings.parkour_settings.items.fall_message.divider");
|
||||
}
|
||||
}
|
||||
|
||||
protected void registerScore(String time, String difficulty, int score) {
|
||||
Leaderboard leaderboard = getMode().getLeaderboard();
|
||||
|
||||
if (leaderboard == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
getPlayers().forEach(player -> leaderboard.put(player.getUUID(), new Score(player.getName(), time, difficulty, score)));
|
||||
Leaderboard lb = getMode().getLeaderboard();
|
||||
if (lb == null) return;
|
||||
getPlayers().forEach(p -> lb.put(p.getUUID(), new Score(p.getName(), time, difficulty, score)));
|
||||
}
|
||||
|
||||
private void deleteSchematic() {
|
||||
if (!deleteSchematic) {
|
||||
return;
|
||||
}
|
||||
LoParkour.log("Deleting schematic");
|
||||
// ── Cleanup ────────────────────────────────────────────────────────────────
|
||||
|
||||
schematicBlocks.forEach(block -> block.setType(Material.AIR));
|
||||
schematicBlocks.clear();
|
||||
|
||||
deleteSchematic = false;
|
||||
schematicCooldown = Config.GENERATION.getInt("advanced.schematic-cooldown");
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the next parkour block or schematic.
|
||||
*/
|
||||
public void generate() {
|
||||
if (waitForSchematicCompletion) {
|
||||
return;
|
||||
}
|
||||
|
||||
Map<BlockGenerationType, Double> chances = new HashMap<>(defaultChances);
|
||||
if (schematicCooldown > 0 || generatorOptions.contains(GeneratorOption.DISABLE_SCHEMATICS) || profile.get("schematicDifficulty").asDouble() == 0.0 || !schematicBlocks.isEmpty()) {
|
||||
chances.remove(BlockGenerationType.SCHEMATIC);
|
||||
}
|
||||
if (!profile.get("useSpecialBlocks").asBoolean()) {
|
||||
chances.remove(BlockGenerationType.SPECIAL);
|
||||
}
|
||||
if (chances.isEmpty()) {
|
||||
chances.put(BlockGenerationType.DEFAULT, 1.0);
|
||||
}
|
||||
|
||||
BlockGenerationType jump = Probs.random(chances);
|
||||
if (jump == BlockGenerationType.SCHEMATIC) {
|
||||
LoParkour.log("Generating schematic jump at index %s".formatted(history.size() + 1));
|
||||
|
||||
double difficulty = profile.get("schematicDifficulty").asDouble();
|
||||
|
||||
// TODO: Implement schematic generation with LoLib
|
||||
LoParkour.getPlugin().getLogger().warning("LPSchematic generation temporarily disabled during migration");
|
||||
return;
|
||||
}
|
||||
|
||||
LoParkour.log("Generating normal jump at index %s".formatted(history.size() + 1));
|
||||
|
||||
List<Block> blocks = selectBlocks();
|
||||
|
||||
if (blocks.isEmpty()) {
|
||||
LoParkour.getPlugin().getLogger().severe("Error while trying to generate parkour: No blocks to generate found");
|
||||
return;
|
||||
}
|
||||
|
||||
List<Block> movedBlocks = new ArrayList<>();
|
||||
for (Block block : blocks) {
|
||||
BlockData data = (jump == BlockGenerationType.SPECIAL && !generatorOptions.contains(GeneratorOption.DISABLE_SPECIAL)) ? Probs.random(specialChances) : selectBlockData();
|
||||
|
||||
if (data instanceof Fence) {
|
||||
block = block.getLocation().subtract(0, 1, 0).getBlock();
|
||||
}
|
||||
|
||||
block.setBlockData(data, data instanceof Fence || data instanceof GlassPane);
|
||||
movedBlocks.add(block);
|
||||
}
|
||||
|
||||
new ParkourBlockGenerateEvent(movedBlocks, this, player).call();
|
||||
|
||||
particles(movedBlocks);
|
||||
sound(movedBlocks);
|
||||
|
||||
history.addAll(movedBlocks);
|
||||
schematicCooldown--;
|
||||
}
|
||||
|
||||
private @NotNull List<Block> rotatedPaste(dev.loki.loparkour.schematic.lpschem.LPSchematic schematic, Location location) {
|
||||
// TODO: Implement schematic rotation with LoLib
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
|
||||
private double angleInY(Vector a, Vector b) {
|
||||
double det = a.getX() * b.getZ() - a.getZ() * b.getX();
|
||||
return Math.atan2(det, a.dot(b));
|
||||
}
|
||||
|
||||
protected Block getLatest() {
|
||||
return history.get(history.size() - 1);
|
||||
}
|
||||
|
||||
// Очистка блоков на большом расстоянии от игрока для экономии памяти
|
||||
protected void cleanupDistantBlocks() {
|
||||
if (history.size() < Option.BLOCK_CLEANUP_DISTANCE * 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
Location playerLoc = player.getLocation();
|
||||
if (history.size() < Option.BLOCK_CLEANUP_DISTANCE * 2) return;
|
||||
Location loc = player.getLocation();
|
||||
int removed = 0;
|
||||
|
||||
Iterator<Block> iterator = history.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
Block block = iterator.next();
|
||||
|
||||
if (block.getLocation().distance(playerLoc) > Option.BLOCK_CLEANUP_DISTANCE) {
|
||||
block.setType(Material.AIR, false);
|
||||
iterator.remove();
|
||||
Iterator<Block> it = history.iterator();
|
||||
while (it.hasNext()) {
|
||||
Block b = it.next();
|
||||
if (b.getLocation().distance(loc) > Option.BLOCK_CLEANUP_DISTANCE) {
|
||||
b.setType(Material.AIR, false);
|
||||
it.remove();
|
||||
removed++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (removed > 0) {
|
||||
lastPositionIndexPlayer = Math.max(0, lastPositionIndexPlayer - removed);
|
||||
} else break;
|
||||
}
|
||||
if (removed > 0) lastPositionIndexPlayer = Math.max(0, lastPositionIndexPlayer - removed);
|
||||
}
|
||||
|
||||
private double getDifficulty(String fileName) {
|
||||
String path = "difficulty.%s".formatted(fileName.split("[-.]")[1]);
|
||||
// ── Misc ───────────────────────────────────────────────────────────────────
|
||||
|
||||
if (!Config.SCHEMATICS.isPath(path)) {
|
||||
return 1.0; // todo remove
|
||||
}
|
||||
public void menu(ParkourPlayer player) { Menus.PARKOUR_SETTINGS.open(player); }
|
||||
|
||||
return Config.SCHEMATICS.getDouble(path);
|
||||
}
|
||||
public Block getLatest() { return history.get(history.size() - 1); }
|
||||
|
||||
/**
|
||||
* Generates a specific amount of blocks ahead of the player
|
||||
*
|
||||
* @param amount The amount
|
||||
*/
|
||||
public void generate(int amount) {
|
||||
for (int i = 0; i < amount + 1; i++) {
|
||||
generate();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the first few blocks (which come off the spawn island)
|
||||
*
|
||||
* @param spawn The spawn of the player
|
||||
* @param block The location used to begin the parkour of off
|
||||
*/
|
||||
public void generateFirst(Location spawn, Location block) {
|
||||
LoParkour.log("First generation");
|
||||
|
||||
playerSpawn = spawn;
|
||||
lastStandingPlayerLocation = spawn;
|
||||
blockSpawn = block;
|
||||
history.add(blockSpawn.getBlock());
|
||||
|
||||
generate(profile.get("blockLead").asInt());
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates a score between 0 (inclusive) and 1 (inclusive) to determine how difficult it was for
|
||||
* the player to achieve this score using their settings.
|
||||
*/
|
||||
public double getDifficultyScore() {
|
||||
double score = 0;
|
||||
|
||||
if (profile.get("useSpecialBlocks").asBoolean()) score += 0.5;
|
||||
if (profile.get("schematicDifficulty").asDouble() > 0) {
|
||||
if (profile.get("schematicDifficulty").asDouble() <= 0.25) score += 0.2;
|
||||
else if (profile.get("schematicDifficulty").asDouble() <= 0.5) score += 0.3;
|
||||
else if (profile.get("schematicDifficulty").asDouble() <= 0.75) score += 0.4;
|
||||
else if (profile.get("schematicDifficulty").asDouble() <= 1.0) score += 0.5;
|
||||
}
|
||||
|
||||
return score;
|
||||
double s = 0;
|
||||
if (profile.get("useSpecialBlocks").asBoolean()) s += 0.5;
|
||||
double d = profile.get("schematicDifficulty").asDouble();
|
||||
if (d > 0) s += d <= 0.25 ? 0.2 : d <= 0.5 ? 0.3 : d <= 0.75 ? 0.4 : 0.5;
|
||||
return s;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The time in custom format.
|
||||
*/
|
||||
public String getFormattedTime() {
|
||||
return getTime(Config.CONFIG.getString("options.time.score-format"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The current detailed duration of the run.
|
||||
*/
|
||||
public String getDetailedTime() {
|
||||
return getTime("mm:ss:SSS");
|
||||
}
|
||||
public String getFormattedTime() { return getTime(Config.CONFIG.getString("options.time.score-format")); }
|
||||
public String getDetailedTime() { return getTime("mm:ss:SSS"); }
|
||||
|
||||
private String getTime(String format) {
|
||||
var timeMs = Instant.now().minusMillis(start != null ? start.toEpochMilli() : Instant.now().toEpochMilli());
|
||||
|
||||
var ms = Instant.now().minusMillis(start != null ? start.toEpochMilli() : Instant.now().toEpochMilli());
|
||||
try {
|
||||
return DateTimeFormatter.ofPattern(format)
|
||||
.withZone(ZoneOffset.UTC)
|
||||
.format(timeMs);
|
||||
return DateTimeFormatter.ofPattern(format).withZone(ZoneOffset.UTC).format(ms);
|
||||
} catch (IllegalArgumentException ex) {
|
||||
LoParkour.getPlugin().getLogger().severe("Invalid score time format %s".formatted(Config.CONFIG.getString("options.time.score-format")) + " - " + ex.getMessage());
|
||||
LoParkour.getPlugin().getLogger().severe("Invalid time format: " + format);
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return This generator's mode.
|
||||
*/
|
||||
public Mode getMode() {
|
||||
return Modes.DEFAULT;
|
||||
}
|
||||
public Mode getMode() { return Modes.DEFAULT; }
|
||||
|
||||
/**
|
||||
* @return The players in this session.
|
||||
*/
|
||||
@NotNull
|
||||
public List<ParkourPlayer> getPlayers() {
|
||||
return session.getPlayers();
|
||||
}
|
||||
@NotNull public List<ParkourPlayer> getPlayers() { return session.getPlayers(); }
|
||||
@NotNull public List<ParkourSpectator> getSpectators() { return session.getSpectators(); }
|
||||
|
||||
/**
|
||||
* @return The spectators in this session.
|
||||
*/
|
||||
@NotNull
|
||||
public List<ParkourSpectator> getSpectators() {
|
||||
return session.getSpectators();
|
||||
}
|
||||
|
||||
public enum BlockGenerationType {
|
||||
DEFAULT, SCHEMATIC, SPECIAL
|
||||
}
|
||||
public enum BlockGenerationType { DEFAULT, SCHEMATIC, SPECIAL }
|
||||
}
|
||||
|
|
|
|||
126
src/main/java/dev/loki/loparkour/generator/ScoreManager.java
Normal file
126
src/main/java/dev/loki/loparkour/generator/ScoreManager.java
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
package dev.loki.loparkour.generator;
|
||||
|
||||
import dev.loki.loparkour.api.event.ParkourScoreEvent;
|
||||
import dev.loki.loparkour.config.Config;
|
||||
import dev.loki.loparkour.leaderboard.Leaderboard;
|
||||
import dev.loki.loparkour.leaderboard.Score;
|
||||
import dev.loki.loparkour.mode.Mode;
|
||||
import dev.loki.loparkour.player.ParkourPlayer;
|
||||
import dev.loki.loparkour.reward.Rewards;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Manages scoring and rewards (SRP - Single Responsibility)
|
||||
*/
|
||||
public class ScoreManager {
|
||||
|
||||
private final ParkourPlayer player;
|
||||
private final Mode mode;
|
||||
private final Profile profile;
|
||||
|
||||
private int score = 0;
|
||||
private int totalScore = 0;
|
||||
private Instant start;
|
||||
|
||||
public ScoreManager(ParkourPlayer player, Mode mode, Profile profile) {
|
||||
this.player = player;
|
||||
this.mode = mode;
|
||||
this.profile = profile;
|
||||
}
|
||||
|
||||
public void addScore(int points) {
|
||||
for (int i = 0; i < points; i++) {
|
||||
score++;
|
||||
totalScore++;
|
||||
checkRewards();
|
||||
new ParkourScoreEvent(player).call();
|
||||
}
|
||||
|
||||
if (start == null) {
|
||||
start = Instant.now();
|
||||
}
|
||||
}
|
||||
|
||||
private void checkRewards() {
|
||||
if (!Rewards.REWARDS_ENABLED || score == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Rewards.SCORE_REWARDS.containsKey(score)) {
|
||||
Rewards.SCORE_REWARDS.get(score).forEach(s -> s.execute(player, mode));
|
||||
}
|
||||
|
||||
int intervalScore = Config.CONFIG.getBoolean("scoring.rewards-use-total-score") ? totalScore : score;
|
||||
for (int interval : Rewards.INTERVAL_REWARDS.keySet()) {
|
||||
if (intervalScore % interval != 0) continue;
|
||||
Rewards.INTERVAL_REWARDS.get(interval).forEach(s -> s.execute(player, mode));
|
||||
}
|
||||
|
||||
if (Rewards.ONE_TIME_REWARDS.containsKey(score) &&
|
||||
!player.collectedRewards.contains(Integer.toString(score))) {
|
||||
Rewards.ONE_TIME_REWARDS.get(score).forEach(s -> s.execute(player, mode));
|
||||
player.collectedRewards.add(Integer.toString(score));
|
||||
}
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
score = 0;
|
||||
start = null;
|
||||
}
|
||||
|
||||
public void registerScore(List<ParkourPlayer> players) {
|
||||
Leaderboard leaderboard = mode.getLeaderboard();
|
||||
if (leaderboard == null) return;
|
||||
|
||||
String time = getDetailedTime();
|
||||
String difficulty = Double.toString(getDifficultyScore()).substring(0, Math.min(3, Double.toString(getDifficultyScore()).length()));
|
||||
|
||||
players.forEach(p -> leaderboard.put(p.getUUID(), new Score(p.getName(), time, difficulty, score)));
|
||||
}
|
||||
|
||||
public String getFormattedTime() {
|
||||
return getTime(Config.CONFIG.getString("options.time.score-format"));
|
||||
}
|
||||
|
||||
public String getDetailedTime() {
|
||||
return getTime("mm:ss:SSS");
|
||||
}
|
||||
|
||||
private String getTime(String format) {
|
||||
var timeMs = Instant.now().minusMillis(start != null ? start.toEpochMilli() : Instant.now().toEpochMilli());
|
||||
|
||||
try {
|
||||
return DateTimeFormatter.ofPattern(format)
|
||||
.withZone(ZoneOffset.UTC)
|
||||
.format(timeMs);
|
||||
} catch (IllegalArgumentException ex) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
private double getDifficultyScore() {
|
||||
double score = 0;
|
||||
if (profile.get("useSpecialBlocks").asBoolean()) score += 0.5;
|
||||
|
||||
double schematicDiff = profile.get("schematicDifficulty").asDouble();
|
||||
if (schematicDiff > 0) {
|
||||
if (schematicDiff <= 0.25) score += 0.2;
|
||||
else if (schematicDiff <= 0.5) score += 0.3;
|
||||
else if (schematicDiff <= 0.75) score += 0.4;
|
||||
else score += 0.5;
|
||||
}
|
||||
return score;
|
||||
}
|
||||
|
||||
public int getScore() {
|
||||
return score;
|
||||
}
|
||||
|
||||
public int getTotalScore() {
|
||||
return totalScore;
|
||||
}
|
||||
}
|
||||
|
|
@ -39,8 +39,9 @@ public class Leaderboard {
|
|||
|
||||
Storage.init(mode);
|
||||
|
||||
// read all data
|
||||
read(true);
|
||||
// Defer initial read until storage is ready (SQL connects async — reading before
|
||||
// connection is established would return empty results and lose all scores).
|
||||
Storage.runWhenReady(() -> read(true));
|
||||
|
||||
var interval = Config.CONFIG.getInt("storage-update-interval");
|
||||
|
||||
|
|
@ -48,10 +49,8 @@ public class Leaderboard {
|
|||
// read/write all data every x seconds after x seconds to allow time for reading/writing
|
||||
Scheduler.get(LoParkour.getPlugin()).runTimerAsync(() -> {
|
||||
if (Config.CONFIG.getBoolean("joining")) {
|
||||
LoParkour.log("Periodic saving of leaderboard data of %s".formatted(mode));
|
||||
write(true);
|
||||
} else {
|
||||
LoParkour.log("Periodic reading of leaderboard data of %s".formatted(mode));
|
||||
read(true);
|
||||
}
|
||||
}, interval * 20, interval * 20);
|
||||
|
|
@ -61,8 +60,6 @@ public class Leaderboard {
|
|||
* Writes all scores to the leaderboard file associated with this leaderboard
|
||||
*/
|
||||
public void write(boolean async) {
|
||||
LoParkour.log("Saving leaderboard data of %s".formatted(mode));
|
||||
|
||||
run(() -> Storage.writeScores(mode, scores), async);
|
||||
}
|
||||
|
||||
|
|
@ -70,8 +67,6 @@ public class Leaderboard {
|
|||
* Reads all scores from the leaderboard file
|
||||
*/
|
||||
public void read(boolean async) {
|
||||
LoParkour.log("Reading leaderboard data of %s".formatted(mode));
|
||||
|
||||
run(() -> {
|
||||
scores.clear();
|
||||
scores.putAll(Storage.readScores(mode));
|
||||
|
|
|
|||
|
|
@ -0,0 +1,133 @@
|
|||
package dev.loki.loparkour.listener;
|
||||
|
||||
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.player.ParkourPlayer;
|
||||
import dev.loki.loparkour.player.ParkourUser;
|
||||
import dev.loki.loparkour.session.Session;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.Cancellable;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.block.BlockBreakEvent;
|
||||
import org.bukkit.event.block.BlockPlaceEvent;
|
||||
import org.bukkit.event.entity.EntityDamageEvent;
|
||||
import org.bukkit.event.inventory.InventoryClickEvent;
|
||||
import org.bukkit.event.inventory.InventoryType;
|
||||
import org.bukkit.event.player.*;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.PlayerInventory;
|
||||
|
||||
/**
|
||||
* Handles hotbar item interactions, chat routing, focus mode, and
|
||||
* general restrictions (drop/place/break/damage) for parkour players.
|
||||
*/
|
||||
public class ParkourRestrictionListener implements Listener {
|
||||
|
||||
@EventHandler
|
||||
public void onChat(AsyncPlayerChatEvent event) {
|
||||
if (!Option.OPTIONS_ENABLED.get(ParkourOption.CHAT)) return;
|
||||
|
||||
Player player = event.getPlayer();
|
||||
ParkourUser user = ParkourUser.getUser(player);
|
||||
if (user == null) return;
|
||||
|
||||
Session session = user.session;
|
||||
if (session.isMuted(user)) return;
|
||||
|
||||
event.setCancelled(true);
|
||||
switch (user.chatType) {
|
||||
case LOBBY_ONLY -> session.getUsers().forEach(u -> u.sendTranslated("settings.chat.formats.lobby", player.getName(), event.getMessage()));
|
||||
case PLAYERS_ONLY -> session.getPlayers().forEach(u -> u.sendTranslated("settings.chat.formats.players", player.getName(), event.getMessage()));
|
||||
default -> event.setCancelled(false);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGHEST)
|
||||
public void onCommand(PlayerCommandPreprocessEvent event) {
|
||||
if (!Config.CONFIG.getBoolean("focus-mode.enabled")) return;
|
||||
ParkourUser user = ParkourUser.getUser(event.getPlayer());
|
||||
if (user == null) return;
|
||||
|
||||
String cmd = event.getMessage().toLowerCase();
|
||||
if (Config.CONFIG.getStringList("focus-mode.whitelist").stream().anyMatch(cmd::contains)) return;
|
||||
|
||||
user.sendTranslated("other.no_do");
|
||||
event.setCancelled(true);
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGHEST)
|
||||
public void onInteract(PlayerInteractEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
ParkourPlayer pp = ParkourPlayer.getPlayer(player);
|
||||
if (pp == null) return;
|
||||
|
||||
// Block container right-click
|
||||
boolean isContainer = event.getClickedBlock() != null && switch (event.getClickedBlock().getType()) {
|
||||
case DISPENSER, DROPPER, HOPPER -> true;
|
||||
default -> false;
|
||||
};
|
||||
if (event.getAction() == org.bukkit.event.block.Action.RIGHT_CLICK_BLOCK
|
||||
&& isContainer && event.getHand() == org.bukkit.inventory.EquipmentSlot.HAND) {
|
||||
event.setCancelled(true);
|
||||
player.closeInventory();
|
||||
return;
|
||||
}
|
||||
|
||||
boolean isRightClick = (event.getAction() == org.bukkit.event.block.Action.RIGHT_CLICK_AIR
|
||||
|| event.getAction() == org.bukkit.event.block.Action.RIGHT_CLICK_BLOCK)
|
||||
&& event.getHand() == org.bukkit.inventory.EquipmentSlot.HAND;
|
||||
if (!isRightClick) return;
|
||||
|
||||
org.bukkit.Material held = heldItem(player).getType();
|
||||
event.setCancelled(true);
|
||||
|
||||
if (held == Locales.getItem(player, "play.item").build().getType()) Menus.PLAY.open(player);
|
||||
else if (held == Locales.getItem(player, "community.item").build().getType()) Menus.COMMUNITY.open(player);
|
||||
else if (held == Locales.getItem(player, "settings.item").build().getType()) Menus.SETTINGS.open(player);
|
||||
else if (held == Locales.getItem(player, "lobby.item").build().getType()) Menus.LOBBY.open(player);
|
||||
else if (held == Locales.getItem(player, "other.quit").build().getType()) ParkourUser.leave(player);
|
||||
else if (!Config.CONFIG.getBoolean("options.disable-inventory-blocks")) event.setCancelled(false);
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.LOW)
|
||||
public void onInventoryClick(InventoryClickEvent event) {
|
||||
if (!(event.getWhoClicked() instanceof Player player)) return;
|
||||
if (event.getInventory().getType() == InventoryType.CHEST) return; // LoLib GUI
|
||||
if (event.getInventory().getType() == InventoryType.CRAFTING) return; // own inv
|
||||
restrict(player, event);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onDrop(PlayerDropItemEvent event) { restrict(event.getPlayer(), event); }
|
||||
@EventHandler
|
||||
public void onPlace(BlockPlaceEvent event) { restrict(event.getPlayer(), event); }
|
||||
@EventHandler
|
||||
public void onBreak(BlockBreakEvent event) { restrict(event.getPlayer(), event); }
|
||||
|
||||
@EventHandler
|
||||
public void onDamage(EntityDamageEvent event) {
|
||||
if (event.getEntity() instanceof Player p) restrict(p, event);
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGHEST)
|
||||
public void onSpectate(PlayerTeleportEvent event) {
|
||||
if (event.getCause() == PlayerTeleportEvent.TeleportCause.SPECTATE)
|
||||
restrict(event.getPlayer(), event);
|
||||
}
|
||||
|
||||
private void restrict(Player player, Cancellable event) {
|
||||
if (ParkourUser.isUser(player)) event.setCancelled(true);
|
||||
}
|
||||
|
||||
private ItemStack heldItem(Player player) {
|
||||
PlayerInventory inv = player.getInventory();
|
||||
return inv.getItemInMainHand().getType() == org.bukkit.Material.AIR
|
||||
? inv.getItemInOffHand()
|
||||
: inv.getItemInMainHand();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
package dev.loki.loparkour.listener;
|
||||
|
||||
import dev.loki.loparkour.config.Config;
|
||||
import dev.loki.loparkour.menu.ParkourOption;
|
||||
import dev.loki.loparkour.mode.Modes;
|
||||
import dev.loki.loparkour.player.ParkourUser;
|
||||
import dev.loki.loparkour.world.World;
|
||||
import io.papermc.lib.PaperLib;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerChangedWorldEvent;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.bukkit.event.player.PlayerQuitEvent;
|
||||
import org.bukkit.event.player.PlayerTeleportEvent;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
/**
|
||||
* Handles join / quit / world-change events.
|
||||
*/
|
||||
public class PlayerConnectionListener implements Listener {
|
||||
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
public void onJoin(PlayerJoinEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
|
||||
if (Config.CONFIG.getBoolean("bungeecord.enabled")) {
|
||||
Modes.DEFAULT.create(player);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!player.getWorld().equals(World.getWorld())) return;
|
||||
|
||||
org.bukkit.World fallback = Bukkit.getWorld(Config.CONFIG.getString("world.fall-back"));
|
||||
if (fallback != null) {
|
||||
PaperLib.teleportAsync(player, fallback.getSpawnLocation(), PlayerTeleportEvent.TeleportCause.PLUGIN);
|
||||
return;
|
||||
}
|
||||
|
||||
PaperLib.teleportAsync(player, Bukkit.getWorlds().stream()
|
||||
.filter(w -> !w.equals(World.getWorld()))
|
||||
.findAny()
|
||||
.orElseThrow(() -> new NoSuchElementException("No fallback world found!"))
|
||||
.getSpawnLocation());
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onQuit(PlayerQuitEvent event) {
|
||||
ParkourUser user = ParkourUser.getUser(event.getPlayer());
|
||||
if (user == null) return;
|
||||
ParkourUser.unregister(user, true, false, true);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onWorldChange(PlayerChangedWorldEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
ParkourUser user = ParkourUser.getUser(player);
|
||||
org.bukkit.World parkour = World.getWorld();
|
||||
|
||||
boolean isAdmin = Config.CONFIG.getBoolean("permissions.enabled")
|
||||
? ParkourOption.ADMIN.mayPerform(player)
|
||||
: player.isOp();
|
||||
|
||||
if (player.getWorld() == parkour && user == null && !isAdmin && player.getTicksLived() > 20) {
|
||||
Bukkit.getWorlds().stream()
|
||||
.filter(w -> !w.equals(parkour))
|
||||
.findAny()
|
||||
.ifPresent(w -> PaperLib.teleportAsync(player, w.getSpawnLocation()));
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.getFrom() == parkour && user != null
|
||||
&& Duration.between(user.joined, Instant.now()).toMillis() > 100) {
|
||||
ParkourUser.unregister(user, true, false, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
package dev.loki.loparkour.listener;
|
||||
|
||||
import dev.loki.loparkour.LoParkour;
|
||||
import dev.loki.loparkour.command.SchematicCommandHandler;
|
||||
import dev.loki.loparkour.util.ColorUtil;
|
||||
import dev.loki.loparkour.util.Locations;
|
||||
import dev.loki.loparkour.util.ParticleUtil;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Particle;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.block.Action;
|
||||
import org.bukkit.event.player.PlayerInteractEvent;
|
||||
import org.bukkit.inventory.EquipmentSlot;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.util.BoundingBox;
|
||||
|
||||
/**
|
||||
* Handles LPSchematic wand clicks (left/right click on blocks).
|
||||
*/
|
||||
public class SchematicWandListener implements Listener {
|
||||
|
||||
@EventHandler
|
||||
public void onWandInteract(PlayerInteractEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
ItemStack item = player.getInventory().getItemInMainHand();
|
||||
|
||||
if (!player.hasPermission("LoParkour.admin")) return;
|
||||
if (item.getItemMeta() == null || !item.getItemMeta().getDisplayName().contains("LPSchematic Wand")) return;
|
||||
if (event.getClickedBlock() == null || event.getHand() != EquipmentSlot.HAND) return;
|
||||
|
||||
Location loc = event.getClickedBlock().getLocation();
|
||||
Location[] sel = SchematicCommandHandler.selections.get(player);
|
||||
event.setCancelled(true);
|
||||
|
||||
if (event.getAction() == Action.LEFT_CLICK_BLOCK) {
|
||||
Location[] updated = new Location[]{loc, sel != null ? sel[1] : null};
|
||||
SchematicCommandHandler.selections.put(player, updated);
|
||||
send(player, LoParkour.PREFIX + "Position 1 set to " + Locations.toString(loc, true));
|
||||
if (updated[1] != null)
|
||||
ParticleUtil.box(BoundingBox.of(loc, updated[1]), player.getWorld(), Particle.END_ROD, player, 0.2);
|
||||
|
||||
} else if (event.getAction() == Action.RIGHT_CLICK_BLOCK) {
|
||||
Location[] updated = new Location[]{sel != null ? sel[0] : null, loc};
|
||||
SchematicCommandHandler.selections.put(player, updated);
|
||||
send(player, LoParkour.PREFIX + "Position 2 set to " + Locations.toString(loc, true));
|
||||
if (updated[0] != null)
|
||||
ParticleUtil.box(BoundingBox.of(updated[0], loc), player.getWorld(), Particle.END_ROD, player, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
private void send(CommandSender sender, String msg) {
|
||||
sender.sendMessage(ColorUtil.color(msg));
|
||||
}
|
||||
}
|
||||
101
src/main/java/dev/loki/loparkour/menu/LPMenu.java
Normal file
101
src/main/java/dev/loki/loparkour/menu/LPMenu.java
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
package dev.loki.loparkour.menu;
|
||||
|
||||
import dev.loki.loparkour.config.Locales;
|
||||
import dev.loki.loparkour.config.Option;
|
||||
import dev.loki.loparkour.util.ColorUtil;
|
||||
import dev.lolib.gui.InventoryGUI;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
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)
|
||||
*
|
||||
* cancelAllClicks = true by default — items cannot be taken out automatically.
|
||||
* Handlers receive Consumer<InventoryClickEvent>, event is already cancelled before handler runs.
|
||||
*/
|
||||
public abstract class LPMenu {
|
||||
|
||||
public abstract void open(@NotNull Player player);
|
||||
|
||||
protected String locale(@NotNull Player player) {
|
||||
var user = dev.loki.loparkour.player.ParkourUser.getUser(player);
|
||||
return user != null ? user.locale : Option.OPTIONS_DEFAULTS.get(ParkourOption.LANG);
|
||||
}
|
||||
|
||||
protected ItemStack filler() {
|
||||
ItemStack item = new ItemStack(Material.GRAY_STAINED_GLASS_PANE);
|
||||
ItemMeta meta = item.getItemMeta();
|
||||
if (meta != null) {
|
||||
meta.setDisplayName(" ");
|
||||
item.setItemMeta(meta);
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
protected ItemStack backItem() {
|
||||
ItemStack item = new ItemStack(Material.RED_DYE);
|
||||
ItemMeta meta = item.getItemMeta();
|
||||
if (meta != null) {
|
||||
meta.setDisplayName(ColorUtil.color("<#DE1F1F><bold>«"));
|
||||
item.setItemMeta(meta);
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
protected ItemStack nextItem() {
|
||||
ItemStack item = new ItemStack(Material.LIME_DYE);
|
||||
ItemMeta meta = item.getItemMeta();
|
||||
if (meta != null) {
|
||||
meta.setDisplayName(ColorUtil.color("<#0DCB07><bold>»"));
|
||||
item.setItemMeta(meta);
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
protected ItemStack closeButton(@NotNull Player player) {
|
||||
return localeItem(player, "other.close");
|
||||
}
|
||||
|
||||
protected ItemStack localeItem(@NotNull Player player, @NotNull String key) {
|
||||
return Locales.getItem(player, key).build();
|
||||
}
|
||||
|
||||
protected ItemStack localeItem(@NotNull Player player, @NotNull String key, String... replace) {
|
||||
return Locales.getItem(player, key, replace).build();
|
||||
}
|
||||
|
||||
protected ItemStack localeItem(@NotNull String locale, @NotNull String key) {
|
||||
return Locales.getItem(locale, key).build();
|
||||
}
|
||||
|
||||
protected ItemStack localeItem(@NotNull String locale, @NotNull String key, String... replace) {
|
||||
return Locales.getItem(locale, key, replace).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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()
|
||||
.title(ColorUtil.color(title))
|
||||
.rows(rows)
|
||||
.fill(filler());
|
||||
}
|
||||
|
||||
protected int[] distributeEvenly(int count, int start, int end) {
|
||||
int[] result = new int[count];
|
||||
if (count == 0) return result;
|
||||
for (int i = 0; i < count; i++) {
|
||||
result[i] = start + Math.round((float) i * (end - start) / Math.max(count - 1, 1));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
@ -2,28 +2,57 @@ 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.loki.loparkour.util.ColorUtil;
|
||||
import dev.lolib.gui.InventoryGUI;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class MainMenu extends DynamicMenu {
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
// TODO: Migrate to LoLib GUI system
|
||||
/*
|
||||
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 class MainMenu extends LPMenu {
|
||||
|
||||
@Override
|
||||
public void open(@NotNull Player player) {
|
||||
String locale = locale(player);
|
||||
String title = Locales.getString(locale, "main.name");
|
||||
|
||||
List<ItemStack> visibleItems = new ArrayList<>();
|
||||
List<Runnable> actions = new ArrayList<>();
|
||||
|
||||
if (ParkourOption.PLAY.mayPerform(player) && Config.CONFIG.getBoolean("joining")) {
|
||||
visibleItems.add(localeItem(player, "play.item"));
|
||||
actions.add(() -> Menus.PLAY.open(player));
|
||||
}
|
||||
if (ParkourOption.COMMUNITY.mayPerform(player)) {
|
||||
visibleItems.add(localeItem(player, "community.item"));
|
||||
actions.add(() -> Menus.COMMUNITY.open(player));
|
||||
}
|
||||
if (ParkourOption.SETTINGS.mayPerform(player) && ParkourUser.isUser(player)) {
|
||||
visibleItems.add(localeItem(player, "settings.item"));
|
||||
actions.add(() -> Menus.SETTINGS.open(player));
|
||||
}
|
||||
if (ParkourOption.LOBBY.mayPerform(player) && ParkourUser.isUser(player)) {
|
||||
visibleItems.add(localeItem(player, "lobby.item"));
|
||||
actions.add(() -> Menus.LOBBY.open(player));
|
||||
}
|
||||
if (ParkourOption.QUIT.mayPerform(player) && ParkourUser.isUser(player)) {
|
||||
visibleItems.add(localeItem(player, "other.quit"));
|
||||
actions.add(() -> ParkourUser.leave(player));
|
||||
}
|
||||
|
||||
int count = visibleItems.size();
|
||||
int[] slots = distributeEvenly(count, 9, 17);
|
||||
|
||||
InventoryGUI gui = baseGui(title, 3);
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
final int idx = i;
|
||||
gui = gui.setItem(slots[i], visibleItems.get(i), e -> actions.get(idx).run());
|
||||
}
|
||||
|
||||
gui.open(player);
|
||||
}
|
||||
*/
|
||||
|
||||
// TODO: Migrate to LoLib GUI system
|
||||
/*
|
||||
public void open(Player player) {
|
||||
display(player, new Menu(3, Locales.getString(player, "main.name"))
|
||||
.distributeRowsEvenly());
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,29 +1,22 @@
|
|||
package dev.loki.loparkour.menu.community;
|
||||
|
||||
import dev.loki.loparkour.config.Locales;
|
||||
import dev.loki.loparkour.menu.DynamicMenu;
|
||||
import dev.loki.loparkour.menu.LPMenu;
|
||||
import dev.loki.loparkour.menu.Menus;
|
||||
import dev.loki.loparkour.menu.ParkourOption;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* The menu for all community-related things
|
||||
*/
|
||||
public class CommunityMenu extends DynamicMenu {
|
||||
public class CommunityMenu extends LPMenu {
|
||||
|
||||
// TODO: Migrate to LoLib GUI system
|
||||
/*
|
||||
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);
|
||||
@Override
|
||||
public void open(@NotNull Player player) {
|
||||
String title = Locales.getString(locale(player), "community.name");
|
||||
|
||||
baseGui(title, 3)
|
||||
.setItem(13, localeItem(player, "community.leaderboards.item"),
|
||||
e -> { if (ParkourOption.LEADERBOARDS.mayPerform(player)) Menus.LEADERBOARDS.open(player); })
|
||||
.setItem(22, closeButton(player), e -> player.closeInventory())
|
||||
.open(player);
|
||||
}
|
||||
*/
|
||||
|
||||
// TODO: Migrate to LoLib GUI system
|
||||
/*
|
||||
public void open(Player player) {
|
||||
display(player, new Menu(3, Locales.getString(player, "community.name"))
|
||||
.distributeRowsEvenly());
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,55 +2,55 @@ 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.LPMenu;
|
||||
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 org.bukkit.Material;
|
||||
import dev.loki.loparkour.util.ColorUtil;
|
||||
import dev.lolib.gui.ScrollableGUI;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Leaderboards menu
|
||||
*/
|
||||
public class LeaderboardsMenu {
|
||||
public class LeaderboardsMenu extends LPMenu {
|
||||
|
||||
// TODO: Migrate to LoLib GUI system
|
||||
/*
|
||||
public void open(Player player) {
|
||||
ParkourUser user = ParkourUser.getUser(player);
|
||||
String locale = user == null ? Option.OPTIONS_DEFAULTS.get(ParkourOption.LANG) : user.locale;
|
||||
@Override
|
||||
public void open(@NotNull Player player) {
|
||||
String locale = locale(player);
|
||||
String title = Locales.getString(locale, ParkourOption.LEADERBOARDS.path + ".name");
|
||||
|
||||
PagedMenu menu = new PagedMenu(3, Locales.getString(player, "%s.name".formatted(ParkourOption.LEADERBOARDS.path)));
|
||||
List<Mode> modes = new ArrayList<>();
|
||||
List<ItemStack> items = new ArrayList<>();
|
||||
|
||||
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 (mode.getLeaderboard() == null) continue;
|
||||
var item = mode.getItem(locale);
|
||||
if (item == null) continue;
|
||||
modes.add(mode);
|
||||
items.add(item.build());
|
||||
}
|
||||
|
||||
if (items.size() == 1) {
|
||||
Menus.SINGLE_LEADERBOARD.open(player, latest, Leaderboard.Sort.SCORE);
|
||||
if (modes.size() == 1) {
|
||||
Menus.SINGLE_LEADERBOARD.open(player, modes.get(0), 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);
|
||||
|
||||
ScrollableGUI gui = ScrollableGUI.create()
|
||||
.title(ColorUtil.color(title))
|
||||
.rows(3)
|
||||
.scrollUpButton(backItem(), 0)
|
||||
.scrollDownButton(nextItem(), 8);
|
||||
|
||||
for (int i = 0; i < items.size(); i++) {
|
||||
final Mode mode = modes.get(i);
|
||||
gui = gui.addItem(items.get(i),
|
||||
e -> Menus.SINGLE_LEADERBOARD.open(player, mode, Leaderboard.Sort.SCORE));
|
||||
}
|
||||
|
||||
gui.open(player);
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,116 +1,84 @@
|
|||
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.LPMenu;
|
||||
import dev.loki.loparkour.menu.ParkourOption;
|
||||
import dev.loki.loparkour.mode.Mode;
|
||||
import dev.loki.loparkour.player.ParkourUser;
|
||||
import org.bukkit.Bukkit;
|
||||
import dev.loki.loparkour.util.ColorUtil;
|
||||
import dev.lolib.gui.ScrollableGUI;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import org.bukkit.inventory.meta.SkullMeta;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
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 class SingleLeaderboardMenu extends LPMenu {
|
||||
|
||||
// TODO: Migrate to LoLib GUI system
|
||||
/*
|
||||
public void open(Player player, Mode mode, Leaderboard.Sort sort) {
|
||||
public void open(@NotNull Player player, @NotNull Mode mode, @NotNull Leaderboard.Sort sort) {
|
||||
Leaderboard leaderboard = mode.getLeaderboard();
|
||||
if (leaderboard == null) return;
|
||||
|
||||
if (leaderboard == null) {
|
||||
return;
|
||||
String locale = locale(player);
|
||||
String title = Locales.getString(locale, ParkourOption.LEADERBOARDS.path + ".name");
|
||||
|
||||
List<ItemStack> items = new ArrayList<>();
|
||||
int rank = 0;
|
||||
|
||||
for (Map.Entry<UUID, Score> entry : leaderboard.sort(sort).entrySet()) {
|
||||
rank++;
|
||||
Score score = entry.getValue();
|
||||
if (score == null) continue;
|
||||
|
||||
ItemStack skull = new ItemStack(Material.PLAYER_HEAD);
|
||||
if (rank <= 20 && !ParkourUser.isBedrockPlayer(player)) {
|
||||
OfflinePlayer op = org.bukkit.Bukkit.getOfflinePlayer(entry.getKey());
|
||||
if (op.getName() != null && !op.getName().startsWith(".")) {
|
||||
SkullMeta meta = (SkullMeta) skull.getItemMeta();
|
||||
if (meta != null) { meta.setOwningPlayer(op); skull.setItemMeta(meta); }
|
||||
}
|
||||
}
|
||||
|
||||
var base = Locales.getItem(locale, ParkourOption.LEADERBOARDS.path + ".head");
|
||||
int r = rank;
|
||||
base.modifyName(n -> n.replace("%r", String.valueOf(r))
|
||||
.replace("%s", String.valueOf(score.score()))
|
||||
.replace("%p", score.name())
|
||||
.replace("%t", score.time())
|
||||
.replace("%d", score.difficulty()))
|
||||
.modifyLore(l -> l.replace("%r", String.valueOf(r))
|
||||
.replace("%s", String.valueOf(score.score()))
|
||||
.replace("%p", score.name())
|
||||
.replace("%t", score.time())
|
||||
.replace("%d", score.difficulty()));
|
||||
ItemMeta built = base.build().getItemMeta();
|
||||
if (built != null) skull.setItemMeta(built);
|
||||
|
||||
items.add(skull);
|
||||
}
|
||||
// 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);
|
||||
|
||||
ScrollableGUI gui = ScrollableGUI.create()
|
||||
.title(ColorUtil.color(title))
|
||||
.rows(3)
|
||||
.scrollUpButton(backItem(), 0)
|
||||
.scrollDownButton(nextItem(), 8);
|
||||
|
||||
for (ItemStack item : items) {
|
||||
gui = gui.addItem(item, e -> { /* view only */ });
|
||||
}
|
||||
|
||||
gui.open(player);
|
||||
}
|
||||
*/
|
||||
|
||||
@Override
|
||||
public void open(@NotNull Player player) { /* use open(player, mode, sort) */ }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,83 +1,77 @@
|
|||
package dev.loki.loparkour.menu.lobby;
|
||||
|
||||
import dev.loki.loparkour.config.Locales;
|
||||
import dev.loki.loparkour.menu.DynamicMenu;
|
||||
import dev.loki.loparkour.menu.LPMenu;
|
||||
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.loki.loparkour.util.ColorUtil;
|
||||
import dev.lolib.gui.InventoryGUI;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class LobbyMenu extends DynamicMenu {
|
||||
public class LobbyMenu extends LPMenu {
|
||||
|
||||
// TODO: Migrate to LoLib GUI system
|
||||
/*
|
||||
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);
|
||||
@Override
|
||||
public void open(@NotNull Player player) {
|
||||
ParkourUser user = ParkourUser.getUser(player);
|
||||
if (user == null) return;
|
||||
|
||||
return ParkourOption.PLAYER_MANAGEMENT.mayPerform(player) && user instanceof ParkourPlayer && user.session.getPlayers().get(0) == user;
|
||||
});
|
||||
String locale = user.locale;
|
||||
String title = Locales.getString(locale, "lobby.name");
|
||||
|
||||
registerMainItem(1, 1, (player, user) -> {
|
||||
if (user == null) {
|
||||
return null;
|
||||
}
|
||||
boolean isHost = !user.session.getPlayers().isEmpty()
|
||||
&& user.session.getPlayers().get(0) == user;
|
||||
|
||||
List<String> values = Locales.getStringList(user.locale, "lobby.visibility.values");
|
||||
InventoryGUI gui = baseGui(title, 3);
|
||||
|
||||
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 (ParkourOption.PLAYER_MANAGEMENT.mayPerform(player) && isHost) {
|
||||
gui = gui.setItem(11, localeItem(player, "lobby.player_management.item"),
|
||||
e -> Menus.PLAYER_MANAGEMENT.open(player));
|
||||
}
|
||||
|
||||
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;
|
||||
if (ParkourOption.VISIBILITY.mayPerform(player) && isHost) {
|
||||
gui = gui.setItem(13, visibilityItem(user), e -> {
|
||||
Session.Visibility next = switch (user.session.getVisibility()) {
|
||||
case PUBLIC -> Session.Visibility.ID_ONLY;
|
||||
case ID_ONLY -> Session.Visibility.PRIVATE;
|
||||
case PRIVATE -> Session.Visibility.PUBLIC;
|
||||
};
|
||||
user.session.setVisibility(next);
|
||||
open(player);
|
||||
});
|
||||
}, 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);
|
||||
gui.setItem(22, closeButton(player), e -> player.closeInventory())
|
||||
.open(player);
|
||||
}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Opens the main menu.
|
||||
*
|
||||
* @param player The player to open the menu to
|
||||
*/
|
||||
// TODO: Migrate to LoLib GUI system
|
||||
/*
|
||||
public void open(Player player) {
|
||||
display(player, new Menu(3, Locales.getString(player, "lobby.name"))
|
||||
.distributeRowsEvenly());
|
||||
private ItemStack visibilityItem(ParkourUser user) {
|
||||
String locale = user.locale;
|
||||
List<String> values = Locales.getStringList(locale, "lobby.visibility.values");
|
||||
String label = switch (user.session.getVisibility()) {
|
||||
case PUBLIC -> values.size() > 2 ? values.get(2) : "Public";
|
||||
case ID_ONLY -> values.size() > 1 ? values.get(1) : "ID Only";
|
||||
case PRIVATE -> !values.isEmpty() ? values.get(0) : "Private";
|
||||
};
|
||||
Material mat = switch (user.session.getVisibility()) {
|
||||
case PUBLIC -> Material.LIME_STAINED_GLASS_PANE;
|
||||
case ID_ONLY -> Material.YELLOW_STAINED_GLASS_PANE;
|
||||
case PRIVATE -> Material.RED_STAINED_GLASS_PANE;
|
||||
};
|
||||
ItemStack item = new ItemStack(mat);
|
||||
ItemMeta meta = item.getItemMeta();
|
||||
if (meta != null) {
|
||||
meta.setDisplayName(ColorUtil.color(
|
||||
Locales.getString(locale, "lobby.visibility") + " §7» §f" + label));
|
||||
item.setItemMeta(meta);
|
||||
}
|
||||
return item;
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,116 +1,78 @@
|
|||
package dev.loki.loparkour.menu.lobby;
|
||||
|
||||
import dev.loki.loparkour.config.Locales;
|
||||
import dev.loki.loparkour.menu.LPMenu;
|
||||
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.loki.loparkour.util.ColorUtil;
|
||||
import dev.lolib.gui.InventoryGUI;
|
||||
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.ItemMeta;
|
||||
import org.bukkit.inventory.meta.SkullMeta;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Menu for managing players
|
||||
*/
|
||||
public class PlayerManagementMenu {
|
||||
public class PlayerManagementMenu extends LPMenu {
|
||||
|
||||
// TODO: Migrate to LoLib GUI system
|
||||
/*
|
||||
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);
|
||||
}
|
||||
*/
|
||||
@Override
|
||||
public void open(@NotNull Player player) {
|
||||
ParkourPlayer viewer = ParkourPlayer.getPlayer(player);
|
||||
if (viewer == null) return;
|
||||
|
||||
// TODO: Migrate to LoLib GUI system
|
||||
/*
|
||||
private void add(PagedMenu menu, ParkourUser viewer, Collection<ParkourUser> users) {
|
||||
Session session = viewer.session;
|
||||
var session = viewer.session;
|
||||
String locale = viewer.locale;
|
||||
String title = Locales.getString(locale, "lobby.player_management.name");
|
||||
|
||||
for (ParkourUser other : users) {
|
||||
if (other == viewer) {
|
||||
continue;
|
||||
List<ParkourUser> others = new ArrayList<>();
|
||||
others.addAll(session.getPlayers().stream().filter(p -> p != viewer).toList());
|
||||
others.addAll(session.getSpectators());
|
||||
|
||||
InventoryGUI gui = baseGui(title, 3)
|
||||
.setItem(22, closeButton(player), e -> Menus.LOBBY.open(player));
|
||||
|
||||
for (int i = 0; i < Math.min(others.size(), 7); i++) {
|
||||
final int idx = i;
|
||||
final ParkourUser other = others.get(i);
|
||||
|
||||
ItemStack skull = new ItemStack(Material.PLAYER_HEAD);
|
||||
if (!ParkourUser.isBedrockPlayer(other.player)
|
||||
&& other.getName() != null && !other.getName().startsWith(".")) {
|
||||
SkullMeta meta = (SkullMeta) skull.getItemMeta();
|
||||
if (meta != null) { meta.setOwningPlayer(other.player); skull.setItemMeta(meta); }
|
||||
}
|
||||
|
||||
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("\\|\\|")));
|
||||
ItemMeta displayMeta = skull.getItemMeta();
|
||||
if (displayMeta != null) {
|
||||
displayMeta.setDisplayName(ColorUtil.color("<yellow>" + other.getName() + (muted ? " §c[muted]" : "")));
|
||||
displayMeta.setLore(List.of(
|
||||
ColorUtil.color("§7Left-click §fto kick"),
|
||||
ColorUtil.color("§7Right-click §f" + (muted ? "to unmute" : "to mute"))
|
||||
));
|
||||
skull.setItemMeta(displayMeta);
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
gui = gui.setItem(10 + idx, skull, e -> {
|
||||
if (e.getClick() == ClickType.LEFT) {
|
||||
Modes.DEFAULT.create(other.player);
|
||||
other.sendTranslated("lobby.player_management.kicked");
|
||||
viewer.sendTranslated("lobby.player_management.advice");
|
||||
} else if (e.getClick() == ClickType.RIGHT) {
|
||||
session.toggleMute(other);
|
||||
boolean nowMuted = session.isMuted(other);
|
||||
other.sendTranslated(nowMuted ? "lobby.player_management.muted" : "lobby.player_management.unmuted");
|
||||
}
|
||||
open(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));
|
||||
}
|
||||
|
||||
gui.open(player);
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,43 +1,25 @@
|
|||
package dev.loki.loparkour.menu.play;
|
||||
|
||||
import dev.loki.loparkour.config.Locales;
|
||||
import dev.loki.loparkour.menu.DynamicMenu;
|
||||
import dev.loki.loparkour.menu.LPMenu;
|
||||
import dev.loki.loparkour.menu.Menus;
|
||||
import dev.loki.loparkour.menu.ParkourOption;
|
||||
import org.bukkit.Bukkit;
|
||||
import dev.lolib.gui.InventoryGUI;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* The menu where players can join modes
|
||||
*/
|
||||
public class PlayMenu extends DynamicMenu {
|
||||
public class PlayMenu extends LPMenu {
|
||||
|
||||
// TODO: Migrate to LoLib GUI system
|
||||
/*
|
||||
public PlayMenu() {
|
||||
registerMainItem(1, 0, (player, user) -> Locales.getItem(player, "play.single.item")
|
||||
.click(event -> Menus.SINGLE.open(event.getPlayer())),
|
||||
ParkourOption.SINGLE::mayPerform);
|
||||
@Override
|
||||
public void open(@NotNull Player player) {
|
||||
String title = Locales.getString(locale(player), "play.name");
|
||||
|
||||
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);
|
||||
baseGui(title, 3)
|
||||
.setItem(11, localeItem(player, "play.single.item"),
|
||||
e -> { if (ParkourOption.SINGLE.mayPerform(player)) Menus.SINGLE.open(player); })
|
||||
.setItem(15, localeItem(player, "play.spectator.item"),
|
||||
e -> { if (ParkourOption.SPECTATOR.mayPerform(player)) Menus.SPECTATOR.open(player); })
|
||||
.setItem(22, closeButton(player), e -> player.closeInventory())
|
||||
.open(player);
|
||||
}
|
||||
*/
|
||||
|
||||
// TODO: Migrate to LoLib GUI system
|
||||
/*
|
||||
public void open(Player player) {
|
||||
display(player, new Menu(3, Locales.getString(player, "play.name"))
|
||||
.distributeRowsEvenly());
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,65 +3,66 @@ 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.menu.LPMenu;
|
||||
import dev.loki.loparkour.mode.Mode;
|
||||
import dev.loki.loparkour.mode.MultiMode;
|
||||
import dev.loki.loparkour.player.ParkourUser;
|
||||
import org.bukkit.Material;
|
||||
import dev.loki.loparkour.util.ColorUtil;
|
||||
import dev.lolib.gui.ScrollableGUI;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
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 class SingleMenu extends LPMenu {
|
||||
|
||||
// TODO: Migrate to LoLib GUI system
|
||||
/*
|
||||
public void open(Player player) {
|
||||
ParkourUser user = ParkourUser.getUser(player);
|
||||
String locale = user == null ? Option.OPTIONS_DEFAULTS.get(ParkourOption.LANG) : user.locale;
|
||||
@Override
|
||||
public void open(@NotNull Player player) {
|
||||
String locale = locale(player);
|
||||
|
||||
List<Mode> modes = Registry.getModes();
|
||||
List<Mode> availableModes = new ArrayList<>();
|
||||
List<ItemStack> items = new ArrayList<>();
|
||||
|
||||
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);
|
||||
}
|
||||
}));
|
||||
for (Mode mode : Registry.getModes()) {
|
||||
boolean blocked = Config.CONFIG.getBoolean("permissions.enabled")
|
||||
&& !player.hasPermission("LoParkour.gamemode." + mode.getName());
|
||||
if (blocked || mode instanceof MultiMode) continue;
|
||||
var item = mode.getItem(locale);
|
||||
if (item == null) continue;
|
||||
availableModes.add(mode);
|
||||
items.add(item.build());
|
||||
}
|
||||
|
||||
if (modeSet.size() == 1) {
|
||||
modeSet.get(0).create(player);
|
||||
if (availableModes.size() == 1) {
|
||||
availableModes.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);
|
||||
}
|
||||
*/
|
||||
|
||||
String title = Locales.getString(locale, "play.single.name");
|
||||
|
||||
ScrollableGUI gui = ScrollableGUI.create()
|
||||
.title(ColorUtil.color(title))
|
||||
.rows(3)
|
||||
.scrollUpButton(backItem(), 0)
|
||||
.scrollDownButton(nextItem(), 8);
|
||||
|
||||
for (int i = 0; i < items.size(); i++) {
|
||||
final Mode mode = availableModes.get(i);
|
||||
gui = gui.addItem(items.get(i), e -> {
|
||||
// Prevent multiple clicks - check if player is already in parkour
|
||||
if (ParkourUser.getUser(player) != null) {
|
||||
return; // Already in parkour, ignore click
|
||||
}
|
||||
|
||||
player.closeInventory();
|
||||
mode.create(player);
|
||||
});
|
||||
}
|
||||
|
||||
gui.open(player);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,83 +1,60 @@
|
|||
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.menu.LPMenu;
|
||||
import dev.loki.loparkour.mode.Modes;
|
||||
import dev.loki.loparkour.player.ParkourUser;
|
||||
import dev.loki.loparkour.session.Session;
|
||||
import dev.loki.loparkour.util.ColorUtil;
|
||||
import dev.loki.loparkour.world.Divider;
|
||||
import dev.lolib.gui.ScrollableGUI;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.SkullMeta;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* The menu to select other players to spectate
|
||||
*/
|
||||
public class SpectatorMenu {
|
||||
public class SpectatorMenu extends LPMenu {
|
||||
|
||||
// TODO: Migrate to LoLib GUI system
|
||||
/*
|
||||
public void open(Player player) {
|
||||
@Override
|
||||
public void open(@NotNull Player player) {
|
||||
String locale = locale(player);
|
||||
ParkourUser user = ParkourUser.getUser(player);
|
||||
String locale = user == null ? Option.OPTIONS_DEFAULTS.get(ParkourOption.LANG) : user.locale;
|
||||
String title = Locales.getString(locale, "play.spectator.name");
|
||||
|
||||
PagedMenu spectator = new PagedMenu(3, Locales.getString(player, "play.spectator.name"));
|
||||
|
||||
List<MenuItem> display = new ArrayList<>();
|
||||
List<Session> sessions = new ArrayList<>();
|
||||
List<ItemStack> items = 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;
|
||||
}
|
||||
if (!session.isAcceptingSpectators()) continue;
|
||||
if (user != null && session == user.session) continue;
|
||||
if (session.getPlayers().isEmpty()) 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);
|
||||
}
|
||||
}
|
||||
ItemStack skull = new ItemStack(Material.PLAYER_HEAD);
|
||||
if (!ParkourUser.isBedrockPlayer(player)
|
||||
&& pp.getName() != null && !pp.getName().startsWith(".")) {
|
||||
SkullMeta meta = (SkullMeta) skull.getItemMeta();
|
||||
if (meta != null) { meta.setOwningPlayer(pp.player); skull.setItemMeta(meta); }
|
||||
}
|
||||
|
||||
item.click(event -> Modes.SPECTATOR.create(player, session));
|
||||
|
||||
display.add(item);
|
||||
sessions.add(session);
|
||||
items.add(skull);
|
||||
}
|
||||
|
||||
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);
|
||||
ScrollableGUI gui = ScrollableGUI.create()
|
||||
.title(ColorUtil.color(title))
|
||||
.rows(3)
|
||||
.scrollUpButton(backItem(), 0)
|
||||
.scrollDownButton(nextItem(), 8);
|
||||
|
||||
for (int i = 0; i < items.size(); i++) {
|
||||
final Session session = sessions.get(i);
|
||||
gui = gui.addItem(items.get(i), e -> Modes.SPECTATOR.create(player, session));
|
||||
}
|
||||
|
||||
gui.open(player);
|
||||
}
|
||||
*/
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,42 +1,55 @@
|
|||
package dev.loki.loparkour.menu.settings;
|
||||
|
||||
import dev.loki.loparkour.config.Locales;
|
||||
import dev.loki.loparkour.menu.LPMenu;
|
||||
import dev.loki.loparkour.menu.Menus;
|
||||
import dev.loki.loparkour.player.ParkourPlayer;
|
||||
import dev.loki.loparkour.util.ColorUtil;
|
||||
import dev.lolib.gui.InventoryGUI;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class LangMenu {
|
||||
public class LangMenu extends LPMenu {
|
||||
|
||||
/**
|
||||
* Opens the language menu
|
||||
*
|
||||
* @param user The ParkourPlayer instance
|
||||
*/
|
||||
// TODO: Migrate to LoLib GUI system
|
||||
/*
|
||||
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);
|
||||
public void open(@NotNull ParkourPlayer user) {
|
||||
if (user == null) return;
|
||||
open(user.player);
|
||||
}
|
||||
*/
|
||||
|
||||
@Override
|
||||
public void open(@NotNull Player player) {
|
||||
ParkourPlayer pp = ParkourPlayer.getPlayer(player);
|
||||
if (pp == null) return;
|
||||
|
||||
String locale = pp.locale;
|
||||
String title = Locales.getString(locale, "settings.lang.name");
|
||||
|
||||
List<String> langs = new ArrayList<>(Locales.locales.keySet());
|
||||
|
||||
InventoryGUI gui = baseGui(title, 3);
|
||||
|
||||
for (int i = 0; i < Math.min(langs.size(), 7); i++) {
|
||||
final String lang = langs.get(i);
|
||||
String langName = Locales.getString(lang, "name");
|
||||
ItemStack item = new ItemStack(Material.PAPER);
|
||||
ItemMeta meta = item.getItemMeta();
|
||||
if (meta != null) {
|
||||
meta.setDisplayName(ColorUtil.color("𺑙&l" + langName));
|
||||
item.setItemMeta(meta);
|
||||
}
|
||||
gui = gui.setItem(10 + i, item, e -> {
|
||||
pp.locale = lang;
|
||||
pp._locale = lang;
|
||||
Menus.SETTINGS.open(player);
|
||||
});
|
||||
}
|
||||
|
||||
gui.open(player);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,351 +1,138 @@
|
|||
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.LPMenu;
|
||||
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 org.bukkit.ChatColor;
|
||||
import dev.loki.loparkour.util.ColorUtil;
|
||||
import dev.lolib.gui.InventoryGUI;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
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 class ParkourSettingsMenu extends LPMenu {
|
||||
|
||||
// TODO: Migrate to LoLib GUI system
|
||||
/*
|
||||
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);
|
||||
public void open(@NotNull ParkourPlayer user) {
|
||||
if (user == null) return;
|
||||
open(user.player);
|
||||
}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Shows the main menu to a valid ParkourPlayer instance
|
||||
*
|
||||
* @param user The ParkourPlayer
|
||||
*/
|
||||
// TODO: Migrate to LoLib GUI system
|
||||
/*
|
||||
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()))));
|
||||
@Override
|
||||
public void open(@NotNull Player player) {
|
||||
ParkourPlayer pp = ParkourPlayer.getPlayer(player);
|
||||
if (pp == null) return;
|
||||
|
||||
String locale = pp.locale;
|
||||
String title = Locales.getString(locale, "settings.name");
|
||||
|
||||
baseGui(title, 4)
|
||||
.setItem(10, toggleItem(locale, "particles", pp.particles), e -> {
|
||||
pp.particles = !pp.particles;
|
||||
pp.updateGeneratorSettings(pp.session.generator);
|
||||
open(pp);
|
||||
})
|
||||
.setItem(11, toggleItem(locale, "sound", pp.sound), e -> {
|
||||
pp.sound = !pp.sound;
|
||||
pp.updateGeneratorSettings(pp.session.generator);
|
||||
open(pp);
|
||||
})
|
||||
.setItem(12, toggleItem(locale, "special_blocks", pp.useSpecialBlocks), e -> {
|
||||
if (pp.session.generator.score == 0) {
|
||||
pp.useSpecialBlocks = !pp.useSpecialBlocks;
|
||||
pp.updateGeneratorSettings(pp.session.generator);
|
||||
}
|
||||
open(pp);
|
||||
})
|
||||
.setItem(13, toggleItem(locale, "fall_message", pp.showFallMessage), e -> {
|
||||
pp.showFallMessage = !pp.showFallMessage;
|
||||
pp.updateGeneratorSettings(pp.session.generator);
|
||||
open(pp);
|
||||
})
|
||||
.setItem(14, toggleItem(locale, "scoreboard", pp.showScoreboard), e -> {
|
||||
pp.showScoreboard = !pp.showScoreboard;
|
||||
pp.updateGeneratorSettings(pp.session.generator);
|
||||
open(pp);
|
||||
})
|
||||
.setItem(19, localeItem(player, "settings.parkour_settings.styles.item"),
|
||||
e -> openStyleMenu(pp))
|
||||
.setItem(20, schemDiffItem(pp), e -> {
|
||||
if (pp.session.generator.score == 0) {
|
||||
List<Double> diffs = List.of(0.0, 0.25, 0.5, 0.75, 1.0);
|
||||
int idx = diffs.indexOf(pp.schematicDifficulty);
|
||||
pp.schematicDifficulty = diffs.get((idx + 1) % diffs.size());
|
||||
pp.updateGeneratorSettings(pp.session.generator);
|
||||
}
|
||||
open(pp);
|
||||
})
|
||||
.setItem(31, closeButton(player), e -> Menus.SETTINGS.open(player))
|
||||
.open(player);
|
||||
}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Opens the style menu
|
||||
*
|
||||
* @param player The ParkourPlayer instance
|
||||
*/
|
||||
// TODO: Migrate to LoLib GUI system
|
||||
/*
|
||||
public void openStyleMenu(ParkourPlayer player) {
|
||||
// init menu
|
||||
PagedMenu menu = new PagedMenu(3, Locales.getString(player.locale, ParkourOption.STYLES.path + ".name"));
|
||||
private ItemStack toggleItem(String locale, String key, boolean value) {
|
||||
Material mat = value ? Material.LIME_STAINED_GLASS_PANE : Material.RED_STAINED_GLASS_PANE;
|
||||
String symbol = Locales.getString(locale, "settings.parkour_settings." + (value ? "enabled" : "disabled"));
|
||||
String label = Locales.getString(locale, "settings.parkour_settings." + key + ".item");
|
||||
ItemStack item = new ItemStack(mat);
|
||||
ItemMeta meta = item.getItemMeta();
|
||||
if (meta != null) {
|
||||
meta.setDisplayName(ColorUtil.color(label + " §7[" + symbol + "§7]"));
|
||||
item.setItemMeta(meta);
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
List<MenuItem> items = new ArrayList<>();
|
||||
private ItemStack schemDiffItem(ParkourPlayer pp) {
|
||||
List<Double> diffs = List.of(0.0, 0.25, 0.5, 0.75, 1.0);
|
||||
List<String> values = Locales.getStringList(pp.locale, "settings.parkour_settings.schematics.values");
|
||||
int idx = Math.max(0, diffs.indexOf(pp.schematicDifficulty));
|
||||
String label = idx < values.size() ? values.get(idx) : String.valueOf(pp.schematicDifficulty);
|
||||
Material[] mats = {Material.RED_STAINED_GLASS_PANE, Material.LIME_STAINED_GLASS_PANE,
|
||||
Material.YELLOW_STAINED_GLASS_PANE, Material.ORANGE_STAINED_GLASS_PANE, Material.SKELETON_SKULL};
|
||||
ItemStack item = new ItemStack(idx < mats.length ? mats[idx] : Material.PAPER);
|
||||
ItemMeta meta = item.getItemMeta();
|
||||
if (meta != null) {
|
||||
meta.setDisplayName(ColorUtil.color("§eSchematics §7» §f" + label));
|
||||
item.setItemMeta(meta);
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
private void openStyleMenu(ParkourPlayer pp) {
|
||||
Player player = pp.player;
|
||||
String locale = pp.locale;
|
||||
String title = Locales.getString(locale, "settings.parkour_settings.styles.name");
|
||||
|
||||
List<Style> styles = new ArrayList<>();
|
||||
InventoryGUI gui = baseGui(title, 3);
|
||||
|
||||
int slot = 10;
|
||||
for (Style style : Registry.getStyles()) {
|
||||
var name = style.getName();
|
||||
if (slot > 16) break;
|
||||
String name = style.getName();
|
||||
String perm = ParkourOption.STYLES.permission + "." + name.toLowerCase().replace(" ", ".");
|
||||
if (Config.CONFIG.getBoolean("permissions.per-style") && !player.hasPermission(perm)) continue;
|
||||
|
||||
var perm = "%s.%s".formatted(ParkourOption.STYLES.permission, name.toLowerCase());
|
||||
if (Config.CONFIG.getBoolean("permissions.per-style") && !player.player.hasPermission(perm.replace(" ", "."))) {
|
||||
continue;
|
||||
}
|
||||
ItemStack item = new ItemStack(style.getNext());
|
||||
ItemMeta meta = item.getItemMeta();
|
||||
if (meta != null) { meta.setDisplayName(ColorUtil.color("&#A020F0" + name)); item.setItemMeta(meta); }
|
||||
|
||||
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);
|
||||
}));
|
||||
final Style s = style;
|
||||
gui = gui.setItem(slot++, item, e -> {
|
||||
pp.style = s.getName();
|
||||
pp.updateGeneratorSettings(pp.session.generator);
|
||||
open(pp);
|
||||
});
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
*/
|
||||
|
||||
// TODO: Migrate to LoLib GUI system
|
||||
/*
|
||||
private boolean handleSettingChange(ParkourPlayer player, Runnable onAllowed) {
|
||||
onAllowed.run();
|
||||
player.updateGeneratorSettings(player.session.generator);
|
||||
return true;
|
||||
}
|
||||
*/
|
||||
|
||||
// TODO: Migrate to LoLib GUI system
|
||||
/*
|
||||
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);
|
||||
gui.open(player);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,79 +1,40 @@
|
|||
package dev.loki.loparkour.menu.settings;
|
||||
|
||||
import dev.loki.loparkour.config.Locales;
|
||||
import dev.loki.loparkour.menu.DynamicMenu;
|
||||
import dev.loki.loparkour.menu.LPMenu;
|
||||
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.lolib.gui.InventoryGUI;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
public class SettingsMenu extends LPMenu {
|
||||
|
||||
/**
|
||||
* The menu for settings
|
||||
*/
|
||||
public class SettingsMenu extends DynamicMenu {
|
||||
@Override
|
||||
public void open(@NotNull Player player) {
|
||||
String locale = locale(player);
|
||||
String title = Locales.getString(locale, "settings.name");
|
||||
|
||||
// TODO: Migrate to LoLib GUI system
|
||||
/*
|
||||
public SettingsMenu() {
|
||||
registerMainItem(1, 0, (player, user) -> Locales.getItem(player, "settings.parkour_settings.item").click(event -> {
|
||||
ParkourPlayer pp = ParkourPlayer.getPlayer(event.getPlayer());
|
||||
InventoryGUI gui = baseGui(title, 3);
|
||||
|
||||
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;
|
||||
if (ParkourOption.PARKOUR_SETTINGS.mayPerform(player) && ParkourPlayer.isPlayer(player)) {
|
||||
gui = gui.setItem(11, localeItem(player, "settings.parkour_settings.item"), e -> {
|
||||
ParkourPlayer pp = ParkourPlayer.getPlayer(player);
|
||||
if (pp != null) pp.session.generator.menu(pp);
|
||||
});
|
||||
}, 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);
|
||||
}
|
||||
*/
|
||||
if (ParkourOption.LANG.mayPerform(player) && ParkourUser.isUser(player)) {
|
||||
String langName = Locales.getString(locale, "name");
|
||||
gui = gui.setItem(13, localeItem(locale, "settings.lang.item", langName), e -> {
|
||||
ParkourPlayer pp = ParkourPlayer.getPlayer(player);
|
||||
if (pp != null) Menus.LANG.open(pp);
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: Migrate to LoLib GUI system
|
||||
/*
|
||||
public void open(Player player) {
|
||||
display(player, new Menu(3, Locales.getString(player, "settings.name")).distributeRowsEvenly());
|
||||
gui.setItem(22, closeButton(player), e -> player.closeInventory())
|
||||
.open(player);
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,9 +37,7 @@ public class SpectatorMode implements Mode {
|
|||
|
||||
@Override
|
||||
public void create(Player player) {
|
||||
// TODO: Migrate to LoLib GUI
|
||||
// Menus.SPECTATOR.open(player);
|
||||
player.sendMessage("§cМеню временно недоступно во время миграции");
|
||||
Menus.SPECTATOR.open(player);
|
||||
}
|
||||
|
||||
public void create(Player player, Session session) {
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@ public class ParkourPlayer extends ParkourUser {
|
|||
*/
|
||||
public static @Nullable ParkourPlayer getPlayer(@NotNull Player player) {
|
||||
return getPlayers().stream()
|
||||
.filter(other -> other.getUUID() == player.getUniqueId())
|
||||
.filter(other -> other.getUUID().equals(player.getUniqueId()))
|
||||
.findAny()
|
||||
.orElse(null);
|
||||
}
|
||||
|
|
@ -130,8 +130,6 @@ public class ParkourPlayer extends ParkourUser {
|
|||
|
||||
@Override
|
||||
public void unregister() {
|
||||
LoParkour.log("Unregistering player %s".formatted(player.getName()));
|
||||
|
||||
if (session.generator != null &&
|
||||
session.generator.getMode() != null &&
|
||||
session.generator.getMode() instanceof MultiMode mode) {
|
||||
|
|
@ -199,19 +197,14 @@ public class ParkourPlayer extends ParkourUser {
|
|||
}
|
||||
|
||||
public void setup(Location to) {
|
||||
LoParkour.log("Setting up player %s".formatted(player.getName()));
|
||||
|
||||
if (to != null) {
|
||||
teleport(to);
|
||||
}
|
||||
|
||||
player.setGameMode(GameMode.ADVENTURE);
|
||||
|
||||
// -= Inventory =-
|
||||
if (Config.CONFIG.getBoolean("options.inventory-handling")) {
|
||||
Scheduler.get(LoParkour.getPlugin()).runLater(() -> {
|
||||
LoParkour.log("Setting up inventory for player %s".formatted(player.getName()));
|
||||
|
||||
player.getInventory().clear();
|
||||
|
||||
List<dev.loki.loparkour.util.Item> items = new ArrayList<>();
|
||||
|
|
|
|||
|
|
@ -19,9 +19,6 @@ import dev.loki.loparkour.player.data.PreviousData;
|
|||
import dev.loki.loparkour.session.Session;
|
||||
import dev.loki.loparkour.storage.Storage;
|
||||
import dev.loki.loparkour.world.Divider;
|
||||
import org.bukkit.scoreboard.Scoreboard;
|
||||
import org.bukkit.scoreboard.Objective;
|
||||
import org.bukkit.scoreboard.DisplaySlot;
|
||||
import dev.loki.loparkour.util.ColorUtil;
|
||||
import io.papermc.lib.PaperLib;
|
||||
import me.clip.placeholderapi.PlaceholderAPI;
|
||||
|
|
@ -29,6 +26,10 @@ import org.bukkit.Bukkit;
|
|||
import org.bukkit.Location;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.messaging.ChannelNotRegisteredException;
|
||||
import org.bukkit.scoreboard.DisplaySlot;
|
||||
import org.bukkit.scoreboard.Objective;
|
||||
import org.bukkit.scoreboard.Scoreboard;
|
||||
import org.bukkit.scoreboard.Team;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
|
|
@ -46,28 +47,17 @@ import java.util.stream.Collectors;
|
|||
*/
|
||||
public abstract class ParkourUser {
|
||||
|
||||
/**
|
||||
* Registers a player. This registers the player internally.
|
||||
* This automatically unregisters the player if it is already registered.
|
||||
*
|
||||
* @param player The player
|
||||
* @return the ParkourPlayer instance of the newly joined player
|
||||
*/
|
||||
public static @NotNull ParkourPlayer register(@NotNull Player player, @NotNull Session session) {
|
||||
PreviousData data = null;
|
||||
ParkourUser existing = getUser(player);
|
||||
|
||||
if (existing != null) {
|
||||
LoParkour.log("Registering player %s with existing data".formatted(player.getName()));
|
||||
|
||||
data = existing.previousData;
|
||||
unregister(existing, false, false, false);
|
||||
} else {
|
||||
LoParkour.log("Registering player %s".formatted(player.getName()));
|
||||
}
|
||||
ParkourPlayer pp = new ParkourPlayer(player, session, data);
|
||||
|
||||
// stats
|
||||
joinCount++;
|
||||
new ParkourJoinEvent(pp).call();
|
||||
|
||||
|
|
@ -75,48 +65,28 @@ public abstract class ParkourUser {
|
|||
return pp;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the same as {@link #leave(ParkourUser)}, but instead for a Bukkit player instance.
|
||||
*
|
||||
* @param player The Bukkit player instance that will be removed from the game if the player is active.
|
||||
* @see #leave(ParkourUser)
|
||||
*/
|
||||
public static void leave(@NotNull Player player) {
|
||||
ParkourUser user = getUser(player);
|
||||
if (user == null) {
|
||||
return;
|
||||
}
|
||||
if (user == null) return;
|
||||
leave(user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Forces user to leave. Follows behaviour of /parkour leave.
|
||||
*
|
||||
* @param user The user.
|
||||
*/
|
||||
public static void leave(@NotNull ParkourUser user) {
|
||||
unregister(user, true, true, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters a Parkour user instance.
|
||||
*
|
||||
* @param user The user to unregister.
|
||||
* @param restorePreviousData Whether to restore the data from before the player joined the parkour.
|
||||
* @param kickIfBungee Whether to kick the player if Bungeecord mode is enabled.
|
||||
*/
|
||||
public static void unregister(@NotNull ParkourUser user, boolean restorePreviousData, boolean kickIfBungee, boolean urgent) {
|
||||
new ParkourLeaveEvent(user).call();
|
||||
LoParkour.log("Unregistering player %s, restorePreviousData = %s, kickIfBungee = %s".formatted(user.getName(), restorePreviousData, kickIfBungee));
|
||||
|
||||
try {
|
||||
user.unregister();
|
||||
|
||||
// TODO: Implement scoreboard deletion with LoLib
|
||||
// if (user.board != null) {
|
||||
// user.board.delete();
|
||||
// }
|
||||
} catch (Exception ex) { // safeguard to prevent people from losing data
|
||||
// Reset scoreboard to main
|
||||
if (user.board != null) {
|
||||
user.player.setScoreboard(Bukkit.getScoreboardManager().getMainScoreboard());
|
||||
user.board = null;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
LoParkour.getPlugin().getLogger().log(java.util.logging.Level.SEVERE,
|
||||
"Error while trying to make player " + user.getName() + " leave", ex);
|
||||
user.send("<red><bold>There was an error while trying to handle leaving.");
|
||||
|
|
@ -143,7 +113,6 @@ public abstract class ParkourUser {
|
|||
}
|
||||
}
|
||||
|
||||
// Sends a player to a BungeeCord server. server is the server name.
|
||||
private static void sendPlayerToServer(Player player, String server) {
|
||||
ByteArrayDataOutput out = ByteStreams.newDataOutput();
|
||||
out.writeUTF("Connect");
|
||||
|
|
@ -152,79 +121,38 @@ public abstract class ParkourUser {
|
|||
try {
|
||||
player.sendPluginMessage(LoParkour.getPlugin(), "BungeeCord", out.toByteArray());
|
||||
} catch (ChannelNotRegisteredException ex) {
|
||||
LoParkour.getPlugin().getLogger().severe("Error while trying to send %s to server %s. This server is not registered.".formatted(player.getName(), server) + " - " + ex.getMessage());
|
||||
LoParkour.getPlugin().getLogger().severe(
|
||||
"Error while trying to send %s to server %s. - %s".formatted(player.getName(), server, ex.getMessage()));
|
||||
player.kickPlayer("Couldn't move you to %s. Please rejoin.".formatted(server));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param player The player.
|
||||
* @return True when this player is a {@link ParkourUser}, false if not.
|
||||
*/
|
||||
public static boolean isUser(@Nullable Player player) {
|
||||
return player != null && getUsers().stream().anyMatch(other -> other.player == player);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param player The player.
|
||||
* @return player as a {@link ParkourUser}, null if not found.
|
||||
*/
|
||||
public static @Nullable ParkourUser getUser(@NotNull Player player) {
|
||||
return getUsers().stream()
|
||||
.filter(other -> other.getUUID() == player.getUniqueId())
|
||||
.filter(other -> other.getUUID().equals(player.getUniqueId()))
|
||||
.findAny()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Set with all users.
|
||||
*/
|
||||
public static Set<ParkourUser> getUsers() {
|
||||
return Divider.sections.keySet().stream()
|
||||
.flatMap(session -> session.getUsers().stream())
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
/**
|
||||
* This user's locale
|
||||
*/
|
||||
@NotNull
|
||||
public String locale = Option.OPTIONS_DEFAULTS.get(ParkourOption.LANG);
|
||||
// ─── Fields ───────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* This user's scoreboard
|
||||
*/
|
||||
public Scoreboard board;
|
||||
|
||||
/**
|
||||
* This user's PreviousData
|
||||
*/
|
||||
@NotNull
|
||||
public PreviousData previousData;
|
||||
|
||||
/**
|
||||
* The selected {@link Session.ChatType}
|
||||
*/
|
||||
@NotNull public String locale = Option.OPTIONS_DEFAULTS.get(ParkourOption.LANG);
|
||||
@Nullable public Scoreboard board;
|
||||
@NotNull public PreviousData previousData;
|
||||
public Session.ChatType chatType = Session.ChatType.PUBLIC;
|
||||
|
||||
/**
|
||||
* The {@link Session} this user is in.
|
||||
*/
|
||||
public final Session session;
|
||||
|
||||
/**
|
||||
* The Bukkit player instance associated with this user.
|
||||
*/
|
||||
public final Player player;
|
||||
|
||||
/**
|
||||
* The {@link Instant} when the player joined.
|
||||
*/
|
||||
public final Instant joined;
|
||||
|
||||
/**
|
||||
* The amount of players that have joined while the plugin has been enabled.
|
||||
*/
|
||||
public static int joinCount;
|
||||
|
||||
public ParkourUser(@NotNull Player player, @NotNull Session session, @Nullable PreviousData previousData) {
|
||||
|
|
@ -235,62 +163,72 @@ public abstract class ParkourUser {
|
|||
|
||||
if (Boolean.parseBoolean(Option.OPTIONS_DEFAULTS.get(ParkourOption.SCOREBOARD))) {
|
||||
this.board = Bukkit.getScoreboardManager().getNewScoreboard();
|
||||
// Create the sidebar objective immediately and assign to player
|
||||
Objective obj = this.board.registerNewObjective("lp_sidebar", "dummy",
|
||||
ColorUtil.color(Locales.getString(locale, "scoreboard.title")));
|
||||
obj.setDisplaySlot(DisplaySlot.SIDEBAR);
|
||||
player.setScoreboard(this.board);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters this user.
|
||||
*/
|
||||
protected abstract void unregister();
|
||||
|
||||
/**
|
||||
* Teleports the player asynchronously.
|
||||
*
|
||||
* @param to Where the player will be teleported to
|
||||
*/
|
||||
public void teleport(@NotNull Location to) {
|
||||
PaperLib.teleportAsync(player, to);
|
||||
player.teleport(to);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a message.
|
||||
*
|
||||
* @param message The message
|
||||
*/
|
||||
public void send(String message) {
|
||||
player.sendMessage(ColorUtil.color(message));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a translated message
|
||||
*
|
||||
* @param key The translation key
|
||||
* @param format Any objects that may be given to the formatting of the string.
|
||||
*/
|
||||
public void sendTranslated(String key, Object... format) {
|
||||
send(Locales.getString(locale, key).formatted(format));
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the scoreboard for the specified generator.
|
||||
*
|
||||
* @param generator The generator.
|
||||
* Updates the sidebar scoreboard for the given generator state.
|
||||
* Uses vanilla Bukkit scoreboard API — Team prefix trick for coloured lines.
|
||||
*/
|
||||
public void updateScoreboard(ParkourGenerator generator) {
|
||||
// board can be null a few ticks after on player leave
|
||||
if (board == null || false || !generator.profile.get("showScoreboard").asBoolean()) {
|
||||
if (board == null || !generator.profile.get("showScoreboard").asBoolean()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Leaderboard leaderboard = generator.getMode().getLeaderboard();
|
||||
Score top = leaderboard == null ? new Score("?", "?", "?", 0) : leaderboard.getScoreAtRank(1);
|
||||
Score high = leaderboard == null ? new Score("?", "?", "?", 0) : leaderboard.get(getUUID());
|
||||
if (top == null) {
|
||||
top = new Score("?", "?", "?", 0);
|
||||
if (top == null) top = new Score("?", "?", "?", 0);
|
||||
|
||||
// Update title
|
||||
Objective obj = board.getObjective("lp_sidebar");
|
||||
if (obj == null) {
|
||||
obj = board.registerNewObjective("lp_sidebar", "dummy",
|
||||
ColorUtil.color(replace(Locales.getString(locale, "scoreboard.title"), top, high, generator)));
|
||||
obj.setDisplaySlot(DisplaySlot.SIDEBAR);
|
||||
} else {
|
||||
obj.setDisplayName(ColorUtil.color(
|
||||
replace(Locales.getString(locale, "scoreboard.title"), top, high, generator)));
|
||||
}
|
||||
|
||||
// board.updateTitle(replace(Locales.getString(locale, "scoreboard.title"), top, high, generator));
|
||||
// board.updateLines(replace(Locales.getStringList(locale, "scoreboard.lines"), top, high, generator));
|
||||
// Update lines using Team prefix trick (each line = unique entry).
|
||||
// We need globally-unique entries: use a combination of colour codes that gives
|
||||
// up to 32 unique strings (§0§0, §0§1, … §1§0 …). This safely covers any
|
||||
// reasonable scoreboard length.
|
||||
List<String> lines = replace(Locales.getStringList(locale, "scoreboard.lines"), top, high, generator);
|
||||
String[] codes = {"0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f"};
|
||||
for (int i = 0; i < lines.size(); i++) {
|
||||
String entry = "§" + codes[i / codes.length % codes.length] + "§" + codes[i % codes.length];
|
||||
String teamName = "lp_line_" + i;
|
||||
|
||||
Team team = board.getTeam(teamName);
|
||||
if (team == null) {
|
||||
team = board.registerNewTeam(teamName);
|
||||
team.addEntry(entry);
|
||||
}
|
||||
|
||||
team.setPrefix(ColorUtil.color(lines.get(i)));
|
||||
obj.getScore(entry).setScore(lines.size() - i);
|
||||
}
|
||||
}
|
||||
|
||||
private List<String> replace(List<String> s, Score top, Score high, ParkourGenerator generator) {
|
||||
|
|
@ -302,45 +240,21 @@ public abstract class ParkourUser {
|
|||
.replace("%score%", Integer.toString(generator.score))
|
||||
.replace("%time%", generator.getFormattedTime())
|
||||
.replace("%difficulty%", Double.toString(generator.getDifficultyScore()))
|
||||
|
||||
.replace("%top_score%", Integer.toString(top.score()))
|
||||
.replace("%top_player%", top.name())
|
||||
.replace("%top_time%", top.time())
|
||||
|
||||
.replace("%high_score%", Integer.toString(high.score()))
|
||||
.replace("%high_score_time%", high.time()));
|
||||
}
|
||||
|
||||
// translate papi
|
||||
private String translate(Player player, String string) {
|
||||
return LoParkour.getPlaceholderHook() == null ? string : PlaceholderAPI.setPlaceholders(player, string);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The player's uuid
|
||||
*/
|
||||
public UUID getUUID() {
|
||||
return player.getUniqueId();
|
||||
}
|
||||
public UUID getUUID() { return player.getUniqueId(); }
|
||||
public Location getLocation() { return player.getLocation(); }
|
||||
public String getName() { return player.getName(); }
|
||||
|
||||
/**
|
||||
* @return The player's location
|
||||
*/
|
||||
public Location getLocation() {
|
||||
return player.getLocation();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The player's name
|
||||
*/
|
||||
public String getName() {
|
||||
return player.getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param player The player
|
||||
* @return true if the player is a Bedrock player, false if not.
|
||||
*/
|
||||
public static boolean isBedrockPlayer(Player player) {
|
||||
return Bukkit.getPluginManager().isPluginEnabled("floodgate") && FloodgateHook.isBedrockPlayer(player);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,30 +31,14 @@ public class Schematics {
|
|||
FOLDER.mkdirs();
|
||||
}
|
||||
|
||||
File[] files = FOLDER.listFiles((dir, name) -> name.contains("parkour-") || name.contains("spawn-island"));
|
||||
File[] files = FOLDER.listFiles((dir, name) -> name.endsWith(".lpschem"));
|
||||
|
||||
if (files == null || files.length == 0) {
|
||||
download();
|
||||
init();
|
||||
LoParkour.log("No schematics found in " + FOLDER.getAbsolutePath());
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// TODO: Migrate to LoLib schematics
|
||||
// dev.efnilite.vilib.schematic.Schematics.addFromFiles(LoParkour.getPlugin(), files);
|
||||
} catch (Exception ex) {
|
||||
LoParkour.getPlugin().getLogger().log(java.util.logging.Level.SEVERE,
|
||||
"Error while trying to load schematics", ex);
|
||||
}
|
||||
LoParkour.log("Found " + files.length + " schematic files");
|
||||
});
|
||||
}
|
||||
|
||||
private static void download() {
|
||||
LoParkour.log("Downloading schematics");
|
||||
|
||||
List<String> schematics = new ArrayList<>();
|
||||
schematics.addAll(Arrays.asList(SPAWN_SCHEMATICS));
|
||||
schematics.addAll(Config.SCHEMATICS.getChildren("difficulty", false).stream().map("parkour-%s"::formatted).toList());
|
||||
schematics.forEach(file -> LoParkour.getPlugin().saveResource("schematics/%s".formatted(file), true));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -168,6 +168,16 @@ public class LPSchematic {
|
|||
this.logic = logic;
|
||||
}
|
||||
|
||||
/** Raw block palette-index array. Do not modify directly. */
|
||||
public int[] getBlockArray() {
|
||||
return blocks;
|
||||
}
|
||||
|
||||
/** The palette of block data strings. Do not modify directly. */
|
||||
public List<String> getPalette() {
|
||||
return palette;
|
||||
}
|
||||
|
||||
private static class SchematicData {
|
||||
int format_version;
|
||||
SchematicMetadata metadata;
|
||||
|
|
|
|||
|
|
@ -1,89 +0,0 @@
|
|||
package dev.loki.loparkour.schematic.lpschem;
|
||||
|
||||
import dev.loki.loparkour.LoParkour;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
public class SchematicConverter {
|
||||
|
||||
public static LPSchematic fromVilib(@NotNull LPSchematic oldSchematic, @NotNull String name, @NotNull String author) {
|
||||
var dimensions = oldSchematic.getDimensions();
|
||||
// TODO: Fix LPSchematic API
|
||||
int width = 10; // dimensions.getWidth();
|
||||
int height = 10; // dimensions.getHeight();
|
||||
int length = 10; // dimensions.getLength();
|
||||
|
||||
LPSchematicBuilder builder = new LPSchematicBuilder(name, author, 0.5, width, height, length);
|
||||
|
||||
// TODO: Fix LPSchematic API - vilib code commented out
|
||||
// var blocks = oldSchematic.getBlocks();
|
||||
// for (Map.Entry<Location, BlockData> entry : blocks.entrySet()) {
|
||||
// var pos = entry.getKey();
|
||||
// BlockData data = entry.getValue();
|
||||
//
|
||||
// if (data != null && data.getMaterial() != Material.AIR) {
|
||||
// int x = (int) pos.getX();
|
||||
// int y = (int) pos.getY();
|
||||
// int z = (int) pos.getZ();
|
||||
//
|
||||
// builder.setBlock(x, y, z, data);
|
||||
//
|
||||
// if (data.getMaterial() == Material.LIME_WOOL) {
|
||||
// builder.setStart(x, y, z);
|
||||
// } else if (data.getMaterial() == Material.RED_WOOL) {
|
||||
// builder.setEnd(x, y, z);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
if (!hasMarkers(builder)) {
|
||||
builder.setStart(0, 0, 0);
|
||||
builder.setEnd(width - 1, 0, length - 1);
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private static boolean hasMarkers(LPSchematicBuilder builder) {
|
||||
try {
|
||||
builder.build();
|
||||
return true;
|
||||
} catch (IllegalStateException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static void convertAll() {
|
||||
LoParkour.log("Starting conversion of old schematics to .lpschem format");
|
||||
int converted = 0;
|
||||
int failed = 0;
|
||||
|
||||
// TODO: Fix vilib schematic conversion - vilib code commented out
|
||||
// try {
|
||||
// for (String name : dev.efnilite.vilib.schematic.dev.loki.loparkour.schematic.Schematics.getSchematicNames(LoParkour.getPlugin())) {
|
||||
// try {
|
||||
// LPSchematic oldSchematic = dev.efnilite.vilib.schematic.dev.loki.loparkour.schematic.Schematics.getSchematic(LoParkour.getPlugin(), name);
|
||||
// if (oldSchematic != null) {
|
||||
// LPSchematic newSchematic = fromVilib(oldSchematic, name, "Converted");
|
||||
// LoParkour.getSchematicManager().saveSchematic(newSchematic);
|
||||
// converted++;
|
||||
// LoParkour.log("Converted: " + name);
|
||||
// }
|
||||
// } catch (Exception e) {
|
||||
// failed++;
|
||||
// LoParkour.getPlugin().getLogger().severe("Failed to convert schematic: " + name);
|
||||
// e.printStackTrace();
|
||||
// }
|
||||
// }
|
||||
// } catch (Exception e) {
|
||||
// LoParkour.getPlugin().getLogger().severe("Error during schematic conversion");
|
||||
// e.printStackTrace();
|
||||
// }
|
||||
|
||||
LoParkour.log("Conversion complete: " + converted + " converted, " + failed + " failed");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,145 @@
|
|||
package dev.loki.loparkour.schematic.lpschem;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.block.data.Directional;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Rotates a {@link LPSchematic} by 90/180/270 degrees around the Y axis.
|
||||
* Preserves block facing where possible via {@link Directional} BlockData.
|
||||
*/
|
||||
public class SchematicTransformer {
|
||||
|
||||
private final LPSchematic source;
|
||||
|
||||
public SchematicTransformer(@NotNull LPSchematic source) {
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new rotated schematic. The original is not modified.
|
||||
*
|
||||
* @param degrees 90, 180, or 270 (clockwise from above).
|
||||
*/
|
||||
@NotNull
|
||||
public LPSchematic rotate(int degrees) {
|
||||
degrees = ((degrees % 360) + 360) % 360;
|
||||
if (degrees == 0) return source;
|
||||
|
||||
SchematicDimensions dim = source.getDimensions();
|
||||
int origW = dim.width;
|
||||
int origH = dim.height;
|
||||
int origL = dim.length;
|
||||
|
||||
// New dimensions after rotation
|
||||
int newW = (degrees == 180) ? origW : origL;
|
||||
int newL = (degrees == 180) ? origL : origW;
|
||||
|
||||
int[] origBlocks = getBlocks();
|
||||
List<String> palette = getPalette();
|
||||
|
||||
int[] rotated = new int[origW * origH * origL];
|
||||
|
||||
for (int y = 0; y < origH; y++) {
|
||||
for (int z = 0; z < origL; z++) {
|
||||
for (int x = 0; x < origW; x++) {
|
||||
int origIdx = x + (z * origW) + (y * origW * origL);
|
||||
int paletteIdx = origBlocks[origIdx];
|
||||
|
||||
int nx, nz;
|
||||
switch (degrees) {
|
||||
case 90 -> { nx = origL - 1 - z; nz = x; }
|
||||
case 180 -> { nx = origW - 1 - x; nz = origL - 1 - z; }
|
||||
case 270 -> { nx = z; nz = origW - 1 - x; }
|
||||
default -> { nx = x; nz = z; }
|
||||
}
|
||||
|
||||
int newIdx = nx + (nz * newW) + (y * newW * newL);
|
||||
rotated[newIdx] = paletteIdx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Rotate palette entries (block facing)
|
||||
List<String> rotatedPalette = rotatePalette(palette, degrees);
|
||||
|
||||
SchematicDimensions newDim = new SchematicDimensions(newW, origH, newL);
|
||||
|
||||
// Rotate markers
|
||||
SchematicMarkers origMarkers = source.getMarkers();
|
||||
SchematicMarkers newMarkers = rotateMarkers(origMarkers, origW, origL, newW, newL, degrees);
|
||||
|
||||
return new LPSchematic(source.getMetadata(), newDim, rotatedPalette, rotated, newMarkers);
|
||||
}
|
||||
|
||||
// ── Internals ────────────────────────────────────────────────────────────
|
||||
|
||||
private int[] getBlocks() {
|
||||
return source.getBlockArray();
|
||||
}
|
||||
|
||||
private List<String> getPalette() {
|
||||
return new ArrayList<>(source.getPalette());
|
||||
}
|
||||
|
||||
private List<String> rotatePalette(List<String> palette, int degrees) {
|
||||
List<String> result = new ArrayList<>(palette.size());
|
||||
for (String blockStr : palette) {
|
||||
result.add(rotateBlockData(blockStr, degrees));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private String rotateBlockData(String blockDataStr, int degrees) {
|
||||
try {
|
||||
BlockData bd = Bukkit.createBlockData(blockDataStr);
|
||||
if (bd instanceof Directional dir) {
|
||||
for (int i = 0; i < degrees / 90; i++) {
|
||||
dir.setFacing(rotateDirection(dir.getFacing()));
|
||||
}
|
||||
return dir.getAsString();
|
||||
}
|
||||
return blockDataStr;
|
||||
} catch (Exception e) {
|
||||
return blockDataStr;
|
||||
}
|
||||
}
|
||||
|
||||
private org.bukkit.block.BlockFace rotateDirection(org.bukkit.block.BlockFace face) {
|
||||
return switch (face) {
|
||||
case NORTH -> org.bukkit.block.BlockFace.EAST;
|
||||
case EAST -> org.bukkit.block.BlockFace.SOUTH;
|
||||
case SOUTH -> org.bukkit.block.BlockFace.WEST;
|
||||
case WEST -> org.bukkit.block.BlockFace.NORTH;
|
||||
default -> face;
|
||||
};
|
||||
}
|
||||
|
||||
private SchematicMarkers rotateMarkers(SchematicMarkers m,
|
||||
int origW, int origL,
|
||||
int newW, int newL,
|
||||
int degrees) {
|
||||
var start = rotateVec(m.getStart(), origW, origL, degrees);
|
||||
var end = rotateVec(m.getEnd(), origW, origL, degrees);
|
||||
|
||||
SchematicMarkers result = new SchematicMarkers(start, end);
|
||||
for (var cp : m.getCheckpoints()) {
|
||||
result.addCheckpoint(rotateVec(cp, origW, origL, degrees));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private SchematicMarkers.Vector3i rotateVec(SchematicMarkers.Vector3i v,
|
||||
int origW, int origL, int degrees) {
|
||||
return switch (degrees) {
|
||||
case 90 -> new SchematicMarkers.Vector3i(origL - 1 - v.z, v.y, v.x);
|
||||
case 180 -> new SchematicMarkers.Vector3i(origW - 1 - v.x, v.y, origL - 1 - v.z);
|
||||
case 270 -> new SchematicMarkers.Vector3i(v.z, v.y, origW - 1 - v.x);
|
||||
default -> new SchematicMarkers.Vector3i(v.x, v.y, v.z);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -74,12 +74,6 @@ public class Session {
|
|||
Function<Session, Boolean> isAcceptingPlayers,
|
||||
Function<Session, Boolean> isAcceptingSpectators,
|
||||
Player... players) {
|
||||
LoParkour.log("Creating session");
|
||||
|
||||
if (players != null) {
|
||||
LoParkour.log("Players in session: %s".formatted(Arrays.stream(players).map(Player::getName).toList()));
|
||||
}
|
||||
|
||||
Session session = new Session();
|
||||
|
||||
if (isAcceptingPlayers != null) session.isAcceptingPlayers = isAcceptingPlayers;
|
||||
|
|
@ -128,8 +122,6 @@ public class Session {
|
|||
*/
|
||||
public void addPlayers(ParkourPlayer... toAdd) {
|
||||
for (ParkourPlayer player : toAdd) {
|
||||
LoParkour.log("Adding player %s to session".formatted(player.getName()));
|
||||
|
||||
for (ParkourPlayer to : getPlayers()) {
|
||||
to.send(Locales.getString(player.locale, "lobby.other_join").formatted(player.getName()));
|
||||
}
|
||||
|
|
@ -145,8 +137,6 @@ public class Session {
|
|||
*/
|
||||
public void removePlayers(ParkourPlayer... toRemove) {
|
||||
for (ParkourPlayer player : toRemove) {
|
||||
LoParkour.log("Removing player %s from session".formatted(player.getName()));
|
||||
|
||||
users.remove(player.getUUID());
|
||||
}
|
||||
|
||||
|
|
@ -180,8 +170,6 @@ public class Session {
|
|||
*/
|
||||
public void addSpectators(ParkourSpectator... spectators) {
|
||||
for (ParkourSpectator spectator : spectators) {
|
||||
LoParkour.log("Adding spectator %s to session".formatted(spectator.getName()));
|
||||
|
||||
for (ParkourPlayer player : getPlayers()) {
|
||||
player.sendTranslated("play.spectator.other_join", spectator.getName());
|
||||
}
|
||||
|
|
@ -197,8 +185,6 @@ public class Session {
|
|||
*/
|
||||
public void removeSpectators(ParkourSpectator... spectators) {
|
||||
for (ParkourSpectator spectator : spectators) {
|
||||
LoParkour.log("Removing spectator %s from session".formatted(spectator.getName()));
|
||||
|
||||
for (ParkourPlayer player : getPlayers()) {
|
||||
player.sendTranslated("play.spectator.other_leave", spectator.getName());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,18 @@ public class Storage {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If SQL is enabled, defers the callback until the connection is ready.
|
||||
* If SQL is disabled (disk), calls the callback immediately.
|
||||
*/
|
||||
public static void runWhenReady(Runnable callback) {
|
||||
if (Option.SQL) {
|
||||
StorageSQL.runWhenConnected(callback);
|
||||
} else {
|
||||
callback.run();
|
||||
}
|
||||
}
|
||||
|
||||
public static void close() {
|
||||
if (Option.SQL) {
|
||||
StorageSQL.close();
|
||||
|
|
|
|||
|
|
@ -81,6 +81,12 @@ class StorageDisk {
|
|||
try (FileReader reader = new FileReader(getPlayerFile(player))) {
|
||||
ParkourPlayer from = LoParkour.getGson().fromJson(reader, ParkourPlayer.class);
|
||||
|
||||
if (from == null) {
|
||||
LoParkour.getPlugin().getLogger().warning("Player data file for %s is empty or corrupted, using defaults".formatted(player.getName()));
|
||||
player.setSettings(new HashMap<>());
|
||||
return;
|
||||
}
|
||||
|
||||
Map<String, Object> settings = new HashMap<>();
|
||||
|
||||
settings.put("style", from.style);
|
||||
|
|
|
|||
|
|
@ -8,14 +8,11 @@ import dev.loki.loparkour.player.ParkourPlayer;
|
|||
import org.bukkit.Bukkit;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import dev.lolib.utils.Colls;
|
||||
import java.sql.*;
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.DecimalFormatSymbols;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
/**
|
||||
* MySQL storage manager.
|
||||
|
|
@ -25,13 +22,35 @@ import java.util.UUID;
|
|||
class StorageSQL {
|
||||
|
||||
private static Connection connection;
|
||||
private static boolean initialized = false;
|
||||
private static volatile boolean connected = false;
|
||||
|
||||
static {
|
||||
connect();
|
||||
// Tables queued to create after connection is established
|
||||
private static final List<String> pendingTableCreations = new CopyOnWriteArrayList<>();
|
||||
|
||||
// Callbacks to run after connection is ready (e.g. initial leaderboard reads)
|
||||
private static final List<Runnable> onConnectCallbacks = new CopyOnWriteArrayList<>();
|
||||
|
||||
/**
|
||||
* Returns true if the SQL connection is ready.
|
||||
*/
|
||||
public static boolean isConnected() {
|
||||
return connected;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the given callback immediately if already connected, or defers it until connect() succeeds.
|
||||
*/
|
||||
public static void runWhenConnected(Runnable callback) {
|
||||
if (connected) {
|
||||
callback.run();
|
||||
} else {
|
||||
onConnectCallbacks.add(callback);
|
||||
}
|
||||
}
|
||||
|
||||
public static void init(String mode) {
|
||||
sendUpdate("""
|
||||
String createTableSql = """
|
||||
CREATE TABLE IF NOT EXISTS `%s`
|
||||
(
|
||||
uuid CHAR(36) NOT NULL PRIMARY KEY,
|
||||
|
|
@ -41,98 +60,89 @@ class StorageSQL {
|
|||
score INT
|
||||
)
|
||||
CHARSET = utf8 ENGINE = InnoDB;
|
||||
"""
|
||||
.formatted(getTableName(mode)));
|
||||
""".formatted(getTableName(mode));
|
||||
|
||||
if (!initialized) {
|
||||
initialized = true;
|
||||
// Queue this table for creation after connect() finishes
|
||||
pendingTableCreations.add(createTableSql);
|
||||
// Connect async — table creation happens inside connect() after connection is ready
|
||||
Bukkit.getScheduler().runTaskAsynchronously(LoParkour.getPlugin(), StorageSQL::connect);
|
||||
} else {
|
||||
// Already connected (or connecting) — send directly
|
||||
pendingTableCreations.add(createTableSql);
|
||||
}
|
||||
}
|
||||
|
||||
public static void close() {
|
||||
try {
|
||||
connection.close();
|
||||
LoParkour.log("Closed connection to MySQL");
|
||||
if (connection != null && !connection.isClosed()) {
|
||||
connection.close();
|
||||
LoParkour.log("Closed connection to MySQL");
|
||||
}
|
||||
} catch (SQLException ex) {
|
||||
LoParkour.getPlugin().getLogger().severe("Error while trying to close connection to SQL database" + " - " + ex.getMessage());
|
||||
LoParkour.getPlugin().getLogger().severe("Error while trying to close connection to SQL database - " + ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public static @NotNull Map<UUID, Score> readScores(@NotNull String mode) {
|
||||
try (ResultSet results = sendQuery(
|
||||
"""
|
||||
SELECT * FROM `%s`;
|
||||
"""
|
||||
.formatted(getTableName(mode)))) {
|
||||
|
||||
if (results == null) {
|
||||
return new HashMap<>();
|
||||
String sql = "SELECT * FROM `%s`;".formatted(getTableName(mode));
|
||||
try (PreparedStatement stmt = prepareStatement(sql)) {
|
||||
if (stmt == null) return new HashMap<>();
|
||||
try (ResultSet results = stmt.executeQuery()) {
|
||||
Map<UUID, Score> scores = new HashMap<>();
|
||||
while (results.next()) {
|
||||
scores.put(UUID.fromString(results.getString("uuid")), new Score(
|
||||
results.getString("name"),
|
||||
results.getString("time"),
|
||||
results.getString("difficulty"),
|
||||
results.getInt("score")));
|
||||
}
|
||||
return scores;
|
||||
}
|
||||
|
||||
Map<UUID, Score> scores = new HashMap<>();
|
||||
|
||||
while (results.next()) { // advance row
|
||||
scores.put(UUID.fromString(results.getString("uuid")), new Score(
|
||||
results.getString("name"),
|
||||
results.getString("time"),
|
||||
results.getString("difficulty"),
|
||||
results.getInt("score")));
|
||||
}
|
||||
|
||||
return scores;
|
||||
} catch (SQLException ex) {
|
||||
LoParkour.getPlugin().getLogger().severe("Error while trying to read SQL data of %s".formatted(mode) + " - " + ex.getMessage());
|
||||
LoParkour.getPlugin().getLogger().severe("Error while trying to read SQL data of %s - %s".formatted(mode, ex.getMessage()));
|
||||
return new HashMap<>();
|
||||
}
|
||||
}
|
||||
|
||||
public static void writeScores(@NotNull String mode, @NotNull Map<UUID, Score> scores) {
|
||||
new HashMap<>(scores).forEach((uuid, score) -> sendUpdate(
|
||||
"""
|
||||
INSERT INTO `%s`
|
||||
(uuid, name, time, difficulty, score)
|
||||
VALUES ('%s', '%s', '%s', '%s', %d)
|
||||
ON DUPLICATE KEY UPDATE name = '%s',
|
||||
time = '%s',
|
||||
difficulty = '%s',
|
||||
score = %d;
|
||||
"""
|
||||
.formatted(getTableName(mode), uuid.toString(), score.name(), score.time(), score.difficulty(), score.score(),
|
||||
score.name(), score.time(), score.difficulty(), score.score())));
|
||||
new HashMap<>(scores).forEach((uuid, score) -> sendUpdate("""
|
||||
INSERT INTO `%s`
|
||||
(uuid, name, time, difficulty, score)
|
||||
VALUES ('%s', '%s', '%s', '%s', %d)
|
||||
ON DUPLICATE KEY UPDATE name = '%s',
|
||||
time = '%s',
|
||||
difficulty = '%s',
|
||||
score = %d;
|
||||
""".formatted(getTableName(mode), uuid, score.name(), score.time(), score.difficulty(), score.score(),
|
||||
score.name(), score.time(), score.difficulty(), score.score())));
|
||||
}
|
||||
|
||||
// returns leaderboard table name
|
||||
private static String getTableName(String mode) {
|
||||
return "%sleaderboard-%s".formatted(Option.SQL_PREFIX, mode);
|
||||
}
|
||||
|
||||
public static void readPlayer(@NotNull ParkourPlayer player) {
|
||||
try (ResultSet results = sendQuery(
|
||||
"""
|
||||
SELECT * FROM `%s` WHERE uuid = '%s';
|
||||
"""
|
||||
.formatted("%soptions".formatted(Option.SQL_PREFIX), player.getUUID()))) {
|
||||
String sql = "SELECT * FROM `%soptions` WHERE uuid = '%s';".formatted(Option.SQL_PREFIX, player.getUUID());
|
||||
try (PreparedStatement stmt = prepareStatement(sql)) {
|
||||
if (stmt == null) { player.setSettings(new HashMap<>()); return; }
|
||||
try (ResultSet results = stmt.executeQuery()) {
|
||||
if (!results.next()) { player.setSettings(new HashMap<>()); return; }
|
||||
|
||||
if (results == null) {
|
||||
player.setSettings(new HashMap<>());
|
||||
return;
|
||||
}
|
||||
|
||||
boolean hasNext = results.next(); // move cursor
|
||||
|
||||
if (!hasNext) {
|
||||
player.setSettings(new HashMap<>());
|
||||
return;
|
||||
}
|
||||
|
||||
Map<String, Object> settings = new HashMap<>();
|
||||
for (String key : ParkourPlayer.PLAYER_COLUMNS.keySet()) {
|
||||
try {
|
||||
settings.put(key, results.getObject(key));
|
||||
} catch (SQLException ex) {
|
||||
LoParkour.getPlugin().getLogger().severe("Error while trying to read SQL data of %s, option = %s - %s".formatted(player.getName(), key, ex.getMessage()));
|
||||
Map<String, Object> settings = new HashMap<>();
|
||||
for (String key : ParkourPlayer.PLAYER_COLUMNS.keySet()) {
|
||||
try {
|
||||
settings.put(key, results.getObject(key));
|
||||
} catch (SQLException ex) {
|
||||
LoParkour.getPlugin().getLogger().severe(
|
||||
"Error reading SQL data of %s, key=%s - %s".formatted(player.getName(), key, ex.getMessage()));
|
||||
}
|
||||
}
|
||||
player.setSettings(settings);
|
||||
}
|
||||
|
||||
player.setSettings(settings);
|
||||
} catch (SQLException ex) {
|
||||
LoParkour.getPlugin().getLogger().severe("Error while trying to read SQL data of %s".formatted(player.getName()) + " - " + ex.getMessage());
|
||||
LoParkour.getPlugin().getLogger().severe("Error reading SQL data of %s - %s".formatted(player.getName(), ex.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -141,7 +151,7 @@ class StorageSQL {
|
|||
String schematicDifficulty = df.format(player.schematicDifficulty);
|
||||
|
||||
sendUpdate("""
|
||||
INSERT INTO `%s`
|
||||
INSERT INTO `%soptions`
|
||||
(uuid, style, blockLead, useParticles, useSpecial, showFallMsg, showScoreboard,
|
||||
selectedTime, collectedRewards, locale, schematicDifficulty, sound)
|
||||
VALUES ('%s', '%s', %d, %b, %b, %b, %b, %d, '%s', '%s', %s, %b)
|
||||
|
|
@ -156,103 +166,117 @@ class StorageSQL {
|
|||
locale = '%s',
|
||||
schematicDifficulty = %s,
|
||||
sound = %b;
|
||||
"""
|
||||
.formatted("%soptions".formatted(Option.SQL_PREFIX), player.getUUID(), player.style, player.blockLead,
|
||||
player.particles, player.useSpecialBlocks, player.showFallMessage,
|
||||
player.showScoreboard, player.selectedTime, String.join(",", player.collectedRewards), player.locale,
|
||||
schematicDifficulty, player.sound,
|
||||
|
||||
player.style, player.blockLead,
|
||||
player.particles, player.useSpecialBlocks, player.showFallMessage,
|
||||
player.showScoreboard, player.selectedTime, String.join(",", player.collectedRewards), player.locale,
|
||||
schematicDifficulty, player.sound));
|
||||
""".formatted(Option.SQL_PREFIX,
|
||||
player.getUUID(), player.style, player.blockLead,
|
||||
player.particles, player.useSpecialBlocks, player.showFallMessage,
|
||||
player.showScoreboard, player.selectedTime, String.join(",", player.collectedRewards),
|
||||
player.locale, schematicDifficulty, player.sound,
|
||||
player.style, player.blockLead,
|
||||
player.particles, player.useSpecialBlocks, player.showFallMessage,
|
||||
player.showScoreboard, player.selectedTime, String.join(",", player.collectedRewards),
|
||||
player.locale, schematicDifficulty, player.sound));
|
||||
}
|
||||
|
||||
public static void connect() {
|
||||
private static void connect() {
|
||||
try {
|
||||
LoParkour.log("Connecting to MySQL");
|
||||
LoParkour.log("Connecting to MySQL...");
|
||||
|
||||
try { // load drivers
|
||||
Class.forName("com.mysql.cj.jdbc.Driver"); // for newer versions
|
||||
} catch (ClassNotFoundException old) {
|
||||
Class.forName("com.mysql.jdbc.Driver"); // for older versions
|
||||
try {
|
||||
Class.forName("com.mysql.cj.jdbc.Driver");
|
||||
} catch (ClassNotFoundException e) {
|
||||
Class.forName("com.mysql.jdbc.Driver");
|
||||
}
|
||||
|
||||
connection = DriverManager.getConnection(("jdbc:mysql://%s:%d/%s?allowPublicKeyRetrieval=true" +
|
||||
"&useSSL=false&useUnicode=true&characterEncoding=utf-8" +
|
||||
"&autoReconnect=true&maxReconnects=5").formatted(Option.SQL_URL, Option.SQL_PORT, Option.SQL_DB), Option.SQL_USERNAME, Option.SQL_PASSWORD);
|
||||
connection = DriverManager.getConnection(
|
||||
("jdbc:mysql://%s:%d/%s?allowPublicKeyRetrieval=true" +
|
||||
"&useSSL=false&useUnicode=true&characterEncoding=utf-8" +
|
||||
"&autoReconnect=true&maxReconnects=2&connectTimeout=5000&socketTimeout=5000")
|
||||
.formatted(Option.SQL_URL, Option.SQL_PORT, Option.SQL_DB),
|
||||
Option.SQL_USERNAME, Option.SQL_PASSWORD);
|
||||
|
||||
sendUpdate("CREATE DATABASE IF NOT EXISTS `%s`;".formatted(Option.SQL_DB));
|
||||
sendUpdate("USE `%s`;".formatted(Option.SQL_DB));
|
||||
|
||||
sendUpdate("CREATE TABLE IF NOT EXISTS `%soptions` (`uuid` CHAR(36) NOT NULL, `time` VARCHAR(8), `style` VARCHAR(32), `blockLead` INT, `useParticles` BOOLEAN, `useDifficulty` BOOLEAN, `useSpecial` BOOLEAN, `showFallMsg` BOOLEAN, `showScoreboard` BOOLEAN, PRIMARY KEY (`uuid`)) ENGINE = InnoDB CHARSET = utf8;".formatted(Option.SQL_PREFIX));
|
||||
// Base options table
|
||||
sendUpdate(("CREATE TABLE IF NOT EXISTS `%soptions` " +
|
||||
"(`uuid` CHAR(36) NOT NULL, `time` VARCHAR(8), `style` VARCHAR(32), `blockLead` INT, " +
|
||||
"`useParticles` BOOLEAN, `useDifficulty` BOOLEAN, `useSpecial` BOOLEAN, " +
|
||||
"`showFallMsg` BOOLEAN, `showScoreboard` BOOLEAN, PRIMARY KEY (`uuid`)) " +
|
||||
"ENGINE = InnoDB CHARSET = utf8;").formatted(Option.SQL_PREFIX));
|
||||
|
||||
// v3.0.0
|
||||
// Migrations
|
||||
sendUpdateSuppressed("ALTER TABLE `%soptions` DROP COLUMN `time`;".formatted(Option.SQL_PREFIX));
|
||||
sendUpdateSuppressed("ALTER TABLE `%soptions` ADD `selectedTime` INT NOT NULL;".formatted(Option.SQL_PREFIX));
|
||||
|
||||
// v3.1.0
|
||||
sendUpdateSuppressed("ALTER TABLE `%soptions` ADD `collectedRewards` MEDIUMTEXT;".formatted(Option.SQL_PREFIX));
|
||||
|
||||
// v3.6.0
|
||||
sendUpdateSuppressed("ALTER TABLE `%s` ADD `locale` VARCHAR(8);".formatted(Option.SQL_PREFIX + "options"));
|
||||
sendUpdateSuppressed("ALTER TABLE `%s` ADD `schematicDifficulty` DOUBLE;".formatted(Option.SQL_PREFIX + "options"));
|
||||
|
||||
// v4.0.0
|
||||
sendUpdateSuppressed("ALTER TABLE `%s` ADD `sound` BOOLEAN;".formatted(Option.SQL_PREFIX + "options"));
|
||||
|
||||
// 5.0.0
|
||||
sendUpdateSuppressed("ALTER TABLE `%soptions` ADD `locale` VARCHAR(8);".formatted(Option.SQL_PREFIX));
|
||||
sendUpdateSuppressed("ALTER TABLE `%soptions` ADD `schematicDifficulty` DOUBLE;".formatted(Option.SQL_PREFIX));
|
||||
sendUpdateSuppressed("ALTER TABLE `%soptions` ADD `sound` BOOLEAN;".formatted(Option.SQL_PREFIX));
|
||||
sendUpdateSuppressed("ALTER TABLE `%soptions` DROP COLUMN `useDifficulty`;".formatted(Option.SQL_PREFIX));
|
||||
sendUpdateSuppressed("ALTER TABLE `%soptions` DROP COLUMN `useStructure`;".formatted(Option.SQL_PREFIX));
|
||||
|
||||
// Now flush all pending table creations (leaderboard tables queued before connection was ready)
|
||||
for (String sql : pendingTableCreations) {
|
||||
sendUpdate(sql);
|
||||
}
|
||||
pendingTableCreations.clear();
|
||||
|
||||
LoParkour.log("Connected to MySQL");
|
||||
connected = true;
|
||||
|
||||
// Run deferred callbacks (e.g. initial leaderboard reads)
|
||||
for (Runnable cb : onConnectCallbacks) {
|
||||
try { cb.run(); } catch (Exception e) {
|
||||
LoParkour.getPlugin().getLogger().severe("Error in SQL onConnect callback: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
onConnectCallbacks.clear();
|
||||
} catch (Exception ex) {
|
||||
LoParkour.getPlugin().getLogger().severe("Could not connect to MySQL - check your SQL settings in the config - " + ex.getMessage());
|
||||
Bukkit.getPluginManager().disablePlugin(LoParkour.getPlugin()); // disable plugin since data handling without db will go horribly wrong
|
||||
LoParkour.getPlugin().getLogger().severe("Could not connect to MySQL - check your SQL settings - " + ex.getMessage());
|
||||
LoParkour.getPlugin().getLogger().severe("Disabling SQL storage, using local storage instead");
|
||||
Option.SQL = false;
|
||||
}
|
||||
}
|
||||
|
||||
private static void validateConnection() {
|
||||
try {
|
||||
if (!connection.isValid(10)) {
|
||||
if (connection == null || !connection.isValid(2)) {
|
||||
LoParkour.getPlugin().getLogger().warning("MySQL connection lost, attempting reconnect...");
|
||||
connect();
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
LoParkour.getPlugin().getLogger().severe("Error while trying to reconnect to MySQL" + " - " + ex.getMessage());
|
||||
LoParkour.getPlugin().getLogger().severe("Error reconnecting to MySQL - " + ex.getMessage());
|
||||
Option.SQL = false;
|
||||
}
|
||||
}
|
||||
|
||||
// send query and get result
|
||||
private static ResultSet sendQuery(String sql) {
|
||||
/** Returns a PreparedStatement with proper connection validation. Caller must close it. */
|
||||
private static PreparedStatement prepareStatement(String sql) {
|
||||
validateConnection();
|
||||
|
||||
if (connection == null) return null;
|
||||
try {
|
||||
return connection.prepareStatement(sql).executeQuery();
|
||||
return connection.prepareStatement(sql);
|
||||
} catch (SQLException ex) {
|
||||
LoParkour.getPlugin().getLogger().severe("Error while sending query %s".formatted(sql) + " - " + ex.getMessage());
|
||||
LoParkour.getPlugin().getLogger().severe("Error preparing statement: %s - %s".formatted(sql, ex.getMessage()));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// send update
|
||||
private static void sendUpdate(String sql) {
|
||||
validateConnection();
|
||||
|
||||
try (PreparedStatement statement = connection.prepareStatement(sql)) {
|
||||
statement.executeUpdate();
|
||||
if (connection == null) return;
|
||||
try (PreparedStatement stmt = connection.prepareStatement(sql)) {
|
||||
stmt.executeUpdate();
|
||||
} catch (SQLException ex) {
|
||||
LoParkour.getPlugin().getLogger().severe("Error while sending query %s".formatted(sql) + " - " + ex.getMessage());
|
||||
LoParkour.getPlugin().getLogger().severe("Error while sending update: %s - %s".formatted(sql, ex.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
// if query throws an error, ignore it
|
||||
private static void sendUpdateSuppressed(String sql) {
|
||||
validateConnection();
|
||||
|
||||
try (PreparedStatement statement = connection.prepareStatement(sql)) {
|
||||
statement.executeUpdate();
|
||||
if (connection == null) return;
|
||||
try (PreparedStatement stmt = connection.prepareStatement(sql)) {
|
||||
stmt.executeUpdate();
|
||||
} catch (SQLException ignored) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,8 +40,6 @@ public class Divider {
|
|||
|
||||
var location = toLocation(session);
|
||||
|
||||
LoParkour.log("Added session at %s".formatted(Locations.toString(location, true)));
|
||||
|
||||
return location;
|
||||
}
|
||||
|
||||
|
|
@ -51,8 +49,6 @@ public class Divider {
|
|||
* @param session The session.
|
||||
*/
|
||||
public static void remove(Session session) {
|
||||
LoParkour.log("Removed session at %s".formatted(Locations.toString(toLocation(session), true)));
|
||||
|
||||
sections.remove(session);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ public class World {
|
|||
|
||||
if (world != null) {
|
||||
LoParkour.getPlugin().getLogger().warning("Crash detected! The parkour world loading twice is not usual behaviour. This only happens after a server crash.");
|
||||
World.world = world; // ensure static field is populated
|
||||
}
|
||||
|
||||
if (Config.CONFIG.getBoolean("world.delete-on-reload")) {
|
||||
|
|
@ -61,6 +62,10 @@ public class World {
|
|||
}
|
||||
|
||||
private static void setup() {
|
||||
if (world == null) {
|
||||
LoParkour.getPlugin().getLogger().severe("Cannot setup world rules — world is null (createWorld failed).");
|
||||
return;
|
||||
}
|
||||
LoParkour.log("Initializing world rules");
|
||||
|
||||
world.setGameRule(GameRule.DO_FIRE_TICK, false);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue