Initial commit: LoParkour v1.1.0
This commit is contained in:
commit
aecb03ccf8
120 changed files with 9668 additions and 0 deletions
235
src/main/java/dev/loki/loparkour/player/ParkourPlayer.java
Normal file
235
src/main/java/dev/loki/loparkour/player/ParkourPlayer.java
Normal file
|
|
@ -0,0 +1,235 @@
|
|||
package dev.loki.loparkour.player;
|
||||
|
||||
import com.google.gson.annotations.Expose;
|
||||
import dev.loki.loparkour.LoParkour;
|
||||
import dev.loki.loparkour.config.Config;
|
||||
import dev.loki.loparkour.config.Locales;
|
||||
import dev.loki.loparkour.config.Option;
|
||||
import dev.loki.loparkour.generator.ParkourGenerator;
|
||||
import dev.loki.loparkour.generator.Profile;
|
||||
import dev.loki.loparkour.menu.ParkourOption;
|
||||
import dev.loki.loparkour.mode.MultiMode;
|
||||
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 dev.efnilite.vilib.inventory.Menu;
|
||||
import dev.efnilite.vilib.inventory.item.Item;
|
||||
import dev.efnilite.vilib.util.Colls;
|
||||
import dev.efnilite.vilib.util.Task;
|
||||
import org.bukkit.GameMode;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.potion.PotionEffect;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
/**
|
||||
* Subclass of {@link ParkourUser}. This class is used for players who are actively playing Parkour in any (default) mode.
|
||||
*
|
||||
* @author loki
|
||||
*/
|
||||
public class ParkourPlayer extends ParkourUser {
|
||||
|
||||
public static final Map<String, OptionContainer> PLAYER_COLUMNS = new HashMap<>();
|
||||
|
||||
static {
|
||||
PLAYER_COLUMNS.put("uuid", new OptionContainer(null, null));
|
||||
PLAYER_COLUMNS.put("style", new OptionContainer(ParkourOption.STYLES, (player, v) -> player.style = v));
|
||||
PLAYER_COLUMNS.put("blockLead", new OptionContainer(ParkourOption.LEADS, (player, v) -> player.blockLead = Integer.parseInt(v)));
|
||||
PLAYER_COLUMNS.put("useParticles", new OptionContainer(ParkourOption.PARTICLES, (player, v) -> player.particles = parseBoolean(v)));
|
||||
PLAYER_COLUMNS.put("useSpecial", new OptionContainer(ParkourOption.SPECIAL_BLOCKS, (player, v) -> player.useSpecialBlocks = parseBoolean(v)));
|
||||
PLAYER_COLUMNS.put("showFallMsg", new OptionContainer(ParkourOption.FALL_MESSAGE, (player, v) -> player.showFallMessage = parseBoolean(v)));
|
||||
PLAYER_COLUMNS.put("showScoreboard", new OptionContainer(ParkourOption.SCOREBOARD, (player, v) -> player.showScoreboard = parseBoolean(v)));
|
||||
PLAYER_COLUMNS.put("selectedTime", new OptionContainer(ParkourOption.TIME, (player, v) -> player.selectedTime = Integer.parseInt(v)));
|
||||
PLAYER_COLUMNS.put("collectedRewards", new OptionContainer(null, (player, v) -> {
|
||||
player.collectedRewards = new ArrayList<>();
|
||||
|
||||
if (!v.isEmpty()) {
|
||||
player.collectedRewards.addAll(Arrays.stream(v.replaceAll("[ \\[\\]]", "").split(","))
|
||||
.distinct()
|
||||
.toList());
|
||||
}
|
||||
}));
|
||||
PLAYER_COLUMNS.put("locale", new OptionContainer(ParkourOption.LANG, (player, v) -> {
|
||||
player._locale = v;
|
||||
player.locale = v;
|
||||
}));
|
||||
PLAYER_COLUMNS.put("schematicDifficulty", new OptionContainer(ParkourOption.SCHEMATICS, (player, v) -> player.schematicDifficulty = Double.parseDouble(v)));
|
||||
PLAYER_COLUMNS.put("sound", new OptionContainer(ParkourOption.SOUND, (player, v) -> player.sound = parseBoolean(v)));
|
||||
}
|
||||
|
||||
public @Expose Double schematicDifficulty;
|
||||
public @Expose Integer blockLead;
|
||||
public @Expose Boolean particles;
|
||||
public @Expose Boolean sound;
|
||||
public @Expose Boolean useSpecialBlocks;
|
||||
public @Expose Boolean showFallMessage;
|
||||
public @Expose Boolean showScoreboard;
|
||||
public @Expose Integer selectedTime;
|
||||
public @Expose String style;
|
||||
public @Expose String _locale;
|
||||
public @Expose List<String> collectedRewards;
|
||||
/**
|
||||
* Creates a new instance of a ParkourPlayer<br>
|
||||
* If you are using the API, please use {@link ParkourPlayer#register(Player, Session)} instead
|
||||
*/
|
||||
public ParkourPlayer(@NotNull Player player, @NotNull Session session, @Nullable PreviousData previousData) {
|
||||
super(player, session, previousData);
|
||||
|
||||
this._locale = locale;
|
||||
|
||||
// generic player settings
|
||||
player.setFlying(false);
|
||||
player.setAllowFlight(false);
|
||||
player.setInvisible(false);
|
||||
|
||||
for (PotionEffect effect : player.getActivePotionEffects()) {
|
||||
player.removePotionEffect(effect.getType());
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean parseBoolean(String string) {
|
||||
return string == null
|
||||
|| string.equals("1") // for MySQL
|
||||
|| string.equals("true"); // for disk
|
||||
}
|
||||
|
||||
/**
|
||||
* @param player The player.
|
||||
* @return True when this player is a {@link ParkourPlayer}, false if not.
|
||||
*/
|
||||
public static boolean isPlayer(@Nullable Player player) {
|
||||
return player != null && getPlayers().stream().anyMatch(other -> other.player == player);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param player The player.
|
||||
* @return player as a {@link ParkourPlayer}, null if not found.
|
||||
*/
|
||||
public static @Nullable ParkourPlayer getPlayer(@NotNull Player player) {
|
||||
return getPlayers().stream()
|
||||
.filter(other -> other.getUUID() == player.getUniqueId())
|
||||
.findAny()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return List with all players.
|
||||
*/
|
||||
public static List<ParkourPlayer> getPlayers() {
|
||||
return Divider.sections.keySet().stream()
|
||||
.flatMap(session -> session.getPlayers().stream())
|
||||
.toList();
|
||||
}
|
||||
|
||||
@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) {
|
||||
mode.leave(player, session);
|
||||
}
|
||||
|
||||
session.removePlayers(this);
|
||||
|
||||
save(LoParkour.getPlugin().isEnabled());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the user's settings. If an item is not included, the setting gets reset.
|
||||
*
|
||||
* @param settings The settings map.
|
||||
*/
|
||||
public void setSettings(@NotNull Map<String, Object> settings) {
|
||||
for (String key : PLAYER_COLUMNS.keySet()) {
|
||||
Object value = settings.get(key);
|
||||
OptionContainer container = PLAYER_COLUMNS.get(key);
|
||||
|
||||
if (container.consumer == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (value == null || !Option.OPTIONS_ENABLED.getOrDefault(container.option, true)) {
|
||||
container.consumer.accept(this, Option.OPTIONS_DEFAULTS.getOrDefault(container.option, ""));
|
||||
continue;
|
||||
}
|
||||
|
||||
container.consumer.accept(this, String.valueOf(value));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Forces this player's generator to match the settings of this player.
|
||||
*/
|
||||
public void updateGeneratorSettings(ParkourGenerator generator) {
|
||||
Profile profile = generator.profile;
|
||||
|
||||
profile.set("schematicDifficulty", schematicDifficulty.toString())
|
||||
.set("blockLead", blockLead.toString())
|
||||
.set("particles", particles.toString())
|
||||
.set("sound", sound.toString())
|
||||
.set("useSpecialBlocks", useSpecialBlocks.toString())
|
||||
.set("showFallMessage", showFallMessage.toString())
|
||||
.set("showScoreboard", showScoreboard.toString())
|
||||
.set("selectedTime", selectedTime.toString())
|
||||
.set("style", style);
|
||||
|
||||
generator.overrideProfile();
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the player's data to their file
|
||||
*/
|
||||
public void save(boolean async) {
|
||||
Runnable write = () -> Storage.writePlayer(this);
|
||||
|
||||
if (async) {
|
||||
Task.create(LoParkour.getPlugin()).async().execute(write).run();
|
||||
} else {
|
||||
write.run();
|
||||
}
|
||||
}
|
||||
|
||||
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")) {
|
||||
Task.create(LoParkour.getPlugin()).delay(5).execute(() -> {
|
||||
LoParkour.log("Setting up inventory for player %s".formatted(player.getName()));
|
||||
|
||||
player.getInventory().clear();
|
||||
|
||||
List<Item> items = new ArrayList<>();
|
||||
|
||||
if (ParkourOption.PLAY.mayPerform(player)) items.add(Locales.getItem(locale, "play.item"));
|
||||
if (ParkourOption.COMMUNITY.mayPerform(player)) items.add(Locales.getItem(locale, "community.item"));
|
||||
if (ParkourOption.SETTINGS.mayPerform(player)) items.add(Locales.getItem(locale, "settings.item"));
|
||||
if (ParkourOption.LOBBY.mayPerform(player)) items.add(Locales.getItem(locale, "lobby.item"));
|
||||
|
||||
if (ParkourOption.QUIT.mayPerform(player)) items.add(Locales.getItem(locale, "other.quit"));
|
||||
|
||||
List<Integer> slots = Menu.getEvenlyDistributedSlots(items.size());
|
||||
Colls.range(0, items.size()).forEach(idx -> player.getInventory().setItem(slots.get(idx), items.get(idx).build()));
|
||||
}).run();
|
||||
} else {
|
||||
sendTranslated("other.customize");
|
||||
}
|
||||
}
|
||||
|
||||
public record OptionContainer(ParkourOption option, BiConsumer<ParkourPlayer, String> consumer) {
|
||||
|
||||
}
|
||||
}
|
||||
110
src/main/java/dev/loki/loparkour/player/ParkourSpectator.java
Normal file
110
src/main/java/dev/loki/loparkour/player/ParkourSpectator.java
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
package dev.loki.loparkour.player;
|
||||
|
||||
import dev.loki.loparkour.LoParkour;
|
||||
import dev.loki.loparkour.api.event.ParkourSpectateEvent;
|
||||
import dev.loki.loparkour.config.Locales;
|
||||
import dev.loki.loparkour.player.data.PreviousData;
|
||||
import dev.loki.loparkour.session.Session;
|
||||
import dev.efnilite.vilib.util.Strings;
|
||||
import dev.efnilite.vilib.util.Task;
|
||||
import net.md_5.bungee.api.ChatMessageType;
|
||||
import net.md_5.bungee.api.chat.TextComponent;
|
||||
import org.bukkit.GameMode;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.scheduler.BukkitTask;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Comparator;
|
||||
|
||||
/**
|
||||
* Class for spectators of a Session.
|
||||
*
|
||||
* @author loki
|
||||
*/
|
||||
public class ParkourSpectator extends ParkourUser {
|
||||
|
||||
private final BukkitTask closestChecker;
|
||||
/**
|
||||
* The closest player.
|
||||
*/
|
||||
@NotNull
|
||||
public ParkourPlayer closest;
|
||||
|
||||
public ParkourSpectator(@NotNull Player player, @NotNull Session session, @Nullable PreviousData previousData) {
|
||||
super(player, session, previousData);
|
||||
|
||||
this.closest = session.getPlayers().get(0);
|
||||
|
||||
new ParkourSpectateEvent(this).call();
|
||||
|
||||
Task.create(LoParkour.getPlugin())
|
||||
.delay(1)
|
||||
.execute(() -> {
|
||||
teleport(closest.getLocation());
|
||||
|
||||
sendTranslated("play.spectator.join");
|
||||
|
||||
player.setGameMode(GameMode.SPECTATOR);
|
||||
player.setAllowFlight(true);
|
||||
player.setFlying(true);
|
||||
if (ParkourUser.isBedrockPlayer(player)) { // bedrock has no spectator mode, so just make the player invisible
|
||||
player.setInvisible(true);
|
||||
player.setCollidable(false);
|
||||
}
|
||||
})
|
||||
.run();
|
||||
|
||||
closestChecker = Task.create(LoParkour.getPlugin())
|
||||
.async()
|
||||
.delay(1)
|
||||
.repeat(10)
|
||||
.execute(() -> {
|
||||
if (session.getPlayers().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
closest = session.getPlayers().stream()
|
||||
.min(Comparator.comparing(other -> other.getLocation().distanceSquared(player.getLocation()))) // x or x^2 doesn't matter in getting smallest
|
||||
.orElse(closest);
|
||||
})
|
||||
.run();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the spectator's action bar, scoreboard and checks distance.
|
||||
*/
|
||||
public void update() {
|
||||
player.spigot().sendMessage(ChatMessageType.ACTION_BAR, TextComponent.fromLegacyText(Strings.colour(Locales.getString(player, "play.spectator.action_bar"))));
|
||||
player.setGameMode(GameMode.SPECTATOR);
|
||||
updateScoreboard(session.generator);
|
||||
|
||||
// spectator is still being teleported to world
|
||||
if (closest.getLocation().getWorld() != player.getLocation().getWorld()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (closest.getLocation().distanceSquared(player.getLocation()) < 100 * 100) { // avoid sqrt
|
||||
return;
|
||||
}
|
||||
|
||||
teleport(closest.getLocation());
|
||||
if (player.getGameMode() != GameMode.SPECTATOR) { // if player isn't in spectator or is a bedrock player
|
||||
return;
|
||||
}
|
||||
|
||||
// if player is a spectator
|
||||
player.setSpectatorTarget(null);
|
||||
player.setSpectatorTarget(player.getSpectatorTarget());
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the closest checker runnable.
|
||||
*/
|
||||
@Override
|
||||
public void unregister() {
|
||||
closestChecker.cancel();
|
||||
session.removeSpectators(this);
|
||||
player.setInvisible(false);
|
||||
}
|
||||
}
|
||||
343
src/main/java/dev/loki/loparkour/player/ParkourUser.java
Normal file
343
src/main/java/dev/loki/loparkour/player/ParkourUser.java
Normal file
|
|
@ -0,0 +1,343 @@
|
|||
package dev.loki.loparkour.player;
|
||||
|
||||
import com.google.common.io.ByteArrayDataOutput;
|
||||
import com.google.common.io.ByteStreams;
|
||||
import dev.loki.loparkour.LoParkour;
|
||||
import dev.loki.loparkour.api.event.ParkourJoinEvent;
|
||||
import dev.loki.loparkour.api.event.ParkourLeaveEvent;
|
||||
import dev.loki.loparkour.config.Config;
|
||||
import dev.loki.loparkour.config.Locales;
|
||||
import dev.loki.loparkour.config.Option;
|
||||
import dev.loki.loparkour.generator.ParkourGenerator;
|
||||
import dev.loki.loparkour.hook.FloodgateHook;
|
||||
import dev.loki.loparkour.leaderboard.Leaderboard;
|
||||
import dev.loki.loparkour.leaderboard.Score;
|
||||
import dev.loki.loparkour.menu.ParkourOption;
|
||||
import dev.loki.loparkour.mode.Mode;
|
||||
import dev.loki.loparkour.mode.Modes;
|
||||
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 dev.efnilite.vilib.fastboard.FastBoard;
|
||||
import dev.efnilite.vilib.util.Strings;
|
||||
import io.papermc.lib.PaperLib;
|
||||
import me.clip.placeholderapi.PlaceholderAPI;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.messaging.ChannelNotRegisteredException;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Superclass of every type of player. This encompasses every player currently in the Parkour world.
|
||||
* This includes active players ({@link ParkourPlayer}) and spectators ({@link ParkourSpectator}).
|
||||
*
|
||||
* @author loki
|
||||
*/
|
||||
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();
|
||||
|
||||
Storage.readPlayer(pp);
|
||||
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;
|
||||
}
|
||||
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();
|
||||
|
||||
if (user.board != null && !user.board.isDeleted()) {
|
||||
user.board.delete();
|
||||
}
|
||||
} catch (Exception ex) { // safeguard to prevent people from losing data
|
||||
LoParkour.logging().stack("Error while trying to make player %s leave".formatted(user.getName()), ex);
|
||||
user.send("<red><bold>There was an error while trying to handle leaving.");
|
||||
}
|
||||
|
||||
if (restorePreviousData && Config.CONFIG.getBoolean("bungeecord.enabled") && kickIfBungee) {
|
||||
sendPlayerToServer(user.player, Config.CONFIG.getString("bungeecord.return_server"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!restorePreviousData) return;
|
||||
|
||||
user.previousData.apply(user.player, urgent);
|
||||
|
||||
Mode mode = user.session.generator.getMode();
|
||||
if (mode == null) {
|
||||
LoParkour.logging().error("Mode is null for %s".formatted(user.getName()));
|
||||
mode = Modes.DEFAULT;
|
||||
}
|
||||
|
||||
if (user instanceof ParkourPlayer player) {
|
||||
Mode finalMode = mode;
|
||||
user.previousData.onLeave.forEach(r -> r.execute(player, finalMode));
|
||||
}
|
||||
}
|
||||
|
||||
// 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");
|
||||
out.writeUTF(server);
|
||||
|
||||
try {
|
||||
player.sendPluginMessage(LoParkour.getPlugin(), "BungeeCord", out.toByteArray());
|
||||
} catch (ChannelNotRegisteredException ex) {
|
||||
LoParkour.logging().stack("Error while trying to send %s to server %s. This server is not registered.".formatted(player.getName(), server), ex);
|
||||
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())
|
||||
.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);
|
||||
|
||||
/**
|
||||
* This user's scoreboard
|
||||
*/
|
||||
public FastBoard board;
|
||||
|
||||
/**
|
||||
* This user's PreviousData
|
||||
*/
|
||||
@NotNull
|
||||
public PreviousData previousData;
|
||||
|
||||
/**
|
||||
* The selected {@link Session.ChatType}
|
||||
*/
|
||||
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) {
|
||||
this.player = player;
|
||||
this.session = session;
|
||||
this.joined = Instant.now();
|
||||
this.previousData = previousData == null ? new PreviousData(player) : previousData;
|
||||
|
||||
if (Boolean.parseBoolean(Option.OPTIONS_DEFAULTS.get(ParkourOption.SCOREBOARD))) {
|
||||
this.board = new FastBoard(player);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a message.
|
||||
*
|
||||
* @param message The message
|
||||
*/
|
||||
public void send(String message) {
|
||||
player.sendMessage(Strings.colour(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.
|
||||
*/
|
||||
public void updateScoreboard(ParkourGenerator generator) {
|
||||
// board can be null a few ticks after on player leave
|
||||
if (board == null || board.isDeleted() || !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);
|
||||
}
|
||||
|
||||
board.updateTitle(replace(Locales.getString(locale, "scoreboard.title"), top, high, generator));
|
||||
board.updateLines(replace(Locales.getStringList(locale, "scoreboard.lines"), top, high, generator));
|
||||
}
|
||||
|
||||
private List<String> replace(List<String> s, Score top, Score high, ParkourGenerator generator) {
|
||||
return s.stream().map(line -> replace(line, top, high, generator)).toList();
|
||||
}
|
||||
|
||||
private String replace(String s, Score top, Score high, ParkourGenerator generator) {
|
||||
return Strings.colour(translate(player, s)
|
||||
.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();
|
||||
}
|
||||
|
||||
/**
|
||||
* @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);
|
||||
}
|
||||
}
|
||||
106
src/main/java/dev/loki/loparkour/player/data/InventoryData.java
Normal file
106
src/main/java/dev/loki/loparkour/player/data/InventoryData.java
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
package dev.loki.loparkour.player.data;
|
||||
|
||||
import dev.loki.loparkour.LoParkour;
|
||||
import dev.loki.loparkour.config.Config;
|
||||
import dev.efnilite.vilib.util.Task;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.Inventory;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.util.io.BukkitObjectInputStream;
|
||||
import org.bukkit.util.io.BukkitObjectOutputStream;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class InventoryData {
|
||||
|
||||
private final File file;
|
||||
private final Player player;
|
||||
private Map<Integer, ItemStack> items = new HashMap<>();
|
||||
|
||||
public InventoryData(Player player) {
|
||||
this.player = player;
|
||||
this.file = LoParkour.getInFolder("inventories/%s".formatted(player.getUniqueId()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gives all items to the player.
|
||||
*/
|
||||
public void apply() {
|
||||
player.getInventory().clear();
|
||||
items.forEach((slot, item) -> player.getInventory().setItem(slot, item));
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads inventory data from file.
|
||||
*
|
||||
* @param onFinish What to do when the async procedure has finished.
|
||||
*/
|
||||
public void load(Consumer<@Nullable InventoryData> onFinish) {
|
||||
if (!file.exists()) {
|
||||
onFinish.accept(null);
|
||||
return;
|
||||
}
|
||||
|
||||
Task.create(LoParkour.getPlugin()).async().execute(() -> loadFile(onFinish)).run();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void loadFile(Consumer<@Nullable InventoryData> onFinish) {
|
||||
try (BukkitObjectInputStream stream = new BukkitObjectInputStream(new BufferedInputStream(new FileInputStream(file)))) {
|
||||
items = (Map<Integer, ItemStack>) stream.readObject();
|
||||
|
||||
onFinish.accept(this);
|
||||
} catch (IOException | ClassNotFoundException ex) {
|
||||
LoParkour.logging().stack("Error while reading inventory of %s from file %s".formatted(player.getName(), file.getName()), ex);
|
||||
onFinish.accept(null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the inventory to cache, so if the player leaves the player gets their items back
|
||||
*
|
||||
* @param toFile Whether the file should be updated.
|
||||
*/
|
||||
public void save(boolean toFile) {
|
||||
int index = 0;
|
||||
|
||||
Inventory inventory = this.player.getInventory();
|
||||
for (ItemStack item : inventory.getContents()) {
|
||||
if (item != null) {
|
||||
this.items.put(index, item);
|
||||
}
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
String command = Config.CONFIG.getString("options.alt-inventory-saving-command");
|
||||
if (!command.isEmpty()) {
|
||||
Bukkit.dispatchCommand(Bukkit.getConsoleSender(), command.replace("%player%", player.getName()));
|
||||
}
|
||||
|
||||
if (toFile) {
|
||||
Task.create(LoParkour.getPlugin()).async().execute(this::saveFile).run();
|
||||
}
|
||||
}
|
||||
|
||||
private void saveFile() {
|
||||
try {
|
||||
file.getParentFile().mkdirs();
|
||||
file.createNewFile();
|
||||
} catch (IOException ex) {
|
||||
LoParkour.logging().stack("Error while creating file to save inventory of %s to file %s".formatted(player.getName(), file.getName()), ex);
|
||||
}
|
||||
|
||||
try (ObjectOutputStream stream = new BukkitObjectOutputStream(new BufferedOutputStream(new FileOutputStream(file)))) {
|
||||
stream.writeObject(items);
|
||||
stream.flush();
|
||||
} catch (IOException ex) {
|
||||
LoParkour.logging().stack("Error while saving inventory of %s to file %s".formatted(player.getName(), file.getName()), ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
package dev.loki.loparkour.player.data;
|
||||
|
||||
import dev.loki.loparkour.LoParkour;
|
||||
import dev.loki.loparkour.config.Config;
|
||||
import dev.loki.loparkour.config.Option;
|
||||
import dev.loki.loparkour.reward.Reward;
|
||||
import io.papermc.lib.PaperLib;
|
||||
import org.bukkit.GameMode;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.potion.PotionEffect;
|
||||
import org.bukkit.util.Vector;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Class for storing previous data of players
|
||||
*/
|
||||
public class PreviousData {
|
||||
|
||||
private final InventoryData inventoryData;
|
||||
|
||||
private final int hunger;
|
||||
private final boolean flying;
|
||||
private final boolean allowFlight;
|
||||
private final GameMode gamemode;
|
||||
private final Location location;
|
||||
private final Collection<PotionEffect> effects;
|
||||
|
||||
/**
|
||||
* List of all {@link Reward} to execute on leave.
|
||||
*/
|
||||
public List<Reward> onLeave = new ArrayList<>();
|
||||
|
||||
public PreviousData(@NotNull Player player) {
|
||||
gamemode = player.getGameMode();
|
||||
location = player.getLocation();
|
||||
hunger = player.getFoodLevel();
|
||||
allowFlight = player.getAllowFlight();
|
||||
flying = player.isFlying();
|
||||
effects = player.getActivePotionEffects();
|
||||
|
||||
for (PotionEffect effect : effects) {
|
||||
player.removePotionEffect(effect.getType());
|
||||
}
|
||||
|
||||
if (Config.CONFIG.getBoolean("options.inventory-handling")) {
|
||||
inventoryData = new InventoryData(player);
|
||||
inventoryData.save(Config.CONFIG.getBoolean("options.inventory-saving"));
|
||||
} else {
|
||||
inventoryData = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void apply(Player player, boolean urgent) {
|
||||
var to = Config.CONFIG.getBoolean("bungeecord.go-back-enabled") ? Option.GO_BACK_LOC : location;
|
||||
|
||||
if (!urgent)
|
||||
PaperLib.teleportAsync(player, to).thenRun(() -> apply(player));
|
||||
else {
|
||||
player.teleport(to);
|
||||
|
||||
apply(player);
|
||||
}
|
||||
}
|
||||
|
||||
private void apply(Player player) {
|
||||
try {
|
||||
player.setFoodLevel(hunger);
|
||||
player.setGameMode(gamemode);
|
||||
player.setAllowFlight(allowFlight);
|
||||
player.setFlying(flying);
|
||||
|
||||
for (PotionEffect effect : player.getActivePotionEffects()) {
|
||||
player.removePotionEffect(effect.getType());
|
||||
}
|
||||
for (PotionEffect effect : effects) {
|
||||
player.addPotionEffect(effect);
|
||||
}
|
||||
|
||||
player.resetPlayerTime();
|
||||
player.resetPlayerWeather();
|
||||
player.setVelocity(new Vector(0, 0, 0));
|
||||
player.setFallDistance(0f);
|
||||
} catch (Exception ex) { // not the best way to do this... too bad!
|
||||
LoParkour.logging().stack("Error while recovering stats of %s".formatted(player.getName()), ex);
|
||||
}
|
||||
|
||||
if (inventoryData != null) {
|
||||
inventoryData.apply();
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue