v1.2.4: Remove roguelike perks, update bStats to 3.2.1, add new game modes and lpschem system
- Removed roguelike perk system (double jump, magnet, shield, second chance) - Updated bStats dependency to 3.2.1 with proper relocation - Changed bStats plugin ID to 29754 - Added new game modes: Speedrun, Gravity Shift, Hardcore - Implemented .lpschem format with GZIP + JSON compression - Added JumpValidator for physics-based jump validation - Added JumpType system (neo-jump, head-hitter, fence, trapdoor, ladder) - Implemented Ghost system for recording/playback top players - Added loparkour-vilib as local dependency - Updated config with new mode settings and removed roguelike section - Cleaned up TODO.md roadmap
This commit is contained in:
parent
aecb03ccf8
commit
8aeb547897
68 changed files with 6186 additions and 163 deletions
|
|
@ -12,6 +12,9 @@ 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 dev.efnilite.vilib.command.ViCommand;
|
||||
import dev.efnilite.vilib.inventory.item.Item;
|
||||
|
|
@ -227,8 +230,11 @@ public class Command extends ViCommand {
|
|||
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 <dark_gray>- <gray>Save your selection to a schematic file");
|
||||
send(player, "<red>/LoParkour schematic paste <file> <dark_gray>- <gray>Paste a schematic file");
|
||||
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.");
|
||||
}
|
||||
|
|
@ -510,6 +516,27 @@ public class Command extends ViCommand {
|
|||
|
||||
Schematic.save(LoParkour.getInFolder("schematics/parkour-%s".formatted(code)), existingSelection[0], existingSelection[1], LoParkour.getPlugin());
|
||||
}
|
||||
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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -521,22 +548,56 @@ public class Command extends ViCommand {
|
|||
}
|
||||
|
||||
if (arg1.equalsIgnoreCase("schematic")) {
|
||||
if (!arg2.equalsIgnoreCase("paste")) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!player.hasPermission(ParkourOption.ADMIN.permission)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Schematic schematic = Schematics.getSchematic(LoParkour.getPlugin(), arg3);
|
||||
if (schematic == null) {
|
||||
send(sender, "%sCouldn't find %s".formatted(LoParkour.PREFIX, arg3));
|
||||
return;
|
||||
}
|
||||
switch (arg2.toLowerCase()) {
|
||||
case "save" -> {
|
||||
if (!cooldown(sender, "LoParkour save schematic", 2500)) {
|
||||
return;
|
||||
}
|
||||
|
||||
schematic.paste(player.getLocation());
|
||||
send(sender, "%sPasted schematic %s".formatted(LoParkour.PREFIX, arg3));
|
||||
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;
|
||||
}
|
||||
|
||||
Schematic oldSchematic = Schematics.getSchematic(LoParkour.getPlugin(), arg3);
|
||||
if (oldSchematic == null) {
|
||||
send(sender, "%sCouldn't find %s".formatted(LoParkour.PREFIX, arg3));
|
||||
return;
|
||||
}
|
||||
|
||||
oldSchematic.paste(player.getLocation());
|
||||
send(sender, "%sPasted old schematic %s".formatted(LoParkour.PREFIX, arg3));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,16 +7,20 @@ import dev.loki.loparkour.config.Option;
|
|||
import dev.loki.loparkour.hook.HoloHook;
|
||||
import dev.loki.loparkour.hook.PAPIHook;
|
||||
import dev.loki.loparkour.mode.DefaultMode;
|
||||
import dev.loki.loparkour.mode.GravityShiftMode;
|
||||
import dev.loki.loparkour.mode.HardcoreMode;
|
||||
import dev.loki.loparkour.mode.Modes;
|
||||
import dev.loki.loparkour.mode.SpectatorMode;
|
||||
import dev.loki.loparkour.mode.SpeedrunMode;
|
||||
import dev.loki.loparkour.player.ParkourUser;
|
||||
import dev.loki.loparkour.reward.Rewards;
|
||||
import dev.loki.loparkour.schematic.lpschem.LPSchematicManager;
|
||||
import dev.loki.loparkour.storage.Storage;
|
||||
import dev.loki.loparkour.world.World;
|
||||
import dev.efnilite.vilib.ViPlugin;
|
||||
import dev.efnilite.vilib.bstats.bukkit.Metrics;
|
||||
import dev.efnilite.vilib.bstats.charts.SimplePie;
|
||||
import dev.efnilite.vilib.bstats.charts.SingleLineChart;
|
||||
import org.bstats.bukkit.Metrics;
|
||||
import org.bstats.charts.SimplePie;
|
||||
import org.bstats.charts.SingleLineChart;
|
||||
import dev.efnilite.vilib.inventory.Menu;
|
||||
import dev.efnilite.vilib.util.Logging;
|
||||
import dev.efnilite.vilib.util.UpdateChecker;
|
||||
|
|
@ -36,6 +40,7 @@ public final class LoParkour extends ViPlugin {
|
|||
|
||||
private static Logging logging;
|
||||
private static LoParkour instance;
|
||||
private static LPSchematicManager schematicManager;
|
||||
|
||||
@Nullable
|
||||
private static PAPIHook placeholderHook;
|
||||
|
|
@ -73,6 +78,10 @@ public final class LoParkour extends ViPlugin {
|
|||
return placeholderHook;
|
||||
}
|
||||
|
||||
public static LPSchematicManager getSchematicManager() {
|
||||
return schematicManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoad() {
|
||||
instance = this;
|
||||
|
|
@ -86,10 +95,18 @@ public final class LoParkour extends ViPlugin {
|
|||
|
||||
Config.reload(true);
|
||||
|
||||
// ----- Schematics -----
|
||||
|
||||
schematicManager = new LPSchematicManager();
|
||||
schematicManager.loadAll();
|
||||
|
||||
// ----- Registry -----
|
||||
|
||||
Registry.register(new DefaultMode());
|
||||
Registry.register(new SpectatorMode());
|
||||
Registry.register(new SpeedrunMode());
|
||||
Registry.register(new GravityShiftMode());
|
||||
Registry.register(new HardcoreMode());
|
||||
|
||||
Modes.init();
|
||||
Menu.init(this);
|
||||
|
|
@ -124,7 +141,7 @@ public final class LoParkour extends ViPlugin {
|
|||
|
||||
// ----- Metrics -----
|
||||
|
||||
Metrics metrics = new Metrics(this, 9272);
|
||||
Metrics metrics = new Metrics(this, 29754);
|
||||
metrics.addCustomChart(new SimplePie("using_sql", () -> Boolean.toString(Option.SQL)));
|
||||
metrics.addCustomChart(new SimplePie("using_rewards", () -> Boolean.toString(Rewards.REWARDS_ENABLED)));
|
||||
metrics.addCustomChart(new SimplePie("locale_count", () -> Integer.toString(Locales.locales.size())));
|
||||
|
|
|
|||
|
|
@ -182,6 +182,32 @@ public class Option {
|
|||
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
|
||||
|
||||
|
|
@ -208,6 +234,33 @@ public class Option {
|
|||
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", false)) {
|
||||
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;
|
||||
|
|
|
|||
53
src/main/java/dev/loki/loparkour/generator/JumpType.java
Normal file
53
src/main/java/dev/loki/loparkour/generator/JumpType.java
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
package dev.loki.loparkour.generator;
|
||||
|
||||
import org.bukkit.Material;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public enum JumpType {
|
||||
|
||||
NORMAL(1.0, "Normal jump", Material.STONE),
|
||||
NEO_JUMP(2.5, "Jump around fence/wall", Material.OAK_FENCE, Material.COBBLESTONE_WALL, Material.NETHER_BRICK_FENCE),
|
||||
HEAD_HITTER(2.0, "Jump with block overhead", Material.STONE_SLAB),
|
||||
FENCE_JUMP(1.8, "Jump onto fence", Material.OAK_FENCE, Material.BIRCH_FENCE, Material.SPRUCE_FENCE),
|
||||
TRAPDOOR_JUMP(1.5, "Jump onto trapdoor", Material.OAK_TRAPDOOR, Material.IRON_TRAPDOOR),
|
||||
LADDER_JUMP(1.7, "Jump onto ladder", Material.LADDER);
|
||||
|
||||
private final double difficulty;
|
||||
private final String description;
|
||||
private final List<Material> materials;
|
||||
|
||||
JumpType(double difficulty, String description, Material... materials) {
|
||||
this.difficulty = difficulty;
|
||||
this.description = description;
|
||||
this.materials = Arrays.asList(materials);
|
||||
}
|
||||
|
||||
public double getDifficulty() {
|
||||
return difficulty;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public List<Material> getMaterials() {
|
||||
return materials;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Material getRandomMaterial() {
|
||||
if (materials.isEmpty()) {
|
||||
return Material.STONE;
|
||||
}
|
||||
return materials.get((int) (Math.random() * materials.size()));
|
||||
}
|
||||
|
||||
public boolean usesMaterial(@NotNull Material material) {
|
||||
return materials.contains(material);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
package dev.loki.loparkour.generator;
|
||||
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.util.Vector;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class JumpValidator {
|
||||
|
||||
private static final double MAX_JUMP_DISTANCE = 4.5;
|
||||
private static final double MAX_HORIZONTAL_DISTANCE = 4.1;
|
||||
private static final double MAX_VERTICAL_UP = 1.25;
|
||||
private static final double MAX_VERTICAL_DOWN = 3.0;
|
||||
|
||||
private final double maxDistance;
|
||||
private final double maxHorizontal;
|
||||
private final double maxVerticalUp;
|
||||
private final double maxVerticalDown;
|
||||
|
||||
public JumpValidator() {
|
||||
this(MAX_JUMP_DISTANCE, MAX_HORIZONTAL_DISTANCE, MAX_VERTICAL_UP, MAX_VERTICAL_DOWN);
|
||||
}
|
||||
|
||||
public JumpValidator(double maxDistance, double maxHorizontal, double maxVerticalUp, double maxVerticalDown) {
|
||||
this.maxDistance = maxDistance;
|
||||
this.maxHorizontal = maxHorizontal;
|
||||
this.maxVerticalUp = maxVerticalUp;
|
||||
this.maxVerticalDown = maxVerticalDown;
|
||||
}
|
||||
|
||||
public boolean canJump(@NotNull Location from, @NotNull Location to) {
|
||||
return canJump(from.toVector(), to.toVector());
|
||||
}
|
||||
|
||||
// Проверка возможности прыжка: sqrt(dx² + dy² + dz²) <= maxDistance
|
||||
public boolean canJump(@NotNull Vector from, @NotNull Vector to) {
|
||||
double dx = to.getX() - from.getX();
|
||||
double dy = to.getY() - from.getY();
|
||||
double dz = to.getZ() - from.getZ();
|
||||
|
||||
double horizontalDistance = Math.sqrt(dx * dx + dz * dz);
|
||||
if (horizontalDistance > maxHorizontal) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (dy > maxVerticalUp || dy < -maxVerticalDown) {
|
||||
return false;
|
||||
}
|
||||
|
||||
double totalDistance = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
||||
return totalDistance <= maxDistance;
|
||||
}
|
||||
|
||||
public double calculateDistance(@NotNull Location from, @NotNull Location to) {
|
||||
return calculateDistance(from.toVector(), to.toVector());
|
||||
}
|
||||
|
||||
public double calculateDistance(@NotNull Vector from, @NotNull Vector to) {
|
||||
double dx = to.getX() - from.getX();
|
||||
double dy = to.getY() - from.getY();
|
||||
double dz = to.getZ() - from.getZ();
|
||||
return Math.sqrt(dx * dx + dy * dy + dz * dz);
|
||||
}
|
||||
|
||||
public double calculateHorizontalDistance(@NotNull Vector from, @NotNull Vector to) {
|
||||
double dx = to.getX() - from.getX();
|
||||
double dz = to.getZ() - from.getZ();
|
||||
return Math.sqrt(dx * dx + dz * dz);
|
||||
}
|
||||
|
||||
public double getMaxDistance() {
|
||||
return maxDistance;
|
||||
}
|
||||
|
||||
public double getMaxHorizontal() {
|
||||
return maxHorizontal;
|
||||
}
|
||||
|
||||
public double getMaxVerticalUp() {
|
||||
return maxVerticalUp;
|
||||
}
|
||||
|
||||
public double getMaxVerticalDown() {
|
||||
return maxVerticalDown;
|
||||
}
|
||||
}
|
||||
|
|
@ -52,49 +52,19 @@ import java.util.*;
|
|||
*/
|
||||
public class ParkourGenerator {
|
||||
|
||||
/**
|
||||
* The amount of blocks that trail behind the player.
|
||||
*/
|
||||
public static final int BLOCK_TRAIL = 2;
|
||||
|
||||
/**
|
||||
* This generator's score
|
||||
*/
|
||||
public int score = 0;
|
||||
|
||||
/**
|
||||
* The total score achieved in this Generator instance
|
||||
*/
|
||||
public int totalScore = 0;
|
||||
|
||||
/**
|
||||
* The schematic cooldown
|
||||
*/
|
||||
public int schematicCooldown = Config.GENERATION.getInt("advanced.schematic-cooldown");
|
||||
|
||||
/**
|
||||
* Whether this generator has been stopped
|
||||
*/
|
||||
public boolean stopped = false;
|
||||
|
||||
/**
|
||||
* The zone in which the parkour can take place. (playable area)
|
||||
*/
|
||||
public Location[] zone;
|
||||
|
||||
/**
|
||||
* The player
|
||||
*/
|
||||
public ParkourPlayer player;
|
||||
|
||||
/**
|
||||
* The task used in checking the player's current location
|
||||
*/
|
||||
public BukkitTask task;
|
||||
|
||||
/**
|
||||
* Where blocks from schematics spawn
|
||||
*/
|
||||
private BukkitTask cleanupTask;
|
||||
|
||||
public Location blockSpawn;
|
||||
|
||||
/**
|
||||
|
|
@ -127,64 +97,19 @@ public class ParkourGenerator {
|
|||
*/
|
||||
public final Profile profile = new Profile();
|
||||
|
||||
/**
|
||||
* The island instance.
|
||||
*/
|
||||
public final Island island;
|
||||
|
||||
/**
|
||||
* The chances of which distance the jump should have
|
||||
*/
|
||||
public final Map<Integer, Double> distanceChances = new HashMap<>();
|
||||
|
||||
/**
|
||||
* The chances of which height the jump should have
|
||||
*/
|
||||
public final Map<Integer, Double> heightChances = new HashMap<>();
|
||||
|
||||
/**
|
||||
* The chances of which type of special jump
|
||||
*/
|
||||
public final Map<BlockData, Double> specialChances = new HashMap<>();
|
||||
public final Map<BlockGenerationType, Double> defaultChances = new HashMap<>();
|
||||
|
||||
/**
|
||||
* The chances of default jump types: schematic, 'special' (ice, etc.) or normal
|
||||
*/
|
||||
public final Map<JumpType, Double> defaultChances = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Whether the schematic should be deleted on the next jump.
|
||||
*/
|
||||
protected boolean deleteSchematic = false;
|
||||
protected boolean waitForSchematicCompletion = false;
|
||||
|
||||
/**
|
||||
* The last location the player was found standing in
|
||||
*/
|
||||
protected Location lastStandingPlayerLocation;
|
||||
|
||||
/**
|
||||
* A list of blocks from the (possibly) spawned structure
|
||||
*/
|
||||
protected List<Block> schematicBlocks = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* The player's current position index.
|
||||
*/
|
||||
protected int lastPositionIndexPlayer = -1;
|
||||
protected List<Block> history = new LinkedList<>();
|
||||
|
||||
/**
|
||||
* The history of generated blocks. The most recently generated block is the last item in the list.
|
||||
*/
|
||||
protected List<Block> history = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Creates a new ParkourGenerator instance
|
||||
*
|
||||
* @param session The session.
|
||||
* @param schematic The schematic to use for the spawn island.
|
||||
* @param generatorOptions The options.
|
||||
*/
|
||||
public ParkourGenerator(@NotNull Session session, @Nullable Schematic schematic, GeneratorOption... generatorOptions) {
|
||||
this.session = session;
|
||||
this.generatorOptions = Arrays.asList(generatorOptions);
|
||||
|
|
@ -196,30 +121,17 @@ public class ParkourGenerator {
|
|||
calculateChances();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new ParkourGenerator instance.
|
||||
*
|
||||
* @param session The session.
|
||||
* @param generatorOptions The options.
|
||||
*/
|
||||
public ParkourGenerator(@NotNull Session session, GeneratorOption... generatorOptions) {
|
||||
this(session, Schematics.getSchematic(LoParkour.getPlugin(), "spawn-island"), generatorOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures generator preferences in profile can't be overridden by the player changing settings.
|
||||
*/
|
||||
public void overrideProfile() { }
|
||||
|
||||
/**
|
||||
* Calculates all chances for every variable.
|
||||
* Modification is possible in the generator constructor or through external map changes.
|
||||
*/
|
||||
protected void calculateChances() {
|
||||
defaultChances.clear();
|
||||
defaultChances.put(JumpType.DEFAULT, Option.TYPE_NORMAL);
|
||||
defaultChances.put(JumpType.SCHEMATIC, Option.TYPE_SCHEMATICS);
|
||||
defaultChances.put(JumpType.SPECIAL, Option.TYPE_SPECIAL);
|
||||
defaultChances.put(BlockGenerationType.DEFAULT, Option.TYPE_NORMAL);
|
||||
defaultChances.put(BlockGenerationType.SCHEMATIC, Option.TYPE_SCHEMATICS);
|
||||
defaultChances.put(BlockGenerationType.SPECIAL, Option.TYPE_SPECIAL);
|
||||
|
||||
heightChances.clear();
|
||||
heightChances.put(1, Option.NORMAL_HEIGHT_1);
|
||||
|
|
@ -388,6 +300,11 @@ public class ParkourGenerator {
|
|||
.repeat(1)
|
||||
.execute(this::tick)
|
||||
.run();
|
||||
|
||||
cleanupTask = Task.create(LoParkour.getPlugin())
|
||||
.repeat(Option.CLEANUP_INTERVAL)
|
||||
.execute(this::cleanupDistantBlocks)
|
||||
.run();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -396,6 +313,9 @@ public class ParkourGenerator {
|
|||
public void tick() {
|
||||
if (stopped) {
|
||||
task.cancel();
|
||||
if (cleanupTask != null) {
|
||||
cleanupTask.cancel();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -462,7 +382,6 @@ public class ParkourGenerator {
|
|||
lastPositionIndexPlayer = currentIndex;
|
||||
|
||||
for (int i = currentIndex - BLOCK_TRAIL - 1; i >= currentIndex - 4 * BLOCK_TRAIL; i--) {
|
||||
// avoid setting beginning block to air
|
||||
if (i <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
|
@ -470,6 +389,8 @@ public class ParkourGenerator {
|
|||
history.get(i).setType(Material.AIR);
|
||||
}
|
||||
|
||||
cleanupDistantBlocks();
|
||||
|
||||
deleteSchematic();
|
||||
|
||||
for (int i = 0; i < (Config.CONFIG.getBoolean("scoring.all-points") ? deltaFromLast : 1); i++) { // score the difference
|
||||
|
|
@ -597,19 +518,19 @@ public class ParkourGenerator {
|
|||
return;
|
||||
}
|
||||
|
||||
Map<JumpType, Double> chances = new HashMap<>(defaultChances);
|
||||
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(JumpType.SCHEMATIC);
|
||||
chances.remove(BlockGenerationType.SCHEMATIC);
|
||||
}
|
||||
if (!profile.get("useSpecialBlocks").asBoolean()) {
|
||||
chances.remove(JumpType.SPECIAL);
|
||||
chances.remove(BlockGenerationType.SPECIAL);
|
||||
}
|
||||
if (chances.isEmpty()) {
|
||||
chances.put(JumpType.DEFAULT, 1.0);
|
||||
chances.put(BlockGenerationType.DEFAULT, 1.0);
|
||||
}
|
||||
|
||||
JumpType jump = Probs.random(chances);
|
||||
if (jump == JumpType.SCHEMATIC) {
|
||||
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();
|
||||
|
|
@ -647,7 +568,7 @@ public class ParkourGenerator {
|
|||
|
||||
List<Block> movedBlocks = new ArrayList<>();
|
||||
for (Block block : blocks) {
|
||||
BlockData data = (jump == JumpType.SPECIAL && !generatorOptions.contains(GeneratorOption.DISABLE_SPECIAL)) ? Probs.random(specialChances) : selectBlockData();
|
||||
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();
|
||||
|
|
@ -731,6 +652,33 @@ public class ParkourGenerator {
|
|||
return history.get(history.size() - 1);
|
||||
}
|
||||
|
||||
// Очистка блоков на большом расстоянии от игрока для экономии памяти
|
||||
protected void cleanupDistantBlocks() {
|
||||
if (history.size() < Option.BLOCK_CLEANUP_DISTANCE * 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
Location playerLoc = 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();
|
||||
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]);
|
||||
|
||||
|
|
@ -837,7 +785,7 @@ public class ParkourGenerator {
|
|||
return session.getSpectators();
|
||||
}
|
||||
|
||||
public enum JumpType {
|
||||
public enum BlockGenerationType {
|
||||
DEFAULT, SCHEMATIC, SPECIAL
|
||||
}
|
||||
}
|
||||
|
|
|
|||
71
src/main/java/dev/loki/loparkour/ghost/GhostData.java
Normal file
71
src/main/java/dev/loki/loparkour/ghost/GhostData.java
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
package dev.loki.loparkour.ghost;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class GhostData {
|
||||
|
||||
private final String playerName;
|
||||
private final int score;
|
||||
private final List<GhostFrame> frames;
|
||||
|
||||
public GhostData(@NotNull String playerName, int score, @NotNull List<GhostFrame> frames) {
|
||||
this.playerName = playerName;
|
||||
this.score = score;
|
||||
this.frames = new ArrayList<>(frames);
|
||||
}
|
||||
|
||||
public String getPlayerName() {
|
||||
return playerName;
|
||||
}
|
||||
|
||||
public int getScore() {
|
||||
return score;
|
||||
}
|
||||
|
||||
public List<GhostFrame> getFrames() {
|
||||
return frames;
|
||||
}
|
||||
|
||||
public void saveToFile(@NotNull File file) throws IOException {
|
||||
try (DataOutputStream out = new DataOutputStream(new FileOutputStream(file))) {
|
||||
out.writeUTF(playerName);
|
||||
out.writeInt(score);
|
||||
out.writeInt(frames.size());
|
||||
|
||||
for (GhostFrame frame : frames) {
|
||||
out.writeLong(frame.getTimestamp());
|
||||
out.writeDouble(frame.getX());
|
||||
out.writeDouble(frame.getY());
|
||||
out.writeDouble(frame.getZ());
|
||||
out.writeFloat(frame.getYaw());
|
||||
out.writeFloat(frame.getPitch());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static GhostData loadFromFile(@NotNull File file) throws IOException {
|
||||
try (DataInputStream in = new DataInputStream(new FileInputStream(file))) {
|
||||
String playerName = in.readUTF();
|
||||
int score = in.readInt();
|
||||
int frameCount = in.readInt();
|
||||
|
||||
List<GhostFrame> frames = new ArrayList<>(frameCount);
|
||||
for (int i = 0; i < frameCount; i++) {
|
||||
long timestamp = in.readLong();
|
||||
double x = in.readDouble();
|
||||
double y = in.readDouble();
|
||||
double z = in.readDouble();
|
||||
float yaw = in.readFloat();
|
||||
float pitch = in.readFloat();
|
||||
|
||||
frames.add(new GhostFrame(timestamp, x, y, z, yaw, pitch));
|
||||
}
|
||||
|
||||
return new GhostData(playerName, score, frames);
|
||||
}
|
||||
}
|
||||
}
|
||||
60
src/main/java/dev/loki/loparkour/ghost/GhostFrame.java
Normal file
60
src/main/java/dev/loki/loparkour/ghost/GhostFrame.java
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
package dev.loki.loparkour.ghost;
|
||||
|
||||
import org.bukkit.Location;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class GhostFrame {
|
||||
|
||||
private final long timestamp;
|
||||
private final double x;
|
||||
private final double y;
|
||||
private final double z;
|
||||
private final float yaw;
|
||||
private final float pitch;
|
||||
|
||||
public GhostFrame(long timestamp, @NotNull Location location) {
|
||||
this.timestamp = timestamp;
|
||||
this.x = location.getX();
|
||||
this.y = location.getY();
|
||||
this.z = location.getZ();
|
||||
this.yaw = location.getYaw();
|
||||
this.pitch = location.getPitch();
|
||||
}
|
||||
|
||||
public GhostFrame(long timestamp, double x, double y, double z, float yaw, float pitch) {
|
||||
this.timestamp = timestamp;
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
this.yaw = yaw;
|
||||
this.pitch = pitch;
|
||||
}
|
||||
|
||||
public long getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public double getX() {
|
||||
return x;
|
||||
}
|
||||
|
||||
public double getY() {
|
||||
return y;
|
||||
}
|
||||
|
||||
public double getZ() {
|
||||
return z;
|
||||
}
|
||||
|
||||
public float getYaw() {
|
||||
return yaw;
|
||||
}
|
||||
|
||||
public float getPitch() {
|
||||
return pitch;
|
||||
}
|
||||
|
||||
public Location toLocation(org.bukkit.World world) {
|
||||
return new Location(world, x, y, z, yaw, pitch);
|
||||
}
|
||||
}
|
||||
131
src/main/java/dev/loki/loparkour/ghost/GhostManager.java
Normal file
131
src/main/java/dev/loki/loparkour/ghost/GhostManager.java
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
package dev.loki.loparkour.ghost;
|
||||
|
||||
import dev.loki.loparkour.LoParkour;
|
||||
import dev.loki.loparkour.config.Config;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
|
||||
public class GhostManager {
|
||||
|
||||
private static final File GHOST_FOLDER = LoParkour.getInFolder("ghosts");
|
||||
private static final int MAX_GHOSTS_PER_MODE = 3;
|
||||
|
||||
private final Map<String, List<GhostData>> ghostsByMode = new HashMap<>();
|
||||
private final List<GhostPlayer> activeGhosts = new ArrayList<>();
|
||||
|
||||
public GhostManager() {
|
||||
if (!GHOST_FOLDER.exists()) {
|
||||
GHOST_FOLDER.mkdirs();
|
||||
}
|
||||
}
|
||||
|
||||
public void loadGhosts(@NotNull String mode) {
|
||||
File modeFolder = new File(GHOST_FOLDER, mode);
|
||||
if (!modeFolder.exists()) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<GhostData> ghosts = new ArrayList<>();
|
||||
File[] files = modeFolder.listFiles((dir, name) -> name.endsWith(".ghost"));
|
||||
|
||||
if (files != null) {
|
||||
for (File file : files) {
|
||||
try {
|
||||
GhostData data = GhostData.loadFromFile(file);
|
||||
ghosts.add(data);
|
||||
} catch (IOException e) {
|
||||
LoParkour.logging().error("Failed to load ghost: " + file.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ghosts.sort((a, b) -> Integer.compare(b.getScore(), a.getScore()));
|
||||
ghostsByMode.put(mode, ghosts);
|
||||
}
|
||||
|
||||
public void saveGhost(@NotNull String mode, @NotNull GhostData data) {
|
||||
File modeFolder = new File(GHOST_FOLDER, mode);
|
||||
if (!modeFolder.exists()) {
|
||||
modeFolder.mkdirs();
|
||||
}
|
||||
|
||||
List<GhostData> ghosts = ghostsByMode.getOrDefault(mode, new ArrayList<>());
|
||||
ghosts.add(data);
|
||||
ghosts.sort((a, b) -> Integer.compare(b.getScore(), a.getScore()));
|
||||
|
||||
while (ghosts.size() > MAX_GHOSTS_PER_MODE) {
|
||||
GhostData removed = ghosts.remove(ghosts.size() - 1);
|
||||
File file = new File(modeFolder, removed.getPlayerName() + ".ghost");
|
||||
file.delete();
|
||||
}
|
||||
|
||||
ghostsByMode.put(mode, ghosts);
|
||||
|
||||
File file = new File(modeFolder, data.getPlayerName() + ".ghost");
|
||||
try {
|
||||
data.saveToFile(file);
|
||||
} catch (IOException e) {
|
||||
LoParkour.logging().error("Failed to save ghost: " + file.getName());
|
||||
}
|
||||
}
|
||||
|
||||
public void spawnGhosts(@NotNull String mode, @NotNull Location startLocation, @NotNull World world) {
|
||||
if (!Config.CONFIG.getBoolean("ghost-mode.enabled")) {
|
||||
return;
|
||||
}
|
||||
|
||||
stopAllGhosts();
|
||||
|
||||
List<GhostData> ghosts = ghostsByMode.get(mode);
|
||||
if (ghosts == null || ghosts.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
int showTop = Math.min(Config.CONFIG.getInt("ghost-mode.show-top"), ghosts.size());
|
||||
for (int i = 0; i < showTop; i++) {
|
||||
GhostPlayer ghost = new GhostPlayer(ghosts.get(i), world);
|
||||
ghost.spawn(startLocation);
|
||||
activeGhosts.add(ghost);
|
||||
}
|
||||
}
|
||||
|
||||
public void stopAllGhosts() {
|
||||
for (GhostPlayer ghost : activeGhosts) {
|
||||
ghost.stop();
|
||||
}
|
||||
activeGhosts.clear();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public GhostData getTopGhost(@NotNull String mode) {
|
||||
List<GhostData> ghosts = ghostsByMode.get(mode);
|
||||
if (ghosts == null || ghosts.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return ghosts.get(0);
|
||||
}
|
||||
|
||||
public List<GhostData> getTopGhosts(@NotNull String mode, int count) {
|
||||
List<GhostData> ghosts = ghostsByMode.get(mode);
|
||||
if (ghosts == null || ghosts.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return ghosts.subList(0, Math.min(count, ghosts.size()));
|
||||
}
|
||||
|
||||
public boolean shouldRecordGhost(@NotNull String mode, int score) {
|
||||
List<GhostData> ghosts = ghostsByMode.get(mode);
|
||||
if (ghosts == null || ghosts.size() < MAX_GHOSTS_PER_MODE) {
|
||||
return true;
|
||||
}
|
||||
|
||||
GhostData lowestGhost = ghosts.get(ghosts.size() - 1);
|
||||
return score > lowestGhost.getScore();
|
||||
}
|
||||
}
|
||||
95
src/main/java/dev/loki/loparkour/ghost/GhostPlayer.java
Normal file
95
src/main/java/dev/loki/loparkour/ghost/GhostPlayer.java
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
package dev.loki.loparkour.ghost;
|
||||
|
||||
import dev.loki.loparkour.LoParkour;
|
||||
import dev.efnilite.vilib.util.Task;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.entity.ArmorStand;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.bukkit.scheduler.BukkitTask;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class GhostPlayer {
|
||||
|
||||
private final GhostData data;
|
||||
private final World world;
|
||||
private ArmorStand entity;
|
||||
private BukkitTask task;
|
||||
private int currentFrame = 0;
|
||||
private long startTime;
|
||||
|
||||
public GhostPlayer(@NotNull GhostData data, @NotNull World world) {
|
||||
this.data = data;
|
||||
this.world = world;
|
||||
}
|
||||
|
||||
public void spawn(@NotNull Location startLocation) {
|
||||
if (data.getFrames().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
entity = (ArmorStand) world.spawnEntity(startLocation, EntityType.ARMOR_STAND);
|
||||
entity.setVisible(false);
|
||||
entity.setGravity(false);
|
||||
entity.setInvulnerable(true);
|
||||
entity.setCustomName("§7" + data.getPlayerName() + " §8(Ghost)");
|
||||
entity.setCustomNameVisible(true);
|
||||
|
||||
startTime = System.currentTimeMillis();
|
||||
currentFrame = 0;
|
||||
|
||||
task = Task.create(LoParkour.getPlugin())
|
||||
.repeat(1)
|
||||
.execute(this::update)
|
||||
.run();
|
||||
}
|
||||
|
||||
private void update() {
|
||||
if (entity == null || !entity.isValid()) {
|
||||
stop();
|
||||
return;
|
||||
}
|
||||
|
||||
long elapsed = System.currentTimeMillis() - startTime;
|
||||
|
||||
while (currentFrame < data.getFrames().size()) {
|
||||
GhostFrame frame = data.getFrames().get(currentFrame);
|
||||
|
||||
if (frame.getTimestamp() > elapsed) {
|
||||
break;
|
||||
}
|
||||
|
||||
Location loc = frame.toLocation(world);
|
||||
entity.teleport(loc);
|
||||
currentFrame++;
|
||||
}
|
||||
|
||||
if (currentFrame >= data.getFrames().size()) {
|
||||
stop();
|
||||
}
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
if (task != null) {
|
||||
task.cancel();
|
||||
task = null;
|
||||
}
|
||||
|
||||
if (entity != null) {
|
||||
entity.remove();
|
||||
entity = null;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isActive() {
|
||||
return entity != null && entity.isValid();
|
||||
}
|
||||
|
||||
public String getPlayerName() {
|
||||
return data.getPlayerName();
|
||||
}
|
||||
|
||||
public int getScore() {
|
||||
return data.getScore();
|
||||
}
|
||||
}
|
||||
53
src/main/java/dev/loki/loparkour/ghost/GhostRecorder.java
Normal file
53
src/main/java/dev/loki/loparkour/ghost/GhostRecorder.java
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
package dev.loki.loparkour.ghost;
|
||||
|
||||
import org.bukkit.Location;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class GhostRecorder {
|
||||
|
||||
private static final double MIN_MOVEMENT = 0.1;
|
||||
|
||||
private final List<GhostFrame> frames = new ArrayList<>();
|
||||
private Location lastLocation;
|
||||
private long startTime;
|
||||
private boolean recording = false;
|
||||
|
||||
public void startRecording(@NotNull Location startLocation) {
|
||||
frames.clear();
|
||||
lastLocation = startLocation.clone();
|
||||
startTime = System.currentTimeMillis();
|
||||
recording = true;
|
||||
|
||||
frames.add(new GhostFrame(0, startLocation.clone()));
|
||||
}
|
||||
|
||||
public void recordFrame(@NotNull Location currentLocation) {
|
||||
if (!recording) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (lastLocation.distance(currentLocation) < MIN_MOVEMENT) {
|
||||
return;
|
||||
}
|
||||
|
||||
long timestamp = System.currentTimeMillis() - startTime;
|
||||
frames.add(new GhostFrame(timestamp, currentLocation.clone()));
|
||||
lastLocation = currentLocation.clone();
|
||||
}
|
||||
|
||||
public GhostData stopRecording(String playerName, int score) {
|
||||
recording = false;
|
||||
return new GhostData(playerName, score, frames);
|
||||
}
|
||||
|
||||
public boolean isRecording() {
|
||||
return recording;
|
||||
}
|
||||
|
||||
public int getFrameCount() {
|
||||
return frames.size();
|
||||
}
|
||||
}
|
||||
172
src/main/java/dev/loki/loparkour/mode/GravityShiftMode.java
Normal file
172
src/main/java/dev/loki/loparkour/mode/GravityShiftMode.java
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
package dev.loki.loparkour.mode;
|
||||
|
||||
import dev.loki.loparkour.config.Config;
|
||||
import dev.loki.loparkour.config.Locales;
|
||||
import dev.loki.loparkour.generator.ParkourGenerator;
|
||||
import dev.loki.loparkour.leaderboard.Leaderboard;
|
||||
import dev.loki.loparkour.player.ParkourPlayer;
|
||||
import dev.loki.loparkour.session.Session;
|
||||
import dev.efnilite.vilib.inventory.item.Item;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.potion.PotionEffect;
|
||||
import org.bukkit.potion.PotionEffectType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
public class GravityShiftMode implements Mode {
|
||||
|
||||
private final Leaderboard leaderboard = new Leaderboard(getName(), Leaderboard.Sort.SCORE);
|
||||
private final Random random = new Random();
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public String getName() {
|
||||
return "gravity-shift";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Item getItem(String locale) {
|
||||
return Locales.getItem(locale, "play.single.gravity-shift");
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Leaderboard getLeaderboard() {
|
||||
return leaderboard;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void create(Player player) {
|
||||
if (!Config.CONFIG.getBoolean("joining")) {
|
||||
player.sendMessage("Joining is currently disabled.");
|
||||
return;
|
||||
}
|
||||
|
||||
ParkourPlayer pp = ParkourPlayer.getPlayer(player);
|
||||
if (pp != null && pp.session.generator != null && pp.session.generator.getMode() instanceof GravityShiftMode) {
|
||||
return;
|
||||
}
|
||||
|
||||
player.closeInventory();
|
||||
|
||||
Session.create(session -> new GravityShiftGenerator(session), null, null, player);
|
||||
}
|
||||
|
||||
private static class GravityShiftGenerator extends ParkourGenerator {
|
||||
private int jumpsUntilShift;
|
||||
private final int shiftInterval;
|
||||
private PotionEffect currentEffect;
|
||||
|
||||
public GravityShiftGenerator(@NotNull Session session) {
|
||||
super(session);
|
||||
this.shiftInterval = Config.CONFIG.getInt("modes.gravity-shift.interval");
|
||||
this.jumpsUntilShift = shiftInterval;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void score() {
|
||||
super.score();
|
||||
|
||||
jumpsUntilShift--;
|
||||
|
||||
if (jumpsUntilShift <= 0) {
|
||||
applyRandomEffect();
|
||||
jumpsUntilShift = shiftInterval;
|
||||
}
|
||||
}
|
||||
|
||||
private void applyRandomEffect() {
|
||||
if (currentEffect != null) {
|
||||
player.player.removePotionEffect(currentEffect.getType());
|
||||
}
|
||||
|
||||
List<EffectData> effects = new ArrayList<>();
|
||||
|
||||
if (Config.CONFIG.getBoolean("modes.gravity-shift.effects.jump-boost.enabled")) {
|
||||
effects.add(new EffectData(
|
||||
PotionEffectType.JUMP,
|
||||
Config.CONFIG.getInt("modes.gravity-shift.effects.jump-boost.amplifier"),
|
||||
Config.CONFIG.getInt("modes.gravity-shift.effects.jump-boost.duration"),
|
||||
"Jump Boost"
|
||||
));
|
||||
}
|
||||
|
||||
if (Config.CONFIG.getBoolean("modes.gravity-shift.effects.speed.enabled")) {
|
||||
effects.add(new EffectData(
|
||||
PotionEffectType.SPEED,
|
||||
Config.CONFIG.getInt("modes.gravity-shift.effects.speed.amplifier"),
|
||||
Config.CONFIG.getInt("modes.gravity-shift.effects.speed.duration"),
|
||||
"Speed"
|
||||
));
|
||||
}
|
||||
|
||||
if (Config.CONFIG.getBoolean("modes.gravity-shift.effects.slowness.enabled")) {
|
||||
effects.add(new EffectData(
|
||||
PotionEffectType.SLOW,
|
||||
Config.CONFIG.getInt("modes.gravity-shift.effects.slowness.amplifier"),
|
||||
Config.CONFIG.getInt("modes.gravity-shift.effects.slowness.duration"),
|
||||
"Slowness"
|
||||
));
|
||||
}
|
||||
|
||||
if (Config.CONFIG.getBoolean("modes.gravity-shift.effects.levitation.enabled")) {
|
||||
effects.add(new EffectData(
|
||||
PotionEffectType.LEVITATION,
|
||||
Config.CONFIG.getInt("modes.gravity-shift.effects.levitation.amplifier"),
|
||||
Config.CONFIG.getInt("modes.gravity-shift.effects.levitation.duration"),
|
||||
"Levitation"
|
||||
));
|
||||
}
|
||||
|
||||
if (effects.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
EffectData selected = effects.get(new Random().nextInt(effects.size()));
|
||||
currentEffect = new PotionEffect(
|
||||
selected.type,
|
||||
selected.duration * 20,
|
||||
selected.amplifier - 1,
|
||||
false,
|
||||
true,
|
||||
true
|
||||
);
|
||||
|
||||
player.player.addPotionEffect(currentEffect);
|
||||
player.sendTranslated("modes.gravity-shift.effect-applied", selected.name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset(boolean regenerate) {
|
||||
if (currentEffect != null && player != null && player.player != null) {
|
||||
player.player.removePotionEffect(currentEffect.getType());
|
||||
}
|
||||
jumpsUntilShift = shiftInterval;
|
||||
super.reset(regenerate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mode getMode() {
|
||||
return Modes.GRAVITY_SHIFT;
|
||||
}
|
||||
}
|
||||
|
||||
private static class EffectData {
|
||||
final PotionEffectType type;
|
||||
final int amplifier;
|
||||
final int duration;
|
||||
final String name;
|
||||
|
||||
EffectData(PotionEffectType type, int amplifier, int duration, String name) {
|
||||
this.type = type;
|
||||
this.amplifier = amplifier;
|
||||
this.duration = duration;
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
}
|
||||
77
src/main/java/dev/loki/loparkour/mode/HardcoreMode.java
Normal file
77
src/main/java/dev/loki/loparkour/mode/HardcoreMode.java
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
package dev.loki.loparkour.mode;
|
||||
|
||||
import dev.loki.loparkour.config.Config;
|
||||
import dev.loki.loparkour.config.Locales;
|
||||
import dev.loki.loparkour.generator.ParkourGenerator;
|
||||
import dev.loki.loparkour.leaderboard.Leaderboard;
|
||||
import dev.loki.loparkour.player.ParkourPlayer;
|
||||
import dev.loki.loparkour.session.Session;
|
||||
import dev.efnilite.vilib.inventory.item.Item;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class HardcoreMode implements Mode {
|
||||
|
||||
private final Leaderboard leaderboard = new Leaderboard(getName(), Leaderboard.Sort.SCORE);
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public String getName() {
|
||||
return "hardcore";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Item getItem(String locale) {
|
||||
return Locales.getItem(locale, "play.single.hardcore");
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Leaderboard getLeaderboard() {
|
||||
return leaderboard;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void create(Player player) {
|
||||
if (!Config.CONFIG.getBoolean("joining")) {
|
||||
player.sendMessage("Joining is currently disabled.");
|
||||
return;
|
||||
}
|
||||
|
||||
ParkourPlayer pp = ParkourPlayer.getPlayer(player);
|
||||
if (pp != null && pp.session.generator != null && pp.session.generator.getMode() instanceof HardcoreMode) {
|
||||
return;
|
||||
}
|
||||
|
||||
player.closeInventory();
|
||||
|
||||
Session.create(session -> new HardcoreGenerator(session), null, null, player);
|
||||
}
|
||||
|
||||
private static class HardcoreGenerator extends ParkourGenerator {
|
||||
|
||||
public HardcoreGenerator(@NotNull Session session) {
|
||||
super(session);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fall() {
|
||||
boolean resetRewards = Config.CONFIG.getBoolean("modes.hardcore.reset-rewards");
|
||||
|
||||
if (resetRewards) {
|
||||
player.sendTranslated("modes.hardcore.rewards-lost");
|
||||
}
|
||||
|
||||
player.sendTranslated("modes.hardcore.full-reset");
|
||||
|
||||
super.fall();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mode getMode() {
|
||||
return Modes.HARDCORE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -6,9 +6,15 @@ public class Modes {
|
|||
|
||||
public static DefaultMode DEFAULT;
|
||||
public static SpectatorMode SPECTATOR;
|
||||
public static SpeedrunMode SPEEDRUN;
|
||||
public static GravityShiftMode GRAVITY_SHIFT;
|
||||
public static HardcoreMode HARDCORE;
|
||||
|
||||
public static void init() {
|
||||
DEFAULT = (DefaultMode) Registry.getMode("default");
|
||||
SPECTATOR = (SpectatorMode) Registry.getMode("spectator");
|
||||
SPEEDRUN = (SpeedrunMode) Registry.getMode("speedrun");
|
||||
GRAVITY_SHIFT = (GravityShiftMode) Registry.getMode("gravity-shift");
|
||||
HARDCORE = (HardcoreMode) Registry.getMode("hardcore");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
112
src/main/java/dev/loki/loparkour/mode/SpeedrunMode.java
Normal file
112
src/main/java/dev/loki/loparkour/mode/SpeedrunMode.java
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
package dev.loki.loparkour.mode;
|
||||
|
||||
import dev.loki.loparkour.LoParkour;
|
||||
import dev.loki.loparkour.config.Config;
|
||||
import dev.loki.loparkour.config.Locales;
|
||||
import dev.loki.loparkour.generator.ParkourGenerator;
|
||||
import dev.loki.loparkour.leaderboard.Leaderboard;
|
||||
import dev.loki.loparkour.player.ParkourPlayer;
|
||||
import dev.loki.loparkour.session.Session;
|
||||
import dev.efnilite.vilib.inventory.item.Item;
|
||||
import dev.efnilite.vilib.util.Task;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Particle;
|
||||
import org.bukkit.Sound;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class SpeedrunMode implements Mode {
|
||||
|
||||
private final Leaderboard leaderboard = new Leaderboard(getName(), Leaderboard.Sort.SCORE);
|
||||
private final Map<Block, Long> blockTimestamps = new HashMap<>();
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public String getName() {
|
||||
return "speedrun";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Item getItem(String locale) {
|
||||
return Locales.getItem(locale, "play.single.speedrun");
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Leaderboard getLeaderboard() {
|
||||
return leaderboard;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void create(Player player) {
|
||||
if (!Config.CONFIG.getBoolean("joining")) {
|
||||
player.sendMessage("Joining is currently disabled.");
|
||||
return;
|
||||
}
|
||||
|
||||
ParkourPlayer pp = ParkourPlayer.getPlayer(player);
|
||||
if (pp != null && pp.session.generator != null && pp.session.generator.getMode() instanceof SpeedrunMode) {
|
||||
return;
|
||||
}
|
||||
|
||||
player.closeInventory();
|
||||
|
||||
Session.create(session -> new SpeedrunGenerator(session), null, null, player);
|
||||
}
|
||||
|
||||
public void onBlockTouch(@NotNull Block block, @NotNull ParkourPlayer player) {
|
||||
if (!blockTimestamps.containsKey(block)) {
|
||||
blockTimestamps.put(block, System.currentTimeMillis());
|
||||
scheduleBlockRemoval(player, block);
|
||||
}
|
||||
}
|
||||
|
||||
private void scheduleBlockRemoval(@NotNull ParkourPlayer player, @NotNull Block block) {
|
||||
double blockLifetime = Config.CONFIG.getDouble("modes.speedrun.block-lifetime");
|
||||
double warningTime = Config.CONFIG.getDouble("modes.speedrun.warning-time");
|
||||
|
||||
long removalTime = (long) (blockLifetime * 1000);
|
||||
long warningTimeMs = (long) (warningTime * 1000);
|
||||
|
||||
Task.create(LoParkour.getPlugin())
|
||||
.delay((int) ((removalTime - warningTimeMs) / 50))
|
||||
.execute(() -> {
|
||||
if (block.getType() != Material.AIR) {
|
||||
player.player.playSound(block.getLocation(), Sound.BLOCK_NOTE_BLOCK_PLING, 1.0f, 2.0f);
|
||||
player.player.spawnParticle(Particle.FLAME, block.getLocation().add(0.5, 1, 0.5), 10, 0.3, 0.3, 0.3, 0.01);
|
||||
}
|
||||
})
|
||||
.run();
|
||||
|
||||
Task.create(LoParkour.getPlugin())
|
||||
.delay((int) (removalTime / 50))
|
||||
.execute(() -> {
|
||||
if (block.getType() != Material.AIR) {
|
||||
block.setType(Material.AIR);
|
||||
blockTimestamps.remove(block);
|
||||
}
|
||||
})
|
||||
.run();
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
blockTimestamps.clear();
|
||||
}
|
||||
|
||||
private static class SpeedrunGenerator extends ParkourGenerator {
|
||||
public SpeedrunGenerator(@NotNull Session session) {
|
||||
super(session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mode getMode() {
|
||||
return Modes.SPEEDRUN;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,179 @@
|
|||
package dev.loki.loparkour.schematic.lpschem;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
import java.util.zip.GZIPOutputStream;
|
||||
|
||||
public class LPSchematic {
|
||||
|
||||
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
|
||||
private static final int FORMAT_VERSION = 2;
|
||||
|
||||
private final SchematicMetadata metadata;
|
||||
private final SchematicDimensions dimensions;
|
||||
private final List<String> palette;
|
||||
private final int[] blocks;
|
||||
private final SchematicMarkers markers;
|
||||
private SchematicVisuals visuals;
|
||||
private SchematicLogic logic;
|
||||
|
||||
public LPSchematic(@NotNull SchematicMetadata metadata,
|
||||
@NotNull SchematicDimensions dimensions,
|
||||
@NotNull List<String> palette,
|
||||
@NotNull int[] blocks,
|
||||
@NotNull SchematicMarkers markers) {
|
||||
this.metadata = metadata;
|
||||
this.dimensions = dimensions;
|
||||
this.palette = palette;
|
||||
this.blocks = blocks;
|
||||
this.markers = markers;
|
||||
}
|
||||
|
||||
public void save(@NotNull File file) throws IOException {
|
||||
SchematicData data = new SchematicData();
|
||||
data.format_version = FORMAT_VERSION;
|
||||
data.metadata = metadata;
|
||||
data.dimensions = dimensions;
|
||||
data.palette = palette;
|
||||
data.blocks = blocks;
|
||||
data.markers = markers;
|
||||
data.visuals = visuals;
|
||||
data.logic = logic;
|
||||
|
||||
String json = GSON.toJson(data);
|
||||
|
||||
try (GZIPOutputStream gzip = new GZIPOutputStream(new FileOutputStream(file))) {
|
||||
gzip.write(json.getBytes("UTF-8"));
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static LPSchematic load(@NotNull File file) throws IOException {
|
||||
StringBuilder json = new StringBuilder();
|
||||
|
||||
try (GZIPInputStream gzip = new GZIPInputStream(new FileInputStream(file));
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(gzip, "UTF-8"))) {
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
json.append(line);
|
||||
}
|
||||
}
|
||||
|
||||
SchematicData data = GSON.fromJson(json.toString(), SchematicData.class);
|
||||
|
||||
if (data.format_version != FORMAT_VERSION) {
|
||||
throw new IOException("Unsupported format version: " + data.format_version);
|
||||
}
|
||||
|
||||
LPSchematic schematic = new LPSchematic(
|
||||
data.metadata,
|
||||
data.dimensions,
|
||||
data.palette,
|
||||
data.blocks,
|
||||
data.markers
|
||||
);
|
||||
|
||||
schematic.visuals = data.visuals;
|
||||
schematic.logic = data.logic;
|
||||
|
||||
return schematic;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public List<Block> paste(@NotNull Location origin, @NotNull World world) {
|
||||
List<Block> placedBlocks = new ArrayList<>();
|
||||
|
||||
int width = dimensions.width;
|
||||
int height = dimensions.height;
|
||||
int length = dimensions.length;
|
||||
|
||||
for (int y = 0; y < height; y++) {
|
||||
for (int z = 0; z < length; z++) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
int index = x + (z * width) + (y * width * length);
|
||||
int paletteIndex = blocks[index];
|
||||
|
||||
if (paletteIndex == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String blockDataString = palette.get(paletteIndex);
|
||||
BlockData blockData = parseBlockData(blockDataString);
|
||||
|
||||
if (blockData == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Block block = world.getBlockAt(
|
||||
origin.getBlockX() + x,
|
||||
origin.getBlockY() + y,
|
||||
origin.getBlockZ() + z
|
||||
);
|
||||
|
||||
block.setBlockData(blockData);
|
||||
placedBlocks.add(block);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return placedBlocks;
|
||||
}
|
||||
|
||||
private BlockData parseBlockData(@NotNull String blockDataString) {
|
||||
try {
|
||||
return org.bukkit.Bukkit.createBlockData(blockDataString);
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Material.MAGENTA_GLAZED_TERRACOTTA.createBlockData();
|
||||
}
|
||||
}
|
||||
|
||||
public SchematicMetadata getMetadata() {
|
||||
return metadata;
|
||||
}
|
||||
|
||||
public SchematicDimensions getDimensions() {
|
||||
return dimensions;
|
||||
}
|
||||
|
||||
public SchematicMarkers getMarkers() {
|
||||
return markers;
|
||||
}
|
||||
|
||||
public SchematicVisuals getVisuals() {
|
||||
return visuals;
|
||||
}
|
||||
|
||||
public void setVisuals(SchematicVisuals visuals) {
|
||||
this.visuals = visuals;
|
||||
}
|
||||
|
||||
public SchematicLogic getLogic() {
|
||||
return logic;
|
||||
}
|
||||
|
||||
public void setLogic(SchematicLogic logic) {
|
||||
this.logic = logic;
|
||||
}
|
||||
|
||||
private static class SchematicData {
|
||||
int format_version;
|
||||
SchematicMetadata metadata;
|
||||
SchematicDimensions dimensions;
|
||||
List<String> palette;
|
||||
int[] blocks;
|
||||
SchematicMarkers markers;
|
||||
SchematicVisuals visuals;
|
||||
SchematicLogic logic;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,122 @@
|
|||
package dev.loki.loparkour.schematic.lpschem;
|
||||
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class LPSchematicBuilder {
|
||||
|
||||
private final SchematicMetadata metadata;
|
||||
private final SchematicDimensions dimensions;
|
||||
private final Map<String, Integer> paletteMap;
|
||||
private final List<String> palette;
|
||||
private final int[] blocks;
|
||||
private SchematicMarkers.Vector3i start;
|
||||
private SchematicMarkers.Vector3i end;
|
||||
private final List<SchematicMarkers.Vector3i> checkpoints;
|
||||
|
||||
public LPSchematicBuilder(@NotNull String name, @NotNull String author, double difficulty,
|
||||
int width, int height, int length) {
|
||||
this.metadata = new SchematicMetadata(name, author, difficulty);
|
||||
this.dimensions = new SchematicDimensions(width, height, length);
|
||||
this.paletteMap = new HashMap<>();
|
||||
this.palette = new ArrayList<>();
|
||||
this.blocks = new int[width * height * length];
|
||||
this.checkpoints = new ArrayList<>();
|
||||
|
||||
palette.add("minecraft:air");
|
||||
paletteMap.put("minecraft:air", 0);
|
||||
}
|
||||
|
||||
public LPSchematicBuilder setBlock(int x, int y, int z, @NotNull BlockData blockData) {
|
||||
String blockString = blockData.getAsString();
|
||||
int paletteIndex = getOrAddToPalette(blockString);
|
||||
int index = x + (z * dimensions.width) + (y * dimensions.width * dimensions.length);
|
||||
blocks[index] = paletteIndex;
|
||||
return this;
|
||||
}
|
||||
|
||||
public LPSchematicBuilder setBlock(int x, int y, int z, @NotNull Material material) {
|
||||
return setBlock(x, y, z, material.createBlockData());
|
||||
}
|
||||
|
||||
public LPSchematicBuilder setStart(int x, int y, int z) {
|
||||
this.start = new SchematicMarkers.Vector3i(x, y, z);
|
||||
return this;
|
||||
}
|
||||
|
||||
public LPSchematicBuilder setEnd(int x, int y, int z) {
|
||||
this.end = new SchematicMarkers.Vector3i(x, y, z);
|
||||
return this;
|
||||
}
|
||||
|
||||
public LPSchematicBuilder addCheckpoint(int x, int y, int z) {
|
||||
checkpoints.add(new SchematicMarkers.Vector3i(x, y, z));
|
||||
return this;
|
||||
}
|
||||
|
||||
public LPSchematicBuilder addTag(@NotNull String tag) {
|
||||
metadata.addTag(tag);
|
||||
return this;
|
||||
}
|
||||
|
||||
public LPSchematicBuilder fromWorld(@NotNull World world, @NotNull Location corner1, @NotNull Location corner2) {
|
||||
int minX = Math.min(corner1.getBlockX(), corner2.getBlockX());
|
||||
int minY = Math.min(corner1.getBlockY(), corner2.getBlockY());
|
||||
int minZ = Math.min(corner1.getBlockZ(), corner2.getBlockZ());
|
||||
|
||||
int maxX = Math.max(corner1.getBlockX(), corner2.getBlockX());
|
||||
int maxY = Math.max(corner1.getBlockY(), corner2.getBlockY());
|
||||
int maxZ = Math.max(corner1.getBlockZ(), corner2.getBlockZ());
|
||||
|
||||
for (int y = minY; y <= maxY; y++) {
|
||||
for (int z = minZ; z <= maxZ; z++) {
|
||||
for (int x = minX; x <= maxX; x++) {
|
||||
Block block = world.getBlockAt(x, y, z);
|
||||
if (block.getType() != Material.AIR) {
|
||||
int relX = x - minX;
|
||||
int relY = y - minY;
|
||||
int relZ = z - minZ;
|
||||
setBlock(relX, relY, relZ, block.getBlockData());
|
||||
|
||||
if (block.getType() == Material.LIME_WOOL) {
|
||||
setStart(relX, relY, relZ);
|
||||
} else if (block.getType() == Material.RED_WOOL) {
|
||||
setEnd(relX, relY, relZ);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public LPSchematic build() {
|
||||
if (start == null || end == null) {
|
||||
throw new IllegalStateException("Start and end markers must be set");
|
||||
}
|
||||
|
||||
SchematicMarkers markers = new SchematicMarkers(start, end);
|
||||
checkpoints.forEach(markers::addCheckpoint);
|
||||
|
||||
return new LPSchematic(metadata, dimensions, palette, blocks, markers);
|
||||
}
|
||||
|
||||
private int getOrAddToPalette(@NotNull String blockString) {
|
||||
if (paletteMap.containsKey(blockString)) {
|
||||
return paletteMap.get(blockString);
|
||||
}
|
||||
|
||||
int index = palette.size();
|
||||
palette.add(blockString);
|
||||
paletteMap.put(blockString, index);
|
||||
return index;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
package dev.loki.loparkour.schematic.lpschem;
|
||||
|
||||
import dev.loki.loparkour.LoParkour;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class LPSchematicManager {
|
||||
|
||||
private static final File SCHEMATIC_FOLDER = LoParkour.getInFolder("schematics-new");
|
||||
private final Map<String, LPSchematic> loadedSchematics = new HashMap<>();
|
||||
|
||||
public LPSchematicManager() {
|
||||
if (!SCHEMATIC_FOLDER.exists()) {
|
||||
SCHEMATIC_FOLDER.mkdirs();
|
||||
}
|
||||
}
|
||||
|
||||
public void loadAll() {
|
||||
File[] files = SCHEMATIC_FOLDER.listFiles((dir, name) -> name.endsWith(".lpschem"));
|
||||
|
||||
if (files == null || files.length == 0) {
|
||||
LoParkour.log("No .lpschem files found");
|
||||
return;
|
||||
}
|
||||
|
||||
for (File file : files) {
|
||||
try {
|
||||
LPSchematic schematic = LPSchematic.load(file);
|
||||
String name = schematic.getMetadata().getName();
|
||||
loadedSchematics.put(name, schematic);
|
||||
LoParkour.log("Loaded schematic: " + name);
|
||||
} catch (IOException e) {
|
||||
LoParkour.logging().error("Failed to load schematic: " + file.getName());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
LoParkour.log("Loaded " + loadedSchematics.size() + " schematics");
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public LPSchematic getSchematic(@NotNull String name) {
|
||||
return loadedSchematics.get(name);
|
||||
}
|
||||
|
||||
public void saveSchematic(@NotNull LPSchematic schematic) throws IOException {
|
||||
String fileName = schematic.getMetadata().getName().replaceAll("[^a-zA-Z0-9-_]", "_") + ".lpschem";
|
||||
File file = new File(SCHEMATIC_FOLDER, fileName);
|
||||
schematic.save(file);
|
||||
loadedSchematics.put(schematic.getMetadata().getName(), schematic);
|
||||
LoParkour.log("Saved schematic: " + schematic.getMetadata().getName());
|
||||
}
|
||||
|
||||
public Map<String, LPSchematic> getAllSchematics() {
|
||||
return new HashMap<>(loadedSchematics);
|
||||
}
|
||||
|
||||
public void reload() {
|
||||
loadedSchematics.clear();
|
||||
loadAll();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
package dev.loki.loparkour.schematic.lpschem;
|
||||
|
||||
import dev.efnilite.vilib.schematic.Schematic;
|
||||
import dev.loki.loparkour.LoParkour;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class SchematicConverter {
|
||||
|
||||
public static LPSchematic fromVilib(@NotNull Schematic oldSchematic, @NotNull String name, @NotNull String author) {
|
||||
var dimensions = oldSchematic.getDimensions();
|
||||
int width = (int) dimensions.getX();
|
||||
int height = (int) dimensions.getY();
|
||||
int length = (int) dimensions.getZ();
|
||||
|
||||
LPSchematicBuilder builder = new LPSchematicBuilder(name, author, 0.5, width, height, length);
|
||||
|
||||
var blocks = oldSchematic.getVectorBlockMap();
|
||||
for (var 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;
|
||||
|
||||
try {
|
||||
for (String name : dev.efnilite.vilib.schematic.Schematics.getSchematicNames(LoParkour.getPlugin())) {
|
||||
try {
|
||||
Schematic oldSchematic = dev.efnilite.vilib.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.logging().error("Failed to convert schematic: " + name);
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LoParkour.logging().error("Error during schematic conversion");
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
LoParkour.log("Conversion complete: " + converted + " converted, " + failed + " failed");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package dev.loki.loparkour.schematic.lpschem;
|
||||
|
||||
public class SchematicDimensions {
|
||||
|
||||
public int width;
|
||||
public int height;
|
||||
public int length;
|
||||
|
||||
public SchematicDimensions(int width, int height, int length) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
public int getTotalBlocks() {
|
||||
return width * height * length;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
package dev.loki.loparkour.schematic.lpschem;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class SchematicLogic {
|
||||
|
||||
private List<int[]> ghost_path;
|
||||
|
||||
public SchematicLogic() {
|
||||
this.ghost_path = new ArrayList<>();
|
||||
}
|
||||
|
||||
public List<int[]> getGhostPath() {
|
||||
return ghost_path;
|
||||
}
|
||||
|
||||
public void setGhostPath(List<int[]> ghost_path) {
|
||||
this.ghost_path = ghost_path;
|
||||
}
|
||||
|
||||
public void addGhostPoint(int x, int y, int z) {
|
||||
ghost_path.add(new int[]{x, y, z});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
package dev.loki.loparkour.schematic.lpschem;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class SchematicMarkers {
|
||||
|
||||
private Vector3i start;
|
||||
private Vector3i end;
|
||||
private List<Vector3i> checkpoints;
|
||||
|
||||
public SchematicMarkers(@NotNull Vector3i start, @NotNull Vector3i end) {
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
this.checkpoints = new ArrayList<>();
|
||||
}
|
||||
|
||||
public Vector3i getStart() {
|
||||
return start;
|
||||
}
|
||||
|
||||
public void setStart(Vector3i start) {
|
||||
this.start = start;
|
||||
}
|
||||
|
||||
public Vector3i getEnd() {
|
||||
return end;
|
||||
}
|
||||
|
||||
public void setEnd(Vector3i end) {
|
||||
this.end = end;
|
||||
}
|
||||
|
||||
public List<Vector3i> getCheckpoints() {
|
||||
return checkpoints;
|
||||
}
|
||||
|
||||
public void addCheckpoint(Vector3i checkpoint) {
|
||||
checkpoints.add(checkpoint);
|
||||
}
|
||||
|
||||
public static class Vector3i {
|
||||
public int x;
|
||||
public int y;
|
||||
public int z;
|
||||
|
||||
public Vector3i(int x, int y, int z) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
package dev.loki.loparkour.schematic.lpschem;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class SchematicMetadata {
|
||||
private String name;
|
||||
private String author;
|
||||
private double difficulty;
|
||||
private List<String> tags;
|
||||
|
||||
public SchematicMetadata(@NotNull String name, @NotNull String author, double difficulty) {
|
||||
this.name = name;
|
||||
this.author = author;
|
||||
this.difficulty = difficulty;
|
||||
this.tags = new ArrayList<>();
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getAuthor() {
|
||||
return author;
|
||||
}
|
||||
|
||||
public double getDifficulty() {
|
||||
return difficulty;
|
||||
}
|
||||
|
||||
public List<String> getTags() {
|
||||
return tags;
|
||||
}
|
||||
|
||||
public void addTag(@NotNull String tag) {
|
||||
tags.add(tag);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
package dev.loki.loparkour.schematic.lpschem;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class SchematicVisuals {
|
||||
|
||||
private List<BlockDisplay> displays;
|
||||
private List<ParticleEffect> particles;
|
||||
|
||||
public SchematicVisuals() {
|
||||
this.displays = new ArrayList<>();
|
||||
this.particles = new ArrayList<>();
|
||||
}
|
||||
|
||||
public List<BlockDisplay> getDisplays() {
|
||||
return displays;
|
||||
}
|
||||
|
||||
public void addDisplay(BlockDisplay display) {
|
||||
displays.add(display);
|
||||
}
|
||||
|
||||
public List<ParticleEffect> getParticles() {
|
||||
return particles;
|
||||
}
|
||||
|
||||
public void addParticle(ParticleEffect particle) {
|
||||
particles.add(particle);
|
||||
}
|
||||
|
||||
public static class BlockDisplay {
|
||||
public String type;
|
||||
public String block;
|
||||
public float[] pos;
|
||||
public float[] scale;
|
||||
public Animation animation;
|
||||
|
||||
public BlockDisplay(String block, float[] pos, float[] scale) {
|
||||
this.type = "block";
|
||||
this.block = block;
|
||||
this.pos = pos;
|
||||
this.scale = scale;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Animation {
|
||||
public String type;
|
||||
public String axis;
|
||||
public float speed;
|
||||
|
||||
public Animation(String type, String axis, float speed) {
|
||||
this.type = type;
|
||||
this.axis = axis;
|
||||
this.speed = speed;
|
||||
}
|
||||
}
|
||||
|
||||
public static class ParticleEffect {
|
||||
public String type;
|
||||
public float[] pos;
|
||||
public int amount;
|
||||
public float spread;
|
||||
|
||||
public ParticleEffect(String type, float[] pos, int amount, float spread) {
|
||||
this.type = type;
|
||||
this.pos = pos;
|
||||
this.amount = amount;
|
||||
this.spread = spread;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue