diff --git a/common/src/main/java/software/bluelib/BlueLibCommon.java b/common/src/main/java/software/bluelib/BlueLibCommon.java
index 69e04322..b807558b 100644
--- a/common/src/main/java/software/bluelib/BlueLibCommon.java
+++ b/common/src/main/java/software/bluelib/BlueLibCommon.java
@@ -9,6 +9,7 @@
import static software.bluelib.BlueLibConstants.SCHEDULER;
+import java.nio.file.Path;
import java.util.concurrent.TimeUnit;
import net.minecraft.network.chat.Component;
import org.jetbrains.annotations.ApiStatus;
@@ -16,16 +17,30 @@
import org.spongepowered.asm.launch.MixinBootstrap;
import software.bluelib.api.event.mod.ModIntegration;
import software.bluelib.api.net.NetworkRegistry;
+import software.bluelib.api.registry.AbstractRegistryBuilder;
+import software.bluelib.api.registry.BlueRegistryBuilder;
import software.bluelib.api.utils.logging.BaseLogLevel;
import software.bluelib.api.utils.logging.BaseLogger;
import software.bluelib.internal.BlueTranslation;
import software.bluelib.internal.registry.BlueNetworkRegistry;
import software.bluelib.internal.registry.BlueRecipeSerializerRegistry;
import software.bluelib.internal.registry.BlueRecipeTypeRegistry;
+import software.bluelib.internal.registry.TestEntityReg;
@ApiStatus.Internal
public class BlueLibCommon {
+ /**
+ * Initializes the {@link AbstractRegistryBuilder} instance with the mod ID. Replace {@link BlueLibConstants#MOD_ID} with your mod's unique mod ID to register content under your mod's namespace.
+ *
+ * This is essential for registering mod content such as items, blocks, and entities.
+ *
+ * Do not remove, as it will break the mod's registration system.
+ *
+ * Do not use this, you need to add this line into your own mod.
+ */
+ public static AbstractRegistryBuilder REGISTRIES = new BlueRegistryBuilder(BlueLibConstants.MOD_ID);
+
private BlueLibCommon() {}
public static void init() {
@@ -49,6 +64,8 @@ public static void doRegistration() {
InternalNetworkRegistry.networkServer();
BlueRecipeTypeRegistry.init();
BlueRecipeSerializerRegistry.init();
+
+ TestEntityReg.init();
}
public static void doClientRegistration() {
diff --git a/common/src/main/java/software/bluelib/api/registry/AbstractRegistryBuilder.java b/common/src/main/java/software/bluelib/api/registry/AbstractRegistryBuilder.java
new file mode 100644
index 00000000..0cc29d0f
--- /dev/null
+++ b/common/src/main/java/software/bluelib/api/registry/AbstractRegistryBuilder.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2024 BlueLib Contributors
+ *
+ * This Source Code Form is subject to the terms of the MIT License.
+ * If a copy of the MIT License was not distributed with this file,
+ * You can obtain one at https://opensource.org/licenses/MIT.
+ */
+package software.bluelib.api.registry;
+
+import java.util.List;
+import java.util.function.Function;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.LivingEntity;
+import net.minecraft.world.entity.MobCategory;
+import net.minecraft.world.item.Item;
+import net.minecraft.world.level.block.Block;
+import net.minecraft.world.level.block.entity.BlockEntity;
+import net.minecraft.world.level.block.entity.BlockEntityType;
+import software.bluelib.api.registry.builders.blocks.BlockBuilder;
+import software.bluelib.api.registry.builders.blocks.BlockEntityBuilder;
+import software.bluelib.api.registry.builders.entity.LivingEntityBuilder;
+import software.bluelib.api.registry.builders.entity.ProjectileBuilder;
+import software.bluelib.api.registry.builders.items.ItemBuilder;
+import software.bluelib.api.registry.builders.keybinds.KeybindBuilder;
+import software.bluelib.api.registry.builders.tabs.CreativeTabBuilder;
+import software.bluelib.api.registry.datagen.entity.EntityTagBuilder;
+
+public abstract class AbstractRegistryBuilder {
+
+ private final String modID;
+
+ public AbstractRegistryBuilder(final String pModId) {
+ modID = pModId;
+ }
+
+ public String getModID() {
+ return modID;
+ }
+
+ public LivingEntityBuilder livingEntity(String pName, EntityType.EntityFactory pFactory, MobCategory pCategory) {
+ return new LivingEntityBuilder<>(pName, pFactory, pCategory, modID);
+ }
+
+ public ProjectileBuilder projectile(String pName, EntityType.EntityFactory pFactory, MobCategory pCategory, Class pEntityClass) {
+ return new ProjectileBuilder<>(pName, pFactory, pCategory, pEntityClass, modID);
+ }
+
+ public BlockBuilder block(String pName, Function pFactory) {
+ return new BlockBuilder<>(pName, pFactory, modID);
+ }
+
+ public BlockEntityBuilder blockEntity(String pName, BlockEntityType.BlockEntitySupplier pFactory) {
+ return new BlockEntityBuilder<>(pName, pFactory, modID);
+ }
+
+ public ItemBuilder item(String pName, Function pConstructor) {
+ return new ItemBuilder<>(pName, pConstructor, modID);
+ }
+
+ public EntityTagBuilder entityTag(String pName) {
+ return new EntityTagBuilder(pName, modID);
+ }
+
+ public CreativeTabBuilder tab(String pId) {
+ return new CreativeTabBuilder(pId, modID);
+ }
+
+ public KeybindBuilder keybind(String pName, int pKeyCode) {
+ return new KeybindBuilder(pName, pKeyCode, modID);
+ }
+}
diff --git a/common/src/main/java/software/bluelib/api/registry/BlueRegistryBuilder.java b/common/src/main/java/software/bluelib/api/registry/BlueRegistryBuilder.java
new file mode 100644
index 00000000..83c07745
--- /dev/null
+++ b/common/src/main/java/software/bluelib/api/registry/BlueRegistryBuilder.java
@@ -0,0 +1,15 @@
+/*
+ * Copyright (C) 2024 BlueLib Contributors
+ *
+ * This Source Code Form is subject to the terms of the MIT License.
+ * If a copy of the MIT License was not distributed with this file,
+ * You can obtain one at https://opensource.org/licenses/MIT.
+ */
+package software.bluelib.api.registry;
+
+public class BlueRegistryBuilder extends AbstractRegistryBuilder {
+
+ public BlueRegistryBuilder(String pModId) {
+ super(pModId);
+ }
+}
diff --git a/common/src/main/java/software/bluelib/api/registry/builders/BuilderUtils.java b/common/src/main/java/software/bluelib/api/registry/builders/BuilderUtils.java
new file mode 100644
index 00000000..b7d31da7
--- /dev/null
+++ b/common/src/main/java/software/bluelib/api/registry/builders/BuilderUtils.java
@@ -0,0 +1,13 @@
+/*
+ * Copyright (C) 2024 BlueLib Contributors
+ *
+ * This Source Code Form is subject to the terms of the MIT License.
+ * If a copy of the MIT License was not distributed with this file,
+ * You can obtain one at https://opensource.org/licenses/MIT.
+ */
+package software.bluelib.api.registry.builders;
+
+public class BuilderUtils {
+
+ private BuilderUtils() {}
+}
diff --git a/common/src/main/java/software/bluelib/api/registry/builders/blocks/BlockBuilder.java b/common/src/main/java/software/bluelib/api/registry/builders/blocks/BlockBuilder.java
new file mode 100644
index 00000000..2cc4e637
--- /dev/null
+++ b/common/src/main/java/software/bluelib/api/registry/builders/blocks/BlockBuilder.java
@@ -0,0 +1,377 @@
+/*
+ * Copyright (C) 2024 BlueLib Contributors
+ *
+ * This Source Code Form is subject to the terms of the MIT License.
+ * If a copy of the MIT License was not distributed with this file,
+ * You can obtain one at https://opensource.org/licenses/MIT.
+ */
+package software.bluelib.api.registry.builders.blocks;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.BiConsumer;
+import java.util.function.Function;
+import java.util.function.Supplier;
+import net.minecraft.data.recipes.RecipeOutput;
+import net.minecraft.world.item.BlockItem;
+import net.minecraft.world.item.Item;
+import net.minecraft.world.level.block.Block;
+import software.bluelib.BlueLibConstants;
+import software.bluelib.api.registry.builders.items.ItemBuilder;
+import software.bluelib.api.registry.datagen.blocks.BlockModelGenerator;
+import software.bluelib.api.registry.datagen.blocks.BlockModelTemplates;
+import software.bluelib.api.registry.datagen.blockstates.BlockstateGenerator;
+import software.bluelib.api.registry.datagen.blockstates.BlockstateTemplates;
+import software.bluelib.api.registry.datagen.items.ItemModelGenerator;
+import software.bluelib.api.registry.datagen.items.ItemModelTemplates;
+import software.bluelib.api.registry.datagen.recipe.RecipeGenerator;
+
+public class BlockBuilder {
+
+ public static final List> REGISTERED_BUILDERS = new ArrayList<>();
+ private final String modId;
+ public final String blockName;
+ public final Function blockConstructor;
+ public Block.Properties properties;
+ public boolean createDefaultItem = false;
+ public T registeredBlock;
+ public static BlockstateTemplates blockstateTemplate;
+ public static BlockModelTemplates blockModelTemplate;
+ private BiConsumer recipeConsumer;
+ private boolean isOre = false;
+ private boolean hasRaw = false;
+ private boolean hasIngot = false;
+ private boolean hasNugget = false;
+ private boolean hasDeepslate = false;
+
+ private boolean hasLog = false;
+ private boolean hasStrippedLog = false;
+ private boolean hasPlanks = false;
+ private boolean hasFence = false;
+ private boolean hasDoor = false;
+ private boolean hasButton = false;
+ private boolean hasSlab = false;
+ private boolean hasPressurePlate = false;
+ private boolean hasStairs = false;
+ private boolean hasTrapdoor = false;
+ private boolean hasFenceGate = false;
+ //private boolean hasSign = false;
+ //private boolean hasHangingSign = false;
+
+ public BlockBuilder(String name, Function blockConstructor, String pModId) {
+ this.blockName = name;
+ this.blockConstructor = blockConstructor;
+ this.modId = pModId;
+ }
+
+ public BlockBuilder properties(Block.Properties properties) {
+ this.properties = properties;
+ return this;
+ }
+
+ public BlockBuilder defaultItem() {
+ this.createDefaultItem = true;
+ return this;
+ }
+
+ public BlockBuilder defaultBlockstate() {
+ return this.datagen()
+ .blockstate(BlockstateTemplates.SIMPLE_BLOCK)
+ .model(BlockModelTemplates.CUBE_ALL)
+ .finish();
+ }
+
+ public OreBuilder ore() {
+ this.isOre = true;
+ return new OreBuilder();
+ }
+
+ public WoodBuilder wood() {
+ return new WoodBuilder();
+ }
+
+ public BlockBuilder recipe(BiConsumer recipeConsumer) {
+ this.recipeConsumer = recipeConsumer;
+ return this;
+ }
+
+ private void generate(String blockName, BlockstateTemplates state, BlockModelTemplates model, ItemModelTemplates item) {
+ BlockstateGenerator.generateBlockstate(modId, blockName, state);
+ BlockModelGenerator.generateBlockModel(modId, blockName, model);
+ ItemModelGenerator.generateItemModel(modId, blockName, item);
+ }
+
+ public BlockstateBuilder datagen() {
+ return new BlockstateBuilder();
+ }
+
+ public Supplier register() {
+ if (properties == null) {
+ properties = Block.Properties.of();
+ }
+
+ Supplier blockSupplier = null;
+
+ if (!isOre) {
+ blockSupplier = BlueLibConstants.PlatformHelper.REGISTRY.registerBlock(blockName, () -> {
+ T block = blockConstructor.apply(properties);
+ this.registeredBlock = block;
+ return block;
+ });
+
+ if (createDefaultItem) {
+ Supplier- itemSupplier = () -> new BlockItem(registeredBlock, new Item.Properties());
+ BlueLibConstants.PlatformHelper.REGISTRY.registerItem(blockName, itemSupplier);
+ }
+ }
+
+ if (isOre) {
+ blockSupplier = BlueLibConstants.PlatformHelper.REGISTRY.registerBlock(blockName + "_block", () -> {
+ T block = blockConstructor.apply(properties);
+ this.registeredBlock = block;
+ return block;
+ });
+
+ generate(blockName + "_ore", BlockstateTemplates.SIMPLE_BLOCK, BlockModelTemplates.CUBE_ALL, ItemModelTemplates.BLOCK_ITEM);
+ generate(blockName + "_block", BlockstateTemplates.SIMPLE_BLOCK, BlockModelTemplates.CUBE_ALL, ItemModelTemplates.BLOCK_ITEM);
+
+ if (createDefaultItem) {
+ Supplier
- itemSupplier = () -> new BlockItem(registeredBlock, new Item.Properties());
+ BlueLibConstants.PlatformHelper.REGISTRY.registerItem(blockName + "_block", itemSupplier);
+ }
+
+ if (hasDeepslate) {
+ generate(blockName + "_deepslate_ore", BlockstateTemplates.SIMPLE_BLOCK, BlockModelTemplates.CUBE_ALL, ItemModelTemplates.BLOCK_ITEM);
+ }
+ if (hasRaw) {
+ ItemBuilder.item("raw_" + blockName, Item::new, modId)
+ .model(ItemModelTemplates.GENERATED)
+ .register();
+ }
+ if (hasIngot) {
+ ItemBuilder.item(blockName + "_ingot", Item::new, modId)
+ .model(ItemModelTemplates.GENERATED)
+ .register();
+ }
+ if (hasNugget) {
+ ItemBuilder.item(blockName + "_nugget", Item::new, modId)
+ .model(ItemModelTemplates.GENERATED)
+ .register();
+ }
+ }
+
+ REGISTERED_BUILDERS.add(this);
+ return blockSupplier;
+ }
+
+ public static void doBlockModelGen(String modId) {
+ for (BlockBuilder> builder : REGISTERED_BUILDERS) {
+ if (!builder.isOre && builder.createDefaultItem) {
+ ItemModelGenerator.generateItemModel(modId, builder.blockName, ItemModelTemplates.BLOCK_ITEM);
+ }
+ if (!builder.isOre && blockstateTemplate != null) {
+ BlockstateGenerator.generateBlockstate(modId, builder.blockName, blockstateTemplate);
+ BlockModelGenerator.generateBlockModel(modId, builder.blockName, blockModelTemplate);
+ }
+ }
+ }
+
+ public static void doRecipeGen(String modId) {
+ for (BlockBuilder> builder : REGISTERED_BUILDERS) {
+ if (builder.recipeConsumer != null) {
+ RecipeGenerator.generateRecipe(modId, builder.blockName, (recipeOutput, jsonSupplier) -> {
+ RecipeContext ctx = new RecipeContext(builder.registeredBlock);
+ builder.recipeConsumer.accept(ctx, recipeOutput);
+ });
+ }
+ }
+ }
+
+ public static class RecipeContext {
+
+ private final Block block;
+
+ public RecipeContext(Block block) {
+ this.block = block;
+ }
+
+ public Block getEntry() {
+ return block;
+ }
+ }
+
+ public class BlockstateBuilder {
+
+ private final BlockBuilder parent;
+
+ public BlockstateBuilder() {
+ this.parent = BlockBuilder.this;
+ }
+
+ public BlockstateBuilder blockstate(BlockstateTemplates blockstates) {
+ blockstateTemplate = blockstates;
+ return this;
+ }
+
+ public BlockstateBuilder model(BlockModelTemplates models) {
+ blockModelTemplate = models;
+ return this;
+ }
+
+ public BlockBuilder finish() {
+ return parent;
+ }
+ }
+
+ public class OreBuilder {
+
+ private final BlockBuilder parent;
+
+ public OreBuilder() {
+ this.parent = BlockBuilder.this;
+ }
+
+ public OreBuilder hasRaw() {
+ hasRaw = true;
+ return this;
+ }
+
+ public OreBuilder hasIngot() {
+ hasIngot = true;
+ return this;
+ }
+
+ public OreBuilder hasNugget() {
+ hasNugget = true;
+ return this;
+ }
+
+ public OreBuilder hasDeepslate() {
+ hasDeepslate = true;
+ return this;
+ }
+
+ public BlockBuilder finish() {
+ return parent;
+ }
+ }
+
+ public class WoodBuilder {
+
+ private final BlockBuilder parent;
+
+ public WoodBuilder() {
+ this.parent = BlockBuilder.this;
+ }
+
+ public WoodBuilder hasLog() {
+ hasLog = true;
+ return this;
+ }
+
+ public WoodBuilder hasStrippedLog() {
+ hasStrippedLog = true;
+ return this;
+ }
+
+ public WoodBuilder hasPlanks() {
+ hasPlanks = true;
+ return this;
+ }
+
+ public WoodBuilder hasFence() {
+ hasFence = true;
+ return this;
+ }
+
+ public WoodBuilder hasDoor() {
+ hasDoor = true;
+ return this;
+ }
+
+ public WoodBuilder hasButton() {
+ hasButton = true;
+ return this;
+ }
+
+ public WoodBuilder hasSlab() {
+ hasSlab = true;
+ return this;
+ }
+
+ public WoodBuilder hasPressurePlate() {
+ hasPressurePlate = true;
+ return this;
+ }
+
+ public WoodBuilder hasStairs() {
+ hasStairs = true;
+ return this;
+ }
+
+ public WoodBuilder hasTrapdoor() {
+ hasTrapdoor = true;
+ return this;
+ }
+
+ public WoodBuilder hasFenceGate() {
+ hasFenceGate = true;
+ return this;
+ }
+
+ /*public WoodBuilder hasSign() {
+ hasSign = true;
+ return this;
+ }
+
+ public WoodBuilder hasHangingSign() {
+ hasHangingSign = true;
+ return this;
+ }*/
+
+ public BlockBuilder finish() {
+ if (hasLog) {
+ generate(blockName + "_log", BlockstateTemplates.ORIENTED_BLOCK, BlockModelTemplates.COLUMN, ItemModelTemplates.BLOCK_ITEM);
+ }
+ if (hasStrippedLog) {
+ generate(blockName + "_log_stripped", BlockstateTemplates.ORIENTED_BLOCK, BlockModelTemplates.COLUMN, ItemModelTemplates.BLOCK_ITEM);
+ }
+ if (hasPlanks) {
+ generate(blockName + "_planks", BlockstateTemplates.SIMPLE_BLOCK, BlockModelTemplates.CUBE_ALL, ItemModelTemplates.BLOCK_ITEM);
+ }
+ if (hasFence) {
+ generate(blockName + "_fence", BlockstateTemplates.FENCE_BLOCK, BlockModelTemplates.FENCE, ItemModelTemplates.BLOCK_WITH_INVENTORY_MODEL);
+ ItemModelGenerator.generateItemModel(modId, blockName + "_fence", ItemModelTemplates.GENERATED);
+ }
+ if (hasDoor) {
+ generate(blockName + "_door", BlockstateTemplates.DOOR_BLOCK, BlockModelTemplates.DOOR, ItemModelTemplates.BLOCK_SPRITE);
+ }
+ if (hasButton) {
+ generate(blockName + "_button", BlockstateTemplates.BUTTON_BLOCK, BlockModelTemplates.BUTTON, ItemModelTemplates.BLOCK_WITH_INVENTORY_MODEL);
+ }
+ if (hasSlab) {
+ generate(blockName + "_slab", BlockstateTemplates.SLAB_BLOCK, BlockModelTemplates.SLAB, ItemModelTemplates.BLOCK_SPRITE);
+ }
+ if (hasPressurePlate) {
+ generate(blockName + "_pressure_plate", BlockstateTemplates.PRESSURE_PLATE_BLOCK, BlockModelTemplates.PRESSURE_PLATE, ItemModelTemplates.BLOCK_SPRITE);
+ }
+ if (hasStairs) {
+ generate(blockName + "_stairs", BlockstateTemplates.STAIRS_BLOCK, BlockModelTemplates.STAIRS, ItemModelTemplates.BLOCK_SPRITE);
+ }
+ if (hasTrapdoor) {
+ generate(blockName + "_trapdoor", BlockstateTemplates.TRAPDOOR_BLOCK, BlockModelTemplates.TRAPDOOR, ItemModelTemplates.BLOCK_SPRITE);
+ }
+ if (hasFenceGate) {
+ generate(blockName + "_fence_gate", BlockstateTemplates.FENCE_GATE_BLOCK, BlockModelTemplates.FENCE_GATE, ItemModelTemplates.BLOCK_SPRITE);
+ }
+ /*if (hasSign) {
+ generate(blockName + "_fence_gate", BlockstateTemplates.FENCE_GATE_BLOCK, BlockModelTemplates.FENCE_GATE, ItemModelTemplates.BLOCK_SPRITE);
+ }
+ if (hasHangingSign) {
+ generate(blockName + "_fence_gate", BlockstateTemplates.FENCE_GATE_BLOCK, BlockModelTemplates.FENCE_GATE, ItemModelTemplates.BLOCK_SPRITE);
+ }*/
+
+ return parent;
+ }
+ }
+}
diff --git a/common/src/main/java/software/bluelib/api/registry/builders/blocks/BlockEntityBuilder.java b/common/src/main/java/software/bluelib/api/registry/builders/blocks/BlockEntityBuilder.java
new file mode 100644
index 00000000..e1829e19
--- /dev/null
+++ b/common/src/main/java/software/bluelib/api/registry/builders/blocks/BlockEntityBuilder.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2024 BlueLib Contributors
+ *
+ * This Source Code Form is subject to the terms of the MIT License.
+ * If a copy of the MIT License was not distributed with this file,
+ * You can obtain one at https://opensource.org/licenses/MIT.
+ */
+package software.bluelib.api.registry.builders.blocks;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.Supplier;
+import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider;
+import net.minecraft.world.level.block.Block;
+import net.minecraft.world.level.block.entity.BlockEntity;
+import net.minecraft.world.level.block.entity.BlockEntityType;
+import software.bluelib.BlueLibConstants;
+import software.bluelib.api.registry.helpers.entity.RenderHelper;
+
+public class BlockEntityBuilder {
+
+ protected static final List> REGISTERED_BUILDERS = new ArrayList<>();
+
+ protected final String modID;
+ protected final String name;
+ protected final BlockEntityType.BlockEntitySupplier blockEntityFactory;
+ protected Supplier> rendererProvider;
+ protected Supplier> blockEntityType;
+ protected List> validBlockSuppliers;
+
+ public BlockEntityBuilder(String name, BlockEntityType.BlockEntitySupplier blockEntityFactory, String pModID) {
+ this.modID = pModID;
+ this.name = name;
+ this.blockEntityFactory = blockEntityFactory;
+ this.validBlockSuppliers = new ArrayList<>();
+ }
+
+ public BlockEntityBuilder validBlocks(Supplier... blockSuppliers) {
+ this.validBlockSuppliers = Arrays.asList(blockSuppliers);
+ return this;
+ }
+
+ public BlockEntityBuilder renderer(BlockEntityRendererProvider renderer) {
+ this.rendererProvider = () -> renderer;
+ return this;
+ }
+
+ public Supplier> register() {
+ Supplier> blockEntityTypeSupplier = BlueLibConstants.PlatformHelper.REGISTRY.registerBlockEntity(name, () -> {
+ Block[] blocks = validBlockSuppliers.stream()
+ .map(Supplier::get)
+ .toArray(Block[]::new);
+ BlockEntityType type = BlockEntityType.Builder.of(blockEntityFactory, blocks).build(null);
+ this.blockEntityType = () -> type;
+ return type;
+ });
+
+ if (rendererProvider != null) {
+ new RenderHelper().queueRenderer((entityConsumer, blockConsumer) -> {
+ blockConsumer.accept(blockEntityType.get(), rendererProvider.get());
+ });
+ }
+
+ REGISTERED_BUILDERS.add(this);
+ return blockEntityTypeSupplier;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public Supplier> getBlockEntityType() {
+ return blockEntityType;
+ }
+}
diff --git a/common/src/main/java/software/bluelib/api/registry/builders/entity/EntityBuilder.java b/common/src/main/java/software/bluelib/api/registry/builders/entity/EntityBuilder.java
new file mode 100644
index 00000000..815604d9
--- /dev/null
+++ b/common/src/main/java/software/bluelib/api/registry/builders/entity/EntityBuilder.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2024 BlueLib Contributors
+ *
+ * This Source Code Form is subject to the terms of the MIT License.
+ * If a copy of the MIT License was not distributed with this file,
+ * You can obtain one at https://opensource.org/licenses/MIT.
+ */
+package software.bluelib.api.registry.builders.entity;
+
+import java.util.function.Supplier;
+import net.minecraft.client.renderer.entity.EntityRendererProvider;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.MobCategory;
+import software.bluelib.BlueLibConstants;
+import software.bluelib.api.registry.helpers.entity.RenderHelper;
+
+public abstract class EntityBuilder> {
+
+ protected final String modId;
+ protected final String name;
+ protected final EntityType.EntityFactory factory;
+ protected final MobCategory category;
+ protected float width;
+ protected float height;
+ protected EntityRendererProvider rendererProvider = null;
+
+ public EntityBuilder(String pName, EntityType.EntityFactory pFactory, MobCategory pCategory, String pModId) {
+ this.modId = pModId;
+ this.name = pName;
+ this.factory = pFactory;
+ this.category = pCategory;
+ }
+
+ public SELF sized(float pWidth, float pHeight) {
+ this.width = pWidth;
+ this.height = pHeight;
+ return self();
+ }
+
+ public SELF renderer(EntityRendererProvider pRendererProvider) {
+ this.rendererProvider = pRendererProvider;
+ return self();
+ }
+
+ public Supplier> register() {
+ Supplier> entityTypeSupplier = BlueLibConstants.PlatformHelper.REGISTRY.registerEntity(name,
+ () -> EntityType.Builder.of(factory, category)
+ .sized(width, height)
+ .build(name));
+
+ if (rendererProvider != null) {
+ new RenderHelper().queueRenderer((entityConsumer, blockConsumer) -> {
+ entityConsumer.accept(entityTypeSupplier.get(), rendererProvider);
+ });
+ }
+
+ return entityTypeSupplier;
+ }
+
+ protected abstract SELF self();
+}
diff --git a/common/src/main/java/software/bluelib/api/registry/builders/entity/LivingEntityBuilder.java b/common/src/main/java/software/bluelib/api/registry/builders/entity/LivingEntityBuilder.java
new file mode 100644
index 00000000..43679751
--- /dev/null
+++ b/common/src/main/java/software/bluelib/api/registry/builders/entity/LivingEntityBuilder.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2024 BlueLib Contributors
+ *
+ * This Source Code Form is subject to the terms of the MIT License.
+ * If a copy of the MIT License was not distributed with this file,
+ * You can obtain one at https://opensource.org/licenses/MIT.
+ */
+package software.bluelib.api.registry.builders.entity;
+
+import java.util.*;
+import java.util.function.Supplier;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.LivingEntity;
+import net.minecraft.world.entity.Mob;
+import net.minecraft.world.entity.MobCategory;
+import net.minecraft.world.entity.ai.attributes.AttributeSupplier;
+import net.minecraft.world.item.CreativeModeTab;
+import net.minecraft.world.item.Item;
+import software.bluelib.BlueLibConstants;
+import software.bluelib.api.registry.datagen.items.ItemModelGenerator;
+import software.bluelib.api.registry.datagen.items.ItemModelTemplates;
+import software.bluelib.api.registry.helpers.entity.AttributeHelper;
+import software.bluelib.api.registry.helpers.entity.RenderHelper;
+import software.bluelib.api.registry.helpers.items.BlueSpawnEggItem;
+
+public class LivingEntityBuilder extends EntityBuilder> {
+
+ private boolean hasSpawnEgg = false;
+ private int primaryEggColor;
+ private int secondaryEggColor;
+ private Supplier attributeBuilder;
+ private boolean hasVariants = false;
+ private final List entityNames = new ArrayList<>();
+ private Supplier tabSupplier;
+ private final Map, List>> spawnEggsByTab = new HashMap<>();
+
+ public LivingEntityBuilder(String pName, EntityType.EntityFactory pFactory, MobCategory pCategory, String pModId) {
+ super(pName, pFactory, pCategory, pModId);
+ }
+
+ public LivingEntityBuilder spawnEgg(int pPrimaryColor, int pSecondaryColor) {
+ this.hasSpawnEgg = true;
+ this.primaryEggColor = pPrimaryColor;
+ this.secondaryEggColor = pSecondaryColor;
+ return this;
+ }
+
+ public LivingEntityBuilder attributes(Supplier pAttributes) {
+ this.attributeBuilder = pAttributes;
+ return this;
+ }
+
+ public LivingEntityBuilder loadVariants() {
+ this.hasVariants = true;
+ return this;
+ }
+
+ public LivingEntityBuilder tab(Supplier pTabSupplier) {
+ this.tabSupplier = pTabSupplier;
+ return this;
+ }
+
+ public List> getSpawnEggsForTab(CreativeModeTab pTab) {
+ List> spawnEggs = new ArrayList<>();
+ for (Map.Entry, List>> entry : spawnEggsByTab.entrySet()) {
+ if (Objects.equals(entry.getKey().get(), pTab)) {
+ spawnEggs.addAll(entry.getValue());
+ }
+ }
+ return Collections.unmodifiableList(spawnEggs);
+ }
+
+ @Override
+ public Supplier> register() {
+ Supplier> entityTypeSupplier = BlueLibConstants.PlatformHelper.REGISTRY.registerEntity(
+ name,
+ () -> EntityType.Builder.of(factory, category).sized(width, height).build(name));
+
+ if (hasSpawnEgg && Mob.class.isAssignableFrom(factory.getClass())) {
+ registerSpawnEgg(name, (Supplier>) (Supplier>) entityTypeSupplier,
+ primaryEggColor, secondaryEggColor, tabSupplier);
+ }
+
+ if (attributeBuilder != null) {
+ new AttributeHelper().queueAttributes(entityTypeSupplier, attributeBuilder);
+ }
+
+ if (rendererProvider != null) {
+ new RenderHelper().queueRenderer((entityConsumer, blockConsumer) -> entityConsumer.accept(entityTypeSupplier.get(), rendererProvider));
+ }
+
+ if (hasVariants) {
+ entityNames.add(name);
+ }
+
+ return entityTypeSupplier;
+ }
+
+ public void doSpawnEggDatagen(String pModId) {
+ if (hasSpawnEgg) {
+ String spawnEggName = name + "_spawn_egg";
+ ItemModelGenerator.generateItemModel(pModId, spawnEggName, ItemModelTemplates.SPAWN_EGG);
+ }
+ }
+
+ public List getEntityNames() {
+ return Collections.unmodifiableList(entityNames);
+ }
+
+ public Supplier
- registerSpawnEgg(
+ String pName,
+ Supplier> pEntityType,
+ int pPrimaryColor,
+ int pSecondaryColor,
+ Supplier pTabSupplier) {
+ hasSpawnEgg = true;
+ Supplier
- spawnEggSupplier = BlueLibConstants.PlatformHelper.REGISTRY.registerItem(
+ pName + "_spawn_egg",
+ () -> new BlueSpawnEggItem(
+ pEntityType.get(),
+ pPrimaryColor,
+ pSecondaryColor,
+ new Item.Properties()));
+ if (pTabSupplier != null) {
+ spawnEggsByTab.computeIfAbsent(pTabSupplier, k -> new ArrayList<>()).add(spawnEggSupplier);
+ }
+ return spawnEggSupplier;
+ }
+
+ @Override
+ protected LivingEntityBuilder self() {
+ return this;
+ }
+}
diff --git a/common/src/main/java/software/bluelib/api/registry/builders/entity/ProjectileBuilder.java b/common/src/main/java/software/bluelib/api/registry/builders/entity/ProjectileBuilder.java
new file mode 100644
index 00000000..9ba7c313
--- /dev/null
+++ b/common/src/main/java/software/bluelib/api/registry/builders/entity/ProjectileBuilder.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2024 BlueLib Contributors
+ *
+ * This Source Code Form is subject to the terms of the MIT License.
+ * If a copy of the MIT License was not distributed with this file,
+ * You can obtain one at https://opensource.org/licenses/MIT.
+ */
+package software.bluelib.api.registry.builders.entity;
+
+import net.minecraft.client.renderer.entity.EntityRendererProvider;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.MobCategory;
+
+public class ProjectileBuilder extends EntityBuilder> {
+
+ private final Class entityClass;
+
+ public ProjectileBuilder(String pName, EntityType.EntityFactory pFactory, MobCategory pCategory, Class pEntityClass, String pModId) {
+ super(pName, pFactory, pCategory, pModId);
+ this.entityClass = pEntityClass;
+ }
+
+ public ProjectileBuilder sized(float pWidth, float pHeight) {
+ this.width = pWidth;
+ this.height = pHeight;
+ return this;
+ }
+
+ public ProjectileBuilder renderer(EntityRendererProvider pRendererProvider) {
+ this.rendererProvider = pRendererProvider;
+ return this;
+ }
+
+ @Override
+ protected ProjectileBuilder self() {
+ return this;
+ }
+}
diff --git a/common/src/main/java/software/bluelib/api/registry/builders/items/ItemBuilder.java b/common/src/main/java/software/bluelib/api/registry/builders/items/ItemBuilder.java
new file mode 100644
index 00000000..1b21b61f
--- /dev/null
+++ b/common/src/main/java/software/bluelib/api/registry/builders/items/ItemBuilder.java
@@ -0,0 +1,230 @@
+/*
+ * Copyright (C) 2024 BlueLib Contributors
+ *
+ * This Source Code Form is subject to the terms of the MIT License.
+ * If a copy of the MIT License was not distributed with this file,
+ * You can obtain one at https://opensource.org/licenses/MIT.
+ */
+package software.bluelib.api.registry.builders.items;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+import net.minecraft.core.Holder;
+import net.minecraft.core.registries.BuiltInRegistries;
+import net.minecraft.data.recipes.RecipeOutput;
+import net.minecraft.resources.ResourceLocation;
+import net.minecraft.world.item.*;
+import software.bluelib.BlueLibConstants;
+import software.bluelib.api.registry.datagen.items.ItemModelGenerator;
+import software.bluelib.api.registry.datagen.items.ItemModelTemplates;
+import software.bluelib.api.registry.datagen.recipe.RecipeGenerator;
+import software.bluelib.api.registry.helpers.ArmorSetConfig;
+import software.bluelib.api.registry.helpers.ToolsetConfig;
+
+@SuppressWarnings("unchecked")
+public class ItemBuilder {
+
+ public final List generatedItems = new ArrayList<>();
+ public final Map customModelMap = new HashMap<>();
+ public final List> REGISTERED_BUILDERS = new ArrayList<>();
+ public final String itemName;
+ public final Function itemConstructor;
+ public Consumer propertiesConsumer = props -> {};
+ public static final Map>> TOOLSETS = new HashMap<>();
+ public static final Map>> ARMORSETS = new HashMap<>();
+ private final String modId;
+ private BiConsumer recipeConsumer;
+ private T registeredItem;
+
+ public ItemBuilder(String name, Function itemConstructor, String pModId) {
+ this.modId = pModId;
+ this.itemName = name;
+ this.itemConstructor = itemConstructor;
+ }
+
+ public static ItemBuilder item(String name, Function itemConstructor, String pModId) {
+ return new ItemBuilder<>(name, itemConstructor, pModId);
+ }
+
+ public void doItemModelGen(String modId) {
+ for (String itemName : generatedItems) {
+ ItemModelTemplates template = customModelMap.getOrDefault(itemName, ItemModelTemplates.HANDHELD);
+
+ if (itemName.endsWith("_sword") || itemName.endsWith("_pickaxe") || itemName.endsWith("_axe") || itemName.endsWith("_shovel") || itemName.endsWith("_hoe")) {
+ template = ItemModelTemplates.HANDHELD;
+ } else if (itemName.endsWith("_helmet") || itemName.endsWith("_chestplate") || itemName.endsWith("_leggings") || itemName.endsWith("_boots")) {
+ template = ItemModelTemplates.GENERATED;
+ }
+
+ ItemModelGenerator.generateItemModel(modId, itemName, template);
+ }
+ }
+
+ public void doRecipeGen(String modId) {
+ for (ItemBuilder> builder : REGISTERED_BUILDERS) {
+ if (builder.registeredItem != null && builder.recipeConsumer != null) {
+ RecipeGenerator.generateRecipe(modId, builder.itemName, (jsonConsumer, jsonSupplier) -> {
+ RecipeContext ctx = new RecipeContext(builder.registeredItem);
+ builder.recipeConsumer.accept(ctx, jsonConsumer);
+ });
+ }
+ }
+ }
+
+ public ItemBuilder properties(Consumer consumer) {
+ this.propertiesConsumer = consumer;
+ return this;
+ }
+
+ public ItemBuilder model(ItemModelTemplates template) {
+ customModelMap.put(itemName, template);
+ return this;
+ }
+
+ public ItemBuilder recipe(BiConsumer recipeConsumer) {
+ this.recipeConsumer = recipeConsumer;
+ return this;
+ }
+
+ private Supplier registerTool(String toolName, Function toolConstructor, Consumer toolProperties) {
+ Item.Properties properties = new Item.Properties();
+ propertiesConsumer.accept(properties);
+ if (toolProperties != null) {
+ toolProperties.accept(properties);
+ }
+ Supplier itemSupplier = () -> toolConstructor.apply(properties);
+ BlueLibConstants.PlatformHelper.REGISTRY.registerItem(toolName, (Supplier
- ) itemSupplier);
+ generatedItems.add(toolName);
+
+ return itemSupplier;
+ }
+
+ public ItemBuilder sword(Consumer swordProperties, Tier tier, int attackDamage, float attackSpeed) {
+ String toolName = itemName + "_sword";
+ registerTool(toolName, props -> new SwordItem(tier, props.attributes(PickaxeItem.createAttributes(tier, attackDamage, attackSpeed))), swordProperties);
+ return (ItemBuilder) this;
+ }
+
+ public ItemBuilder pickaxe(Consumer pickaxeProperties, Tier tier, int attackDamage, float attackSpeed) {
+ String toolName = itemName + "_pickaxe";
+ registerTool(toolName, props -> new PickaxeItem(tier, props.attributes(PickaxeItem.createAttributes(tier, attackDamage, attackSpeed))), pickaxeProperties);
+ return (ItemBuilder) this;
+ }
+
+ public ItemBuilder axe(Consumer axeProperties, Tier tier, int attackDamage, float attackSpeed) {
+ String toolName = itemName + "_axe";
+ registerTool(toolName, props -> new AxeItem(tier, props.attributes(PickaxeItem.createAttributes(tier, attackDamage, attackSpeed))), axeProperties);
+ return (ItemBuilder) this;
+ }
+
+ public ItemBuilder shovel(Consumer shovelProperties, Tier tier, int attackDamage, float attackSpeed) {
+ String toolName = itemName + "_shovel";
+ registerTool(toolName, props -> new ShovelItem(tier, props.attributes(PickaxeItem.createAttributes(tier, attackDamage, attackSpeed))), shovelProperties);
+ return (ItemBuilder) this;
+ }
+
+ public ItemBuilder hoe(Consumer hoeProperties, Tier tier, int attackDamage, float attackSpeed) {
+ String toolName = itemName + "_hoe";
+ registerTool(toolName, props -> new HoeItem(tier, props.attributes(PickaxeItem.createAttributes(tier, attackDamage, attackSpeed))), hoeProperties);
+ return (ItemBuilder) this;
+ }
+
+ public ItemBuilder toolset(Tier tier, ToolsetConfig config) {
+ if (config.swordProperties != null) {
+ sword(config.swordProperties, tier, config.swordAttackDamage, config.swordAttackSpeed);
+ }
+ if (config.pickaxeProperties != null) {
+ pickaxe(config.pickaxeProperties, tier, config.pickaxeAttackDamage, config.pickaxeAttackSpeed);
+ }
+ if (config.axeProperties != null) {
+ axe(config.axeProperties, tier, config.axeAttackDamage, config.axeAttackSpeed);
+ }
+ if (config.shovelProperties != null) {
+ shovel(config.shovelProperties, tier, config.shovelAttackDamage, config.shovelAttackSpeed);
+ }
+ if (config.hoeProperties != null) {
+ hoe(config.hoeProperties, tier, config.hoeAttackDamage, config.hoeAttackSpeed);
+ }
+
+ TOOLSETS.put(itemName, generatedItems.stream()
+ .map(itemName -> (Supplier
- ) () -> BuiltInRegistries.ITEM.get(ResourceLocation.fromNamespaceAndPath(modId, itemName)))
+ .collect(Collectors.toList()));
+
+ return this;
+ }
+
+ public ItemBuilder helmet(Consumer properties, Holder material) {
+ String armorName = itemName + "_helmet";
+ registerTool(armorName, props -> new ArmorItem(material, ArmorItem.Type.HELMET, props), properties);
+ return (ItemBuilder) this;
+ }
+
+ public ItemBuilder chestplate(Consumer properties, Holder material) {
+ String armorName = itemName + "_chestplate";
+ registerTool(armorName, props -> new ArmorItem(material, ArmorItem.Type.CHESTPLATE, props), properties);
+ return (ItemBuilder) this;
+ }
+
+ public ItemBuilder leggings(Consumer properties, Holder material) {
+ String armorName = itemName + "_leggings";
+ registerTool(armorName, props -> new ArmorItem(material, ArmorItem.Type.LEGGINGS, props), properties);
+ return (ItemBuilder) this;
+ }
+
+ public ItemBuilder boots(Consumer properties, Holder material) {
+ String armorName = itemName + "_boots";
+ registerTool(armorName, props -> new ArmorItem(material, ArmorItem.Type.BOOTS, props), properties);
+ return (ItemBuilder) this;
+ }
+
+ public ItemBuilder armorSet(Holder material, ArmorSetConfig config) {
+ if (config.helmetProperties != null)
+ helmet(config.helmetProperties, material);
+ if (config.chestplateProperties != null)
+ chestplate(config.chestplateProperties, material);
+ if (config.leggingsProperties != null)
+ leggings(config.leggingsProperties, material);
+ if (config.bootsProperties != null)
+ boots(config.bootsProperties, material);
+
+ ARMORSETS.put(itemName, generatedItems.stream()
+ .map(name -> (Supplier
- ) () -> BuiltInRegistries.ITEM.get(ResourceLocation.fromNamespaceAndPath(modId, name)))
+ .collect(Collectors.toList()));
+
+ return this;
+ }
+
+ public Supplier
- register() {
+ Item.Properties properties = new Item.Properties();
+ propertiesConsumer.accept(properties);
+ generatedItems.add(itemName);
+ Supplier
- itemSupplier = () -> {
+ T item = itemConstructor.apply(properties);
+ this.registeredItem = item;
+ return item;
+ };
+ BlueLibConstants.PlatformHelper.REGISTRY.registerItem(itemName, itemSupplier);
+ REGISTERED_BUILDERS.add(this);
+ return itemSupplier;
+ }
+
+ public static class RecipeContext {
+
+ private final Item item;
+
+ public RecipeContext(Item item) {
+ this.item = item;
+ }
+
+ public Item getEntry() {
+ return item;
+ }
+ }
+}
diff --git a/common/src/main/java/software/bluelib/api/registry/builders/keybinds/KeybindBuilder.java b/common/src/main/java/software/bluelib/api/registry/builders/keybinds/KeybindBuilder.java
new file mode 100644
index 00000000..2091364a
--- /dev/null
+++ b/common/src/main/java/software/bluelib/api/registry/builders/keybinds/KeybindBuilder.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2024 BlueLib Contributors
+ *
+ * This Source Code Form is subject to the terms of the MIT License.
+ * If a copy of the MIT License was not distributed with this file,
+ * You can obtain one at https://opensource.org/licenses/MIT.
+ */
+package software.bluelib.api.registry.builders.keybinds;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Supplier;
+import net.minecraft.client.KeyMapping;
+import software.bluelib.BlueLibConstants;
+
+public class KeybindBuilder {
+
+ protected final List REGISTERED_BUILDERS = new ArrayList<>();
+ protected final String modId;
+ private final String name;
+ private final int keyCode;
+ private String category;
+ private Supplier keyMappingSupplier;
+
+ public KeybindBuilder(String name, int keyCode, String pModId) {
+ this.name = name;
+ this.keyCode = keyCode;
+ this.modId = pModId;
+ this.category = "key.categories." + pModId;
+ }
+
+ public KeybindBuilder category(String category) {
+ this.category = category;
+ return this;
+ }
+
+ public Supplier register() {
+ keyMappingSupplier = BlueLibConstants.PlatformHelper.REGISTRY.registerKeybind(name, () -> new KeyMapping(
+ "key." + modId + "." + name,
+ keyCode,
+ category));
+
+ REGISTERED_BUILDERS.add(this);
+ return keyMappingSupplier;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public Supplier getKeyMapping() {
+ return keyMappingSupplier;
+ }
+}
diff --git a/common/src/main/java/software/bluelib/api/registry/builders/menu/MenuBuilder.java b/common/src/main/java/software/bluelib/api/registry/builders/menu/MenuBuilder.java
new file mode 100644
index 00000000..a54fa816
--- /dev/null
+++ b/common/src/main/java/software/bluelib/api/registry/builders/menu/MenuBuilder.java
@@ -0,0 +1,14 @@
+/*
+ * Copyright (C) 2024 BlueLib Contributors
+ *
+ * This Source Code Form is subject to the terms of the MIT License.
+ * If a copy of the MIT License was not distributed with this file,
+ * You can obtain one at https://opensource.org/licenses/MIT.
+ */
+package software.bluelib.api.registry.builders.menu;
+
+import net.minecraft.client.gui.screens.Screen;
+import net.minecraft.client.gui.screens.inventory.MenuAccess;
+import net.minecraft.world.inventory.AbstractContainerMenu;
+
+public class MenuBuilder> {}
diff --git a/common/src/main/java/software/bluelib/api/registry/builders/tabs/CreativeTabBuilder.java b/common/src/main/java/software/bluelib/api/registry/builders/tabs/CreativeTabBuilder.java
new file mode 100644
index 00000000..3d00a0f4
--- /dev/null
+++ b/common/src/main/java/software/bluelib/api/registry/builders/tabs/CreativeTabBuilder.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2024 BlueLib Contributors
+ *
+ * This Source Code Form is subject to the terms of the MIT License.
+ * If a copy of the MIT License was not distributed with this file,
+ * You can obtain one at https://opensource.org/licenses/MIT.
+ */
+package software.bluelib.api.registry.builders.tabs;
+
+import java.util.*;
+import java.util.function.Supplier;
+import net.minecraft.core.registries.BuiltInRegistries;
+import net.minecraft.network.chat.Component;
+import net.minecraft.resources.ResourceLocation;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.item.CreativeModeTab;
+import net.minecraft.world.item.Item;
+import net.minecraft.world.item.ItemStack;
+import net.minecraft.world.item.Items;
+import software.bluelib.BlueLibConstants;
+import software.bluelib.api.registry.builders.entity.LivingEntityBuilder;
+import software.bluelib.api.registry.builders.items.ItemBuilder;
+import software.bluelib.api.utils.logging.BaseLogLevel;
+import software.bluelib.api.utils.logging.BaseLogger;
+
+public class CreativeTabBuilder {
+
+ private final String id;
+ private final String modId;
+ private Supplier
- iconSupplier;
+ private CreativeModeTab.DisplayItemsGenerator displayItemsGenerator;
+ private String backgroundSuffix;
+ private final Map, CreativeTabBuilder> TAB_BUILDERS = new HashMap<>();
+
+ public CreativeTabBuilder(String id, String modId) {
+ this.id = id;
+ this.modId = modId;
+ }
+
+ public CreativeTabBuilder icon(Supplier
- iconSupplier) {
+ this.iconSupplier = iconSupplier;
+ return this;
+ }
+
+ public CreativeTabBuilder icon(Item iconItem) {
+ this.iconSupplier = () -> iconItem;
+ return this;
+ }
+
+ public Supplier
- useSpawnEgg(Supplier extends EntityType>> entityTypeSupplier) {
+ return () -> {
+ EntityType> entityType = entityTypeSupplier.get();
+ if (entityType != null) {
+ String entityId = BuiltInRegistries.ENTITY_TYPE.getKey(entityType).getPath();
+ String spawnEggId = entityId + "_spawn_egg";
+ return BuiltInRegistries.ITEM.get(ResourceLocation.fromNamespaceAndPath(modId, spawnEggId));
+ }
+ return Items.AIR;
+ };
+ }
+
+ public CreativeTabBuilder displayItems(CreativeModeTab.DisplayItemsGenerator displayItemsGenerator) {
+ this.displayItemsGenerator = displayItemsGenerator;
+ return this;
+ }
+
+ public CreativeTabBuilder background(String backgroundSuffix) {
+ this.backgroundSuffix = backgroundSuffix;
+ return this;
+ }
+
+ public Supplier register() {
+ CreativeModeTab.Builder tabBuilder = CreativeModeTab.builder(CreativeModeTab.Row.TOP, 0)
+ .title(Component.translatable(id))
+ .icon(() -> {
+ Item item = iconSupplier.get();
+ return item != null ? new ItemStack(item) : ItemStack.EMPTY;
+ })
+ .displayItems((parameters, output) -> {
+ Set
- addedItems = new HashSet<>(); // Track added items
+ if (displayItemsGenerator != null) {
+ displayItemsGenerator.accept(parameters, (stack, tabVisibility) -> {
+ Item item = stack.getItem();
+ if (item != null && addedItems.add(item)) {
+ output.accept(stack, tabVisibility);
+ BaseLogger.log(BaseLogLevel.INFO, "Adding item from displayItemsGenerator: " + BuiltInRegistries.ITEM.getKey(item));
+ }
+ });
+ }
+ Item item = iconSupplier.get();
+ if (item != null) {
+ List> toolsetItems = ItemBuilder.TOOLSETS.get(item.getDescriptionId());
+ if (toolsetItems != null) {
+ for (Supplier
- tool : toolsetItems) {
+ Item toolItem = tool.get();
+ if (toolItem != null && addedItems.add(toolItem)) {
+ output.accept(new ItemStack(toolItem), CreativeModeTab.TabVisibility.PARENT_AND_SEARCH_TABS);
+ BaseLogger.log(BaseLogLevel.INFO, "Adding icon toolset item: " + BuiltInRegistries.ITEM.getKey(toolItem));
+ }
+ }
+ } else if (addedItems.add(item)) {
+ output.accept(new ItemStack(item), CreativeModeTab.TabVisibility.PARENT_AND_SEARCH_TABS);
+ BaseLogger.log(BaseLogLevel.INFO, "Adding icon item: " + BuiltInRegistries.ITEM.getKey(item));
+ }
+ }
+ });
+
+ CreativeModeTab tab = tabBuilder.build();
+ Supplier tabSupplier = () -> tab;
+ TAB_BUILDERS.put(tabSupplier, this);
+ BlueLibConstants.PlatformHelper.REGISTRY.registerTab(id, tabSupplier);
+ return tabSupplier;
+ }
+
+ public void addToolset(Item item, CreativeModeTab.Output populator) {
+ if (item != null) {
+ Set
- addedItems = new HashSet<>(); // Track added items
+ String fullId = BuiltInRegistries.ITEM.getKey(item).getPath();
+ String[] toolSuffixes = { "_sword", "_pickaxe", "_axe", "_shovel", "_hoe" };
+ String baseId = fullId;
+ for (String suffix : toolSuffixes) {
+ if (fullId.endsWith(suffix)) {
+ baseId = fullId.substring(0, fullId.length() - suffix.length());
+ break;
+ }
+ }
+
+ List> toolset = ItemBuilder.TOOLSETS.get(baseId);
+ if (toolset != null) {
+ for (Supplier
- tool : toolset) {
+ Item toolItem = tool.get();
+ if (toolItem != null && addedItems.add(toolItem)) { // Only add if not already present
+ populator.accept(new ItemStack(toolItem), CreativeModeTab.TabVisibility.PARENT_AND_SEARCH_TABS);
+ BaseLogger.log(BaseLogLevel.INFO, "Adding toolset item: " + BuiltInRegistries.ITEM.getKey(toolItem));
+ }
+ }
+ return;
+ }
+
+ if (addedItems.add(item)) { // Only add if not already present
+ populator.accept(new ItemStack(item), CreativeModeTab.TabVisibility.PARENT_AND_SEARCH_TABS);
+ BaseLogger.log(BaseLogLevel.INFO, "Adding single toolset item: " + fullId);
+ }
+ }
+ }
+
+ public void addArmorSet(Item item, CreativeModeTab.Output populator) {
+ if (item != null) {
+ Set
- addedItems = new HashSet<>(); // Track added items
+ String fullId = BuiltInRegistries.ITEM.getKey(item).getPath();
+ String[] toolSuffixes = { "_helmet", "_chestplate", "_leggings", "_boots" };
+ String baseId = fullId;
+ for (String suffix : toolSuffixes) {
+ if (fullId.endsWith(suffix)) {
+ baseId = fullId.substring(0, fullId.length() - suffix.length());
+ break;
+ }
+ }
+
+ List> armorSets = ItemBuilder.ARMORSETS.get(baseId);
+ if (armorSets != null) {
+ for (Supplier
- armor : armorSets) {
+ Item armorItem = armor.get();
+ if (armorItem != null && addedItems.add(armorItem)) { // Only add if not already present
+ populator.accept(new ItemStack(armorItem), CreativeModeTab.TabVisibility.PARENT_AND_SEARCH_TABS);
+ BaseLogger.log(BaseLogLevel.INFO, "Adding armor item: " + BuiltInRegistries.ITEM.getKey(armorItem));
+ }
+ }
+ return;
+ }
+
+ if (addedItems.add(item)) { // Only add if not already present
+ populator.accept(new ItemStack(item), CreativeModeTab.TabVisibility.PARENT_AND_SEARCH_TABS);
+ BaseLogger.log(BaseLogLevel.INFO, "Adding single armor item: " + fullId);
+ }
+ }
+ }
+
+ public void addSpawnEgg(EntityType> entityType, CreativeModeTab.Output populator) {
+ if (entityType != null) {
+ String entityId = BuiltInRegistries.ENTITY_TYPE.getKey(entityType).getPath();
+ String spawnEggId = entityId + "_spawn_egg";
+ Item spawnEggItem = BuiltInRegistries.ITEM.get(ResourceLocation.fromNamespaceAndPath(modId, spawnEggId));
+ populator.accept(spawnEggItem);
+ }
+ }
+
+ /*public void addAllSpawnEggs(CreativeModeTab.Output populator) {
+ List names = LivingEntityBuilder.getEntityNames();
+ for (String name : names) {
+ String spawnEggId = name + "_spawn_egg";
+ try {
+ Item spawnEggItem = BuiltInRegistries.ITEM.get(ResourceLocation.fromNamespaceAndPath(modId, spawnEggId));
+ populator.accept(spawnEggItem);
+ } catch (Exception e) {
+ BaseLogger.log(BaseLogLevel.ERROR, Component.literal("Spawn egg for entity " + name + " not found!"));
+ }
+ }
+ }*/
+}
diff --git a/common/src/main/java/software/bluelib/api/registry/datagen/DataGenUtils.java b/common/src/main/java/software/bluelib/api/registry/datagen/DataGenUtils.java
new file mode 100644
index 00000000..e6d77a8d
--- /dev/null
+++ b/common/src/main/java/software/bluelib/api/registry/datagen/DataGenUtils.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2024 BlueLib Contributors
+ *
+ * This Source Code Form is subject to the terms of the MIT License.
+ * If a copy of the MIT License was not distributed with this file,
+ * You can obtain one at https://opensource.org/licenses/MIT.
+ */
+package software.bluelib.api.registry.datagen;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+
+public class DataGenUtils {
+
+ public static final Gson GSON = new GsonBuilder()
+ .setPrettyPrinting()
+ .disableHtmlEscaping()
+ .create();
+}
diff --git a/common/src/main/java/software/bluelib/api/registry/datagen/blocks/BlockModelGenerator.java b/common/src/main/java/software/bluelib/api/registry/datagen/blocks/BlockModelGenerator.java
new file mode 100644
index 00000000..43e1d7b9
--- /dev/null
+++ b/common/src/main/java/software/bluelib/api/registry/datagen/blocks/BlockModelGenerator.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2024 BlueLib Contributors
+ *
+ * This Source Code Form is subject to the terms of the MIT License.
+ * If a copy of the MIT License was not distributed with this file,
+ * You can obtain one at https://opensource.org/licenses/MIT.
+ */
+package software.bluelib.api.registry.datagen.blocks;
+
+import com.google.gson.JsonObject;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.util.Collections;
+import java.util.Map;
+import software.bluelib.BlueLibConstants;
+import software.bluelib.api.registry.datagen.DataGenUtils;
+
+public class BlockModelGenerator extends DataGenUtils {
+
+ public static void generateBlockModel(String modId, String name, BlockModelTemplates blockModelTemplate) {
+ generateBlockModel(modId, name, blockModelTemplate, Collections.emptyMap());
+ }
+
+ public static void generateBlockModel(String modId, String name, BlockModelTemplates blockModelTemplate, Map properties) {
+ Map blockModelJsons = blockModelTemplate.generateBlockModel(modId, name, properties);
+
+ for (Map.Entry entry : blockModelJsons.entrySet()) {
+ String modelName = entry.getKey();
+ JsonObject blockModelJson = entry.getValue();
+ Path blockModelPath = Path.of(BlueLibConstants.PlatformHelper.PLATFORM.getAssetsDir(true, modId) + "/models/block/" + name + ".json");
+
+ try {
+ if (Files.exists(blockModelPath)) {
+ System.out.println("Block model for '" + modelName + "' already exists at: " + blockModelPath + ". Skipping creation.");
+ continue;
+ }
+
+ Files.createDirectories(blockModelPath.getParent());
+ Files.write(blockModelPath, GSON.toJson(blockModelJson).getBytes(), StandardOpenOption.CREATE_NEW);
+ System.out.println("Block model for '" + modelName + "' created at: " + blockModelPath);
+ System.out.println("Generated JSON for '" + modId + ":models/block/" + modelName + "':\n" + GSON.toJson(blockModelJson));
+
+ } catch (IOException e) {
+ System.err.println("[ERROR]: Failed to create block model for '" + modelName + "' at " + blockModelPath + ": " + e.getMessage());
+ }
+ }
+ }
+}
diff --git a/common/src/main/java/software/bluelib/api/registry/datagen/blocks/BlockModelTemplates.java b/common/src/main/java/software/bluelib/api/registry/datagen/blocks/BlockModelTemplates.java
new file mode 100644
index 00000000..374b91c1
--- /dev/null
+++ b/common/src/main/java/software/bluelib/api/registry/datagen/blocks/BlockModelTemplates.java
@@ -0,0 +1,327 @@
+/*
+ * Copyright (C) 2024 BlueLib Contributors
+ *
+ * This Source Code Form is subject to the terms of the MIT License.
+ * If a copy of the MIT License was not distributed with this file,
+ * You can obtain one at https://opensource.org/licenses/MIT.
+ */
+package software.bluelib.api.registry.datagen.blocks;
+
+import com.google.gson.JsonObject;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+public abstract class BlockModelTemplates {
+
+ public abstract Map generateBlockModel(String modId, String blockName, Map properties);
+
+ public static final BlockModelTemplates CUBE_ALL = new BlockModelTemplates() {
+
+ @Override
+ public Map generateBlockModel(String modId, String blockName, Map properties) {
+ JsonObject model = new JsonObject();
+ model.addProperty("parent", "minecraft:block/cube_all");
+ JsonObject textures = new JsonObject();
+ String texture = properties.getOrDefault("all", modId + ":block/" + blockName);
+ textures.addProperty("all", texture);
+ model.add("textures", textures);
+ return Collections.singletonMap(blockName, model);
+ }
+ };
+
+ public static final BlockModelTemplates COLUMN = new BlockModelTemplates() {
+
+ @Override
+ public Map generateBlockModel(String modId, String blockName, Map properties) {
+ JsonObject model = new JsonObject();
+ model.addProperty("parent", "minecraft:block/cube_column");
+ JsonObject textures = new JsonObject();
+ String topTexture = properties.getOrDefault("top", modId + ":block/" + blockName + "_top");
+ String sideTexture = properties.getOrDefault("side", modId + ":block/" + blockName);
+ textures.addProperty("end", topTexture);
+ textures.addProperty("side", sideTexture);
+ model.add("textures", textures);
+ return Collections.singletonMap(blockName, model);
+ }
+ };
+
+ public static final BlockModelTemplates CUBE = new BlockModelTemplates() {
+
+ @Override
+ public Map generateBlockModel(String modId, String blockName, Map properties) {
+ JsonObject model = new JsonObject();
+ model.addProperty("parent", "minecraft:block/cube");
+ JsonObject textures = new JsonObject();
+ textures.addProperty("up", properties.getOrDefault("up", modId + ":block/" + blockName + "_top"));
+ textures.addProperty("down", properties.getOrDefault("down", modId + ":block/" + blockName + "_bottom"));
+ textures.addProperty("north", properties.getOrDefault("north", modId + ":block/" + blockName));
+ textures.addProperty("south", properties.getOrDefault("south", modId + ":block/" + blockName));
+ textures.addProperty("east", properties.getOrDefault("east", modId + ":block/" + blockName));
+ textures.addProperty("west", properties.getOrDefault("west", modId + ":block/" + blockName));
+ model.add("textures", textures);
+ return Collections.singletonMap(blockName, model);
+ }
+ };
+
+ public static final BlockModelTemplates DOOR = new BlockModelTemplates() {
+
+ @Override
+ public Map generateBlockModel(String modId, String blockName, Map properties) {
+ Map models = new HashMap<>();
+ String bottomTexture = properties.getOrDefault("bottom", modId + ":block/" + blockName + "_bottom");
+ String topTexture = properties.getOrDefault("top", modId + ":block/" + blockName + "_top");
+
+ String[] halves = { "bottom", "top" };
+ String[] hinges = { "left", "right" };
+ String[] opens = { "", "_open" };
+
+ for (String half : halves) {
+ for (String hinge : hinges) {
+ for (String open : opens) {
+ String modelName = String.format("%s_%s_%s%s", blockName, half, hinge, open);
+ JsonObject model = new JsonObject();
+ String parent = String.format("minecraft:block/door_%s_%s%s", half, hinge, open);
+ model.addProperty("parent", parent);
+ JsonObject textures = new JsonObject();
+ textures.addProperty("bottom", bottomTexture);
+ textures.addProperty("top", topTexture);
+ model.add("textures", textures);
+ models.put(modelName, model);
+ }
+ }
+ }
+
+ return models;
+ }
+ };
+
+ public static final BlockModelTemplates FENCE = new BlockModelTemplates() {
+
+ @Override
+ public Map generateBlockModel(String modId, String blockName, Map properties) {
+ Map models = new HashMap<>();
+ String prefix = properties.getOrDefault("prefix", "");
+ String s = prefix.isEmpty() ? blockName : prefix + "/" + blockName;
+ String texture = properties.getOrDefault("texture", modId + ":block/" + s);
+
+ JsonObject postModel = new JsonObject();
+ postModel.addProperty("parent", "minecraft:block/fence_post");
+ JsonObject postTextures = new JsonObject();
+ postTextures.addProperty("texture", texture);
+ postModel.add("textures", postTextures);
+ models.put(s + "_fence_post", postModel);
+
+ JsonObject sideModel = new JsonObject();
+ sideModel.addProperty("parent", "minecraft:block/fence_side");
+ JsonObject sideTextures = new JsonObject();
+ sideTextures.addProperty("texture", texture);
+ sideModel.add("textures", sideTextures);
+ models.put(s + "_fence_side", sideModel);
+
+ JsonObject inventoryModel = new JsonObject();
+ inventoryModel.addProperty("parent", "minecraft:block/fence_inventory");
+ JsonObject inventoryTextures = new JsonObject();
+ inventoryTextures.addProperty("texture", texture);
+ inventoryModel.add("textures", inventoryTextures);
+ models.put(blockName + "_fence_inventory", inventoryModel);
+
+ return models;
+ }
+ };
+
+ public static final BlockModelTemplates BUTTON = new BlockModelTemplates() {
+
+ @Override
+ public Map generateBlockModel(String modId, String blockName, Map properties) {
+ Map models = new HashMap<>();
+ String texture = properties.getOrDefault("texture", modId + ":block/" + blockName);
+
+ JsonObject buttonModel = new JsonObject();
+ buttonModel.addProperty("parent", "minecraft:block/button");
+ JsonObject buttonTextures = new JsonObject();
+ buttonTextures.addProperty("texture", texture);
+ buttonModel.add("textures", buttonTextures);
+ models.put(blockName, buttonModel);
+
+ JsonObject pressedModel = new JsonObject();
+ pressedModel.addProperty("parent", "minecraft:block/button_pressed");
+ JsonObject pressedTextures = new JsonObject();
+ pressedTextures.addProperty("texture", texture);
+ pressedModel.add("textures", pressedTextures);
+ models.put(blockName + "_pressed", pressedModel);
+
+ JsonObject inventoryModel = new JsonObject();
+ inventoryModel.addProperty("parent", "minecraft:block/button_inventory");
+ JsonObject inventoryTextures = new JsonObject();
+ inventoryTextures.addProperty("texture", texture);
+ inventoryModel.add("textures", inventoryTextures);
+ models.put(blockName + "_inventory", inventoryModel);
+
+ return models;
+ }
+ };
+
+ public static final BlockModelTemplates SLAB = new BlockModelTemplates() {
+
+ @Override
+ public Map generateBlockModel(String modId, String blockName, Map properties) {
+ Map models = new HashMap<>();
+ String texture = properties.getOrDefault("texture", modId + ":block/" + blockName.replace("_slab", "_planks"));
+
+ JsonObject slabModel = new JsonObject();
+ slabModel.addProperty("parent", "minecraft:block/slab");
+ JsonObject slabTextures = new JsonObject();
+ slabTextures.addProperty("bottom", texture);
+ slabTextures.addProperty("top", texture);
+ slabTextures.addProperty("side", texture);
+ slabModel.add("textures", slabTextures);
+ models.put(blockName, slabModel);
+
+ JsonObject slabTopModel = new JsonObject();
+ slabTopModel.addProperty("parent", "minecraft:block/slab_top");
+ JsonObject slabTopTextures = new JsonObject();
+ slabTopTextures.addProperty("bottom", texture);
+ slabTopTextures.addProperty("top", texture);
+ slabTopTextures.addProperty("side", texture);
+ slabTopModel.add("textures", slabTopTextures);
+ models.put(blockName + "_top", slabTopModel);
+
+ return models;
+ }
+ };
+
+ public static final BlockModelTemplates TRAPDOOR = new BlockModelTemplates() {
+
+ @Override
+ public Map generateBlockModel(String modId, String blockName, Map properties) {
+ Map models = new HashMap<>();
+ String texture = properties.getOrDefault("texture", modId + ":block/" + blockName);
+
+ JsonObject bottomModel = new JsonObject();
+ bottomModel.addProperty("parent", "minecraft:block/trapdoor_bottom");
+ JsonObject bottomTextures = new JsonObject();
+ bottomTextures.addProperty("texture", texture);
+ bottomModel.add("textures", bottomTextures);
+ models.put(blockName + "_bottom", bottomModel);
+
+ JsonObject topModel = new JsonObject();
+ topModel.addProperty("parent", "minecraft:block/trapdoor_top");
+ JsonObject topTextures = new JsonObject();
+ topTextures.addProperty("texture", texture);
+ topModel.add("textures", topTextures);
+ models.put(blockName + "_top", topModel);
+
+ JsonObject openModel = new JsonObject();
+ openModel.addProperty("parent", "minecraft:block/trapdoor_open");
+ JsonObject openTextures = new JsonObject();
+ openTextures.addProperty("texture", texture);
+ openModel.add("textures", openTextures);
+ models.put(blockName + "_open", openModel);
+
+ return models;
+ }
+ };
+
+ public static final BlockModelTemplates STAIRS = new BlockModelTemplates() {
+
+ @Override
+ public Map generateBlockModel(String modId, String blockName, Map properties) {
+ Map models = new HashMap<>();
+ String texture = properties.getOrDefault("texture", modId + ":block/" + blockName.replace("_stairs", "_planks"));
+
+ JsonObject stairsModel = new JsonObject();
+ stairsModel.addProperty("parent", "minecraft:block/stairs");
+ JsonObject stairsTextures = new JsonObject();
+ stairsTextures.addProperty("bottom", texture);
+ stairsTextures.addProperty("top", texture);
+ stairsTextures.addProperty("side", texture);
+ stairsModel.add("textures", stairsTextures);
+ models.put(blockName, stairsModel);
+
+ JsonObject innerModel = new JsonObject();
+ innerModel.addProperty("parent", "minecraft:block/inner_stairs");
+ JsonObject innerTextures = new JsonObject();
+ innerTextures.addProperty("bottom", texture);
+ innerTextures.addProperty("top", texture);
+ innerTextures.addProperty("side", texture);
+ innerModel.add("textures", innerTextures);
+ models.put(blockName + "_inner", innerModel);
+
+ JsonObject outerModel = new JsonObject();
+ outerModel.addProperty("parent", "minecraft:block/outer_stairs");
+ JsonObject outerTextures = new JsonObject();
+ outerTextures.addProperty("bottom", texture);
+ outerTextures.addProperty("top", texture);
+ outerTextures.addProperty("side", texture);
+ outerModel.add("textures", outerTextures);
+ models.put(blockName + "_outer", outerModel);
+
+ return models;
+ }
+ };
+
+ public static final BlockModelTemplates PRESSURE_PLATE = new BlockModelTemplates() {
+
+ @Override
+ public Map generateBlockModel(String modId, String blockName, Map properties) {
+ Map models = new HashMap<>();
+ String texture = properties.getOrDefault("texture", modId + ":block/" + blockName);
+
+ JsonObject plateModel = new JsonObject();
+ plateModel.addProperty("parent", "minecraft:block/pressure_plate_up");
+ JsonObject plateTextures = new JsonObject();
+ plateTextures.addProperty("texture", texture);
+ plateModel.add("textures", plateTextures);
+ models.put(blockName, plateModel);
+
+ JsonObject downModel = new JsonObject();
+ downModel.addProperty("parent", "minecraft:block/pressure_plate_down");
+ JsonObject downTextures = new JsonObject();
+ downTextures.addProperty("texture", texture);
+ downModel.add("textures", downTextures);
+ models.put(blockName + "_down", downModel);
+
+ return models;
+ }
+ };
+
+ public static final BlockModelTemplates FENCE_GATE = new BlockModelTemplates() {
+
+ @Override
+ public Map generateBlockModel(String modId, String blockName, Map properties) {
+ Map models = new HashMap<>();
+ String texture = properties.getOrDefault("texture", modId + ":block/" + blockName.replace("_fence_gate", "_planks"));
+
+ JsonObject gateModel = new JsonObject();
+ gateModel.addProperty("parent", "minecraft:block/fence_gate");
+ JsonObject gateTextures = new JsonObject();
+ gateTextures.addProperty("texture", texture);
+ gateModel.add("textures", gateTextures);
+ models.put(blockName, gateModel);
+
+ JsonObject gateOpenModel = new JsonObject();
+ gateOpenModel.addProperty("parent", "minecraft:block/fence_gate_open");
+ JsonObject gateOpenTextures = new JsonObject();
+ gateOpenTextures.addProperty("texture", texture);
+ gateOpenModel.add("textures", gateOpenTextures);
+ models.put(blockName + "_open", gateOpenModel);
+
+ JsonObject gateWallModel = new JsonObject();
+ gateWallModel.addProperty("parent", "minecraft:block/fence_gate_wall");
+ JsonObject gateWallTextures = new JsonObject();
+ gateWallTextures.addProperty("texture", texture);
+ gateWallModel.add("textures", gateWallTextures);
+ models.put(blockName + "_wall", gateWallModel);
+
+ JsonObject gateWallOpenModel = new JsonObject();
+ gateWallOpenModel.addProperty("parent", "minecraft:block/fence_gate_wall_open");
+ JsonObject gateWallOpenTextures = new JsonObject();
+ gateWallOpenTextures.addProperty("texture", texture);
+ gateWallOpenModel.add("textures", gateWallOpenTextures);
+ models.put(blockName + "_wall_open", gateWallOpenModel);
+
+ return models;
+ }
+ };
+}
diff --git a/common/src/main/java/software/bluelib/api/registry/datagen/blockstates/BlockstateGenerator.java b/common/src/main/java/software/bluelib/api/registry/datagen/blockstates/BlockstateGenerator.java
new file mode 100644
index 00000000..db74b0bd
--- /dev/null
+++ b/common/src/main/java/software/bluelib/api/registry/datagen/blockstates/BlockstateGenerator.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2024 BlueLib Contributors
+ *
+ * This Source Code Form is subject to the terms of the MIT License.
+ * If a copy of the MIT License was not distributed with this file,
+ * You can obtain one at https://opensource.org/licenses/MIT.
+ */
+package software.bluelib.api.registry.datagen.blockstates;
+
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.util.Collections;
+import java.util.Map;
+import software.bluelib.BlueLibConstants;
+import software.bluelib.api.registry.datagen.DataGenUtils;
+
+public class BlockstateGenerator extends DataGenUtils {
+
+ public static void generateBlockstate(String modId, String name, BlockstateTemplates blockstateTemplate) {
+ generateBlockstate(modId, name, blockstateTemplate, Collections.emptyMap());
+ }
+
+ public static void generateBlockstate(String modId, String name, BlockstateTemplates blockstateTemplate, Map properties) {
+ Path blockstatePath = Path.of(BlueLibConstants.PlatformHelper.PLATFORM.getAssetsDir(true, modId) + "/blockstates/" + name + ".json");
+
+ try {
+ if (Files.exists(blockstatePath)) {
+ System.out.println("Blockstate for '" + name + "' already exists at: " + blockstatePath + ". Skipping creation.");
+ return;
+ }
+
+ JsonElement blockstateJson = generateBlockstateJson(modId, name, blockstateTemplate, properties);
+
+ Files.createDirectories(blockstatePath.getParent());
+ Files.write(blockstatePath, GSON.toJson(blockstateJson).getBytes(), StandardOpenOption.CREATE_NEW);
+ System.out.println("Blockstate for '" + name + "' created at: " + blockstatePath);
+
+ } catch (IOException e) {
+ System.err.println("[ERROR]: Failed to create blockstate for '" + name + "' at " + blockstatePath + ": " + e.getMessage());
+ }
+ }
+
+ private static JsonElement generateBlockstateJson(String modId, String name, BlockstateTemplates blockstateTemplate, Map properties) {
+ JsonObject blockstateJson = blockstateTemplate.generateBlockstate(modId, name, properties);
+ System.out.println("Generated JSON for '" + modId + ":blockstates/" + name + "':\n" + GSON.toJson(blockstateJson));
+ return blockstateJson;
+ }
+}
diff --git a/common/src/main/java/software/bluelib/api/registry/datagen/blockstates/BlockstateTemplates.java b/common/src/main/java/software/bluelib/api/registry/datagen/blockstates/BlockstateTemplates.java
new file mode 100644
index 00000000..2dcabbd5
--- /dev/null
+++ b/common/src/main/java/software/bluelib/api/registry/datagen/blockstates/BlockstateTemplates.java
@@ -0,0 +1,426 @@
+/*
+ * Copyright (C) 2024 BlueLib Contributors
+ *
+ * This Source Code Form is subject to the terms of the MIT License.
+ * If a copy of the MIT License was not distributed with this file,
+ * You can obtain one at https://opensource.org/licenses/MIT.
+ */
+package software.bluelib.api.registry.datagen.blockstates;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
+import java.util.Map;
+
+public abstract class BlockstateTemplates {
+
+ public abstract JsonObject generateBlockstate(String modId, String blockName, Map properties);
+
+ public static final BlockstateTemplates SIMPLE_BLOCK = new BlockstateTemplates() {
+
+ @Override
+ public JsonObject generateBlockstate(String modId, String blockName, Map properties) {
+ JsonObject blockstate = new JsonObject();
+ JsonObject variants = new JsonObject();
+ JsonObject variant = new JsonObject();
+ variant.addProperty("model", modId + ":block/" + blockName);
+ variants.add("", variant);
+ blockstate.add("variants", variants);
+ return blockstate;
+ }
+ };
+
+ public static final BlockstateTemplates ORIENTED_BLOCK = new BlockstateTemplates() {
+
+ @Override
+ public JsonObject generateBlockstate(String modId, String blockName, Map properties) {
+ JsonObject blockstate = new JsonObject();
+ JsonObject variants = new JsonObject();
+
+ String model = modId + ":block/" + blockName;
+ String horizontalModel = properties.getOrDefault("horizontal_model", model);
+
+ JsonObject xVariant = new JsonObject();
+ xVariant.addProperty("model", horizontalModel);
+ xVariant.addProperty("x", 90);
+ xVariant.addProperty("y", 90);
+ variants.add("axis=x", xVariant);
+
+ JsonObject yVariant = new JsonObject();
+ yVariant.addProperty("model", model);
+ variants.add("axis=y", yVariant);
+
+ JsonObject zVariant = new JsonObject();
+ zVariant.addProperty("model", horizontalModel);
+ zVariant.addProperty("x", 90);
+ variants.add("axis=z", zVariant);
+
+ blockstate.add("variants", variants);
+ return blockstate;
+ }
+ };
+
+ public static final BlockstateTemplates VARIANT_BLOCK = new BlockstateTemplates() {
+
+ @Override
+ public JsonObject generateBlockstate(String modId, String blockName, Map properties) {
+ JsonObject blockstate = new JsonObject();
+ JsonObject variants = new JsonObject();
+ String propertyName = properties.getOrDefault("property", "type");
+ String[] values = properties.getOrDefault("values", "default").split(",");
+ for (String value : values) {
+ JsonObject variant = new JsonObject();
+ variant.addProperty("model", modId + ":block/" + blockName + "_" + value.trim());
+ variants.add(propertyName + "=" + value.trim(), variant);
+ }
+ blockstate.add("variants", variants);
+ return blockstate;
+ }
+ };
+
+ public static final BlockstateTemplates MULTIPART_BLOCK = new BlockstateTemplates() {
+
+ @Override
+ public JsonObject generateBlockstate(String modId, String blockName, Map properties) {
+ JsonObject blockstate = new JsonObject();
+ JsonArray multipart = new JsonArray();
+
+ JsonObject basePart = new JsonObject();
+ basePart.addProperty("model", modId + ":block/" + blockName);
+ multipart.add(basePart);
+
+ String[] directions = { "north", "east", "south", "west" };
+ for (String dir : directions) {
+ JsonObject part = new JsonObject();
+ JsonObject when = new JsonObject();
+ when.addProperty(dir, "true");
+ part.add("when", when);
+ JsonObject apply = new JsonObject();
+ apply.addProperty("model", modId + ":block/" + blockName + "_" + dir);
+ part.add("apply", apply);
+ multipart.add(part);
+ }
+
+ blockstate.add("multipart", multipart);
+ return blockstate;
+ }
+ };
+
+ public static final BlockstateTemplates DOOR_BLOCK = new BlockstateTemplates() {
+
+ @Override
+ public JsonObject generateBlockstate(String modId, String blockName, Map properties) {
+ JsonObject blockstate = new JsonObject();
+ JsonObject variants = new JsonObject();
+
+ String modelPrefix = properties.getOrDefault("model_prefix", modId + ":block/" + blockName);
+
+ String[] facings = { "east", "north", "south", "west" };
+ String[] halves = { "lower", "upper" };
+ String[] hinges = { "left", "right" };
+ String[] opens = { "false", "true" };
+
+ int[][] rotations = {
+ { 0, 90, 270 },
+ { 270, 0, 180 },
+ { 90, 180, 0 },
+ { 180, 270, 90 }
+ };
+
+ for (int f = 0; f < facings.length; f++) {
+ String facing = facings[f];
+ for (String half : halves) {
+ for (String hinge : hinges) {
+ for (String open : opens) {
+ String variantKey = String.format("facing=%s,half=%s,hinge=%s,open=%s", facing, half, hinge, open);
+ JsonObject variant = new JsonObject();
+
+ String modelSuffix = String.format("%s_%s%s",
+ half.equals("lower") ? "bottom" : "top",
+ hinge,
+ open.equals("true") ? "_open" : "");
+ String model = modelPrefix + "_" + modelSuffix;
+ variant.addProperty("model", model);
+
+ int rotationIndex = open.equals("true") ? (hinge.equals("left") ? 1 : 2) : 0;
+ int yRotation = rotations[f][rotationIndex];
+ if (yRotation != 0) {
+ variant.addProperty("y", yRotation);
+ }
+
+ variants.add(variantKey, variant);
+ }
+ }
+ }
+ }
+
+ blockstate.add("variants", variants);
+ return blockstate;
+ }
+ };
+
+ public static final BlockstateTemplates FENCE_BLOCK = new BlockstateTemplates() {
+
+ @Override
+ public JsonObject generateBlockstate(String modId, String blockName, Map properties) {
+ JsonObject blockstate = new JsonObject();
+ JsonArray multipart = new JsonArray();
+ String prefix = properties.getOrDefault("prefix", "");
+ String modelPath = prefix.isEmpty() ? modId + ":block/" + blockName : modId + ":block/" + prefix + "/" + blockName;
+
+ JsonObject postPart = new JsonObject();
+ postPart.addProperty("model", modelPath + "_fence_post");
+ multipart.add(postPart);
+
+ String[] directions = { "north", "east", "south", "west" };
+ int[] rotations = { 0, 90, 180, 270 };
+ for (int i = 0; i < directions.length; i++) {
+ String dir = directions[i];
+ JsonObject part = new JsonObject();
+ JsonObject when = new JsonObject();
+ when.addProperty(dir, "true");
+ part.add("when", when);
+ JsonObject apply = new JsonObject();
+ apply.addProperty("model", modelPath + "_fence_side");
+ apply.addProperty("uvlock", true);
+ if (rotations[i] != 0) {
+ apply.addProperty("y", rotations[i]);
+ }
+ part.add("apply", apply);
+ multipart.add(part);
+ }
+
+ blockstate.add("multipart", multipart);
+ return blockstate;
+ }
+ };
+
+ public static final BlockstateTemplates BUTTON_BLOCK = new BlockstateTemplates() {
+
+ @Override
+ public JsonObject generateBlockstate(String modId, String blockName, Map properties) {
+ JsonObject blockstate = new JsonObject();
+ JsonObject variants = new JsonObject();
+ String[] faces = { "ceiling", "floor", "wall" };
+ String[] facings = { "east", "north", "south", "west" };
+ String[] powered = { "false", "true" };
+ int[][] ceilingRotations = { { 270, 180, 0, 90 } }; // east, north, south, west
+ int[][] floorRotations = { { 90, 0, 180, 270 } };
+ int[][] wallRotations = { { 90, 0, 180, 270 } };
+
+ for (String face : faces) {
+ for (int f = 0; f < facings.length; f++) {
+ String facing = facings[f];
+ for (String power : powered) {
+ String variantKey = String.format("face=%s,facing=%s,powered=%s", face, facing, power);
+ JsonObject variant = new JsonObject();
+ String model = power.equals("true") ? modId + ":block/" + blockName + "_pressed" : modId + ":block/" + blockName;
+ variant.addProperty("model", model);
+
+ int xRotation = face.equals("ceiling") ? 180 : face.equals("wall") ? 90 : 0;
+ int yRotation = switch (face) {
+ case "ceiling" -> ceilingRotations[0][f];
+ case "floor" -> floorRotations[0][f];
+ case "wall" -> wallRotations[0][f];
+ default -> 0;
+ };
+
+ if (xRotation != 0) {
+ variant.addProperty("x", xRotation);
+ }
+ if (yRotation != 0) {
+ variant.addProperty("y", yRotation);
+ }
+ if (face.equals("wall")) {
+ variant.addProperty("uvlock", true);
+ }
+
+ variants.add(variantKey, variant);
+ }
+ }
+ }
+
+ blockstate.add("variants", variants);
+ return blockstate;
+ }
+ };
+
+ public static final BlockstateTemplates SLAB_BLOCK = new BlockstateTemplates() {
+
+ @Override
+ public JsonObject generateBlockstate(String modId, String blockName, Map properties) {
+ JsonObject blockstate = new JsonObject();
+ JsonObject variants = new JsonObject();
+ String doubleModel = properties.getOrDefault("double_model", modId + ":block/" + blockName.replace("_slab", "_planks"));
+
+ JsonObject bottomVariant = new JsonObject();
+ bottomVariant.addProperty("model", modId + ":block/" + blockName);
+ variants.add("type=bottom", bottomVariant);
+
+ JsonObject topVariant = new JsonObject();
+ topVariant.addProperty("model", modId + ":block/" + blockName + "_top");
+ variants.add("type=top", topVariant);
+
+ JsonObject doubleVariant = new JsonObject();
+ doubleVariant.addProperty("model", doubleModel);
+ variants.add("type=double", doubleVariant);
+
+ blockstate.add("variants", variants);
+ return blockstate;
+ }
+ };
+
+ public static final BlockstateTemplates TRAPDOOR_BLOCK = new BlockstateTemplates() {
+
+ @Override
+ public JsonObject generateBlockstate(String modId, String blockName, Map properties) {
+ JsonObject blockstate = new JsonObject();
+ JsonObject variants = new JsonObject();
+ String[] facings = { "east", "north", "south", "west" };
+ String[] halves = { "bottom", "top" };
+ String[] opens = { "false", "true" };
+ int[][] rotations = { { 90, 0, 180, 270 }, { 270, 180, 0, 90 } }; // bottom/top: east, north, south, west
+
+ for (String facing : facings) {
+ for (String half : halves) {
+ for (String open : opens) {
+ String variantKey = String.format("facing=%s,half=%s,open=%s", facing, half, open);
+ JsonObject variant = new JsonObject();
+ String model;
+ if (open.equals("true")) {
+ model = modId + ":block/" + blockName + "_open";
+ } else {
+ model = modId + ":block/" + blockName + (half.equals("bottom") ? "_bottom" : "_top");
+ }
+ variant.addProperty("model", model);
+
+ int yRotation = rotations[half.equals("bottom") ? 0 : 1][java.util.Arrays.asList(facings).indexOf(facing)];
+ if (yRotation != 0) {
+ variant.addProperty("y", yRotation);
+ }
+ if (open.equals("true") && half.equals("top")) {
+ variant.addProperty("x", 180);
+ }
+
+ variants.add(variantKey, variant);
+ }
+ }
+ }
+
+ blockstate.add("variants", variants);
+ return blockstate;
+ }
+ };
+
+ public static final BlockstateTemplates STAIRS_BLOCK = new BlockstateTemplates() {
+
+ @Override
+ public JsonObject generateBlockstate(String modId, String blockName, Map properties) {
+ JsonObject blockstate = new JsonObject();
+ JsonObject variants = new JsonObject();
+ String[] facings = { "east", "north", "south", "west" };
+ String[] halves = { "bottom", "top" };
+ String[] shapes = { "inner_left", "inner_right", "outer_left", "outer_right", "straight" };
+ int[][] bottomRotations = { { 0, 270, 90, 180 }, { 270, 0, 180, 90 } }; // inner_left/inner_right, outer_left/outer_right: east, north, south, west
+ int[][] topRotations = { { 0, 270, 90, 180 }, { 90, 0, 180, 270 } };
+
+ for (String facing : facings) {
+ for (String half : halves) {
+ for (String shape : shapes) {
+ String variantKey = String.format("facing=%s,half=%s,shape=%s", facing, half, shape);
+ JsonObject variant = new JsonObject();
+ String modelSuffix = shape.startsWith("inner") ? "_inner" : shape.startsWith("outer") ? "_outer" : "";
+ variant.addProperty("model", modId + ":block/" + blockName + modelSuffix);
+
+ int facingIndex = java.util.Arrays.asList(facings).indexOf(facing);
+ int yRotation = 0;
+ boolean uvlock = !shape.equals("straight");
+
+ if (half.equals("bottom")) {
+ yRotation = switch (shape) {
+ case "inner_left", "outer_left" -> bottomRotations[0][facingIndex];
+ case "inner_right", "outer_right" -> bottomRotations[1][facingIndex];
+ case "straight" -> bottomRotations[1][facingIndex];
+ default -> yRotation;
+ };
+ } else {
+ variant.addProperty("x", 180);
+ yRotation = switch (shape) {
+ case "inner_left", "outer_left" -> topRotations[0][facingIndex];
+ case "inner_right", "outer_right" -> topRotations[1][facingIndex];
+ case "straight" -> topRotations[1][facingIndex];
+ default -> yRotation;
+ };
+ }
+
+ if (yRotation != 0) {
+ variant.addProperty("y", yRotation);
+ }
+ if (uvlock) {
+ variant.addProperty("uvlock", true);
+ }
+
+ variants.add(variantKey, variant);
+ }
+ }
+ }
+
+ blockstate.add("variants", variants);
+ return blockstate;
+ }
+ };
+
+ public static final BlockstateTemplates PRESSURE_PLATE_BLOCK = new BlockstateTemplates() {
+
+ @Override
+ public JsonObject generateBlockstate(String modId, String blockName, Map properties) {
+ JsonObject blockstate = new JsonObject();
+ JsonObject variants = new JsonObject();
+
+ JsonObject unpoweredVariant = new JsonObject();
+ unpoweredVariant.addProperty("model", modId + ":block/" + blockName);
+ variants.add("powered=false", unpoweredVariant);
+
+ JsonObject poweredVariant = new JsonObject();
+ poweredVariant.addProperty("model", modId + ":block/" + blockName + "_down");
+ variants.add("powered=true", poweredVariant);
+
+ blockstate.add("variants", variants);
+ return blockstate;
+ }
+ };
+
+ public static final BlockstateTemplates FENCE_GATE_BLOCK = new BlockstateTemplates() {
+
+ @Override
+ public JsonObject generateBlockstate(String modId, String blockName, Map properties) {
+ JsonObject blockstate = new JsonObject();
+ JsonObject variants = new JsonObject();
+ String[] facings = { "east", "north", "south", "west" };
+ String[] inWalls = { "false", "true" };
+ String[] opens = { "false", "true" };
+ int[] yRotations = { 270, 180, 0, 90 }; // east, north, south, west
+
+ for (int f = 0; f < facings.length; f++) {
+ String facing = facings[f];
+ for (String inWall : inWalls) {
+ for (String open : opens) {
+ String variantKey = String.format("facing=%s,in_wall=%s,open=%s", facing, inWall, open);
+ JsonObject variant = new JsonObject();
+ String modelSuffix = inWall.equals("true") ? "_wall" : "";
+ modelSuffix += open.equals("true") ? "_open" : "";
+ String model = modId + ":block/" + blockName + modelSuffix;
+ variant.addProperty("model", model);
+ variant.addProperty("uvlock", true);
+ if (yRotations[f] != 0) {
+ variant.addProperty("y", yRotations[f]);
+ }
+ variants.add(variantKey, variant);
+ }
+ }
+ }
+
+ blockstate.add("variants", variants);
+ return blockstate;
+ }
+ };
+}
diff --git a/common/src/main/java/software/bluelib/api/registry/datagen/entity/EntityTagBuilder.java b/common/src/main/java/software/bluelib/api/registry/datagen/entity/EntityTagBuilder.java
new file mode 100644
index 00000000..688f33cb
--- /dev/null
+++ b/common/src/main/java/software/bluelib/api/registry/datagen/entity/EntityTagBuilder.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2024 BlueLib Contributors
+ *
+ * This Source Code Form is subject to the terms of the MIT License.
+ * If a copy of the MIT License was not distributed with this file,
+ * You can obtain one at https://opensource.org/licenses/MIT.
+ */
+package software.bluelib.api.registry.datagen.entity;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardOpenOption;
+import java.util.*;
+import net.minecraft.core.registries.Registries;
+import net.minecraft.resources.ResourceLocation;
+import net.minecraft.tags.TagKey;
+import net.minecraft.world.entity.EntityType;
+import software.bluelib.api.registry.AbstractRegistryBuilder;
+import software.bluelib.api.registry.datagen.DataGenUtils;
+
+public class EntityTagBuilder extends DataGenUtils {
+
+ private final List generatedTags = new ArrayList<>();
+ private final Map>> tagEntityTypes = new HashMap<>();
+ private final String name;
+ protected final String modId;
+ private final List> entityTypes = new ArrayList<>();
+
+ public EntityTagBuilder(String name, String pModId) {
+ this.name = name;
+ this.modId = pModId;
+ }
+
+ public EntityTagBuilder addEntries(EntityType>... entityTypes) {
+ this.entityTypes.addAll(Arrays.asList(entityTypes));
+ return this;
+ }
+
+ public TagKey> build() {
+ generatedTags.add(name);
+ tagEntityTypes.put(name, new ArrayList<>(entityTypes));
+ return TagKey.create(Registries.ENTITY_TYPE, ResourceLocation.fromNamespaceAndPath(modId, name));
+ }
+
+ public void doTagJsonGen(String modId) {
+ for (String tagName : generatedTags) {
+ List> entities = tagEntityTypes.getOrDefault(tagName, new ArrayList<>());
+ generateTagJson(modId, tagName, entities);
+ }
+ }
+
+ private void generateTagJson(String modId, String tagName, List> entityTypes) {
+ Path tagPath = findProjectRoot().resolve(modId + "/tags/entity_type/" + tagName + ".json");
+
+ try {
+ if (Files.exists(tagPath)) {
+ System.out.println("Entity tag for '" + tagName + "' already exists at: " + tagPath + ". Skipping creation.");
+ return;
+ }
+
+ JsonObject tagJson = new JsonObject();
+ JsonArray values = new JsonArray();
+
+ for (EntityType> entityType : entityTypes) {
+ ResourceLocation registryName = EntityType.getKey(entityType);
+ if (registryName != null) {
+ values.add(registryName.toString());
+ }
+ }
+
+ tagJson.add("values", values);
+
+ Files.createDirectories(tagPath.getParent());
+ Files.write(tagPath, GSON.toJson(tagJson).getBytes(), StandardOpenOption.CREATE_NEW);
+ System.out.println("Entity tag for '" + tagName + "' created at: " + tagPath);
+
+ } catch (IOException e) {
+ System.err.println("Failed [ERROR]: Failed to create entity tag for '" + tagName + "' at " + tagPath + ": " + e.getMessage());
+ }
+ }
+
+ public Path findProjectRoot() {
+ Path current = Paths.get(System.getProperty("user.dir")).toAbsolutePath();
+ while (current != null) {
+ Path resources = findResourcesPath(current);
+ if (resources != null) return resources;
+ current = current.getParent();
+ }
+ throw new IllegalStateException("Could not locate project root");
+ }
+
+ private Path findResourcesPath(Path current) {
+ String[] potentialPaths = {
+ "src/main/resources/data",
+ "common/src/main/resources/data"
+ };
+
+ for (String path : potentialPaths) {
+ Path resources = current.resolve(path);
+ if (Files.exists(resources) && Files.isDirectory(resources)) {
+ return resources;
+ }
+ }
+
+ String currentDirName = current.getFileName() != null ? current.getFileName().toString() : "";
+ if (currentDirName.matches("fabric|forge|neoforge|quilt")) {
+ for (String path : potentialPaths) {
+ Path parentResources = current.getParent().resolve(path);
+ if (Files.exists(parentResources) && Files.isDirectory(parentResources)) {
+ return parentResources;
+ }
+ }
+ }
+
+ return null;
+ }
+}
diff --git a/common/src/main/java/software/bluelib/api/registry/datagen/items/ItemModelGenerator.java b/common/src/main/java/software/bluelib/api/registry/datagen/items/ItemModelGenerator.java
new file mode 100644
index 00000000..4690e64b
--- /dev/null
+++ b/common/src/main/java/software/bluelib/api/registry/datagen/items/ItemModelGenerator.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2024 BlueLib Contributors
+ *
+ * This Source Code Form is subject to the terms of the MIT License.
+ * If a copy of the MIT License was not distributed with this file,
+ * You can obtain one at https://opensource.org/licenses/MIT.
+ */
+package software.bluelib.api.registry.datagen.items;
+
+import com.google.gson.JsonElement;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.util.function.BiConsumer;
+import java.util.function.Supplier;
+import net.minecraft.data.models.model.ModelTemplate;
+import net.minecraft.data.models.model.TextureMapping;
+import net.minecraft.resources.ResourceLocation;
+import software.bluelib.BlueLibConstants;
+import software.bluelib.api.registry.datagen.DataGenUtils;
+
+public class ItemModelGenerator extends DataGenUtils {
+
+ public static void generateItemModel(String modId, String name, ItemModelTemplates modelTemplate) {
+ Path itemModelPath = Path.of(BlueLibConstants.PlatformHelper.PLATFORM.getAssetsDir(true, modId) + "/models/item/" + name + ".json");
+
+ try {
+ if (Files.exists(itemModelPath)) {
+ System.out.println("Item model for '" + name + "' already exists at: " + itemModelPath + ". Skipping creation.");
+ return;
+ }
+
+ JsonElement modelJson = generateModelJson(modId, name, modelTemplate);
+
+ Files.createDirectories(itemModelPath.getParent());
+ Files.write(itemModelPath, GSON.toJson(modelJson).getBytes(), StandardOpenOption.CREATE_NEW);
+ System.out.println("Item model for '" + name + "' created at: " + itemModelPath);
+
+ } catch (IOException e) {
+ System.err.println("Failed [ERROR]: Failed to create item model for '" + name + "' at " + itemModelPath + ": " + e.getMessage());
+ }
+ }
+
+ private static JsonElement generateModelJson(String modId, String name, ItemModelTemplates modelTemplate) {
+ ResourceLocation modelLocation = ResourceLocation.fromNamespaceAndPath(modId, "item/" + name);
+ ModelTemplate template = modelTemplate.getTemplate();
+
+ final JsonElement[] capturedJson = new JsonElement[1];
+ BiConsumer> tempConsumer = (location, jsonSupplier) -> {
+ capturedJson[0] = jsonSupplier.get();
+ System.out.println("Generated JSON for '" + location + "':\n" + GSON.toJson(capturedJson[0]));
+ };
+
+ template.create(modelLocation, TextureMapping.layer0(ResourceLocation.fromNamespaceAndPath(modId, "item/" + name)), tempConsumer);
+ return capturedJson[0];
+ }
+}
diff --git a/common/src/main/java/software/bluelib/api/registry/datagen/items/ItemModelTemplates.java b/common/src/main/java/software/bluelib/api/registry/datagen/items/ItemModelTemplates.java
new file mode 100644
index 00000000..2fd288a8
--- /dev/null
+++ b/common/src/main/java/software/bluelib/api/registry/datagen/items/ItemModelTemplates.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2024 BlueLib Contributors
+ *
+ * This Source Code Form is subject to the terms of the MIT License.
+ * If a copy of the MIT License was not distributed with this file,
+ * You can obtain one at https://opensource.org/licenses/MIT.
+ */
+package software.bluelib.api.registry.datagen.items;
+
+import com.google.gson.JsonObject;
+import java.util.Map;
+import java.util.Optional;
+import net.minecraft.data.models.model.ModelTemplate;
+import net.minecraft.data.models.model.TextureSlot;
+import net.minecraft.resources.ResourceLocation;
+import org.jetbrains.annotations.NotNull;
+
+public abstract class ItemModelTemplates {
+
+ public static final ItemModelTemplates BLOCK_ITEM = new ItemModelTemplates() {
+
+ @Override
+ public ModelTemplate getTemplate() {
+ return new ModelTemplate(
+ Optional.empty(),
+ Optional.empty()) {
+
+ @Override
+ public @NotNull JsonObject createBaseTemplate(@NotNull ResourceLocation modelLocation, @NotNull Map modelGetter) {
+ JsonObject jsonObject = new JsonObject();
+ String blockName = modelLocation.getPath().replace("item/", "block/");
+ jsonObject.addProperty("parent", modelLocation.getNamespace() + ":" + blockName);
+ return jsonObject;
+ }
+ };
+ }
+ };
+
+ public static final ItemModelTemplates BLOCK_WITH_INVENTORY_MODEL = new ItemModelTemplates() {
+
+ @Override
+ public ModelTemplate getTemplate() {
+ return new ModelTemplate(Optional.empty(), Optional.empty()) {
+
+ @Override
+ public @NotNull JsonObject createBaseTemplate(@NotNull ResourceLocation modelLocation, @NotNull Map modelGetter) {
+ JsonObject jsonObject = new JsonObject();
+ String path = modelLocation.getPath().startsWith("item/")
+ ? modelLocation.getPath().substring(5)
+ : modelLocation.getPath();
+ String blockName = path + "_fence_inventory";
+ jsonObject.addProperty("parent", modelLocation.getNamespace() + ":block/" + blockName);
+ return jsonObject;
+ }
+ };
+ }
+ };
+
+ public static final ItemModelTemplates BLOCK_SPRITE = new ItemModelTemplates() {
+
+ @Override
+ public ModelTemplate getTemplate() {
+ return new ModelTemplate(
+ Optional.of(ResourceLocation.withDefaultNamespace("item/generated")),
+ Optional.empty(),
+ TextureSlot.LAYER0) {
+
+ @Override
+ public @NotNull JsonObject createBaseTemplate(@NotNull ResourceLocation modelLocation, @NotNull Map modelGetter) {
+ JsonObject jsonObject = new JsonObject();
+ jsonObject.addProperty("parent", "minecraft:item/generated");
+ JsonObject textures = new JsonObject();
+ ResourceLocation texture = modelGetter.getOrDefault(TextureSlot.LAYER0,
+ ResourceLocation.fromNamespaceAndPath(modelLocation.getNamespace(), modelLocation.getPath().replace("item/", "block/")));
+ textures.addProperty("layer0", texture.toString());
+ jsonObject.add("textures", textures);
+ return jsonObject;
+ }
+ };
+ }
+ };
+
+ public static final ItemModelTemplates GENERATED = new ItemModelTemplates() {
+
+ @Override
+ public ModelTemplate getTemplate() {
+ return new ModelTemplate(
+ Optional.of(ResourceLocation.withDefaultNamespace("item/generated")),
+ Optional.empty(),
+ TextureSlot.LAYER0) {
+
+ @Override
+ public @NotNull JsonObject createBaseTemplate(@NotNull ResourceLocation modelLocation, @NotNull Map modelGetter) {
+ JsonObject jsonObject = new JsonObject();
+ jsonObject.addProperty("parent", "minecraft:item/generated");
+ JsonObject textures = new JsonObject();
+ int layerCount = 0;
+ for (TextureSlot slot : modelGetter.keySet()) {
+ if (slot == TextureSlot.LAYER0 || slot.getId().startsWith("layer")) {
+ textures.addProperty("layer" + layerCount, modelGetter.get(slot).toString());
+ layerCount++;
+ }
+ }
+ if (!modelGetter.containsKey(TextureSlot.LAYER0)) {
+ textures.addProperty("layer0", modelLocation.getNamespace() + ":item/" + modelLocation.getPath().replace("item/", ""));
+ }
+ jsonObject.add("textures", textures);
+ return jsonObject;
+ }
+ };
+ }
+ };
+
+ public static final ItemModelTemplates HANDHELD = new ItemModelTemplates() {
+
+ @Override
+ public ModelTemplate getTemplate() {
+ return new ModelTemplate(
+ Optional.of(ResourceLocation.withDefaultNamespace("item/handheld")),
+ Optional.empty(),
+ TextureSlot.LAYER0) {
+
+ @Override
+ public @NotNull JsonObject createBaseTemplate(@NotNull ResourceLocation modelLocation, @NotNull Map modelGetter) {
+ JsonObject jsonObject = new JsonObject();
+ jsonObject.addProperty("parent", "minecraft:item/handheld");
+ JsonObject textures = new JsonObject();
+ ResourceLocation texture = modelGetter.getOrDefault(TextureSlot.LAYER0,
+ ResourceLocation.fromNamespaceAndPath(modelLocation.getNamespace(), modelLocation.getPath()));
+ textures.addProperty("layer0", texture.toString());
+ jsonObject.add("textures", textures);
+ return jsonObject;
+ }
+ };
+ }
+ };
+
+ public static final ItemModelTemplates SPAWN_EGG = new ItemModelTemplates() {
+
+ @Override
+ public ModelTemplate getTemplate() {
+ return new ModelTemplate(
+ Optional.of(ResourceLocation.fromNamespaceAndPath("minecraft", "item/template_spawn_egg")),
+ Optional.empty()) {
+
+ @Override
+ public @NotNull JsonObject createBaseTemplate(@NotNull ResourceLocation modelLocation, @NotNull Map modelGetter) {
+ JsonObject jsonObject = new JsonObject();
+ jsonObject.addProperty("parent", "minecraft:item/template_spawn_egg");
+ return jsonObject;
+ }
+ };
+ }
+ };
+
+ public abstract ModelTemplate getTemplate();
+}
diff --git a/common/src/main/java/software/bluelib/api/registry/datagen/recipe/RecipeGenerator.java b/common/src/main/java/software/bluelib/api/registry/datagen/recipe/RecipeGenerator.java
new file mode 100644
index 00000000..ba3180cf
--- /dev/null
+++ b/common/src/main/java/software/bluelib/api/registry/datagen/recipe/RecipeGenerator.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2024 BlueLib Contributors
+ *
+ * This Source Code Form is subject to the terms of the MIT License.
+ * If a copy of the MIT License was not distributed with this file,
+ * You can obtain one at https://opensource.org/licenses/MIT.
+ */
+package software.bluelib.api.registry.datagen.recipe;
+
+import com.google.gson.JsonElement;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.util.function.BiConsumer;
+import java.util.function.Supplier;
+import net.minecraft.data.recipes.RecipeOutput;
+import software.bluelib.BlueLibConstants;
+import software.bluelib.api.registry.datagen.DataGenUtils;
+
+public class RecipeGenerator extends DataGenUtils {
+
+ public static void generateRecipe(String modId, String name, BiConsumer> recipeConsumer) {
+ Path recipePath = Path.of(BlueLibConstants.PlatformHelper.PLATFORM.getDataDir(true, modId) + "/recipe/" + name + ".json");
+
+ try {
+ if (Files.exists(recipePath)) {
+ System.out.println("Recipe for '" + name + "' already exists at: " + recipePath + ". Skipping creation.");
+ return;
+ }
+
+ JsonElement recipeJson = BlueLibConstants.PlatformHelper.PLATFORM.generateRecipeJson(modId, name, recipeConsumer);
+
+ if (recipeJson == null) {
+ System.err.println("Failed to generate recipe JSON for '" + name + "'. Skipping file creation.");
+ return;
+ }
+
+ Files.createDirectories(recipePath.getParent());
+ Files.write(recipePath, GSON.toJson(recipeJson).getBytes(), StandardOpenOption.CREATE_NEW);
+ System.out.println("Recipe for '" + name + "' created at: " + recipePath);
+
+ } catch (IOException e) {
+ System.err.println("Failed [ERROR]: Failed to create recipe for '" + name + "' at " + recipePath + ": " + e.getMessage());
+ }
+ }
+}
diff --git a/common/src/main/java/software/bluelib/api/registry/datagen/recipe/RecipeUtils.java b/common/src/main/java/software/bluelib/api/registry/datagen/recipe/RecipeUtils.java
new file mode 100644
index 00000000..b17409ca
--- /dev/null
+++ b/common/src/main/java/software/bluelib/api/registry/datagen/recipe/RecipeUtils.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2024 BlueLib Contributors
+ *
+ * This Source Code Form is subject to the terms of the MIT License.
+ * If a copy of the MIT License was not distributed with this file,
+ * You can obtain one at https://opensource.org/licenses/MIT.
+ */
+package software.bluelib.api.registry.datagen.recipe;
+
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.mojang.serialization.JsonOps;
+import java.lang.reflect.Field;
+import net.minecraft.core.registries.BuiltInRegistries;
+import net.minecraft.resources.ResourceLocation;
+import net.minecraft.world.item.ItemStack;
+import net.minecraft.world.item.crafting.Ingredient;
+import net.minecraft.world.item.crafting.Recipe;
+
+public class RecipeUtils {
+
+ public static JsonObject serializeIngredient(Ingredient ingredient) {
+ JsonElement json = Ingredient.CODEC.encodeStart(JsonOps.INSTANCE, ingredient).result().orElseThrow();
+ return json.getAsJsonObject();
+ }
+
+ public static JsonObject serializeResult(ItemStack stack) {
+ JsonObject result = new JsonObject();
+ ResourceLocation id = BuiltInRegistries.ITEM.getKey(stack.getItem());
+ result.addProperty("id", id.toString());
+ if (stack.getCount() > 1) {
+ result.addProperty("count", stack.getCount());
+ }
+ return result;
+ }
+
+ public static ItemStack getSmithingRecipeResult(Recipe> recipe) {
+ try {
+ Field resultField = recipe.getClass().getDeclaredField("result");
+ resultField.setAccessible(true);
+ Object value = resultField.get(recipe);
+ if (value instanceof ItemStack stack) {
+ return stack;
+ }
+ } catch (Exception ignored) {}
+ return ItemStack.EMPTY;
+ }
+}
diff --git a/common/src/main/java/software/bluelib/api/registry/helpers/ArmorSetConfig.java b/common/src/main/java/software/bluelib/api/registry/helpers/ArmorSetConfig.java
new file mode 100644
index 00000000..f45caa72
--- /dev/null
+++ b/common/src/main/java/software/bluelib/api/registry/helpers/ArmorSetConfig.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2024 BlueLib Contributors
+ *
+ * This Source Code Form is subject to the terms of the MIT License.
+ * If a copy of the MIT License was not distributed with this file,
+ * You can obtain one at https://opensource.org/licenses/MIT.
+ */
+package software.bluelib.api.registry.helpers;
+
+import java.util.function.Consumer;
+import net.minecraft.world.item.Item;
+
+public class ArmorSetConfig {
+
+ public Consumer helmetProperties;
+ public Consumer chestplateProperties;
+ public Consumer leggingsProperties;
+ public Consumer bootsProperties;
+
+ public ArmorSetConfig helmet(Consumer properties) {
+ this.helmetProperties = properties;
+ return this;
+ }
+
+ public ArmorSetConfig chestplate(Consumer properties) {
+ this.chestplateProperties = properties;
+ return this;
+ }
+
+ public ArmorSetConfig leggings(Consumer properties) {
+ this.leggingsProperties = properties;
+ return this;
+ }
+
+ public ArmorSetConfig boots(Consumer properties) {
+ this.bootsProperties = properties;
+ return this;
+ }
+}
diff --git a/common/src/main/java/software/bluelib/api/registry/helpers/ToolsetConfig.java b/common/src/main/java/software/bluelib/api/registry/helpers/ToolsetConfig.java
new file mode 100644
index 00000000..65137cfa
--- /dev/null
+++ b/common/src/main/java/software/bluelib/api/registry/helpers/ToolsetConfig.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2024 BlueLib Contributors
+ *
+ * This Source Code Form is subject to the terms of the MIT License.
+ * If a copy of the MIT License was not distributed with this file,
+ * You can obtain one at https://opensource.org/licenses/MIT.
+ */
+package software.bluelib.api.registry.helpers;
+
+import java.util.function.Consumer;
+import net.minecraft.world.item.Item;
+
+public class ToolsetConfig {
+
+ public Consumer swordProperties;
+ public Consumer pickaxeProperties;
+ public Consumer