1
0
mirror of https://github.com/GeyserMC/Rainbow.git synced 2025-12-19 14:59:16 +00:00

Refactor item suggestion mapper into custom item provider, only map items if their model hasn't been mapped before, don't export textures more than once

This commit is contained in:
Eclipse
2025-07-04 08:14:44 +00:00
parent ba0ddc3eb5
commit c0b88453b1
10 changed files with 190 additions and 163 deletions

View File

@@ -9,7 +9,7 @@ import net.minecraft.commands.synchronization.SingletonArgumentInfo;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import org.geysermc.packgenerator.command.CommandSuggestionsArgumentType; import org.geysermc.packgenerator.command.CommandSuggestionsArgumentType;
import org.geysermc.packgenerator.command.PackGeneratorCommand; import org.geysermc.packgenerator.command.PackGeneratorCommand;
import org.geysermc.packgenerator.mapper.PackMappers; import org.geysermc.packgenerator.mapper.PackMapper;
import org.slf4j.Logger; import org.slf4j.Logger;
public class GeyserMappingsGenerator implements ClientModInitializer { public class GeyserMappingsGenerator implements ClientModInitializer {
@@ -19,12 +19,12 @@ public class GeyserMappingsGenerator implements ClientModInitializer {
public static final Logger LOGGER = LogUtils.getLogger(); public static final Logger LOGGER = LogUtils.getLogger();
private final PackManager packManager = new PackManager(); private final PackManager packManager = new PackManager();
private final PackMappers packMappers = new PackMappers(packManager); private final PackMapper packMapper = new PackMapper();
@Override @Override
public void onInitializeClient() { public void onInitializeClient() {
ClientCommandRegistrationCallback.EVENT.register((dispatcher, buildContext) -> PackGeneratorCommand.register(dispatcher, packManager, packMappers)); ClientCommandRegistrationCallback.EVENT.register((dispatcher, buildContext) -> PackGeneratorCommand.register(dispatcher, packManager, packMapper));
ClientTickEvents.START_CLIENT_TICK.register(packMappers::tick); ClientTickEvents.START_CLIENT_TICK.register(minecraft -> packMapper.tick(packManager, minecraft));
ArgumentTypeRegistry.registerArgumentType(getModdedLocation("command_suggestions"), ArgumentTypeRegistry.registerArgumentType(getModdedLocation("command_suggestions"),
CommandSuggestionsArgumentType.class, SingletonArgumentInfo.contextFree(CommandSuggestionsArgumentType::new)); CommandSuggestionsArgumentType.class, SingletonArgumentInfo.contextFree(CommandSuggestionsArgumentType::new));

View File

@@ -1,39 +1,36 @@
package org.geysermc.packgenerator; package org.geysermc.packgenerator;
import net.minecraft.world.item.ItemStack; import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
import org.geysermc.packgenerator.pack.BedrockPack; import org.geysermc.packgenerator.pack.BedrockPack;
import java.io.IOException; import java.io.IOException;
import java.util.Optional; import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Function;
public final class PackManager { public final class PackManager {
private BedrockPack currentPack; private Optional<BedrockPack> currentPack = Optional.empty();
public void startPack(String name) throws IOException { public void startPack(String name) throws IOException {
if (currentPack != null) { if (currentPack.isPresent()) {
throw new IllegalStateException("Already started a pack (" + currentPack.name() + ")"); throw new IllegalStateException("Already started a pack (" + currentPack.get().name() + ")");
} }
currentPack = new BedrockPack(name); currentPack = Optional.of(new BedrockPack(name));
} }
public Optional<Boolean> map(ItemStack stack) { public void run(Consumer<BedrockPack> consumer) {
ensurePackIsCreated(); currentPack.ifPresent(consumer);
return currentPack.map(stack);
} }
public boolean finish() { public <T> Optional<T> run(Function<BedrockPack, T> function) {
ensurePackIsCreated(); return currentPack.map(function);
boolean success = currentPack.save(); }
currentPack = null;
public Optional<Boolean> finish() {
Optional<Boolean> success = currentPack.map(BedrockPack::save);
currentPack = Optional.empty();
return success; return success;
} }
public void ensurePackIsCreated() {
if (currentPack == null) {
throw new IllegalStateException("Create a new pack first!");
}
}
} }

View File

@@ -10,9 +10,9 @@ import net.minecraft.ChatFormatting;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
import net.minecraft.world.entity.player.Inventory; import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
import org.geysermc.packgenerator.GeyserMappingsGenerator;
import org.geysermc.packgenerator.PackManager; import org.geysermc.packgenerator.PackManager;
import org.geysermc.packgenerator.mapper.PackMappers; import org.geysermc.packgenerator.mapper.ItemSuggestionProvider;
import org.geysermc.packgenerator.mapper.PackMapper;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
@@ -20,7 +20,7 @@ import java.util.function.Consumer;
public class PackGeneratorCommand { public class PackGeneratorCommand {
public static void register(CommandDispatcher<FabricClientCommandSource> dispatcher, PackManager packManager, PackMappers mappers) { public static void register(CommandDispatcher<FabricClientCommandSource> dispatcher, PackManager packManager, PackMapper packMapper) {
dispatcher.register(ClientCommandManager.literal("packgenerator") dispatcher.register(ClientCommandManager.literal("packgenerator")
.then(ClientCommandManager.literal("create") .then(ClientCommandManager.literal("create")
.then(ClientCommandManager.argument("name", StringArgumentType.word()) .then(ClientCommandManager.argument("name", StringArgumentType.word())
@@ -39,14 +39,16 @@ public class PackGeneratorCommand {
) )
.then(ClientCommandManager.literal("map") .then(ClientCommandManager.literal("map")
.executes(context -> { .executes(context -> {
ItemStack heldItem = context.getSource().getPlayer().getMainHandItem(); packManager.run(pack -> {
Optional<Boolean> problems = packManager.map(heldItem); ItemStack heldItem = context.getSource().getPlayer().getMainHandItem();
if (problems.isEmpty()) { Optional<Boolean> problems = pack.map(heldItem);
context.getSource().sendError(Component.literal("No item found to map!")); if (problems.isEmpty()) {
} else if (problems.get()) { context.getSource().sendError(Component.literal("No item found to map!"));
context.getSource().sendError(Component.literal("Problems occurred whilst mapping the item!")); } else if (problems.get()) {
} context.getSource().sendError(Component.literal("Problems occurred whilst mapping the item!"));
context.getSource().sendFeedback(Component.literal("Added held item to Geyser mappings")); }
context.getSource().sendFeedback(Component.literal("Added held item to Geyser mappings"));
});
return 0; return 0;
}) })
) )
@@ -55,24 +57,27 @@ public class PackGeneratorCommand {
) )
.then(ClientCommandManager.literal("finish") .then(ClientCommandManager.literal("finish")
.executes(context -> { .executes(context -> {
if (!packManager.finish()) { packManager.finish().ifPresent(success -> {
context.getSource().sendError(Component.literal("Errors occurred whilst trying to write the pack to disk!")); if (!success) {
} context.getSource().sendError(Component.literal("Errors occurred whilst trying to write the pack to disk!"));
context.getSource().sendFeedback(Component.literal("Wrote pack to disk")); } else {
context.getSource().sendFeedback(Component.literal("Wrote pack to disk"));
}
});
return 0; return 0;
}) })
) )
.then(ClientCommandManager.literal("auto") .then(ClientCommandManager.literal("auto")
.then(ClientCommandManager.argument("suggestions", CommandSuggestionsArgumentType.TYPE) .then(ClientCommandManager.argument("suggestions", CommandSuggestionsArgumentType.TYPE)
.executes(context -> { .executes(context -> {
packManager.ensurePackIsCreated();
Pair<String, CompletableFuture<Suggestions>> suggestions = CommandSuggestionsArgumentType.getSuggestions(context, "suggestions"); Pair<String, CompletableFuture<Suggestions>> suggestions = CommandSuggestionsArgumentType.getSuggestions(context, "suggestions");
String baseCommand = suggestions.getFirst(); String baseCommand = suggestions.getFirst();
suggestions.getSecond().thenAccept(completed -> { suggestions.getSecond().thenAccept(completed -> {
mappers.getSuggestionMapper().start(completed.getList().stream() ItemSuggestionProvider provider = new ItemSuggestionProvider(completed.getList().stream()
.map(suggestion -> baseCommand.substring(0, suggestion.getRange().getStart()) + suggestion.getText()) .map(suggestion -> baseCommand.substring(0, suggestion.getRange().getStart()) + suggestion.getText())
.toList()); .toList());
context.getSource().sendFeedback(Component.literal("Running " + mappers.getSuggestionMapper().queueSize() + " commands to obtain custom items to map")); packMapper.setItemProvider(provider);
context.getSource().sendFeedback(Component.literal("Running " + provider.queueSize() + " commands to obtain custom items to map"));
}); });
return 0; return 0;
}) })
@@ -82,23 +87,26 @@ public class PackGeneratorCommand {
} }
public static int mapInventory(PackManager manager, Inventory inventory, Consumer<Component> feedback, boolean feedbackOnEmpty) { public static int mapInventory(PackManager manager, Inventory inventory, Consumer<Component> feedback, boolean feedbackOnEmpty) {
int mapped = 0; return manager.run(pack -> {
boolean errors = false; int mapped = 0;
for (ItemStack stack : inventory) { boolean errors = false;
Optional<Boolean> problems = manager.map(stack); for (ItemStack stack : inventory) {
if (problems.isPresent()) { Optional<Boolean> problems = pack.map(stack);
mapped++; if (problems.isPresent()) {
errors |= problems.get(); mapped++;
errors |= problems.get();
}
} }
} if (mapped > 0) {
if (mapped > 0) { if (errors) {
if (errors) { feedback.accept(Component.literal("Problems occurred whilst mapping items!").withStyle(ChatFormatting.RED));
feedback.accept(Component.literal("Problems occurred whilst mapping items!").withStyle(ChatFormatting.RED)); }
feedback.accept(Component.literal("Mapped " + mapped + " items from your inventory"));
} else if (feedbackOnEmpty) {
feedback.accept(Component.literal("No items were mapped"));
} }
feedback.accept(Component.literal("Mapped " + mapped + " items from your inventory"));
} else if (feedbackOnEmpty) { return mapped;
feedback.accept(Component.literal("No items were mapped")); }).orElse(0);
}
return mapped;
} }
} }

View File

@@ -0,0 +1,14 @@
package org.geysermc.packgenerator.mapper;
import net.minecraft.client.multiplayer.ClientPacketListener;
import net.minecraft.client.player.LocalPlayer;
import net.minecraft.world.item.ItemStack;
import java.util.stream.Stream;
public interface CustomItemProvider {
Stream<ItemStack> nextItems(LocalPlayer player, ClientPacketListener connection);
boolean isDone();
}

View File

@@ -1,11 +1,23 @@
package org.geysermc.packgenerator.mapper; package org.geysermc.packgenerator.mapper;
import org.geysermc.packgenerator.PackManager; import net.minecraft.client.multiplayer.ClientPacketListener;
import net.minecraft.client.player.LocalPlayer;
import net.minecraft.world.item.ItemStack;
public class InventoryMapper { import java.util.stream.Stream;
private final PackManager packManager;
public InventoryMapper(PackManager packManager) { public class InventoryMapper implements CustomItemProvider {
this.packManager = packManager;
public InventoryMapper() {
}
@Override
public Stream<ItemStack> nextItems(LocalPlayer player, ClientPacketListener connection) {
return Stream.empty();
}
@Override
public boolean isDone() {
return false;
} }
} }

View File

@@ -1,73 +0,0 @@
package org.geysermc.packgenerator.mapper;
import net.minecraft.client.Minecraft;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.game.ServerboundChatCommandPacket;
import org.geysermc.packgenerator.PackManager;
import org.geysermc.packgenerator.command.PackGeneratorCommand;
import java.util.ArrayList;
import java.util.List;
// TODO safety
public final class ItemSuggestionMapper {
private final PackManager packManager;
private final List<String> remainingCommands = new ArrayList<>();
private boolean waitingOnItem = false;
private boolean waitingOnClear = false;
private int mapped = 0;
public ItemSuggestionMapper(PackManager packManager) {
this.packManager = packManager;
}
// TODO
public boolean start(List<String> commands) {
if (remainingCommands.isEmpty()) {
remainingCommands.addAll(commands);
return true;
}
return false;
}
public void tick(Minecraft minecraft) {
if (minecraft.player == null || minecraft.getConnection() == null) {
stop();
return;
}
if (!remainingCommands.isEmpty() || waitingOnItem) {
if (waitingOnClear && minecraft.player.getInventory().isEmpty()) {
waitingOnClear = false;
} else if (!waitingOnItem) {
minecraft.getConnection().send(new ServerboundChatCommandPacket(remainingCommands.removeFirst()));
waitingOnItem = true;
} else {
if (!minecraft.player.getInventory().isEmpty()) {
mapped += PackGeneratorCommand.mapInventory(packManager, minecraft.player.getInventory(),
component -> minecraft.player.displayClientMessage(component, false), false);
minecraft.getConnection().send(new ServerboundChatCommandPacket("clear"));
waitingOnItem = false;
if (remainingCommands.isEmpty()) {
minecraft.player.displayClientMessage(Component.literal("Done, " + mapped + " items have been mapped"), false);
} else {
waitingOnClear = true;
}
}
}
}
}
public int queueSize() {
return remainingCommands.size();
}
public void stop() {
remainingCommands.clear();
waitingOnItem = false;
waitingOnClear = false;
mapped = 0;
}
}

View File

@@ -0,0 +1,52 @@
package org.geysermc.packgenerator.mapper;
import net.minecraft.client.multiplayer.ClientPacketListener;
import net.minecraft.client.player.LocalPlayer;
import net.minecraft.network.protocol.game.ServerboundChatCommandPacket;
import net.minecraft.world.item.ItemStack;
import java.util.List;
import java.util.stream.Stream;
// TODO safety
public class ItemSuggestionProvider implements CustomItemProvider {
private final List<String> remainingCommands;
private boolean waitingOnItem = false;
private boolean waitingOnClear = false;
public ItemSuggestionProvider(List<String> commands) {
remainingCommands = commands;
}
public Stream<ItemStack> nextItems(LocalPlayer player, ClientPacketListener connection) {
if (!remainingCommands.isEmpty() || waitingOnItem) {
if (waitingOnClear && player.getInventory().isEmpty()) {
waitingOnClear = false;
} else if (!waitingOnItem) {
connection.send(new ServerboundChatCommandPacket(remainingCommands.removeFirst()));
waitingOnItem = true;
} else {
if (!player.getInventory().isEmpty()) {
Stream<ItemStack> items = player.getInventory().getNonEquipmentItems().stream();
connection.send(new ServerboundChatCommandPacket("clear"));
waitingOnItem = false;
if (!remainingCommands.isEmpty()) {
waitingOnClear = true;
}
return items;
}
}
}
return Stream.empty();
}
public int queueSize() {
return remainingCommands.size();
}
@Override
public boolean isDone() {
return remainingCommands.isEmpty();
}
}

View File

@@ -0,0 +1,38 @@
package org.geysermc.packgenerator.mapper;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.ClientPacketListener;
import net.minecraft.client.player.LocalPlayer;
import net.minecraft.network.chat.Component;
import org.geysermc.packgenerator.PackManager;
import java.util.Objects;
import java.util.Optional;
public class PackMapper {
private CustomItemProvider itemProvider;
public void setItemProvider(CustomItemProvider itemProvider) {
this.itemProvider = itemProvider;
}
public void tick(PackManager packManager, Minecraft minecraft) {
if (itemProvider != null) {
LocalPlayer player = Objects.requireNonNull(minecraft.player);
ClientPacketListener connection = Objects.requireNonNull(minecraft.getConnection());
packManager.run(pack -> {
// TODO maybe report problems here... probably better to do so in pack class though
long mapped = itemProvider.nextItems(player, connection)
.map(pack::map)
.filter(Optional::isPresent)
.count();
player.displayClientMessage(Component.literal("Mapped " + mapped + " items"), false);
if (itemProvider.isDone()) {
player.displayClientMessage(Component.literal("Finished mapping items from provider"), false);
itemProvider = null;
}
});
}
}
}

View File

@@ -1,26 +0,0 @@
package org.geysermc.packgenerator.mapper;
import net.minecraft.client.Minecraft;
import org.geysermc.packgenerator.PackManager;
public class PackMappers {
private final ItemSuggestionMapper suggestionMapper;
private final InventoryMapper inventoryMapper;
public PackMappers(PackManager packManager) {
this.suggestionMapper = new ItemSuggestionMapper(packManager);
this.inventoryMapper = new InventoryMapper(packManager);
}
public ItemSuggestionMapper getSuggestionMapper() {
return suggestionMapper;
}
public InventoryMapper getInventoryMapper() {
return inventoryMapper;
}
public void tick(Minecraft minecraft) {
suggestionMapper.tick(minecraft);
}
}

View File

@@ -3,6 +3,7 @@ package org.geysermc.packgenerator.pack;
import net.fabricmc.loader.api.FabricLoader; import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.components.SplashRenderer; import net.minecraft.client.gui.components.SplashRenderer;
import net.minecraft.core.component.DataComponents;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.ProblemReporter; import net.minecraft.util.ProblemReporter;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
@@ -22,8 +23,10 @@ import java.io.OutputStream;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
@@ -45,7 +48,9 @@ public class BedrockPack {
private final GeyserMappings mappings; private final GeyserMappings mappings;
private final BedrockTextures.Builder itemTextures; private final BedrockTextures.Builder itemTextures;
private final List<BedrockAttachable> attachables = new ArrayList<>(); private final List<BedrockAttachable> attachables = new ArrayList<>();
private final List<ResourceLocation> texturesToExport = new ArrayList<>(); private final Set<ResourceLocation> texturesToExport = new HashSet<>();
private final Set<ResourceLocation> modelsMapped = new HashSet<>();
private final ProblemReporter.Collector reporter; private final ProblemReporter.Collector reporter;
@@ -67,7 +72,7 @@ public class BedrockPack {
} }
public Optional<Boolean> map(ItemStack stack) { public Optional<Boolean> map(ItemStack stack) {
if (stack.isEmpty()) { if (stack.isEmpty() || !modelsMapped.add(stack.get(DataComponents.ITEM_MODEL))) {
return Optional.empty(); return Optional.empty();
} }
AtomicBoolean problems = new AtomicBoolean(); AtomicBoolean problems = new AtomicBoolean();