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

View File

@@ -1,39 +1,36 @@
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 java.io.IOException;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Function;
public final class PackManager {
private BedrockPack currentPack;
private Optional<BedrockPack> currentPack = Optional.empty();
public void startPack(String name) throws IOException {
if (currentPack != null) {
throw new IllegalStateException("Already started a pack (" + currentPack.name() + ")");
if (currentPack.isPresent()) {
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) {
ensurePackIsCreated();
return currentPack.map(stack);
public void run(Consumer<BedrockPack> consumer) {
currentPack.ifPresent(consumer);
}
public boolean finish() {
ensurePackIsCreated();
boolean success = currentPack.save();
currentPack = null;
public <T> Optional<T> run(Function<BedrockPack, T> function) {
return currentPack.map(function);
}
public Optional<Boolean> finish() {
Optional<Boolean> success = currentPack.map(BedrockPack::save);
currentPack = Optional.empty();
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.world.entity.player.Inventory;
import net.minecraft.world.item.ItemStack;
import org.geysermc.packgenerator.GeyserMappingsGenerator;
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.concurrent.CompletableFuture;
@@ -20,7 +20,7 @@ import java.util.function.Consumer;
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")
.then(ClientCommandManager.literal("create")
.then(ClientCommandManager.argument("name", StringArgumentType.word())
@@ -39,14 +39,16 @@ public class PackGeneratorCommand {
)
.then(ClientCommandManager.literal("map")
.executes(context -> {
packManager.run(pack -> {
ItemStack heldItem = context.getSource().getPlayer().getMainHandItem();
Optional<Boolean> problems = packManager.map(heldItem);
Optional<Boolean> problems = pack.map(heldItem);
if (problems.isEmpty()) {
context.getSource().sendError(Component.literal("No item found to map!"));
} 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"));
});
return 0;
})
)
@@ -55,24 +57,27 @@ public class PackGeneratorCommand {
)
.then(ClientCommandManager.literal("finish")
.executes(context -> {
if (!packManager.finish()) {
packManager.finish().ifPresent(success -> {
if (!success) {
context.getSource().sendError(Component.literal("Errors occurred whilst trying to write the pack to disk!"));
}
} else {
context.getSource().sendFeedback(Component.literal("Wrote pack to disk"));
}
});
return 0;
})
)
.then(ClientCommandManager.literal("auto")
.then(ClientCommandManager.argument("suggestions", CommandSuggestionsArgumentType.TYPE)
.executes(context -> {
packManager.ensurePackIsCreated();
Pair<String, CompletableFuture<Suggestions>> suggestions = CommandSuggestionsArgumentType.getSuggestions(context, "suggestions");
String baseCommand = suggestions.getFirst();
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())
.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;
})
@@ -82,10 +87,11 @@ public class PackGeneratorCommand {
}
public static int mapInventory(PackManager manager, Inventory inventory, Consumer<Component> feedback, boolean feedbackOnEmpty) {
return manager.run(pack -> {
int mapped = 0;
boolean errors = false;
for (ItemStack stack : inventory) {
Optional<Boolean> problems = manager.map(stack);
Optional<Boolean> problems = pack.map(stack);
if (problems.isPresent()) {
mapped++;
errors |= problems.get();
@@ -99,6 +105,8 @@ public class PackGeneratorCommand {
} else if (feedbackOnEmpty) {
feedback.accept(Component.literal("No items were mapped"));
}
return mapped;
}).orElse(0);
}
}

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;
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 {
private final PackManager packManager;
import java.util.stream.Stream;
public InventoryMapper(PackManager packManager) {
this.packManager = packManager;
public class InventoryMapper implements CustomItemProvider {
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.minecraft.client.Minecraft;
import net.minecraft.client.gui.components.SplashRenderer;
import net.minecraft.core.component.DataComponents;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.ProblemReporter;
import net.minecraft.world.item.ItemStack;
@@ -22,8 +23,10 @@ import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -45,7 +48,9 @@ public class BedrockPack {
private final GeyserMappings mappings;
private final BedrockTextures.Builder itemTextures;
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;
@@ -67,7 +72,7 @@ public class BedrockPack {
}
public Optional<Boolean> map(ItemStack stack) {
if (stack.isEmpty()) {
if (stack.isEmpty() || !modelsMapped.add(stack.get(DataComponents.ITEM_MODEL))) {
return Optional.empty();
}
AtomicBoolean problems = new AtomicBoolean();