diff --git a/src/main/java/net/modfest/scatteredshards/client/command/ClientShardCommand.java b/src/main/java/net/modfest/scatteredshards/client/command/ClientShardCommand.java index 37d7b26..17945ec 100644 --- a/src/main/java/net/modfest/scatteredshards/client/command/ClientShardCommand.java +++ b/src/main/java/net/modfest/scatteredshards/client/command/ClientShardCommand.java @@ -21,6 +21,7 @@ import net.modfest.scatteredshards.api.shard.ShardType; import net.modfest.scatteredshards.client.screen.ShardCreatorGuiDescription; import net.modfest.scatteredshards.client.screen.ShardTabletGuiDescription; +import net.modfest.scatteredshards.command.ShardCommand; import java.util.concurrent.CompletableFuture; @@ -33,7 +34,6 @@ private static DynamicCommandExceptionType createInvalidException(String item) { } private static final DynamicCommandExceptionType INVALID_SET_ID = createInvalidException("set_id"); - private static final DynamicCommandExceptionType INVALID_SHARD_TYPE = createInvalidException("shard_type"); private static final DynamicCommandExceptionType INVALID_SHARD_ID = createInvalidException("shard_id"); public static int view(CommandContext context) throws CommandSyntaxException { @@ -49,7 +49,7 @@ public static int creatorNew(CommandContext context) String modId = StringArgumentType.getString(context, "mod_id"); Identifier shardTypeId = context.getArgument("shard_type", Identifier.class); ShardType shardType = ScatteredShardsAPI.getClientLibrary().shardTypes().get(shardTypeId) - .orElseThrow(() -> INVALID_SHARD_TYPE.create(shardTypeId)); + .orElseThrow(() -> ShardCommand.INVALID_SHARD_TYPE.create(shardTypeId)); var client = context.getSource().getClient(); client.send(() -> client.setScreen(ShardCreatorGuiDescription.Screen.newShard(modId, shardType))); diff --git a/src/main/java/net/modfest/scatteredshards/command/LibraryCommand.java b/src/main/java/net/modfest/scatteredshards/command/LibraryCommand.java index 096b912..015794e 100644 --- a/src/main/java/net/modfest/scatteredshards/command/LibraryCommand.java +++ b/src/main/java/net/modfest/scatteredshards/command/LibraryCommand.java @@ -1,13 +1,19 @@ package net.modfest.scatteredshards.command; import java.util.Optional; +import java.util.concurrent.CompletableFuture; import com.mojang.brigadier.Command; +import com.mojang.brigadier.arguments.StringArgumentType; import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.suggestion.Suggestions; +import com.mojang.brigadier.suggestion.SuggestionsBuilder; import com.mojang.brigadier.tree.CommandNode; import me.lucko.fabric.api.permissions.v0.Permissions; +import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource; +import net.fabricmc.loader.api.FabricLoader; import net.minecraft.server.command.ServerCommandSource; import net.minecraft.text.Text; import net.minecraft.util.Identifier; @@ -15,11 +21,13 @@ import net.modfest.scatteredshards.api.ScatteredShardsAPI; import net.modfest.scatteredshards.api.impl.ShardLibraryPersistentState; import net.modfest.scatteredshards.api.shard.Shard; +import net.modfest.scatteredshards.api.shard.ShardType; import net.modfest.scatteredshards.networking.S2CDeleteShard; import net.modfest.scatteredshards.networking.S2CSyncLibrary; +import net.modfest.scatteredshards.networking.S2CSyncShard; public class LibraryCommand { - + public static int delete(CommandContext ctx) throws CommandSyntaxException { Identifier shardId = ctx.getArgument("shard_id", Identifier.class); @@ -53,6 +61,33 @@ public static int deleteAll(CommandContext ctx) throws Comm return toDelete; } + + public static int migrate(CommandContext ctx) throws CommandSyntaxException { + Identifier shardId = ctx.getArgument("shard_id", Identifier.class); + String modId = StringArgumentType.getString(ctx, "mod_id"); + Identifier shardTypeId = ctx.getArgument("shard_type", Identifier.class); + Identifier newShardId = ShardType.createModId(shardTypeId, modId); + + var library = ScatteredShardsAPI.getServerLibrary(); + library.shardTypes().get(shardTypeId).orElseThrow(() -> ShardCommand.INVALID_SHARD_TYPE.create(shardTypeId)); + Shard shard = library.shards().get(shardId).orElseThrow(() -> ShardCommand.INVALID_SHARD.create(shardId)); + + library.shards().remove(shardId); + shard.setShardType(shardTypeId); + library.shards().put(newShardId, shard); + + var server = ctx.getSource().getServer(); + ShardLibraryPersistentState.get(server).markDirty(); + + S2CDeleteShard.sendToAll(server, shardId); + for (var player : server.getPlayerManager().getPlayerList()) { + S2CSyncShard.send(player, newShardId, shard); + } + + ctx.getSource().sendFeedback(() -> Text.stringifiedTranslatable("commands.scattered_shards.shard.library.migrate", shardId, newShardId), true); + + return Command.SINGLE_SUCCESS; + } public static void register(CommandNode parent) { var library = Node.literal("library") @@ -81,6 +116,19 @@ public static void register(CommandNode parent) { ) .build(); deleteCommand.addChild(deleteAllCommand); + + var migrateCommand = Node.literal("migrate") + .requires( + Permissions.require(ScatteredShards.permission("command.library.migrate"), 3) + ).build(); + var migrateShardArg = Node.shardId("shard_id").build(); + var migrateModArg = Node.stringArgument("mod_id").suggests(Node::suggestModIds).build(); + var migrateShardTypeArg = Node.identifier("shard_type").suggests(Node::suggestShardTypes) + .executes(LibraryCommand::migrate).build(); + migrateModArg.addChild(migrateShardTypeArg); + migrateShardArg.addChild(migrateModArg); + migrateCommand.addChild(migrateShardArg); + library.addChild(migrateCommand); parent.addChild(library); } diff --git a/src/main/java/net/modfest/scatteredshards/command/Node.java b/src/main/java/net/modfest/scatteredshards/command/Node.java index e46c11d..05fd760 100644 --- a/src/main/java/net/modfest/scatteredshards/command/Node.java +++ b/src/main/java/net/modfest/scatteredshards/command/Node.java @@ -1,11 +1,18 @@ package net.modfest.scatteredshards.command; +import java.util.concurrent.CompletableFuture; + import com.mojang.brigadier.arguments.BoolArgumentType; import com.mojang.brigadier.arguments.FloatArgumentType; +import com.mojang.brigadier.arguments.StringArgumentType; import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.builder.RequiredArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.suggestion.Suggestions; +import com.mojang.brigadier.suggestion.SuggestionsBuilder; import com.mojang.brigadier.tree.CommandNode; +import net.fabricmc.loader.api.FabricLoader; import net.minecraft.command.EntitySelector; import net.minecraft.command.argument.EntityArgumentType; import net.minecraft.command.argument.IdentifierArgumentType; @@ -13,6 +20,7 @@ import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.util.Identifier; import net.modfest.scatteredshards.api.ScatteredShardsAPI; +import net.modfest.scatteredshards.api.shard.ShardType; public class Node { public static LiteralArgumentBuilder literal(String name) { @@ -68,6 +76,10 @@ public static RequiredArgumentBuilder floatValue(Str public static RequiredArgumentBuilder booleanValue(String name) { return RequiredArgumentBuilder.argument(name, BoolArgumentType.bool()); } + + public static RequiredArgumentBuilder stringArgument(String name) { + return RequiredArgumentBuilder.argument(name, StringArgumentType.string()); + } /** * Creates literal nodes as necessary to extend a command path to include the desired command node, and returns the @@ -90,4 +102,20 @@ public CommandNode getOrCreate(CommandNode suggestModIds(CommandContext context, SuggestionsBuilder builder) { + for (var mod : FabricLoader.getInstance().getAllMods()) { + builder.suggest(mod.getMetadata().getId()); + } + return builder.buildFuture(); + } + + public static CompletableFuture suggestShardTypes(CommandContext context, SuggestionsBuilder builder) { + ScatteredShardsAPI.getServerLibrary().shardTypes().forEach((id, shardSet) -> { + if (!id.equals(ShardType.MISSING_ID)) { + builder.suggest(id.toString()); + } + }); + return builder.buildFuture(); + } } diff --git a/src/main/java/net/modfest/scatteredshards/command/ShardCommand.java b/src/main/java/net/modfest/scatteredshards/command/ShardCommand.java index deaeeda..df66eef 100644 --- a/src/main/java/net/modfest/scatteredshards/command/ShardCommand.java +++ b/src/main/java/net/modfest/scatteredshards/command/ShardCommand.java @@ -13,6 +13,10 @@ public class ShardCommand { public static final DynamicCommandExceptionType NO_ROOM_FOR_ITEM = new DynamicCommandExceptionType( it -> Text.translatable("error.scattered_shards.no_inventory_room", it) ); + + public static final DynamicCommandExceptionType INVALID_SHARD_TYPE = new DynamicCommandExceptionType( + it -> Text.translatable("error.scattered_shards.invalid_shard_type", it) + ); public static void register() { CommandRegistrationCallback.EVENT.register((dispatcher, context, environment) -> { diff --git a/src/main/resources/assets/scattered_shards/lang/en_us.json b/src/main/resources/assets/scattered_shards/lang/en_us.json index b1ceb90..2e92201 100644 --- a/src/main/resources/assets/scattered_shards/lang/en_us.json +++ b/src/main/resources/assets/scattered_shards/lang/en_us.json @@ -16,6 +16,7 @@ "commands.scattered_shards.shard.uncollect.all": "Un-Collected %s shards", "commands.scattered_shards.shard.library.delete": "Deleted shard '%s' from the library", "commands.scattered_shards.shard.library.delete.all": "Deleted %s shards from the library", + "commands.scattered_shards.shard.library.migrate": "Migrated shard %s to %s", "gui.scattered_shards.creator.title": "Shard Creator", "gui.scattered_shards.creator.field.name": "Name...", "gui.scattered_shards.creator.field.lore": "Lore...",