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

Make BedrockPack more modular

This commit is contained in:
Eclipse
2025-10-14 09:53:45 +00:00
parent b741eac4c6
commit a38cbdf413
6 changed files with 261 additions and 112 deletions

View File

@@ -1,14 +1,40 @@
package org.geysermc.rainbow.client;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.components.SplashRenderer;
import net.minecraft.util.RandomSource;
import net.minecraft.util.StringUtil;
import org.geysermc.rainbow.CodecUtil;
import org.geysermc.rainbow.Rainbow;
import org.geysermc.rainbow.client.mixin.SplashRendererAccessor;
import org.geysermc.rainbow.client.render.MinecraftGeometryRenderer;
import org.geysermc.rainbow.pack.BedrockItem;
import org.geysermc.rainbow.pack.BedrockPack;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
public final class PackManager {
private static final List<String> PACK_SUMMARY_COMMENTS = List.of("Use the custom item API v2 build!", "bugrock moment", "RORY",
"use !!plshelp", "rm -rf --no-preserve-root /*", "welcome to the internet!", "beep beep. boop boop?", "FROG", "it is frog day", "it is cat day!",
"eclipse will hear about this.", "you must now say the word 'frog' in the #general channel", "You Just Lost The Game", "you are now breathing manually",
"you are now blinking manually", "you're eligible for a free hug token! <3", "don't mind me!", "hissss", "Gayser and Floodgayte, my favourite plugins.",
"meow", "we'll be done here soon™", "got anything else to say?", "we're done now!", "this will be fixed by v6053", "expect it to be done within 180 business days!",
"any colour you like", "someone tell Mojang about this", "you can't unbake baked models, so we'll store the unbaked models", "soon fully datagen ready",
"packconverter when", "codecs ftw");
private static final RandomSource RANDOM = RandomSource.create();
private static final Path EXPORT_DIRECTORY = FabricLoader.getInstance().getGameDir().resolve(Rainbow.MOD_ID);
private static final Path PACK_DIRECTORY = Path.of("pack");
private static final Path MAPPINGS_FILE = Path.of("geyser_mappings.json");
private static final Path PACK_ZIP_FILE = Path.of("pack.zip");
private static final Path REPORT_FILE = Path.of("report.txt");
private Optional<BedrockPack> currentPack = Optional.empty();
@@ -17,7 +43,13 @@ public final class PackManager {
throw new IllegalStateException("Already started a pack (" + currentPack.get().name() + ")");
}
currentPack = Optional.of(new BedrockPack(name, new MinecraftAssetResolver(Minecraft.getInstance())));
Path packDirectory = createPackDirectory(name);
BedrockPack pack = BedrockPack.builder(name, packDirectory.resolve(MAPPINGS_FILE), packDirectory.resolve(PACK_DIRECTORY), new MinecraftAssetResolver(Minecraft.getInstance()))
.withPackZipFile(packDirectory.resolve(PACK_ZIP_FILE))
.withGeometryRenderer(MinecraftGeometryRenderer.INSTANCE)
.reportSuccesses()
.build();
currentPack = Optional.of(pack);
}
public void run(Consumer<BedrockPack> consumer) {
@@ -29,12 +61,69 @@ public final class PackManager {
}
public Optional<Path> getExportPath() {
return currentPack.map(BedrockPack::getExportPath);
return currentPack.map(pack -> EXPORT_DIRECTORY.resolve(pack.name()));
}
public Optional<Boolean> finish() {
Optional<Boolean> success = currentPack.map(BedrockPack::save);
Optional<Boolean> success = currentPack.map(pack -> {
try {
Files.writeString(getExportPath().orElseThrow().resolve(REPORT_FILE), createPackSummary(pack));
} catch (IOException exception) {
// TODO log
}
return pack.save();
});
currentPack = Optional.empty();
return success;
}
private static String createPackSummary(BedrockPack pack) {
String problems = pack.getReporter().getTreeReport();
if (StringUtil.isBlank(problems)) {
problems = "Well that's odd... there's nothing here!";
}
Set<BedrockItem> bedrockItems = pack.getBedrockItems();
long attachables = bedrockItems.stream().filter(item -> item.attachable().isPresent()).count();
long geometries = bedrockItems.stream().filter(item -> item.geometry().isPresent()).count();
long animations = bedrockItems.stream().filter(item -> item.animation().isPresent()).count();
return """
-- PACK GENERATION REPORT --
// %s
Generated pack: %s
Mappings written: %d
Item texture atlas size: %d
Attachables tried to export: %d
Geometry files tried to export: %d
Animations tried to export: %d
Textures tried to export: %d
-- MAPPING TREE REPORT --
%s
""".formatted(randomSummaryComment(), pack.name(), pack.getMappings(), pack.getItemTextureAtlasSize(),
attachables, geometries, animations, pack.getAdditionalExportedTextures(), problems);
}
private static String randomSummaryComment() {
if (RANDOM.nextDouble() < 0.5) {
SplashRenderer splash = Minecraft.getInstance().getSplashManager().getSplash();
if (splash == null) {
return "Undefined Undefined :(";
}
return ((SplashRendererAccessor) splash).getSplash();
}
return randomBuiltinSummaryComment();
}
private static String randomBuiltinSummaryComment() {
return PACK_SUMMARY_COMMENTS.get(RANDOM.nextInt(PACK_SUMMARY_COMMENTS.size()));
}
private static Path createPackDirectory(String name) throws IOException {
Path path = EXPORT_DIRECTORY.resolve(name);
CodecUtil.ensureDirectoryExists(path);
return path;
}
}

View File

@@ -26,6 +26,7 @@ import java.util.Objects;
// TODO maybe just use this even for normal 2D items, not sure, could be useful for composite models and stuff
// TODO output in a size bedrock likes
public class MinecraftGeometryRenderer implements GeometryRenderer {
public static final MinecraftGeometryRenderer INSTANCE = new MinecraftGeometryRenderer();
@Override
public boolean render(ItemStack stack, Path path) {

View File

@@ -284,7 +284,7 @@ public class BedrockItemMapper {
if (customModel.isPresent()) {
texture = texture.withPath(path -> path + "_icon");
// FIXME Bit of a hack, preferably render geometry at a later stage
exportTexture = !packContext.geometryRenderer().render(stack, packContext.packPath().resolve(BedrockTextures.TEXTURES_FOLDER + texture.getPath() + ".png"));
exportTexture = !packContext.geometryRenderer().render(stack, packContext.paths().packRoot().resolve(BedrockTextures.TEXTURES_FOLDER + texture.getPath() + ".png"));
packContext.additionalTextureConsumer().accept(geometryTexture);
}

View File

@@ -3,10 +3,11 @@ package org.geysermc.rainbow.mapping;
import net.minecraft.resources.ResourceLocation;
import org.geysermc.rainbow.mapping.geometry.GeometryRenderer;
import org.geysermc.rainbow.definition.GeyserMappings;
import org.geysermc.rainbow.pack.PackPaths;
import java.nio.file.Path;
import java.util.function.Consumer;
public record PackContext(GeyserMappings mappings, Path packPath, BedrockItemConsumer itemConsumer, AssetResolver assetResolver,
GeometryRenderer geometryRenderer, Consumer<ResourceLocation> additionalTextureConsumer) {
public record PackContext(GeyserMappings mappings, PackPaths paths, BedrockItemConsumer itemConsumer, AssetResolver assetResolver,
GeometryRenderer geometryRenderer, Consumer<ResourceLocation> additionalTextureConsumer,
boolean reportSuccesses) {
}

View File

@@ -3,22 +3,22 @@ package org.geysermc.rainbow.pack;
import com.mojang.serialization.JsonOps;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntSet;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.core.Holder;
import net.minecraft.core.component.DataComponentPatch;
import net.minecraft.core.component.DataComponents;
import net.minecraft.resources.RegistryOps;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.ProblemReporter;
import net.minecraft.util.RandomSource;
import net.minecraft.util.StringUtil;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.component.CustomModelData;
import org.apache.commons.io.IOUtils;
import org.geysermc.rainbow.CodecUtil;
import org.geysermc.rainbow.PackConstants;
import org.geysermc.rainbow.Rainbow;
import org.geysermc.rainbow.mapping.AssetResolver;
import org.geysermc.rainbow.mapping.BedrockItemMapper;
import org.geysermc.rainbow.mapping.PackContext;
import org.geysermc.rainbow.mapping.geometry.GeometryRenderer;
import org.geysermc.rainbow.mapping.geometry.NoopGeometryRenderer;
import org.geysermc.rainbow.definition.GeyserMappings;
import org.jetbrains.annotations.NotNull;
@@ -27,7 +27,6 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashSet;
import java.util.List;
@@ -35,68 +34,38 @@ import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.UnaryOperator;
public class BedrockPack {
private static final List<String> PACK_SUMMARY_COMMENTS = List.of("Use the custom item API v2 build!", "bugrock moment", "RORY",
"use !!plshelp", "rm -rf --no-preserve-root /*", "welcome to the internet!", "beep beep. boop boop?", "FROG", "it is frog day", "it is cat day!",
"eclipse will hear about this.", "you must now say the word 'frog' in the #general channel", "You Just Lost The Game", "you are now breathing manually",
"you are now blinking manually", "you're eligible for a free hug token! <3", "don't mind me!", "hissss", "Gayser and Floodgayte, my favourite plugins.",
"meow", "we'll be done here soon™", "got anything else to say?", "we're done now!", "this will be fixed by v6053", "expect it to be done within 180 business days!",
"any colour you like", "someone tell Mojang about this", "you can't unbake baked models, so we'll store the unbaked models", "soon fully datagen ready",
"packconverter when", "codecs ftw");
private static final RandomSource RANDOM = RandomSource.create();
private static final Path EXPORT_DIRECTORY = FabricLoader.getInstance().getGameDir().resolve(Rainbow.MOD_ID);
private static final Path PACK_DIRECTORY = Path.of("pack");
private static final Path ATTACHABLES_DIRECTORY = Path.of("attachables");
private static final Path GEOMETRY_DIRECTORY = Path.of("models/entity");
private static final Path ANIMATION_DIRECTORY = Path.of("animations");
private static final Path MAPPINGS_FILE = Path.of("geyser_mappings.json");
private static final Path MANIFEST_FILE = Path.of("manifest.json");
private static final Path ITEM_ATLAS_FILE = Path.of("textures/item_texture.json");
private static final Path PACK_ZIP_FILE = Path.of("pack.zip");
private static final Path REPORT_FILE = Path.of("report.txt");
private final String name;
private final Path exportPath;
private final Path packPath;
private final PackManifest manifest;
private final GeyserMappings mappings;
private final BedrockTextures.Builder itemTextures;
private final PackPaths paths;
private final BedrockTextures.Builder itemTextures = BedrockTextures.builder();
private final Set<BedrockItem> bedrockItems = new HashSet<>();
private final Set<ResourceLocation> texturesToExport = new HashSet<>();
private final Set<ResourceLocation> modelsMapped = new HashSet<>();
private final IntSet customModelDataMapped = new IntOpenHashSet();
private final PackContext context;
private final PackManifest manifest;
private final ProblemReporter.Collector reporter;
private final PackContext context;
public BedrockPack(String name, AssetResolver assetResolver) throws IOException {
public BedrockPack(String name, PackPaths paths, AssetResolver assetResolver,
GeometryRenderer geometryRenderer, ProblemReporter.Collector reporter,
boolean reportSuccesses) {
this.name = name;
this.paths = paths;
// Not reading existing item mappings/texture atlas for now since that doesn't work all that well yet
exportPath = createPackDirectory(name);
packPath = exportPath.resolve(PACK_DIRECTORY);
//mappings = CodecUtil.readOrCompute(GeyserMappings.CODEC, exportPath.resolve(MAPPINGS_FILE), GeyserMappings::new);
mappings = new GeyserMappings();
manifest = CodecUtil.readOrCompute(PackManifest.CODEC, packPath.resolve(MANIFEST_FILE), () -> defaultManifest(name)).increment();
/*itemTextures = CodecUtil.readOrCompute(BedrockTextureAtlas.ITEM_ATLAS_CODEC, packPath.resolve(ITEM_ATLAS_FILE),
() -> BedrockTextureAtlas.itemAtlas(name, BedrockTextures.builder())).textures().toBuilder();*/
itemTextures = BedrockTextures.builder();
reporter = new ProblemReporter.Collector(() -> "Bedrock pack " + name + " ");
context = new PackContext(mappings, packPath, item -> {
this.context = new PackContext(new GeyserMappings(), paths, item -> {
itemTextures.withItemTexture(item);
if (item.exportTexture()) {
texturesToExport.add(item.texture());
}
bedrockItems.add(item);
}, assetResolver, NoopGeometryRenderer.INSTANCE, texturesToExport::add);
}, assetResolver, geometryRenderer, texturesToExport::add, reportSuccesses);
manifest = defaultManifest(name);
this.reporter = reporter;
}
public String name() {
@@ -146,20 +115,26 @@ public class BedrockPack {
return problems.get() ? MappingResult.PROBLEMS_OCCURRED : MappingResult.MAPPED_SUCCESSFULLY;
}
public MappingResult map(Holder<Item> item, DataComponentPatch patch) {
ItemStack stack = new ItemStack(item);
stack.applyComponents(patch);
return map(stack);
}
public boolean save() {
boolean success = true;
try {
CodecUtil.trySaveJson(GeyserMappings.CODEC, mappings, exportPath.resolve(MAPPINGS_FILE), RegistryOps.create(JsonOps.INSTANCE, context.assetResolver().registries()));
CodecUtil.trySaveJson(PackManifest.CODEC, manifest, packPath.resolve(MANIFEST_FILE));
CodecUtil.trySaveJson(BedrockTextureAtlas.CODEC, BedrockTextureAtlas.itemAtlas(name, itemTextures), packPath.resolve(ITEM_ATLAS_FILE));
CodecUtil.trySaveJson(GeyserMappings.CODEC, context.mappings(), paths.mappings(), RegistryOps.create(JsonOps.INSTANCE, context.assetResolver().registries()));
CodecUtil.trySaveJson(PackManifest.CODEC, manifest, paths.manifest());
CodecUtil.trySaveJson(BedrockTextureAtlas.CODEC, BedrockTextureAtlas.itemAtlas(name, itemTextures), paths.itemAtlas());
} catch (IOException | NullPointerException exception) {
reporter.forChild(() -> "saving Geyser mappings, pack manifest, and texture atlas ").report(() -> "failed to save to pack: " + exception);
success = false;
}
for (BedrockItem item : bedrockItems) {
try {
item.save(packPath.resolve(ATTACHABLES_DIRECTORY), packPath.resolve(GEOMETRY_DIRECTORY), packPath.resolve(ANIMATION_DIRECTORY));
item.save(paths.attachables(), paths.geometry(), paths.animation());
} catch (IOException exception) {
reporter.forChild(() -> "files for bedrock item " + item.identifier() + " ").report(() -> "failed to save to pack: " + exception);
success = false;
@@ -169,7 +144,7 @@ public class BedrockPack {
for (ResourceLocation texture : texturesToExport) {
texture = texture.withPath(path -> "textures/" + path + ".png");
try (InputStream inputTexture = context.assetResolver().getTexture(texture)) {
Path texturePath = packPath.resolve(texture.getPath());
Path texturePath = paths.packRoot().resolve(texture.getPath());
CodecUtil.ensureDirectoryExists(texturePath.getParent());
try (OutputStream outputTexture = new FileOutputStream(texturePath.toFile())) {
IOUtils.copy(inputTexture, outputTexture);
@@ -181,71 +156,35 @@ public class BedrockPack {
}
}
try {
CodecUtil.tryZipDirectory(packPath, exportPath.resolve(PACK_ZIP_FILE));
} catch (IOException exception) {
success = false;
if (paths.zipOutput().isPresent()) {
try {
CodecUtil.tryZipDirectory(paths.packRoot(), paths.zipOutput().get());
} catch (IOException exception) {
success = false;
}
}
try {
Files.writeString(exportPath.resolve(REPORT_FILE), createPackSummary());
} catch (IOException exception) {
// TODO log
}
return success;
}
public Path getExportPath() {
return exportPath;
public int getMappings() {
return context.mappings().size();
}
private String createPackSummary() {
String problems = reporter.getTreeReport();
if (StringUtil.isBlank(problems)) {
problems = "Well that's odd... there's nothing here!";
}
long attachables = bedrockItems.stream().filter(item -> item.attachable().isPresent()).count();
long geometries = bedrockItems.stream().filter(item -> item.geometry().isPresent()).count();
long animations = bedrockItems.stream().filter(item -> item.animation().isPresent()).count();
return """
-- PACK GENERATION REPORT --
// %s
Generated pack: %s
Mappings written: %d
Item texture atlas size: %d
Attachables tried to export: %d
Geometry files tried to export: %d
Animations tried to export: %d
Textures tried to export: %d
-- MAPPING TREE REPORT --
%s
""".formatted(randomSummaryComment(), name, mappings.size(), itemTextures.build().size(),
attachables, geometries, animations, texturesToExport.size(), problems);
public Set<BedrockItem> getBedrockItems() {
return Set.copyOf(bedrockItems);
}
private static String randomSummaryComment() {
if (RANDOM.nextDouble() < 0.6) {
/*SplashRenderer splash = Minecraft.getInstance().getSplashManager().getSplash();
if (splash == null) {
return "Undefined Undefined :(";
}
return ((SplashRendererAccessor) splash).getSplash();*/ // TODO
}
return randomBuiltinSummaryComment();
public int getItemTextureAtlasSize() {
return itemTextures.build().size();
}
private static String randomBuiltinSummaryComment() {
return PACK_SUMMARY_COMMENTS.get(RANDOM.nextInt(PACK_SUMMARY_COMMENTS.size()));
public int getAdditionalExportedTextures() {
return texturesToExport.size();
}
private static Path createPackDirectory(String name) throws IOException {
Path path = EXPORT_DIRECTORY.resolve(name);
CodecUtil.ensureDirectoryExists(path);
return path;
public ProblemReporter.Collector getReporter() {
return reporter;
}
private static PackManifest defaultManifest(String name) {
@@ -253,6 +192,117 @@ Textures tried to export: %d
List.of(new PackManifest.Module(name, PackConstants.DEFAULT_PACK_DESCRIPTION, UUID.randomUUID(), BedrockVersion.of(0))));
}
public static Builder builder(String name, Path mappingsPath, Path packRootPath, AssetResolver assetResolver) {
return new Builder(name, mappingsPath, packRootPath, assetResolver);
}
public static class Builder {
private static final Path ATTACHABLES_DIRECTORY = Path.of("attachables");
private static final Path GEOMETRY_DIRECTORY = Path.of("models/entity");
private static final Path ANIMATION_DIRECTORY = Path.of("animations");
private static final Path MANIFEST_FILE = Path.of("manifest.json");
private static final Path ITEM_ATLAS_FILE = Path.of("textures/item_texture.json");
private final String name;
private final Path mappingsPath;
private final Path packRootPath;
private final AssetResolver assetResolver;
private UnaryOperator<Path> attachablesPath = resolve(ATTACHABLES_DIRECTORY);
private UnaryOperator<Path> geometryPath = resolve(GEOMETRY_DIRECTORY);
private UnaryOperator<Path> animationPath = resolve(ANIMATION_DIRECTORY);
private UnaryOperator<Path> manifestPath = resolve(MANIFEST_FILE);
private UnaryOperator<Path> itemAtlasPath = resolve(ITEM_ATLAS_FILE);
private Path packZipFile = null;
private GeometryRenderer geometryRenderer = NoopGeometryRenderer.INSTANCE;
private ProblemReporter.Collector reporter;
private boolean reportSuccesses = false;
public Builder(String name, Path mappingsPath, Path packRootPath, AssetResolver assetResolver) {
this.name = name;
this.mappingsPath = mappingsPath;
this.packRootPath = packRootPath;
this.reporter = new ProblemReporter.Collector(() -> "Bedrock pack " + name + " ");
this.assetResolver = assetResolver;
}
public Builder withAttachablesPath(Path absolute) {
return withAttachablesPath(path -> absolute);
}
public Builder withAttachablesPath(UnaryOperator<Path> path) {
attachablesPath = path;
return this;
}
public Builder withGeometryPath(Path absolute) {
return withGeometryPath(path -> absolute);
}
public Builder withGeometryPath(UnaryOperator<Path> path) {
geometryPath = path;
return this;
}
public Builder withAnimationPath(Path absolute) {
return withAnimationPath(path -> absolute);
}
public Builder withAnimationPath(UnaryOperator<Path> path) {
animationPath = path;
return this;
}
public Builder withManifestPath(Path absolute) {
return withManifestPath(path -> absolute);
}
public Builder withManifestPath(UnaryOperator<Path> path) {
manifestPath = path;
return this;
}
public Builder withItemAtlasPath(Path absolute) {
return withItemAtlasPath(path -> absolute);
}
public Builder withItemAtlasPath(UnaryOperator<Path> path) {
itemAtlasPath = path;
return this;
}
public Builder withPackZipFile(Path absolute) {
packZipFile = absolute;
return this;
}
public Builder withGeometryRenderer(GeometryRenderer renderer) {
geometryRenderer = renderer;
return this;
}
public Builder withReporter(ProblemReporter.Collector reporter) {
this.reporter = reporter;
return this;
}
public Builder reportSuccesses() {
this.reportSuccesses = true;
return this;
}
public BedrockPack build() {
PackPaths paths = new PackPaths(mappingsPath, packRootPath, attachablesPath.apply(packRootPath),
geometryPath.apply(packRootPath), animationPath.apply(packRootPath), manifestPath.apply(packRootPath),
itemAtlasPath.apply(packRootPath), Optional.ofNullable(packZipFile));
return new BedrockPack(name, paths, assetResolver, geometryRenderer, reporter, reportSuccesses);
}
private static UnaryOperator<Path> resolve(Path child) {
return root -> root.resolve(child);
}
}
public enum MappingResult {
NONE_MAPPED,
MAPPED_SUCCESSFULLY,

View File

@@ -0,0 +1,8 @@
package org.geysermc.rainbow.pack;
import java.nio.file.Path;
import java.util.Optional;
public record PackPaths(Path mappings, Path packRoot, Path attachables, Path geometry, Path animation,
Path manifest, Path itemAtlas, Optional<Path> zipOutput) {
}