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

Sort Geyser mappings, async texture stitching, fix datagen

This commit is contained in:
Eclipse
2025-10-16 07:38:59 +00:00
parent 0c4a877220
commit 7f67a40c13
17 changed files with 145 additions and 75 deletions

View File

@@ -87,9 +87,9 @@ public final class PackManager {
}
Set<BedrockItem> bedrockItems = pack.getBedrockItems();
long attachables = bedrockItems.stream().filter(item -> item.attachable().isPresent()).count();
long geometries = bedrockItems.stream().filter(item -> item.geometry().geometry().isPresent()).count();
long animations = bedrockItems.stream().filter(item -> item.geometry().animation().isPresent()).count();
//long attachables = bedrockItems.stream().filter(item -> item.attachableCreator().isPresent()).count();
long geometries = bedrockItems.stream().filter(item -> item.geometryContext().geometry().isPresent()).count();
long animations = bedrockItems.stream().filter(item -> item.geometryContext().animation().isPresent()).count();
return """
-- PACK GENERATION REPORT --
@@ -98,15 +98,15 @@ public final class PackManager {
Generated pack: %s
Mappings written: %d
Item texture atlas size: %d
Attachables tried to export: %d
Attachables tried to export: FIXME
Geometry files tried to export: %d
Animations tried to export: %d
Textures tried to export: %d
Textures tried to export: FIXME
-- MAPPING TREE REPORT --
%s
""".formatted(randomSummaryComment(), pack.name(), pack.getMappings(), pack.getItemTextureAtlasSize(),
attachables, geometries, animations, pack.getAdditionalExportedTextures(), problems);
geometries, animations, problems);
}
private static String randomSummaryComment() {

View File

@@ -70,7 +70,7 @@ public abstract class RainbowModelProvider extends FabricModelProvider {
CompletableFuture<BedrockPack> bedrockPack = ClientPackLoader.openClientResources()
.thenCompose(resourceManager -> registries.thenApply(registries -> {
try (resourceManager) {
BedrockPack pack = createBedrockPack(outputRoot, new Serializer(output, resourceManager, registries),
BedrockPack pack = createBedrockPack(outputRoot, new Serializer(output, registries),
new DatagenResolver(resourceManager, equipmentInfos, itemInfos, models)).build();
for (Item item : itemInfos.keySet()) {
@@ -105,7 +105,7 @@ public abstract class RainbowModelProvider extends FabricModelProvider {
this.models = models;
}
private record Serializer(CachedOutput output, ResourceManager resourceManager, HolderLookup.Provider registries) implements PackSerializer {
private record Serializer(CachedOutput output, HolderLookup.Provider registries) implements PackSerializer {
@Override
public <T> CompletableFuture<?> saveJson(Codec<T> codec, T object, Path path) {
@@ -113,12 +113,10 @@ public abstract class RainbowModelProvider extends FabricModelProvider {
}
@Override
public CompletableFuture<?> saveTexture(ResourceLocation texture, Path path) {
public CompletableFuture<?> saveTexture(byte[] texture, Path path) {
return CompletableFuture.runAsync(() -> {
ResourceLocation texturePath = texture.withPath(p -> "textures/" + p + ".png");
try (InputStream inputTexture = resourceManager.open(texturePath)) {
byte[] textureBytes = inputTexture.readAllBytes();
output.writeIfNeeded(path, textureBytes, HashCode.fromBytes(textureBytes));
try {
output.writeIfNeeded(path, texture, HashCode.fromBytes(texture));
} catch (IOException exception) {
LOGGER.error("Failed to save file to {}", path, exception);
}
@@ -181,5 +179,10 @@ public abstract class RainbowModelProvider extends FabricModelProvider {
public Optional<EquipmentClientInfo> getEquipmentInfo(ResourceKey<EquipmentAsset> key) {
return Optional.ofNullable(equipmentInfos.get(key));
}
@Override
public InputStream openAsset(ResourceLocation location) throws IOException {
return resourceManager.open(location);
}
}
}

View File

@@ -3,9 +3,12 @@ package org.geysermc.rainbow.definition;
import com.mojang.serialization.MapCodec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import net.minecraft.resources.ResourceLocation;
import org.jetbrains.annotations.NotNull;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Stream;
public record GeyserGroupDefinition(Optional<ResourceLocation> model, List<GeyserMapping> definitions) implements GeyserMapping {
@@ -18,7 +21,9 @@ public record GeyserGroupDefinition(Optional<ResourceLocation> model, List<Geyse
);
public GeyserGroupDefinition with(GeyserMapping mapping) {
return new GeyserGroupDefinition(model, Stream.concat(definitions.stream(), Stream.of(mapping)).toList());
return new GeyserGroupDefinition(model, Stream.concat(definitions.stream(), Stream.of(mapping))
.sorted(Comparator.comparing(Function.identity()))
.toList());
}
public boolean isFor(Optional<ResourceLocation> model) {
@@ -53,4 +58,26 @@ public record GeyserGroupDefinition(Optional<ResourceLocation> model, List<Geyse
public Type type() {
return Type.GROUP;
}
@Override
public int compareTo(@NotNull GeyserMapping other) {
if (other instanceof GeyserGroupDefinition(Optional<ResourceLocation> otherModel, List<GeyserMapping> otherDefinitions)) {
if (model.isPresent() && otherModel.isPresent()) {
return model.get().compareTo(otherModel.get());
} else if (model.isPresent()) {
return 1; // Groups with models are always greater than groups without
} else if (otherModel.isPresent()) {
return -1;
} else if (definitions.isEmpty() && otherDefinitions.isEmpty()) {
return 0;
} else if (definitions.isEmpty()) {
return -1; // Groups with definitions are always greater than groups without
} else if (otherDefinitions.isEmpty()) {
return 1;
}
// Compare the first definition as a last resort
return definitions.getFirst().compareTo(otherDefinitions.getFirst());
}
return 1; // Groups are always greater than individual mappings
}
}

View File

@@ -1,6 +1,7 @@
package org.geysermc.rainbow.definition;
import net.minecraft.resources.ResourceLocation;
import org.jetbrains.annotations.NotNull;
import java.util.Optional;
@@ -9,4 +10,12 @@ public interface GeyserItemDefinition extends GeyserMapping {
GeyserBaseDefinition base();
boolean conflictsWith(Optional<ResourceLocation> parentModel, GeyserItemDefinition other);
@Override
default int compareTo(@NotNull GeyserMapping other) {
if (other instanceof GeyserItemDefinition itemDefinition) {
return base().bedrockIdentifier().compareTo(itemDefinition.base().bedrockIdentifier());
}
return -1; // Groups are always greater than individual mappings
}
}

View File

@@ -6,7 +6,7 @@ import com.mojang.serialization.MapCodec;
import net.minecraft.util.StringRepresentable;
import org.jetbrains.annotations.NotNull;
public interface GeyserMapping {
public interface GeyserMapping extends Comparable<GeyserMapping> {
Codec<GeyserMapping> CODEC = Codec.lazyInitialized(() -> Type.CODEC.dispatch(GeyserMapping::type, Type::codec));
// Not perfect since we're not checking single definitions in groups without a model... but good enough

View File

@@ -11,6 +11,7 @@ import org.geysermc.rainbow.CodecUtil;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@@ -26,7 +27,10 @@ public class GeyserMappings {
).apply(instance, (format, mappings) -> new GeyserMappings(mappings))
);
private final Multimap<Holder<Item>, GeyserMapping> mappings = MultimapBuilder.hashKeys().hashSetValues().build();
private final Multimap<Holder<Item>, GeyserMapping> mappings = MultimapBuilder
.hashKeys()
.<GeyserMapping>treeSetValues(Comparator.comparing(mapping -> mapping))
.build();
public GeyserMappings() {}

View File

@@ -123,7 +123,7 @@ public class BedrockItemMapper {
bedrockIdentifier = itemModelLocation;
}
BedrockGeometryContext geometry = BedrockGeometryContext.create(itemModel);
BedrockGeometryContext geometry = BedrockGeometryContext.create(bedrockIdentifier, itemModel, context.packContext);
if (context.packContext.reportSuccesses()) {
// Not a problem, but just report to get the model printed in the report file
context.report("creating mapping for block model " + itemModelLocation);
@@ -238,9 +238,8 @@ public class BedrockItemMapper {
return;
}
// TODO move attachable mapping somewhere else for cleaner code?
packContext.itemConsumer().accept(new BedrockItem(bedrockIdentifier, base.textureName(), geometry,
AttachableMapper.mapItem(stack.getComponentsPatch(), bedrockIdentifier, geometry, packContext.assetResolver(), packContext.additionalTextureConsumer())));
AttachableMapper.mapItem(packContext.assetResolver(), geometry, stack.getComponentsPatch())));
}
public void report(String problem) {

View File

@@ -2,12 +2,7 @@ package org.geysermc.rainbow.mapping;
import org.geysermc.rainbow.mapping.geometry.GeometryRenderer;
import org.geysermc.rainbow.definition.GeyserMappings;
import org.geysermc.rainbow.mapping.geometry.TextureHolder;
import org.geysermc.rainbow.pack.PackPaths;
import java.util.function.Consumer;
public record PackContext(GeyserMappings mappings, PackPaths paths, BedrockItemConsumer itemConsumer, AssetResolver assetResolver,
GeometryRenderer geometryRenderer, Consumer<TextureHolder> additionalTextureConsumer,
boolean reportSuccesses) {
}
GeometryRenderer geometryRenderer, boolean reportSuccesses) {}

View File

@@ -19,12 +19,12 @@ import java.util.function.Consumer;
public class AttachableMapper {
public static Optional<BedrockAttachable> mapItem(DataComponentPatch components, ResourceLocation bedrockIdentifier, BedrockGeometryContext geometryContext,
AssetResolver assetResolver, Consumer<TextureHolder> textureConsumer) {
public static AttachableCreator mapItem(AssetResolver assetResolver, BedrockGeometryContext geometryContext, DataComponentPatch components) {
// Crazy optional statement
// Unfortunately we can't have both equippables and custom models, so we prefer the latter :(
return geometryContext.geometry()
.map(geometry -> BedrockAttachable.geometry(bedrockIdentifier, geometry.definitions().getFirst(), geometryContext.texture().location().getPath()))
return (bedrockIdentifier, stitchedGeometry, textureConsumer) -> stitchedGeometry
.map(BedrockGeometryContext.StitchedGeometry::geometry)
.map(geometry -> BedrockAttachable.geometry(bedrockIdentifier, geometry.definitions().getFirst(), geometryContext.icon().location().getPath()))
.or(() -> Optional.ofNullable(components.get(DataComponents.EQUIPPABLE))
.flatMap(optional -> (Optional<Equippable>) optional)
.flatMap(equippable -> equippable.assetId().flatMap(assetResolver::getEquipmentInfo).map(info -> Pair.of(equippable.slot(), info)))
@@ -55,4 +55,10 @@ public class AttachableMapper {
private static ResourceLocation getTexture(List<EquipmentClientInfo.Layer> info, EquipmentClientInfo.LayerType layer) {
return info.getFirst().textureId().withPath(path -> "entity/equipment/" + layer.getSerializedName() + "/" + path);
}
@FunctionalInterface
public interface AttachableCreator {
Optional<BedrockAttachable> create(ResourceLocation bedrockIdentifier, Optional<BedrockGeometryContext.StitchedGeometry> geometry, Consumer<TextureHolder> textureConsumer);
}
}

View File

@@ -1,53 +1,62 @@
package org.geysermc.rainbow.mapping.geometry;
import com.google.common.base.Suppliers;
import net.minecraft.client.renderer.block.model.TextureSlots;
import net.minecraft.client.resources.model.Material;
import net.minecraft.client.resources.model.ResolvedModel;
import net.minecraft.resources.ResourceLocation;
import org.geysermc.rainbow.Rainbow;
import org.geysermc.rainbow.mapping.PackContext;
import org.geysermc.rainbow.mapping.animation.AnimationMapper;
import org.geysermc.rainbow.mapping.animation.BedrockAnimationContext;
import org.geysermc.rainbow.pack.geometry.BedrockGeometry;
import java.util.List;
import java.util.Optional;
import java.util.function.Supplier;
import java.util.stream.Stream;
public record BedrockGeometryContext(Optional<BedrockGeometry> geometry, Optional<BedrockAnimationContext> animation, TextureHolder texture,
public record BedrockGeometryContext(Optional<Supplier<StitchedGeometry>> geometry,
Optional<BedrockAnimationContext> animation, TextureHolder icon,
boolean handheld) {
private static final List<ResourceLocation> HANDHELD_MODELS = Stream.of("item/handheld", "item/handheld_rod", "item/handheld_mace")
.map(ResourceLocation::withDefaultNamespace)
.toList();
public static BedrockGeometryContext create(ResolvedModel model) {
public static BedrockGeometryContext create(ResourceLocation bedrockIdentifier, ResolvedModel model, PackContext context) {
ResolvedModel parentModel = model.parent();
// debugName() returns the resource location of the model as a string
boolean handheld = parentModel != null && HANDHELD_MODELS.contains(ResourceLocation.parse(parentModel.debugName()));
TextureSlots textures = model.getTopTextureSlots();
Material layer0Texture = textures.getMaterial("layer0");
Optional<BedrockGeometry> geometry;
Optional<Supplier<StitchedGeometry>> geometry;
Optional<BedrockAnimationContext> animation;
TextureHolder texture;
TextureHolder icon;
if (layer0Texture != null) {
geometry = Optional.empty();
animation = Optional.empty();
texture = new TextureHolder(layer0Texture.texture());
icon = new TextureHolder(layer0Texture.texture());
} else {
// Unknown model (doesn't use layer0), so we immediately assume the geometry is custom
// This check should probably be done differently (actually check if the model is 2D or 3D)
ResourceLocation modelLocation = ResourceLocation.parse(model.debugName());
String safeIdentifier = Rainbow.fileSafeResourceLocation(modelLocation);
String safeIdentifier = Rainbow.fileSafeResourceLocation(bedrockIdentifier);
geometry = Optional.of(Suppliers.memoize(() -> {
StitchedTextures stitchedTextures = StitchedTextures.stitchModelTextures(textures, context);
BedrockGeometry mappedGeometry = GeometryMapper.mapGeometry(safeIdentifier, "bone", model, stitchedTextures);
return new StitchedGeometry(mappedGeometry, new TextureHolder(modelLocation.withSuffix("_stitched"), stitchedTextures.stitched()));
}));
StitchedTextures stitchedTextures = StitchedTextures.stitchModelTextures(textures);
geometry = GeometryMapper.mapGeometry(safeIdentifier, "bone", model, stitchedTextures);
animation = Optional.of(AnimationMapper.mapAnimation(safeIdentifier, "bone", model.getTopTransforms()));
texture = new TextureHolder(modelLocation.withSuffix("_stitched"), Optional.of(stitchedTextures.stitched()));
// TODO geometry rendering
icon = new TextureHolder(modelLocation); // TODO
}
return new BedrockGeometryContext(geometry, animation, texture, handheld);
return new BedrockGeometryContext(geometry, animation, icon, handheld);
}
public record StitchedGeometry(BedrockGeometry geometry, TextureHolder stitchedTextures) {}
}

View File

@@ -14,15 +14,14 @@ import org.joml.Vector3f;
import org.joml.Vector3fc;
import java.util.Map;
import java.util.Optional;
public class GeometryMapper {
private static final Vector3fc CENTRE_OFFSET = new Vector3f(8.0F, 0.0F, 8.0F);
public static Optional<BedrockGeometry> mapGeometry(String identifier, String boneName, ResolvedModel model, StitchedTextures textures) {
public static BedrockGeometry mapGeometry(String identifier, String boneName, ResolvedModel model, StitchedTextures textures) {
UnbakedGeometry top = model.getTopGeometry();
if (top == UnbakedGeometry.EMPTY) {
return Optional.empty();
return BedrockGeometry.EMPTY;
}
BedrockGeometry.Builder builder = BedrockGeometry.builder(identifier);
@@ -54,7 +53,7 @@ public class GeometryMapper {
// Bind to the bone of the current item slot
bone.withBinding("q.item_slot_to_bone_name(context.item_slot)");
return Optional.of(builder.withBone(bone).build());
return builder.withBone(bone).build();
}
private static BedrockGeometry.Cube.Builder mapBlockElement(BlockElement element, StitchedTextures textures) {

View File

@@ -11,6 +11,8 @@ import net.minecraft.client.resources.metadata.animation.FrameSize;
import net.minecraft.client.resources.model.Material;
import net.minecraft.data.AtlasIds;
import net.minecraft.resources.ResourceLocation;
import org.geysermc.rainbow.Rainbow;
import org.geysermc.rainbow.mapping.PackContext;
import org.geysermc.rainbow.mixin.SpriteContentsAccessor;
import org.geysermc.rainbow.mixin.SpriteLoaderAccessor;
import org.geysermc.rainbow.mixin.TextureSlotsAccessor;
@@ -33,9 +35,9 @@ public record StitchedTextures(Map<String, TextureAtlasSprite> sprites, Supplier
return Optional.ofNullable(sprites.get(key));
}
public static StitchedTextures stitchModelTextures(TextureSlots textures) {
public static StitchedTextures stitchModelTextures(TextureSlots textures, PackContext context) {
Map<String, Material> materials = ((TextureSlotsAccessor) textures).getResolvedValues();
SpriteLoader.Preparations preparations = prepareStitching(materials.values().stream().map(Material::texture));
SpriteLoader.Preparations preparations = prepareStitching(materials.values().stream().map(Material::texture), context);
Map<String, TextureAtlasSprite> sprites = new HashMap<>();
for (Map.Entry<String, Material> material : materials.entrySet()) {
@@ -44,19 +46,19 @@ public record StitchedTextures(Map<String, TextureAtlasSprite> sprites, Supplier
return new StitchedTextures(Map.copyOf(sprites), () -> stitchTextureAtlas(preparations), preparations.width(), preparations.height());
}
private static SpriteLoader.Preparations prepareStitching(Stream<ResourceLocation> textures) {
private static SpriteLoader.Preparations prepareStitching(Stream<ResourceLocation> textures, PackContext context) {
// Atlas ID doesn't matter much here, but BLOCKS is the most appropriate
// Not sure if 1024 should be the max supported texture size, but it seems to work
SpriteLoader spriteLoader = new SpriteLoader(AtlasIds.BLOCKS, 1024, 16, 16);
List<SpriteContents> sprites = textures.distinct().map(StitchedTextures::readSpriteContents).toList();
List<SpriteContents> sprites = textures.distinct().map(texture -> readSpriteContents(texture, context)).toList();
return ((SpriteLoaderAccessor) spriteLoader).invokeStitch(sprites, 0, Util.backgroundExecutor());
}
private static SpriteContents readSpriteContents(ResourceLocation location) {
private static SpriteContents readSpriteContents(ResourceLocation location, PackContext context) {
// TODO decorate path util
// TODO don't use ResourceManager
// TODO IO is on main thread here?
try (InputStream textureStream = Minecraft.getInstance().getResourceManager().open(location.withPath(path -> "textures/" + path + ".png"))) {
try (InputStream textureStream = context.assetResolver().openAsset(Rainbow.decorateTextureLocation(location))) {
NativeImage texture = NativeImage.read(textureStream);
return new SpriteContents(location, new FrameSize(texture.getWidth(), texture.getHeight()), texture);
} catch (IOException exception) {

View File

@@ -8,6 +8,10 @@ import java.util.function.Supplier;
public record TextureHolder(ResourceLocation location, Optional<Supplier<NativeImage>> supplier) {
public TextureHolder(ResourceLocation location, Supplier<NativeImage> supplier) {
this(location, Optional.of(supplier));
}
public TextureHolder(ResourceLocation location) {
this(location, Optional.empty());
}

View File

@@ -3,24 +3,39 @@ package org.geysermc.rainbow.pack;
import net.minecraft.resources.ResourceLocation;
import org.geysermc.rainbow.Rainbow;
import org.geysermc.rainbow.mapping.PackSerializer;
import org.geysermc.rainbow.mapping.attachable.AttachableMapper;
import org.geysermc.rainbow.mapping.geometry.BedrockGeometryContext;
import org.geysermc.rainbow.mapping.geometry.TextureHolder;
import org.geysermc.rainbow.pack.attachable.BedrockAttachable;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Stream;
import java.util.function.Function;
import java.util.function.Supplier;
public record BedrockItem(ResourceLocation identifier, String textureName, BedrockGeometryContext geometry, Optional<BedrockAttachable> attachable) {
public record BedrockItem(ResourceLocation identifier, String textureName, BedrockGeometryContext geometryContext, AttachableMapper.AttachableCreator attachableCreator) {
public List<CompletableFuture<?>> save(PackSerializer serializer, Path attachableDirectory, Path geometryDirectory, Path animationDirectory) {
return Stream.concat(
attachable.stream().map(present -> present.save(serializer, attachableDirectory)),
Stream.concat(
geometry.geometry().stream().map(present -> present.save(serializer, geometryDirectory)),
geometry.animation().stream().map(context -> context.animation().save(serializer, animationDirectory, Rainbow.fileSafeResourceLocation(identifier)))
)
).toList();
public CompletableFuture<?> save(PackSerializer serializer, Path attachableDirectory, Path geometryDirectory, Path animationDirectory,
Function<TextureHolder, CompletableFuture<?>> textureSaver) {
return CompletableFuture.supplyAsync(() -> geometryContext.geometry().map(Supplier::get))
.thenCompose(stitchedGeometry -> {
List<TextureHolder> attachableTextures = new ArrayList<>();
Optional<BedrockAttachable> createdAttachable = attachableCreator.create(identifier, stitchedGeometry, attachableTextures::add);
return CompletableFuture.allOf(
textureSaver.apply(geometryContext.icon()),
createdAttachable.map(attachable -> attachable.save(serializer, attachableDirectory)).orElse(noop()),
CompletableFuture.allOf(attachableTextures.stream().map(textureSaver).toArray(CompletableFuture[]::new)),
stitchedGeometry.map(BedrockGeometryContext.StitchedGeometry::geometry).map(geometry -> geometry.save(serializer, geometryDirectory)).orElse(noop()),
stitchedGeometry.map(BedrockGeometryContext.StitchedGeometry::stitchedTextures).map(textureSaver).orElse(noop()),
geometryContext.animation().map(context -> context.animation().save(serializer, animationDirectory, Rainbow.fileSafeResourceLocation(identifier))).orElse(noop())
);
});
}
private static <T> CompletableFuture<T> noop() {
return CompletableFuture.completedFuture(null);
}
}

View File

@@ -35,6 +35,7 @@ import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Function;
import java.util.function.UnaryOperator;
public class BedrockPack {
@@ -45,7 +46,6 @@ public class BedrockPack {
private final BedrockTextures.Builder itemTextures = BedrockTextures.builder();
private final Set<BedrockItem> bedrockItems = new HashSet<>();
private final Set<TextureHolder> texturesToExport = new HashSet<>();
private final Set<ResourceLocation> modelsMapped = new HashSet<>();
private final IntSet customModelDataMapped = new IntOpenHashSet();
@@ -63,9 +63,8 @@ public class BedrockPack {
// Not reading existing item mappings/texture atlas for now since that doesn't work all that well yet
this.context = new PackContext(new GeyserMappings(), paths, item -> {
itemTextures.withItemTexture(item);
texturesToExport.add(item.geometry().texture());
bedrockItems.add(item);
}, assetResolver, geometryRenderer, texturesToExport::add, reportSuccesses);
}, assetResolver, geometryRenderer, reportSuccesses);
this.reporter = reporter;
}
@@ -128,13 +127,10 @@ public class BedrockPack {
futures.add(serializer.saveJson(GeyserMappings.CODEC, context.mappings(), paths.mappings()));
futures.add(serializer.saveJson(PackManifest.CODEC, manifest, paths.manifest()));
futures.add(serializer.saveJson(BedrockTextureAtlas.CODEC, BedrockTextureAtlas.itemAtlas(name, itemTextures), paths.itemAtlas()));
for (BedrockItem item : bedrockItems) {
futures.addAll(item.save(serializer, paths.attachables(), paths.geometry(), paths.animation()));
}
for (TextureHolder texture : texturesToExport) {
Function<TextureHolder, CompletableFuture<?>> textureSaver = texture -> {
ResourceLocation textureLocation = Rainbow.decorateTextureLocation(texture.location());
texture.supplier()
return texture.supplier()
.flatMap(image -> {
try {
return Optional.of(NativeImageUtil.writeToByteArray(image.get()));
@@ -152,7 +148,11 @@ public class BedrockPack {
}
})
.map(bytes -> serializer.saveTexture(bytes, paths.packRoot().resolve(textureLocation.getPath())))
.ifPresent(futures::add);
.orElse(CompletableFuture.completedFuture(null));
};
for (BedrockItem item : bedrockItems) {
futures.add(item.save(serializer, paths.attachables(), paths.geometry(), paths.animation(), textureSaver));
}
if (paths.zipOutput().isPresent()) {
@@ -178,10 +178,6 @@ public class BedrockPack {
return itemTextures.build().size();
}
public int getAdditionalExportedTextures() {
return texturesToExport.size();
}
public ProblemReporter.Collector getReporter() {
return reporter;
}

View File

@@ -33,7 +33,7 @@ public record BedrockTextures(Map<String, String> textures) {
private final Map<String, String> textures = new HashMap<>();
public Builder withItemTexture(BedrockItem item) {
return withTexture(item.textureName(), TEXTURES_FOLDER + item.geometry().texture().location().getPath());
return withTexture(item.textureName(), TEXTURES_FOLDER + item.geometryContext().icon().location().getPath());
}
public Builder withTexture(String name, String texture) {

View File

@@ -32,6 +32,8 @@ public record BedrockGeometry(BedrockVersion formatVersion, List<GeometryDefinit
).apply(instance, BedrockGeometry::new)
);
public static BedrockGeometry EMPTY = new BedrockGeometry(FORMAT_VERSION, List.of());
public CompletableFuture<?> save(PackSerializer serializer, Path geometryDirectory) {
return serializer.saveJson(CODEC, this, geometryDirectory.resolve(definitions.getFirst().info.identifier + ".geo.json"));
}