Skip to content

Commit

Permalink
feat: add @ALL special region selector
Browse files Browse the repository at this point in the history
  • Loading branch information
Restioson committed Jun 11, 2024
1 parent 95b2ba4 commit c3ef1f3
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 17 deletions.
2 changes: 2 additions & 0 deletions src/main/java/xyz/nucleoid/creator_tools/CreatorTools.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ public final class CreatorTools implements ModInitializer {
public void onInitialize() {
CreatorToolsItems.register();

MapMetadataCommand.registerArgumentTypes();

CommandRegistrationCallback.EVENT.register((dispatcher, dedicated, environment) -> {
MapManageCommand.register(dispatcher);
MapMetadataCommand.register(dispatcher);
Expand Down
123 changes: 106 additions & 17 deletions src/main/java/xyz/nucleoid/creator_tools/command/MapMetadataCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,26 @@

import com.mojang.brigadier.Command;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.StringReader;
import com.mojang.brigadier.arguments.ArgumentType;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.exceptions.DynamicCommandExceptionType;
import com.mojang.brigadier.exceptions.SimpleCommandExceptionType;
import com.mojang.brigadier.suggestion.SuggestionProvider;
import com.mojang.datafixers.util.Either;
import me.lucko.fabric.api.permissions.v0.Permissions;
import net.fabricmc.fabric.api.command.v2.ArgumentTypeRegistry;
import net.minecraft.command.CommandSource;
import net.minecraft.command.argument.BlockPosArgumentType;
import net.minecraft.command.argument.EntityArgumentType;
import net.minecraft.command.argument.IdentifierArgumentType;
import net.minecraft.command.argument.NbtCompoundArgumentType;
import net.minecraft.command.argument.NbtElementArgumentType;
import net.minecraft.command.argument.NbtPathArgumentType;
import net.minecraft.command.argument.serialize.ConstantArgumentSerializer;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.nbt.NbtCompound;
Expand All @@ -30,11 +36,13 @@
import net.minecraft.util.math.BlockPos;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import xyz.nucleoid.creator_tools.CreatorTools;
import xyz.nucleoid.creator_tools.workspace.MapWorkspace;
import xyz.nucleoid.creator_tools.workspace.MapWorkspaceManager;
import xyz.nucleoid.creator_tools.workspace.WorkspaceRegion;
import xyz.nucleoid.map_templates.BlockBounds;

import java.util.Arrays;
import java.util.Collection;
import java.util.stream.Stream;

Expand Down Expand Up @@ -64,13 +72,92 @@ public final class MapMetadataCommand {
arg -> Text.stringifiedTranslatable("commands.data.modify.expected_object", arg)
);

public static final DynamicCommandExceptionType INVALID_REGION_SELECTOR = new DynamicCommandExceptionType(
arg -> Text.stringifiedTranslatable("text.nucleoid_creator_tools.map.region.reserved_name", arg)
);

private enum SpecialRegionSelector {
All("all");

private final static char PREFIX = '@';

public final String name;
SpecialRegionSelector(String name) {
this.name = name;
}

public static Stream<String> suggestions() {
return Arrays.stream(values()).map(selector -> PREFIX + selector.name);
}
}

private record RegionSelector(Either<String, SpecialRegionSelector> inner) {
static RegionSelector special(SpecialRegionSelector selector) {
return new RegionSelector(Either.right(selector));
}

static RegionSelector named(String name) {
return new RegionSelector(Either.left(name));
}

public boolean matches(WorkspaceRegion region) {
// This is to make sure we don't miss this if we add more special selectors:
// noinspection ConstantValue
return this.inner.map(
name -> region.marker().equals(name),
special -> switch (special) {
case All -> true;
}
);
}
}

private static final class RegionSelectorArgumentType implements ArgumentType<RegionSelector> {
@Override
public RegionSelector parse(StringReader reader) throws CommandSyntaxException {
if (reader.peek() == SpecialRegionSelector.PREFIX) {
reader.skip(); // Skip prefix

// Find whichever special selector matches
String selectorStr = reader.readString();
SpecialRegionSelector selector = Arrays.stream(SpecialRegionSelector.values())
.filter(s -> s.name.equals(selectorStr))
.findAny()
.orElseThrow(() -> INVALID_REGION_SELECTOR.create(SpecialRegionSelector.PREFIX + selectorStr));
return RegionSelector.special(selector);
} else {
return RegionSelector.named(reader.readString());
}
}

public static RegionSelector get(final CommandContext<?> context, final String name) {
return context.getArgument(name, RegionSelector.class);
}
}

private static RequiredArgumentBuilder<ServerCommandSource, RegionSelector> localRegionMarkerArg(String name) {
return argument(name, new RegionSelectorArgumentType()).suggests(localRegionSuggestions());
}

private static RequiredArgumentBuilder<ServerCommandSource, RegionSelector> regionMarkerArg(String name) {
return argument(name, new RegionSelectorArgumentType()).suggests(regionSuggestions(true));
}

public static void registerArgumentTypes() {
ArgumentTypeRegistry.registerArgumentType(
new Identifier(CreatorTools.ID, "region_selector"),
MapMetadataCommand.RegionSelectorArgumentType.class,
ConstantArgumentSerializer.of(MapMetadataCommand.RegionSelectorArgumentType::new)
);
}

// @formatter:off
public static void register(CommandDispatcher<ServerCommandSource> dispatcher) {
dispatcher.register(
literal("map").requires(Permissions.require("nucleoid_creator_extras.map", 2))
.then(literal("region")
.then(literal("add")
.then(argument("marker", StringArgumentType.word())
.then(argument("marker", StringArgumentType.word()).suggests(regionSuggestions(false))
.then(argument("min", BlockPosArgumentType.blockPos())
.then(argument("max", BlockPosArgumentType.blockPos())
.executes(MapMetadataCommand::addRegion)
Expand All @@ -79,25 +166,25 @@ public static void register(CommandDispatcher<ServerCommandSource> dispatcher) {
)))))
.then(literal("rename")
.then(literal("all")
.then(argument("old", StringArgumentType.word()).suggests(regionSuggestions())
.then(argument("new", StringArgumentType.word())
.then(regionMarkerArg("old")
.then(argument("new", StringArgumentType.word()).suggests(regionSuggestions(false))
.executes(context -> renameRegions(context, (region, oldMarker, pos) -> region.marker().equals(oldMarker)))
)))
.then(literal("here")
.then(argument("old", StringArgumentType.word()).suggests(localRegionSuggestions())
.then(argument("new", StringArgumentType.word())
.then(localRegionMarkerArg("old")
.then(argument("new", StringArgumentType.word()).suggests(regionSuggestions(false))
.executes(
context -> renameRegions(context, (region, oldMarker, pos) -> region.marker().equals(oldMarker)
&& region.bounds().contains(pos))
)
)))
)
.then(literal("bounds")
.then(argument("marker", StringArgumentType.word()).suggests(regionSuggestions())
.then(regionMarkerArg("marker")
.executes(MapMetadataCommand::getRegionBounds))
)
.then(literal("data")
.then(argument("marker", StringArgumentType.word()).suggests(localRegionSuggestions())
.then(localRegionMarkerArg("marker")
.then(literal("get").executes(executeInRegions("", MapMetadataCommand::executeRegionDataGet)))
.then(literal("merge")
.then(argument("nbt", NbtCompoundArgumentType.nbtCompound())
Expand All @@ -114,7 +201,7 @@ public static void register(CommandDispatcher<ServerCommandSource> dispatcher) {
))
.then(literal("remove")
.then(literal("here")
.then(argument("marker", StringArgumentType.word()).suggests(localRegionSuggestions())
.then(localRegionMarkerArg("marker")
.executes(executeInRegions("Removed %d regions.", MapMetadataCommand::executeRemoveNamedRegionsHere))
))
.then(literal("at")
Expand All @@ -123,7 +210,7 @@ public static void register(CommandDispatcher<ServerCommandSource> dispatcher) {
))
)
.then(literal("commit")
.then(argument("marker", StringArgumentType.word())
.then(argument("marker", StringArgumentType.word()).suggests(regionSuggestions(false))
.executes(MapMetadataCommand::commitRegion)
.then(argument("data", NbtCompoundArgumentType.nbtCompound())
.executes(context -> commitRegion(context, NbtCompoundArgumentType.getNbtCompound(context, "data")))
Expand Down Expand Up @@ -234,10 +321,10 @@ private static int getRegionBounds(CommandContext<ServerCommandSource> context)
var source = context.getSource();
var map = getWorkspaceForSource(source);

var marker = StringArgumentType.getString(context, "marker");
var regionSelector = RegionSelectorArgumentType.get(context, "marker");

var regions = map.getRegions().stream()
.filter(region -> region.marker().equals(marker))
.filter(regionSelector::matches)
.toList();

source.sendFeedback(() -> Text.translatable("text.nucleoid_creator_tools.map.region.bounds.get.header", regions.size()).formatted(Formatting.BOLD), false);
Expand Down Expand Up @@ -549,11 +636,12 @@ private static SuggestionProvider<ServerCommandSource> entityTypeSuggestions() {
return (ctx, builder) -> CommandSource.suggestIdentifiers(Registries.ENTITY_TYPE.getIds(), builder);
}

private static SuggestionProvider<ServerCommandSource> regionSuggestions() {
private static SuggestionProvider<ServerCommandSource> regionSuggestions(boolean includeAll) {
return (context, builder) -> {
var map = getWorkspaceForSource(context.getSource());
var regions = map.getRegions().stream().map(WorkspaceRegion::marker);
return CommandSource.suggestMatching(
map.getRegions().stream().map(WorkspaceRegion::marker),
includeAll ? Stream.concat(SpecialRegionSelector.suggestions(), regions) : regions,
builder
);
};
Expand All @@ -563,9 +651,9 @@ private static SuggestionProvider<ServerCommandSource> localRegionSuggestions()
return (context, builder) -> {
var map = getWorkspaceForSource(context.getSource());
var sourcePos = context.getSource().getPlayerOrThrow().getBlockPos();
var localRegions = map.getRegions().stream().filter(region -> region.bounds().contains(sourcePos)).map(WorkspaceRegion::marker);
return CommandSource.suggestMatching(
map.getRegions().stream().filter(region -> region.bounds().contains(sourcePos))
.map(WorkspaceRegion::marker),
Stream.concat(SpecialRegionSelector.suggestions(), localRegions),
builder
);
};
Expand All @@ -586,11 +674,12 @@ private static Command<ServerCommandSource> executeInRegions(String message, Reg
var source = context.getSource();
var pos = source.getPlayerOrThrow().getBlockPos();

var marker = StringArgumentType.getString(context, "marker");
var regionSelector = RegionSelectorArgumentType.get(context, "marker");

var map = getWorkspaceForSource(context.getSource());
var regions = map.getRegions().stream()
.filter(region -> region.bounds().contains(pos) && region.marker().equals(marker))
.filter(region -> region.bounds().contains(pos))
.filter(regionSelector::matches)
.toList();

int count = 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
"text.nucleoid_creator_tools.map.open.map_already_exists": "Map with id '%s' already exists!",
"text.nucleoid_creator_tools.map.open.success": "Opened workspace '%s'! Use %s to join this map",
"text.nucleoid_creator_tools.map.origin.set": "Updated origin for workspace",
"text.nucleoid_creator_tools.map.region.reserved_name": "Invalid region selector '%s'.",
"text.nucleoid_creator_tools.map.region.add.success": "Added region '%s'.",
"text.nucleoid_creator_tools.map.region.add.success.excited": "Added region '%s'!",
"text.nucleoid_creator_tools.map.region.bounds.get": "%s to %s",
Expand Down

0 comments on commit c3ef1f3

Please sign in to comment.