Add advanced Skyblock world generation and clean up existing code.
- Introduced tiered island network generation. - Enhanced player/team-specific world spawning. - Refactored persistent data usage for key workflows. - Added unit tests for circle placement logic.
This commit is contained in:
parent
3e21e35757
commit
e6f5230c58
25 changed files with 559 additions and 208 deletions
|
@ -18,13 +18,19 @@ repositories {
|
|||
name = "sonatype"
|
||||
url = "https://oss.sonatype.org/content/groups/public/"
|
||||
}
|
||||
maven {
|
||||
name = "multiverseMultiverseReleases"
|
||||
url = uri("https://repo.onarandombox.com/multiverse-releases")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly("io.papermc.paper:paper-api:1.21.5-R0.1-SNAPSHOT")
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
||||
|
||||
implementation 'com.github.DigitalSmile:hexagon:v0.2.1'
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
|
||||
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
|
||||
}
|
||||
|
||||
tasks {
|
||||
|
@ -52,6 +58,7 @@ tasks.withType(JavaCompile).configureEach {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
processResources {
|
||||
def props = [version: version]
|
||||
inputs.properties props
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
package com.ncguy.usefulSkyblock;
|
||||
|
||||
import org.digitalsmile.hexgrid.HexagonGrid;
|
||||
import org.digitalsmile.hexgrid.layout.Orientation;
|
||||
import org.digitalsmile.hexgrid.shapes.hexagonal.HexagonalShape;
|
||||
|
||||
public class HexagonalWorld {
|
||||
|
||||
public void Init() {
|
||||
var hexagonGrid = new HexagonGrid.HexagonGridBuilder<>()
|
||||
.shape(new HexagonalShape(5), Orientation.FLAT)
|
||||
.hexagonWidth(150)
|
||||
.build();
|
||||
hexagonGrid.generateHexagons();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
package com.ncguy.usefulSkyblock;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
public final class UsefulSkyblock extends JavaPlugin implements Listener {
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
// Plugin startup logic
|
||||
Bukkit.getPluginManager().registerEvents(this, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
// Plugin shutdown logic
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerJoin(PlayerJoinEvent event) {
|
||||
event.getPlayer().sendMessage(Component.text("Hello, " + event.getPlayer().getName() + "!"));
|
||||
}
|
||||
}
|
|
@ -1,109 +0,0 @@
|
|||
package com.ncguy.usefulSkyblock.command;
|
||||
|
||||
import com.mojang.brigadier.context.CommandContext;
|
||||
import com.mojang.brigadier.tree.LiteralCommandNode;
|
||||
import com.ncguy.usefulSkyblock.StructureRef;
|
||||
import com.ncguy.usefulSkyblock.utils.MathsUtils;
|
||||
import io.papermc.paper.command.brigadier.CommandSourceStack;
|
||||
import io.papermc.paper.command.brigadier.Commands;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.block.structure.Mirror;
|
||||
import org.bukkit.block.structure.StructureRotation;
|
||||
import org.bukkit.structure.Structure;
|
||||
import org.bukkit.util.Vector;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Random;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class SkyblockGenCommand extends AbstractSkyblockCommand {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(SkyblockGenCommand.class);
|
||||
|
||||
private StructureRef[] centralIslands = {
|
||||
new StructureRef(key("classic")),
|
||||
};
|
||||
|
||||
private StructureRef[] t1Islands = {
|
||||
new StructureRef(key("classic-sand")),
|
||||
};
|
||||
|
||||
private StructureRef[] t2Islands = {
|
||||
};
|
||||
|
||||
private StructureRef[] t3Islands = {
|
||||
};
|
||||
|
||||
|
||||
private float playerIslandSpacing = 1024;
|
||||
|
||||
private static final int T1_ISLAND_SLOTS = 8;
|
||||
private static final int T2_ISLAND_SLOTS = 16;
|
||||
private static final int T3_ISLAND_SLOTS = 32;
|
||||
|
||||
private static float T1_ISLAND_SPACING = T1_ISLAND_SLOTS << 3;
|
||||
private static float T2_ISLAND_SPACING = T2_ISLAND_SLOTS << 3;
|
||||
private static float T3_ISLAND_SPACING = T3_ISLAND_SLOTS << 3;
|
||||
|
||||
private Location placeStructureAtLocation(Structure structure, Location loc) {
|
||||
Vector extents = structure.getSize().clone().multiply(0.5);
|
||||
loc.subtract(extents);
|
||||
|
||||
structure.place(loc, true, randomEnum(StructureRotation.class), randomEnum(Mirror.class), 0, 1, new Random());
|
||||
return loc;
|
||||
}
|
||||
|
||||
private <T extends Enum<?>> T randomEnum(Class<T> enumCls) {
|
||||
assert(enumCls.isEnum());
|
||||
return randomElement(enumCls.getEnumConstants());
|
||||
}
|
||||
|
||||
private <T> T randomElement(T[] array) {
|
||||
int idx = (int) (Math.random() * array.length);
|
||||
return array[idx];
|
||||
}
|
||||
|
||||
private Location generateIslandNetwork(Location origin) {
|
||||
Location centralIslandSpawnLoc = placeStructureAtLocation(randomElement(centralIslands), origin.clone());
|
||||
|
||||
int[] t1Slots = MathsUtils.sampleUniqueInts(T1_ISLAND_SLOTS, t1Islands.length);
|
||||
// int[] t2Slots = MathsUtils.sampleUniqueInts(T2_ISLAND_SLOTS, t2Islands.length);
|
||||
// int[] t3Slots = MathsUtils.sampleUniqueInts(T3_ISLAND_SLOTS, t3Islands.length);
|
||||
double t1Step = 360.0 / T1_ISLAND_SLOTS;
|
||||
|
||||
Supplier<Float> yNoiseFunc = () -> 0f;
|
||||
|
||||
for (int i = 0; i < t1Islands.length; i++) {
|
||||
StructureRef island = t1Islands[i];
|
||||
int slot = t1Slots[i];
|
||||
double angle = t1Step * slot;
|
||||
double x = Math.cos(angle) * T1_ISLAND_SPACING;
|
||||
double z = Math.sin(angle) * T1_ISLAND_SPACING;
|
||||
double y = yNoiseFunc.get();
|
||||
Location pos = origin.clone().add(x, y, z);
|
||||
placeStructureAtLocation(island, pos);
|
||||
}
|
||||
return centralIslandSpawnLoc;
|
||||
}
|
||||
|
||||
public int executeGenerate(CommandContext<CommandSourceStack> ctx) {
|
||||
ctx.getSource().getExecutor().sendMessage("Generating skyblock world for " + ctx.getSource().getExecutor().getName() + "...");
|
||||
|
||||
// TODO Add team-specific offsets
|
||||
Location originLoc = new Location(getOverworld(), 0, 128, 0);
|
||||
Location tpLoc = generateIslandNetwork(originLoc);
|
||||
ctx.getSource().getExecutor().teleport(tpLoc);
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static LiteralCommandNode<CommandSourceStack> create() {
|
||||
var root = Commands.literal("skyblock");
|
||||
|
||||
var cmd = Get(SkyblockGenCommand.class);
|
||||
root.then(Commands.literal("generate").executes(cmd::executeGenerate));
|
||||
|
||||
return root.build();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package com.ncguy.usefulSkyblock;
|
||||
package com.ncguy.usefulskyblock;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
104
src/main/java/com/ncguy/usefulskyblock/UsefulSkyblock.java
Normal file
104
src/main/java/com/ncguy/usefulskyblock/UsefulSkyblock.java
Normal file
|
@ -0,0 +1,104 @@
|
|||
package com.ncguy.usefulskyblock;
|
||||
|
||||
import com.ncguy.usefulskyblock.world.PortalHandler;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.block.structure.Mirror;
|
||||
import org.bukkit.block.structure.StructureRotation;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.bukkit.event.player.PlayerTeleportEvent;
|
||||
import org.bukkit.event.server.ServerLoadEvent;
|
||||
import org.bukkit.event.world.WorldLoadEvent;
|
||||
import org.bukkit.persistence.PersistentDataContainer;
|
||||
import org.bukkit.persistence.PersistentDataType;
|
||||
import org.bukkit.plugin.PluginManager;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
public final class UsefulSkyblock extends JavaPlugin implements Listener {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(UsefulSkyblock.class);
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
// Plugin startup logic
|
||||
PluginManager pluginManager = Bukkit.getPluginManager();
|
||||
pluginManager.registerEvents(this, this);
|
||||
pluginManager.registerEvents(new PortalHandler(), this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
// Plugin shutdown logic
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerJoin(PlayerJoinEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
player.sendMessage(Component.text("Hello, " + player.getName() + "!"));
|
||||
PersistentDataContainer pdc = player.getPersistentDataContainer();
|
||||
NamespacedKey initKey = new NamespacedKey(this, "player_init");
|
||||
if (pdc.has(initKey))
|
||||
return;
|
||||
|
||||
NamespacedKey worldKey = new NamespacedKey(this, "void");
|
||||
World world = Bukkit.getWorld(worldKey);
|
||||
player.teleport(world.getSpawnLocation(), PlayerTeleportEvent.TeleportCause.PLUGIN);
|
||||
pdc.set(initKey, PersistentDataType.BOOLEAN, true);
|
||||
player.sendMessage(Component.text("This is your first time playing on this server."));
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onServerLoad(ServerLoadEvent event) {
|
||||
if(event.getType() != ServerLoadEvent.LoadType.STARTUP)
|
||||
return;
|
||||
|
||||
World world = Bukkit.getWorld(new NamespacedKey(this, "void"));
|
||||
if (world == null)
|
||||
return;
|
||||
|
||||
PersistentDataContainer pdc = world.getPersistentDataContainer();
|
||||
NamespacedKey initKey = new NamespacedKey(this, "world_init");
|
||||
if(pdc.has(initKey))
|
||||
return;
|
||||
pdc.set(initKey, PersistentDataType.BOOLEAN, true);
|
||||
|
||||
log.info("Generating spawn point for world {}", world.getName());
|
||||
StructureRef spawn = new StructureRef(new NamespacedKey(this, "spawn"));
|
||||
log.info("Entities in spawn: {}", spawn.getEntities().size());
|
||||
log.info("Palettes in spawn: {}", spawn.getPalettes().size());
|
||||
log.info("Palette count in spawn: {}", spawn.getPaletteCount());
|
||||
log.info("spawn.getSize(): {}", spawn.getSize());
|
||||
spawn.place(new Location(world, 0, 64, 0), true, StructureRotation.NONE, Mirror.NONE, 0, 1, new Random(System.currentTimeMillis()));
|
||||
world.setSpawnLocation(0, 64, 0);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onWorldLoad(WorldLoadEvent event) {
|
||||
World world = event.getWorld();
|
||||
if (!world.getKey().equals(new NamespacedKey(this, "void")))
|
||||
return;
|
||||
|
||||
PersistentDataContainer pdc = world.getPersistentDataContainer();
|
||||
NamespacedKey initKey = new NamespacedKey(this, "world_init");
|
||||
if(pdc.has(initKey))
|
||||
return;
|
||||
pdc.set(initKey, PersistentDataType.BOOLEAN, true);
|
||||
|
||||
log.info("Generating spawn point for world {}", world.getName());
|
||||
StructureRef spawn = new StructureRef(new NamespacedKey(this, "spawn"));
|
||||
spawn.place(new Location(world, 0, 64, 0), true, StructureRotation.NONE, Mirror.NONE, 0, 1, new Random(System.currentTimeMillis()));
|
||||
world.setSpawnLocation(0, 64, 0);
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -1,8 +1,8 @@
|
|||
package com.ncguy.usefulSkyblock;
|
||||
package com.ncguy.usefulskyblock;
|
||||
|
||||
import com.ncguy.usefulSkyblock.command.SkyblockAdminCommand;
|
||||
import com.ncguy.usefulSkyblock.command.SkyblockGenCommand;
|
||||
import com.ncguy.usefulSkyblock.hexagon.HexagonRenderer;
|
||||
import com.ncguy.usefulskyblock.command.SkyblockAdminCommand;
|
||||
import com.ncguy.usefulskyblock.command.SkyblockGenCommand;
|
||||
import com.ncguy.usefulskyblock.hexagon.HexagonRenderer;
|
||||
import io.papermc.paper.datapack.DatapackRegistrar;
|
||||
import io.papermc.paper.datapack.DiscoveredDatapack;
|
||||
import io.papermc.paper.plugin.bootstrap.BootstrapContext;
|
|
@ -1,4 +1,4 @@
|
|||
package com.ncguy.usefulSkyblock;
|
||||
package com.ncguy.usefulskyblock;
|
||||
|
||||
import io.papermc.paper.plugin.loader.PluginClasspathBuilder;
|
||||
import io.papermc.paper.plugin.loader.PluginLoader;
|
|
@ -1,4 +1,4 @@
|
|||
package com.ncguy.usefulSkyblock.command;
|
||||
package com.ncguy.usefulskyblock.command;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.NamespacedKey;
|
|
@ -1,4 +1,4 @@
|
|||
package com.ncguy.usefulSkyblock.command;
|
||||
package com.ncguy.usefulskyblock.command;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
|
@ -1,42 +1,33 @@
|
|||
package com.ncguy.usefulSkyblock.command;
|
||||
package com.ncguy.usefulskyblock.command;
|
||||
|
||||
import com.mojang.brigadier.StringReader;
|
||||
import com.mojang.brigadier.arguments.ArgumentType;
|
||||
import com.mojang.brigadier.arguments.IntegerArgumentType;
|
||||
import com.mojang.brigadier.arguments.StringArgumentType;
|
||||
import com.mojang.brigadier.context.CommandContext;
|
||||
import com.mojang.brigadier.context.StringRange;
|
||||
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||
import com.mojang.brigadier.suggestion.Suggestion;
|
||||
import com.mojang.brigadier.suggestion.Suggestions;
|
||||
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
|
||||
import com.mojang.brigadier.tree.LiteralCommandNode;
|
||||
import com.ncguy.usefulSkyblock.StructureRef;
|
||||
import com.ncguy.usefulskyblock.StructureRef;
|
||||
import com.ncguy.usefulskyblock.utils.BoxVisualizer;
|
||||
import io.papermc.paper.command.brigadier.CommandSourceStack;
|
||||
import io.papermc.paper.command.brigadier.Commands;
|
||||
import io.papermc.paper.command.brigadier.argument.ArgumentTypes;
|
||||
import io.papermc.paper.command.brigadier.argument.CustomArgumentType;
|
||||
import io.papermc.paper.math.BlockPosition;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.structure.Mirror;
|
||||
import org.bukkit.block.structure.StructureRotation;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.persistence.PersistentDataContainer;
|
||||
import org.bukkit.persistence.PersistentDataType;
|
||||
import org.bukkit.structure.Structure;
|
||||
import org.bukkit.structure.StructureManager;
|
||||
import org.bukkit.util.BlockVector;
|
||||
import org.bukkit.util.RayTraceResult;
|
||||
import org.bukkit.util.Vector;
|
||||
import org.codehaus.plexus.util.cli.Arg;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.xml.stream.events.Namespace;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
|
@ -45,6 +36,8 @@ public class SkyblockAdminCommand extends AbstractSkyblockCommand {
|
|||
|
||||
private static final Logger log = LoggerFactory.getLogger(SkyblockAdminCommand.class);
|
||||
|
||||
private BoxVisualizer box = new BoxVisualizer();
|
||||
|
||||
public int executeStructuresList(CommandContext<CommandSourceStack> ctx) {
|
||||
|
||||
StructureManager structureManager = getServer().getStructureManager();
|
||||
|
@ -131,7 +124,19 @@ public class SkyblockAdminCommand extends AbstractSkyblockCommand {
|
|||
RayTraceResult rayTraceResult = p.rayTraceBlocks(8);
|
||||
|
||||
Vector hitPosition = rayTraceResult.getHitPosition();
|
||||
p.getPersistentDataContainer().set(key("_p0"), PersistentDataType.INTEGER_ARRAY, new int[]{hitPosition.getBlockX(), hitPosition.getBlockY(), hitPosition.getBlockZ()});
|
||||
PersistentDataContainer pdc = p.getPersistentDataContainer();
|
||||
pdc.set(key("_p0"), PersistentDataType.INTEGER_ARRAY, new int[]{hitPosition.getBlockX(), hitPosition.getBlockY(), hitPosition.getBlockZ()});
|
||||
|
||||
Location p0 = new Location(p.getWorld(), hitPosition.getX(), hitPosition.getY(), hitPosition.getZ());
|
||||
Location p1 = p0.clone();
|
||||
|
||||
NamespacedKey p1Key = key("_p1");
|
||||
if(pdc.has(p1Key, PersistentDataType.INTEGER_ARRAY)) {
|
||||
int[] ints = pdc.get(p1Key, PersistentDataType.INTEGER_ARRAY);
|
||||
p1.set(ints[0], ints[1], ints[2]);
|
||||
}
|
||||
|
||||
box.createBox(p0, p1);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
@ -146,7 +151,20 @@ public class SkyblockAdminCommand extends AbstractSkyblockCommand {
|
|||
RayTraceResult rayTraceResult = p.rayTraceBlocks(8);
|
||||
|
||||
Vector hitPosition = rayTraceResult.getHitPosition();
|
||||
p.getPersistentDataContainer().set(key("_p1"), PersistentDataType.INTEGER_ARRAY, new int[]{hitPosition.getBlockX(), hitPosition.getBlockY(), hitPosition.getBlockZ()});
|
||||
PersistentDataContainer pdc = p.getPersistentDataContainer();
|
||||
pdc.set(key("_p1"), PersistentDataType.INTEGER_ARRAY, new int[]{hitPosition.getBlockX(), hitPosition.getBlockY(), hitPosition.getBlockZ()});
|
||||
|
||||
Location p1 = new Location(p.getWorld(), hitPosition.getX(), hitPosition.getY(), hitPosition.getZ());
|
||||
Location p0 = p1.clone();
|
||||
|
||||
NamespacedKey p0Key = key("_p0");
|
||||
if(pdc.has(p0Key, PersistentDataType.INTEGER_ARRAY)) {
|
||||
int[] ints = pdc.get(p0Key, PersistentDataType.INTEGER_ARRAY);
|
||||
p0.set(ints[0], ints[1], ints[2]);
|
||||
}
|
||||
|
||||
box.createBox(p0, p1);
|
||||
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
@ -160,8 +178,9 @@ public class SkyblockAdminCommand extends AbstractSkyblockCommand {
|
|||
if (!(executor instanceof Player)) return 0;
|
||||
Player p = (Player) executor;
|
||||
|
||||
int[] p0 = p.getPersistentDataContainer().get(key("_p0"), PersistentDataType.INTEGER_ARRAY);
|
||||
int[] p1 = p.getPersistentDataContainer().get(key("_p1"), PersistentDataType.INTEGER_ARRAY);
|
||||
PersistentDataContainer pdc = p.getPersistentDataContainer();
|
||||
int[] p0 = pdc.get(key("_p0"), PersistentDataType.INTEGER_ARRAY);
|
||||
int[] p1 = pdc.get(key("_p1"), PersistentDataType.INTEGER_ARRAY);
|
||||
|
||||
if (p0 == null) {
|
||||
executor.sendMessage("No P0 found");
|
||||
|
@ -190,6 +209,9 @@ public class SkyblockAdminCommand extends AbstractSkyblockCommand {
|
|||
f.getParentFile().mkdirs();
|
||||
getServer().getStructureManager().saveStructure(f, structure);
|
||||
log.info("Saved structure {} to {}", name, f.getAbsolutePath());
|
||||
pdc.remove(key("_p0"));
|
||||
pdc.remove(key("_p1"));
|
||||
box.cleanup();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
|
@ -0,0 +1,198 @@
|
|||
package com.ncguy.usefulskyblock.command;
|
||||
|
||||
import com.mojang.brigadier.context.CommandContext;
|
||||
import com.mojang.brigadier.tree.LiteralCommandNode;
|
||||
import com.ncguy.usefulskyblock.StructureRef;
|
||||
import com.ncguy.usefulskyblock.utils.MathsUtils;
|
||||
import io.papermc.paper.command.brigadier.CommandSourceStack;
|
||||
import io.papermc.paper.command.brigadier.Commands;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.block.structure.Mirror;
|
||||
import org.bukkit.block.structure.StructureRotation;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.persistence.PersistentDataContainer;
|
||||
import org.bukkit.persistence.PersistentDataType;
|
||||
import org.bukkit.scoreboard.Scoreboard;
|
||||
import org.bukkit.scoreboard.ScoreboardManager;
|
||||
import org.bukkit.scoreboard.Team;
|
||||
import org.bukkit.structure.Structure;
|
||||
import org.bukkit.util.Vector;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Random;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class SkyblockGenCommand extends AbstractSkyblockCommand {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(SkyblockGenCommand.class);
|
||||
|
||||
private StructureRef[] centralIslands = {
|
||||
new StructureRef(key("classic")),
|
||||
};
|
||||
|
||||
private StructureRef[] t1Islands = {
|
||||
new StructureRef(key("classic-sand")),
|
||||
new StructureRef(key("classic-sand")),
|
||||
new StructureRef(key("classic-sand")),
|
||||
new StructureRef(key("classic-sand")),
|
||||
};
|
||||
|
||||
private StructureRef[] t2Islands = {
|
||||
new StructureRef(key("classic")),
|
||||
new StructureRef(key("classic-sand")),
|
||||
new StructureRef(key("classic")),
|
||||
new StructureRef(key("classic-sand")),
|
||||
new StructureRef(key("classic")),
|
||||
new StructureRef(key("classic-sand")),
|
||||
new StructureRef(key("classic")),
|
||||
new StructureRef(key("classic-sand")),
|
||||
};
|
||||
|
||||
private StructureRef[] t3Islands = {
|
||||
new StructureRef(key("classic")),
|
||||
new StructureRef(key("classic-sand")),
|
||||
new StructureRef(key("classic")),
|
||||
new StructureRef(key("classic-sand")),
|
||||
new StructureRef(key("classic")),
|
||||
new StructureRef(key("classic-sand")),
|
||||
new StructureRef(key("classic")),
|
||||
new StructureRef(key("classic-sand")),
|
||||
new StructureRef(key("classic")),
|
||||
new StructureRef(key("classic-sand")),
|
||||
new StructureRef(key("classic")),
|
||||
new StructureRef(key("classic-sand")),
|
||||
new StructureRef(key("classic")),
|
||||
new StructureRef(key("classic-sand")),
|
||||
new StructureRef(key("classic")),
|
||||
new StructureRef(key("classic-sand")),
|
||||
};
|
||||
|
||||
|
||||
private float playerIslandSpacing = 1024;
|
||||
|
||||
private static final int T1_ISLAND_SLOTS = 8;
|
||||
private static final int T2_ISLAND_SLOTS = 24;
|
||||
private static final int T3_ISLAND_SLOTS = 48;
|
||||
|
||||
private static float T1_ISLAND_SPACING = T1_ISLAND_SLOTS << 3;
|
||||
private static float T2_ISLAND_SPACING = T2_ISLAND_SLOTS << 3;
|
||||
private static float T3_ISLAND_SPACING = T3_ISLAND_SLOTS << 3;
|
||||
|
||||
private Location placeStructureAtLocation(Structure structure, Location loc) {
|
||||
Vector extents = structure.getSize().clone().multiply(0.5);
|
||||
loc.subtract(extents);
|
||||
|
||||
structure.place(loc, true, randomEnum(StructureRotation.class), randomEnum(Mirror.class), 0, 1, new Random());
|
||||
return loc;
|
||||
}
|
||||
|
||||
private <T extends Enum<?>> T randomEnum(Class<T> enumCls) {
|
||||
assert(enumCls.isEnum());
|
||||
return randomElement(enumCls.getEnumConstants());
|
||||
}
|
||||
|
||||
private <T> T randomElement(T[] array) {
|
||||
int idx = (int) (Math.random() * array.length);
|
||||
return array[idx];
|
||||
}
|
||||
|
||||
private Location generateIslandNetwork(Location origin) {
|
||||
Location centralIslandSpawnLoc = placeStructureAtLocation(randomElement(centralIslands), origin.clone());
|
||||
|
||||
int[] t1Slots = MathsUtils.sampleUniqueInts(T1_ISLAND_SLOTS, t1Islands.length);
|
||||
int[] t2Slots = MathsUtils.sampleUniqueInts(T2_ISLAND_SLOTS, t2Islands.length);
|
||||
int[] t3Slots = MathsUtils.sampleUniqueInts(T3_ISLAND_SLOTS, t3Islands.length);
|
||||
double t1Step = 360.0 / T1_ISLAND_SLOTS;
|
||||
double t2Step = 360.0 / T2_ISLAND_SLOTS;
|
||||
double t3Step = 360.0 / T3_ISLAND_SLOTS;
|
||||
|
||||
Supplier<Float> yNoiseFunc = () -> 0f;
|
||||
|
||||
extracted(origin, t1Islands, T1_ISLAND_SPACING, t1Slots, t1Step, yNoiseFunc);
|
||||
extracted(origin, t2Islands, T2_ISLAND_SPACING, t2Slots, t2Step, yNoiseFunc);
|
||||
extracted(origin, t3Islands, T3_ISLAND_SPACING, t3Slots, t3Step, yNoiseFunc);
|
||||
return centralIslandSpawnLoc;
|
||||
}
|
||||
|
||||
private void extracted(Location origin, StructureRef[] islands, float islandSpacing, int[] slots, double step, Supplier<Float> yNoiseFunc) {
|
||||
for (int i = 0; i < islands.length; i++) {
|
||||
StructureRef island = islands[i];
|
||||
int slot = slots[i];
|
||||
double angle = step * slot;
|
||||
double x = Math.cos(angle) * islandSpacing;
|
||||
double z = Math.sin(angle) * islandSpacing;
|
||||
double y = yNoiseFunc.get();
|
||||
Location pos = origin.clone().add(x, y, z);
|
||||
placeStructureAtLocation(island, pos);
|
||||
}
|
||||
}
|
||||
|
||||
public int executeGenerate(CommandContext<CommandSourceStack> ctx) {
|
||||
Entity executor = ctx.getSource().getExecutor();
|
||||
if(!(executor instanceof Player))
|
||||
return 0;
|
||||
|
||||
World overworld = getServer().getWorld(new NamespacedKey("minecraft", "overworld"));
|
||||
|
||||
PersistentDataContainer pdc = executor.getPersistentDataContainer();
|
||||
|
||||
ScoreboardManager scoreboardManager = getServer().getScoreboardManager();
|
||||
Scoreboard scoreboard = scoreboardManager.getMainScoreboard();
|
||||
Player player = (Player) executor;
|
||||
Team playerTeam = scoreboard.getPlayerTeam(player);
|
||||
if(playerTeam == null) {
|
||||
executor.sendMessage("Not part of a team, can't generate skyblock world");
|
||||
return 0;
|
||||
}
|
||||
|
||||
executor.sendMessage("Generating skyblock world for " + playerTeam.getName() + "...");
|
||||
PersistentDataContainer worldPDC = overworld.getPersistentDataContainer();
|
||||
|
||||
int count = 0;
|
||||
if(worldPDC.has(key("skyblock.team.count"), PersistentDataType.INTEGER)) {
|
||||
//noinspection DataFlowIssue
|
||||
count = worldPDC.get(key("skyblock.team.count"), PersistentDataType.INTEGER);
|
||||
}
|
||||
|
||||
Location originLoc;
|
||||
if(count == 0) {
|
||||
originLoc = new Location(overworld, 0, 96, 0);
|
||||
}else{
|
||||
int ring = MathsUtils.getRing(count);
|
||||
int slot = MathsUtils.getSlot(count);
|
||||
|
||||
int numSlots = MathsUtils.getSlotsInRing(ring);
|
||||
|
||||
float step = numSlots / 360f;
|
||||
float angle = slot * step;
|
||||
float x = (float) Math.cos(angle) * playerIslandSpacing;
|
||||
float y = (float) Math.sin(angle) * playerIslandSpacing;
|
||||
originLoc = new Location(overworld, x, 96, y);
|
||||
}
|
||||
|
||||
Location tpLoc = generateIslandNetwork(originLoc);
|
||||
|
||||
// TODO Sanitize playerTeam.getName()
|
||||
worldPDC.set(key("skyblock.team." + playerTeam.getName()), PersistentDataType.INTEGER_ARRAY, new int[]{tpLoc.getBlockX(), tpLoc.getBlockY(), tpLoc.getBlockZ()});
|
||||
worldPDC.set(key("skyblock.team.count"), PersistentDataType.INTEGER, count + 1);
|
||||
|
||||
executor.teleport(tpLoc);
|
||||
pdc.set(key("island.home.loc"), PersistentDataType.INTEGER_ARRAY, new int[]{tpLoc.getBlockX(), tpLoc.getBlockY(), tpLoc.getBlockZ()});
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static LiteralCommandNode<CommandSourceStack> create() {
|
||||
var root = Commands.literal("skyblock");
|
||||
|
||||
var cmd = Get(SkyblockGenCommand.class);
|
||||
root.then(Commands.literal("generate").executes(cmd::executeGenerate));
|
||||
|
||||
return root.build();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package com.ncguy.usefulSkyblock.hexagon;
|
||||
package com.ncguy.usefulskyblock.hexagon;
|
||||
|
||||
import com.mojang.brigadier.arguments.IntegerArgumentType;
|
||||
import com.mojang.brigadier.tree.LiteralCommandNode;
|
|
@ -1,5 +1,6 @@
|
|||
package com.ncguy.usefulSkyblock.utils;
|
||||
package com.ncguy.usefulskyblock.utils;
|
||||
|
||||
import io.papermc.paper.entity.LookAnchor;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.World;
|
||||
|
@ -32,12 +33,12 @@ public class BoxVisualizer {
|
|||
}
|
||||
|
||||
// Get min and max coordinates
|
||||
double minX = Math.min(corner1.getX(), corner2.getX());
|
||||
double minY = Math.min(corner1.getY(), corner2.getY());
|
||||
double minZ = Math.min(corner1.getZ(), corner2.getZ());
|
||||
double maxX = Math.max(corner1.getX(), corner2.getX());
|
||||
double maxY = Math.max(corner1.getY(), corner2.getY());
|
||||
double maxZ = Math.max(corner1.getZ(), corner2.getZ());
|
||||
double minX = Math.min(corner1.getBlockX(), corner2.getBlockX());
|
||||
double minY = Math.min(corner1.getBlockY(), corner2.getBlockY());
|
||||
double minZ = Math.min(corner1.getBlockZ(), corner2.getBlockZ());
|
||||
double maxX = Math.max(corner1.getBlockX(), corner2.getBlockX());
|
||||
double maxY = Math.max(corner1.getBlockY(), corner2.getBlockY());
|
||||
double maxZ = Math.max(corner1.getBlockZ(), corner2.getBlockZ());
|
||||
|
||||
// Create the 12 edges of the box
|
||||
// Bottom rectangle
|
||||
|
@ -58,6 +59,31 @@ public class BoxVisualizer {
|
|||
createEdge(world, minX, minY, maxZ, minX, maxY, maxZ); // Back left
|
||||
createEdge(world, maxX, minY, maxZ, maxX, maxY, maxZ); // Back right
|
||||
|
||||
Location center = new Location(world, (minX + maxX) / 2, (minY + maxY) / 2, (minZ + maxZ) / 2);
|
||||
|
||||
Location min = new Location(world, minX, minY, minZ);
|
||||
|
||||
BlockDisplay glass = world.spawn(min, BlockDisplay.class, entity -> {
|
||||
|
||||
Vector3f delta = new Vector3f((float) (maxX - minX), (float) (maxY - minY), (float) (maxZ - minZ));
|
||||
|
||||
entity.setBlock(Material.GLASS.createBlockData());
|
||||
// Create transformation
|
||||
Transformation transformation = new Transformation(
|
||||
new Vector3f(0, 0, 0),
|
||||
new AxisAngle4f(0, 0, 0, 1),
|
||||
delta,
|
||||
new AxisAngle4f(0, 0, 0, 1)
|
||||
);
|
||||
|
||||
entity.setTransformation(transformation);
|
||||
entity.setBrightness(new Display.Brightness(15, 15)); // Full brightness
|
||||
entity.setViewRange(64f); // Visible up to 64 blocks away
|
||||
entity.setPersistent(false); // Will be removed when chunk unloads
|
||||
});
|
||||
|
||||
displays.add(glass);
|
||||
|
||||
return new ArrayList<>(displays);
|
||||
}
|
||||
|
||||
|
@ -86,19 +112,17 @@ public class BoxVisualizer {
|
|||
double dy = y2 - y1;
|
||||
double dz = z2 - z1;
|
||||
|
||||
// Calculate rotation angles (in radians)
|
||||
double yaw = Math.atan2(dz, dx);
|
||||
double pitch = Math.atan2(dy, Math.sqrt(dx * dx + dz * dz));
|
||||
|
||||
// Create transformation
|
||||
Transformation transformation = new Transformation(
|
||||
new Vector3f(0, 0, 0), // Translation (already at center)
|
||||
new AxisAngle4f((float)pitch, 0, 1, 0), // Pitch rotation
|
||||
new Vector3f((float)length, LINE_THICKNESS, LINE_THICKNESS), // Scale
|
||||
new AxisAngle4f(0, 0, 0, 1) // No additional rotation
|
||||
new Vector3f(0, 0, 0),
|
||||
new AxisAngle4f(0, 0, 0, 1),
|
||||
new Vector3f(LINE_THICKNESS, LINE_THICKNESS, (float) length),
|
||||
new AxisAngle4f(0, 0, 0, 1)
|
||||
);
|
||||
|
||||
entity.setTransformation(transformation);
|
||||
entity.lookAt(centerX + dx, centerY + dy, centerZ + dz, LookAnchor.FEET);
|
||||
entity.setBrightness(new Display.Brightness(15, 15)); // Full brightness
|
||||
entity.setViewRange(64f); // Visible up to 64 blocks away
|
||||
entity.setPersistent(false); // Will be removed when chunk unloads
|
|
@ -1,9 +1,6 @@
|
|||
package com.ncguy.usefulSkyblock.utils;
|
||||
package com.ncguy.usefulskyblock.utils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
public class MathsUtils {
|
||||
|
@ -45,5 +42,32 @@ public class MathsUtils {
|
|||
return result;
|
||||
}
|
||||
|
||||
public static int getRing(int count) {
|
||||
if(count == 0)
|
||||
return 0;
|
||||
double log = Math.log(count) / Math.log(2);
|
||||
log /= 2;
|
||||
return (int) Math.max(1, Math.ceil(log));
|
||||
}
|
||||
|
||||
public static int getSlot(int count) {
|
||||
if(count == 0)
|
||||
return 0;
|
||||
|
||||
// TODO make respect value from getSlotsInRing
|
||||
// Count exceeding 12 will cause the tests to fail,
|
||||
// as the slot returned from here exceeds getSlotsInRing
|
||||
int ring = getRing(count);
|
||||
int base = ring << (ring+1);
|
||||
int diff = -(count - base);
|
||||
return getSlotsInRing(ring)-(diff+1);
|
||||
}
|
||||
|
||||
public static int getSlotsInRing(int ring) {
|
||||
if(ring == 0)
|
||||
return 1;
|
||||
|
||||
return ring << 2;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package com.ncguy.usefulskyblock.world;
|
||||
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.world.PortalCreateEvent;
|
||||
|
||||
public class PortalHandler implements Listener {
|
||||
|
||||
@EventHandler
|
||||
public void onPortalCreate(PortalCreateEvent event) {
|
||||
if(event.getReason() != PortalCreateEvent.CreateReason.FIRE)
|
||||
return;
|
||||
|
||||
Entity entity = event.getEntity();
|
||||
if(entity == null)
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"type": "minecraft:overworld",
|
||||
"generator": {
|
||||
"type": "flat",
|
||||
"settings":{
|
||||
"layers": [],
|
||||
"biome": "minecraft:void",
|
||||
"structure_overrides": []
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"type": "minecraft:the_nether",
|
||||
"generator": {
|
||||
"type": "flat",
|
||||
"settings":{
|
||||
"layers": [],
|
||||
"biome": "minecraft:void",
|
||||
"structure_overrides": []
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,10 +1,15 @@
|
|||
{
|
||||
"type": "minecraft:overworld",
|
||||
"type": "usefulskyblock:skyblock",
|
||||
"generator": {
|
||||
"type": "flat",
|
||||
"settings":{
|
||||
"layers": [],
|
||||
"biome": "minecraft:void",
|
||||
"layers": [
|
||||
{
|
||||
"block": "minecraft:grass",
|
||||
"height": 64
|
||||
}
|
||||
],
|
||||
"biome": "minecraft:plains",
|
||||
"structure_overrides": []
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"ultrawarm": false,
|
||||
"natural": true,
|
||||
"coordinate_scale": 1,
|
||||
"has_skylight": true,
|
||||
"has_ceiling": false,
|
||||
"piglin_safe": false,
|
||||
"infiniburn": "#minecraft:infiniburn_overworld",
|
||||
"ambient_light": 0,
|
||||
"bed_works": true,
|
||||
"respawn_anchor_works": true,
|
||||
"has_raids": true,
|
||||
"logical_height": 256,
|
||||
"min_y": 0,
|
||||
"height": 256,
|
||||
"effects": "minecraft:overworld",
|
||||
"has_portals": true,
|
||||
"monster_spawn_light_level": {
|
||||
"type": "minecraft:uniform",
|
||||
"min_inclusive": 0,
|
||||
"max_inclusive": 7
|
||||
},
|
||||
"monster_spawn_block_light_limit": 0
|
||||
}
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -1,12 +1,12 @@
|
|||
name: UsefulSkyblock
|
||||
version: '1.0-SNAPSHOT'
|
||||
main: com.ncguy.usefulSkyblock.UsefulSkyblock
|
||||
main: com.ncguy.usefulskyblock.UsefulSkyblock
|
||||
description: A useful skyblock plugin
|
||||
api-version: '1.21'
|
||||
bootstrapper: com.ncguy.usefulSkyblock.UsefulSkyblockBootstrap
|
||||
loader: com.ncguy.usefulSkyblock.UsefulSkyblockLoader
|
||||
bootstrapper: com.ncguy.usefulskyblock.UsefulSkyblockBootstrap
|
||||
loader: com.ncguy.usefulskyblock.UsefulSkyblockLoader
|
||||
|
||||
data-pack:
|
||||
load: true
|
||||
name: usefulskyblock
|
||||
description: "UsefulSkyblock Structures"
|
||||
description: "UsefulSkyblock"
|
57
src/test/java/com/ncguy/usefulskyblock/SimpleTests.java
Normal file
57
src/test/java/com/ncguy/usefulskyblock/SimpleTests.java
Normal file
|
@ -0,0 +1,57 @@
|
|||
package com.ncguy.usefulskyblock;
|
||||
|
||||
import com.ncguy.usefulskyblock.utils.MathsUtils;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class SimpleTests {
|
||||
|
||||
public static class Datum {
|
||||
public int count;
|
||||
public int ring;
|
||||
public int slot;
|
||||
|
||||
public Datum(int count, int ring, int slot) {
|
||||
this.count = count;
|
||||
this.ring = ring;
|
||||
this.slot = slot;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCirclePlacement() {
|
||||
Datum[] data = new Datum[] {
|
||||
new Datum(0, 0, 0),
|
||||
new Datum(1, 1, 0),
|
||||
new Datum(2, 1, 1),
|
||||
new Datum(3, 1, 2),
|
||||
new Datum(4, 1, 3),
|
||||
new Datum(5, 2, 0),
|
||||
new Datum(6, 2, 1),
|
||||
new Datum(7, 2, 2),
|
||||
new Datum(8, 2, 3),
|
||||
new Datum(9, 2, 4),
|
||||
new Datum(10, 2, 5),
|
||||
new Datum(11, 2, 6),
|
||||
new Datum(12, 2, 7),
|
||||
};
|
||||
|
||||
for(int i = 0; i < data.length; i++) {
|
||||
Datum datum = data[i];
|
||||
int count = datum.count;
|
||||
int ring = MathsUtils.getRing(count);
|
||||
int slot = MathsUtils.getSlot(count);
|
||||
|
||||
System.out.printf("Count: %d, Ring: %d, Slot: %d\n", count, ring, slot);
|
||||
Assertions.assertEquals(datum.ring, ring);
|
||||
Assertions.assertEquals(datum.slot, slot);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void t() {
|
||||
Assertions.assertEquals(1, Integer.highestOneBit(1));
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in a new issue