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:
Nick Guy 2025-07-22 23:57:44 +01:00
parent 3e21e35757
commit e6f5230c58
25 changed files with 559 additions and 208 deletions

View file

@ -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

View file

@ -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();
}
}

View file

@ -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() + "!"));
}
}

View file

@ -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();
}
}

View file

@ -1,4 +1,4 @@
package com.ncguy.usefulSkyblock;
package com.ncguy.usefulskyblock;
import org.bukkit.Bukkit;
import org.bukkit.Location;

View 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);
}
}

View file

@ -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;

View file

@ -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;

View file

@ -1,4 +1,4 @@
package com.ncguy.usefulSkyblock.command;
package com.ncguy.usefulskyblock.command;
import org.bukkit.Bukkit;
import org.bukkit.NamespacedKey;

View file

@ -1,4 +1,4 @@
package com.ncguy.usefulSkyblock.command;
package com.ncguy.usefulskyblock.command;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;

View file

@ -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);
}

View file

@ -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();
}
}

View file

@ -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;

View file

@ -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

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -0,0 +1,11 @@
{
"type": "minecraft:overworld",
"generator": {
"type": "flat",
"settings":{
"layers": [],
"biome": "minecraft:void",
"structure_overrides": []
}
}
}

View file

@ -0,0 +1,11 @@
{
"type": "minecraft:the_nether",
"generator": {
"type": "flat",
"settings":{
"layers": [],
"biome": "minecraft:void",
"structure_overrides": []
}
}
}

View file

@ -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": []
}
}

View file

@ -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
}

View file

@ -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"

View 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));
}
}