mirror of
https://github.com/Samsuik/Sakura.git
synced 2025-12-19 14:59:30 +00:00
add regions to debug command and fix large overlapping regions
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
package me.samsuik.sakura.command;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@NullMarked
|
||||
public abstract class BaseMenuCommand extends BaseSubCommand {
|
||||
private static final String HEADER_MESSAGE = "<dark_purple>| <white><message>";
|
||||
private static final String COMMAND_MSG = "<dark_purple>| <dark_gray>*</dark_gray> /<light_purple><command>";
|
||||
|
||||
public BaseMenuCommand(String name) {
|
||||
super(name);
|
||||
}
|
||||
|
||||
public abstract String header();
|
||||
|
||||
public Iterable<Command> helpCommands() {
|
||||
return this.subCommands();
|
||||
}
|
||||
|
||||
public abstract Iterable<Command> subCommands();
|
||||
|
||||
@Override
|
||||
public final void execute(CommandSender sender, String[] args) {
|
||||
if (args.length > 0) {
|
||||
for (final Command base : this.subCommands()) {
|
||||
if (base.getName().equalsIgnoreCase(args[0])) {
|
||||
base.execute(sender, "", Arrays.copyOfRange(args, 1, args.length));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.sendHelpMessage(sender);
|
||||
}
|
||||
|
||||
private void sendHelpMessage(CommandSender sender) {
|
||||
sender.sendMessage(Component.text(".", NamedTextColor.DARK_PURPLE));
|
||||
for (final String header : this.header().split("\n")) {
|
||||
if (!header.isEmpty()) {
|
||||
sender.sendRichMessage(HEADER_MESSAGE, Placeholder.unparsed("message", header));
|
||||
}
|
||||
}
|
||||
|
||||
for (final Command command : this.helpCommands()) {
|
||||
if (command != this) {
|
||||
sender.sendRichMessage(COMMAND_MSG, Placeholder.unparsed("command", command.getName()));
|
||||
}
|
||||
}
|
||||
|
||||
sender.sendMessage(Component.text("'", NamedTextColor.DARK_PURPLE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public final List<String> tabComplete(CommandSender sender, String alias, String[] args) throws IllegalArgumentException {
|
||||
if (!this.testPermissionSilent(sender) || args.length == 0) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
final Command command = SakuraCommands.getCommand(args[0]);
|
||||
final List<String> completions = new ArrayList<>();
|
||||
if (command != null && args.length > 1) {
|
||||
final String[] newArgs = Arrays.copyOfRange(args, 1, args.length);
|
||||
completions.addAll(command.tabComplete(sender, alias, newArgs));
|
||||
} else {
|
||||
for (final Command subCommand : SakuraCommands.SUB_COMMANDS) {
|
||||
final String commandName = subCommand.getName();
|
||||
if (commandName.startsWith(args[0])) {
|
||||
completions.add(commandName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final String lastArg = args[args.length - 1];
|
||||
return completions.stream()
|
||||
.filter(result -> result.startsWith(lastArg))
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
@@ -42,6 +42,10 @@ public abstract class BaseSubCommand extends Command {
|
||||
return completions;
|
||||
}
|
||||
|
||||
public void sendPlayerOnlyMessage(CommandSender sender) {
|
||||
sender.sendRichMessage("<red>This command can only be ran by players");
|
||||
}
|
||||
|
||||
protected final Optional<Integer> parseInt(String[] args, int index) {
|
||||
return this.parse(args, index, Integer::parseInt);
|
||||
}
|
||||
|
||||
@@ -1,84 +1,33 @@
|
||||
package me.samsuik.sakura.command;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
@NullMarked
|
||||
public final class SakuraCommand extends Command {
|
||||
private static final Component HEADER_MESSAGE = MiniMessage.miniMessage().deserialize("""
|
||||
<dark_purple>.</dark_purple>
|
||||
<dark_purple>| <white>This is the main command for <gradient:red:light_purple:0.5>Sakura</gradient>.
|
||||
<dark_purple>| <white>All exclusive commands are listed below."""
|
||||
);
|
||||
|
||||
private static final String COMMAND_MSG = "<dark_purple>| <dark_gray>*</dark_gray> /<light_purple><command>";
|
||||
|
||||
public final class SakuraCommand extends BaseMenuCommand {
|
||||
public SakuraCommand(String name) {
|
||||
super(name);
|
||||
this.description = "";
|
||||
this.usageMessage = "/sakura";
|
||||
this.setPermission("bukkit.command.sakura");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean execute(CommandSender sender, String commandLabel, String[] args) {
|
||||
if (args.length > 0) {
|
||||
public String header() {
|
||||
return "This is the main command for <gradient:red:light_purple:0.5>Sakura</gradient>.\n" +
|
||||
"All exclusive commands are listed below.";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<Command> helpCommands() {
|
||||
return SakuraCommands.COMMANDS.values();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<Command> subCommands() {
|
||||
final Command versionCommand = MinecraftServer.getServer().server.getCommandMap().getCommand("version");
|
||||
for (final Command base : Iterables.concat(SakuraCommands.SUB_COMMANDS, List.of(versionCommand))) {
|
||||
if (base.getName().equalsIgnoreCase(args[0])) {
|
||||
return base.execute(sender, commandLabel, Arrays.copyOfRange(args, 1, args.length));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.sendHelpMessage(sender);
|
||||
return false;
|
||||
}
|
||||
|
||||
private void sendHelpMessage(CommandSender sender) {
|
||||
sender.sendMessage(HEADER_MESSAGE);
|
||||
|
||||
for (final Command command : SakuraCommands.COMMANDS.values()) {
|
||||
if (command != this) {
|
||||
sender.sendRichMessage(COMMAND_MSG, Placeholder.unparsed("command", command.getName()));
|
||||
}
|
||||
}
|
||||
|
||||
sender.sendMessage(Component.text("'", NamedTextColor.DARK_PURPLE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> tabComplete(CommandSender sender, String alias, String[] args) throws IllegalArgumentException {
|
||||
if (!this.testPermissionSilent(sender) || args.length == 0) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
final Command command = SakuraCommands.getCommand(args[0]);
|
||||
final List<String> completions = new ArrayList<>();
|
||||
if (command != null && args.length > 1) {
|
||||
final String[] newArgs = Arrays.copyOfRange(args, 1, args.length);
|
||||
completions.addAll(command.tabComplete(sender, alias, newArgs));
|
||||
} else {
|
||||
for (final Command subCommand : SakuraCommands.SUB_COMMANDS) {
|
||||
final String commandName = subCommand.getName();
|
||||
if (commandName.startsWith(args[0])) {
|
||||
completions.add(commandName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final String lastArg = args[args.length - 1];
|
||||
return completions.stream()
|
||||
.filter(result -> result.startsWith(lastArg))
|
||||
.toList();
|
||||
return Iterables.concat(SakuraCommands.SUB_COMMANDS, List.of(versionCommand));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package me.samsuik.sakura.command;
|
||||
|
||||
import me.samsuik.sakura.command.subcommands.*;
|
||||
import me.samsuik.sakura.command.subcommands.debug.DebugLocalRegions;
|
||||
import me.samsuik.sakura.command.subcommands.debug.DebugRedstoneCache;
|
||||
import me.samsuik.sakura.player.visibility.VisibilityTypes;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import org.bukkit.command.Command;
|
||||
@@ -14,8 +16,9 @@ import java.util.Set;
|
||||
|
||||
@NullMarked
|
||||
public final class SakuraCommands {
|
||||
static final Map<String, Command> COMMANDS = new HashMap<>();
|
||||
static final Set<Command> SUB_COMMANDS = new HashSet<>();
|
||||
public static final Map<String, Command> COMMANDS = new HashMap<>();
|
||||
public static final Set<Command> SUB_COMMANDS = new HashSet<>();
|
||||
public static final Set<Command> DEBUG_COMMANDS = new HashSet<>();
|
||||
|
||||
static {
|
||||
COMMANDS.put("config", new ConfigCommand("config"));
|
||||
@@ -27,6 +30,8 @@ public final class SakuraCommands {
|
||||
SUB_COMMANDS.add(new DebugCommand("debug"));
|
||||
// "sakura" isn't a subcommand
|
||||
COMMANDS.put("sakura", new SakuraCommand("sakura"));
|
||||
DEBUG_COMMANDS.add(new DebugRedstoneCache("redstone-cache"));
|
||||
DEBUG_COMMANDS.add(new DebugLocalRegions("local-regions"));
|
||||
}
|
||||
|
||||
public static void registerCommands(MinecraftServer server) {
|
||||
|
||||
@@ -1,74 +1,23 @@
|
||||
package me.samsuik.sakura.command.subcommands;
|
||||
|
||||
import me.samsuik.sakura.command.BaseSubCommand;
|
||||
import me.samsuik.sakura.redstone.RedstoneNetwork;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.level.Level;
|
||||
import org.bukkit.DyeColor;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.craftbukkit.entity.CraftPlayer;
|
||||
import org.bukkit.craftbukkit.util.CraftLocation;
|
||||
import org.bukkit.entity.Player;
|
||||
import me.samsuik.sakura.command.BaseMenuCommand;
|
||||
import me.samsuik.sakura.command.SakuraCommands;
|
||||
import org.bukkit.command.Command;
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
@NullMarked
|
||||
public final class DebugCommand extends BaseSubCommand {
|
||||
public final class DebugCommand extends BaseMenuCommand {
|
||||
public DebugCommand(String name) {
|
||||
super(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandSender sender, String[] args) {
|
||||
if (!(sender instanceof Player player) || args.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
ServerPlayer nmsPlayer = ((CraftPlayer) player).getHandle();
|
||||
if (args[0].equalsIgnoreCase("redstone-cache")) {
|
||||
this.showCachedWires(player, nmsPlayer.level());
|
||||
}
|
||||
public String header() {
|
||||
return "Debug command for testing Sakura features and api";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tabComplete(List<String> list, String[] args) throws IllegalArgumentException {
|
||||
list.add("redstone-cache");
|
||||
}
|
||||
|
||||
private void showCachedWires(Player player, Level level) {
|
||||
Set<Location> locations = new HashSet<>();
|
||||
for (RedstoneNetwork network : level.redstoneWireCache.getNetworkCache().values()) {
|
||||
byte randomColour = (byte) ThreadLocalRandom.current().nextInt(16);
|
||||
DyeColor dyeColour = DyeColor.getByWoolData(randomColour);
|
||||
Material material = Material.matchMaterial(dyeColour.name() + "_WOOL");
|
||||
|
||||
if (!network.isRegistered()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (BlockPos pos : network.getWirePositions()) {
|
||||
Location location = CraftLocation.toBukkit(pos, level);
|
||||
if (player.getLocation().distance(location) >= 64.0) {
|
||||
continue;
|
||||
}
|
||||
player.sendBlockChange(location, material.createBlockData());
|
||||
locations.add(location);
|
||||
}
|
||||
}
|
||||
|
||||
player.sendRichMessage("<red>Displaying %dx cached redstone wires".formatted(locations.size()));
|
||||
|
||||
level.levelTickScheduler.delayedTask(() -> {
|
||||
for (Location loc : locations) {
|
||||
player.sendBlockChange(loc, loc.getBlock().getBlockData());
|
||||
}
|
||||
}, 1200);
|
||||
public Iterable<Command> subCommands() {
|
||||
return SakuraCommands.DEBUG_COMMANDS;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
package me.samsuik.sakura.command.subcommands.debug;
|
||||
|
||||
import me.samsuik.sakura.command.BaseSubCommand;
|
||||
import me.samsuik.sakura.local.LocalRegion;
|
||||
import me.samsuik.sakura.local.storage.LocalStorageHandler;
|
||||
import me.samsuik.sakura.local.storage.LocalValueStorage;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
@NullMarked
|
||||
public final class DebugLocalRegions extends BaseSubCommand {
|
||||
private static final int DEFAULT_REGION_SIZE = 16;
|
||||
|
||||
public DebugLocalRegions(String name) {
|
||||
super(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandSender sender, String[] args) {
|
||||
if (!(sender instanceof Player player)) {
|
||||
this.sendPlayerOnlyMessage(sender);
|
||||
return;
|
||||
}
|
||||
|
||||
final Location location = player.getLocation();
|
||||
final World world = location.getWorld();
|
||||
final LocalStorageHandler storageHandler = world.getStorageHandler();
|
||||
final int blockX = location.getBlockX();
|
||||
final int blockZ = location.getBlockZ();
|
||||
final Optional<LocalRegion> currentRegion = storageHandler.locate(blockX, blockZ);
|
||||
|
||||
if ("create".equalsIgnoreCase(args[0])) {
|
||||
final int size = parseInt(args, 1).orElse(DEFAULT_REGION_SIZE);
|
||||
final LocalRegion region = LocalRegion.at(blockX, blockZ, size);
|
||||
storageHandler.put(region, new LocalValueStorage());
|
||||
}
|
||||
|
||||
if ("get".equalsIgnoreCase(args[0])) {
|
||||
sender.sendRichMessage("<red>" + (currentRegion.isPresent() ? currentRegion.get() : "not inside of a region"));
|
||||
}
|
||||
|
||||
if (currentRegion.isPresent()) {
|
||||
final LocalRegion region = currentRegion.get();
|
||||
if ("delete".equalsIgnoreCase(args[0])) {
|
||||
storageHandler.remove(region);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package me.samsuik.sakura.command.subcommands.debug;
|
||||
|
||||
import me.samsuik.sakura.command.BaseSubCommand;
|
||||
import me.samsuik.sakura.redstone.RedstoneNetwork;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.level.Level;
|
||||
import org.bukkit.DyeColor;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.craftbukkit.entity.CraftPlayer;
|
||||
import org.bukkit.craftbukkit.util.CraftLocation;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
@NullMarked
|
||||
public final class DebugRedstoneCache extends BaseSubCommand {
|
||||
public DebugRedstoneCache(String name) {
|
||||
super(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandSender sender, String[] args) {
|
||||
if (!(sender instanceof Player player)) {
|
||||
this.sendPlayerOnlyMessage(sender);
|
||||
return;
|
||||
}
|
||||
|
||||
ServerPlayer nmsPlayer = ((CraftPlayer) player).getHandle();
|
||||
Level level = nmsPlayer.level();
|
||||
Set<Location> locations = new HashSet<>();
|
||||
for (RedstoneNetwork network : level.redstoneWireCache.getNetworkCache().values()) {
|
||||
byte randomColour = (byte) ThreadLocalRandom.current().nextInt(16);
|
||||
DyeColor dyeColour = DyeColor.getByWoolData(randomColour);
|
||||
Material material = Material.matchMaterial(dyeColour.name() + "_WOOL");
|
||||
|
||||
if (!network.isRegistered()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (BlockPos pos : network.getWirePositions()) {
|
||||
Location location = CraftLocation.toBukkit(pos, level);
|
||||
if (player.getLocation().distance(location) >= 64.0) {
|
||||
continue;
|
||||
}
|
||||
player.sendBlockChange(location, material.createBlockData());
|
||||
locations.add(location);
|
||||
}
|
||||
}
|
||||
|
||||
player.sendRichMessage("<red>Displaying %dx cached redstone wires".formatted(locations.size()));
|
||||
|
||||
level.levelTickScheduler.delayedTask(() -> {
|
||||
for (Location loc : locations) {
|
||||
player.sendBlockChange(loc, loc.getBlock().getBlockData());
|
||||
}
|
||||
}, 1200);
|
||||
}
|
||||
}
|
||||
@@ -62,10 +62,12 @@ public final class LocalConfigManager implements LocalStorageHandler {
|
||||
|
||||
@Override
|
||||
public synchronized void put(@NonNull LocalRegion region, @NonNull LocalValueStorage storage) {
|
||||
this.ensureNotOverlapping(region);
|
||||
int shift = this.regionExponent;
|
||||
int regionChunks = regionChunks(region, shift);
|
||||
|
||||
// make sure there's no overlapping regions
|
||||
this.ensureRegionIsNotOverlapping(region, regionChunks);
|
||||
|
||||
if (regionChunks <= SMALL_REGION_SIZE) {
|
||||
this.forEachRegionChunks(region, pos -> {
|
||||
this.smallRegions.computeIfAbsent(pos, k -> new ArrayList<>())
|
||||
@@ -73,8 +75,9 @@ public final class LocalConfigManager implements LocalStorageHandler {
|
||||
});
|
||||
} else {
|
||||
this.largeRegions.add(region);
|
||||
// The region exponent might be too small
|
||||
if (this.largeRegions.size() % 24 == 0) {
|
||||
|
||||
// The region exponent may be too small
|
||||
if ((this.largeRegions.size() & 15) == 0) {
|
||||
this.resizeRegions();
|
||||
}
|
||||
}
|
||||
@@ -189,14 +192,18 @@ public final class LocalConfigManager implements LocalStorageHandler {
|
||||
return config;
|
||||
}
|
||||
|
||||
private void ensureNotOverlapping(LocalRegion region) {
|
||||
private void ensureRegionIsNotOverlapping(LocalRegion region, int regionChunks) {
|
||||
Set<LocalRegion> nearbyRegions = new ReferenceOpenHashSet<>();
|
||||
if (regionChunks > SMALL_REGION_SIZE) {
|
||||
nearbyRegions.addAll(this.storageMap.keySet());
|
||||
} else {
|
||||
this.forEachRegionChunks(region, pos -> {
|
||||
nearbyRegions.addAll(this.smallRegions.getOrDefault(pos, List.of()));
|
||||
});
|
||||
}
|
||||
for (LocalRegion present : Iterables.concat(nearbyRegions, this.largeRegions)) {
|
||||
if (present != region && present.intersects(region)) {
|
||||
throw new UnsupportedOperationException("overlapping region (%s, %s)".formatted(present, region));
|
||||
throw new OverlappingRegionException(present, region);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
package me.samsuik.sakura.configuration.local;
|
||||
|
||||
import me.samsuik.sakura.local.LocalRegion;
|
||||
|
||||
public final class OverlappingRegionException extends RuntimeException {
|
||||
public OverlappingRegionException(LocalRegion presentRegion, LocalRegion region) {
|
||||
super("overlapping region (%s, %s)".formatted(presentRegion, region));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user