diff --git a/gradle.properties b/gradle.properties index a631032..19a3caf 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,7 +6,7 @@ org.gradle.parallel=true # check these on https://fabricmc.net/develop minecraft_version=1.21.5 yarn_mappings=1.21.5+build.1 -loader_version=0.16.12 +loader_version=0.17.2 loom_version=1.11-SNAPSHOT # Mod Properties @@ -15,4 +15,5 @@ maven_group=com.uncreativebunch.magicmod archives_base_name=magic_mod # Dependencies +# Fabric API fabric_version=0.128.2+1.21.5 \ No newline at end of file diff --git a/gradlew b/gradlew old mode 100644 new mode 100755 diff --git a/src/client/java/com/uncreativebunch/magicmod/screen/MagicalCraftingScreen.java b/src/client/java/com/uncreativebunch/magicmod/screen/MagicalCraftingScreen.java index 1cfed57..80791e3 100644 --- a/src/client/java/com/uncreativebunch/magicmod/screen/MagicalCraftingScreen.java +++ b/src/client/java/com/uncreativebunch/magicmod/screen/MagicalCraftingScreen.java @@ -8,7 +8,7 @@ import net.minecraft.text.Text; import net.minecraft.util.Identifier; public class MagicalCraftingScreen extends HandledScreen { - private static final Identifier TEXTURE = Identifier.of("textures/gui/container/anvil.png"); + private static final Identifier TEXTURE = Identifier.of("textures/gui/container/dispenser.png"); public MagicalCraftingScreen(MagicalCraftingScreenHandler handler, PlayerInventory inventory, Text title) { super(handler, inventory, title); diff --git a/src/main/java/com/uncreativebunch/magicmod/MagicMod.java b/src/main/java/com/uncreativebunch/magicmod/MagicMod.java index 787bf10..6be6486 100644 --- a/src/main/java/com/uncreativebunch/magicmod/MagicMod.java +++ b/src/main/java/com/uncreativebunch/magicmod/MagicMod.java @@ -1,8 +1,11 @@ package com.uncreativebunch.magicmod; import com.uncreativebunch.magicmod.block.ModBlocks; +import com.uncreativebunch.magicmod.block.entity.ModBlockEntityTypes; import com.uncreativebunch.magicmod.item.ModItemGroups; import com.uncreativebunch.magicmod.item.ModItems; +import com.uncreativebunch.magicmod.recipe.ModRecipeSerializers; +import com.uncreativebunch.magicmod.recipe.ModRecipeTypes; import com.uncreativebunch.magicmod.screen.ModScreenHandlers; import net.fabricmc.api.ModInitializer; @@ -31,6 +34,9 @@ public class MagicMod implements ModInitializer { ModItems.init(); ModItemGroups.init(); ModScreenHandlers.init(); + ModRecipeSerializers.init(); + ModRecipeTypes.init(); + ModBlockEntityTypes.init(); LOGGER.info("Finished Magic Mod initialization!"); } } \ No newline at end of file diff --git a/src/main/java/com/uncreativebunch/magicmod/block/crafting/MagicalCrafterBlock.java b/src/main/java/com/uncreativebunch/magicmod/block/crafting/MagicalCrafterBlock.java index af5da16..2a26c7b 100644 --- a/src/main/java/com/uncreativebunch/magicmod/block/crafting/MagicalCrafterBlock.java +++ b/src/main/java/com/uncreativebunch/magicmod/block/crafting/MagicalCrafterBlock.java @@ -1,30 +1,44 @@ package com.uncreativebunch.magicmod.block.crafting; +import com.mojang.serialization.MapCodec; import com.uncreativebunch.magicmod.MagicMod; -import com.uncreativebunch.magicmod.screen.MagicalCraftingScreenHandler; -import net.minecraft.block.AbstractBlock; -import net.minecraft.block.BlockState; -import net.minecraft.block.CraftingTableBlock; -import net.minecraft.entity.EntityType; -import net.minecraft.entity.LightningEntity; +import com.uncreativebunch.magicmod.block.entity.MagicalCrafterBlockEntity; +import com.uncreativebunch.magicmod.block.entity.ModBlockEntityTypes; +import net.minecraft.block.*; +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.block.entity.BlockEntityTicker; +import net.minecraft.block.entity.BlockEntityType; +import net.minecraft.block.enums.SlabType; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.screen.NamedScreenHandlerFactory; -import net.minecraft.screen.ScreenHandlerContext; -import net.minecraft.screen.SimpleNamedScreenHandlerFactory; import net.minecraft.stat.Stats; -import net.minecraft.text.Text; +import net.minecraft.state.StateManager; import net.minecraft.util.ActionResult; import net.minecraft.util.hit.BlockHitResult; import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; -public class MagicalCrafterBlock extends CraftingTableBlock { +public class MagicalCrafterBlock extends BlockWithEntity { + + private static final MapCodec CODEC = null; /** * Creates a new MagicalCrafterBlock. * @param settings The settings of the block. */ - public MagicalCrafterBlock(AbstractBlock.Settings settings) { super(settings); } + public MagicalCrafterBlock(AbstractBlock.Settings settings) { + super(settings); + this.setDefaultState( + this.stateManager.getDefaultState() + .with(SlabBlock.TYPE, SlabType.BOTTOM) + .with(SlabBlock.WATERLOGGED, false) + ); + } + + @Override + protected MapCodec getCodec() { + return CODEC; + } /** * Occurs whenever this block is used. @@ -44,13 +58,6 @@ public class MagicalCrafterBlock extends CraftingTableBlock { if (!world.isClient) { MagicMod.LOGGER.info("Block was used at location {}", pos.toShortString()); - MagicMod.LOGGER.info("Electrocuting player..."); - BlockPos playerPos = player.getBlockPos(); - LightningEntity lightningEntity = new LightningEntity(EntityType.LIGHTNING_BOLT, world); - lightningEntity.setPosition(playerPos.toCenterPos()); - world.spawnEntity(lightningEntity); - MagicMod.LOGGER.info("Player electrocuted."); - NamedScreenHandlerFactory screenFactory = state.createScreenHandlerFactory(world, pos); if (screenFactory != null) { player.openHandledScreen(screenFactory); @@ -64,11 +71,30 @@ public class MagicalCrafterBlock extends CraftingTableBlock { } @Override - public NamedScreenHandlerFactory createScreenHandlerFactory(BlockState state, World world, BlockPos pos) { - return new SimpleNamedScreenHandlerFactory( - ((syncId, playerInventory, player) -> - new MagicalCraftingScreenHandler(syncId, playerInventory, ScreenHandlerContext.create(world, pos))), - Text.translatable("container.magical_crafting_table") - ); + public BlockEntity createBlockEntity(BlockPos pos, BlockState state) { + return new MagicalCrafterBlockEntity(pos, state); + } + + /** + * Adds properties to the block state. This crafting table is a slab, so it has a type and waterlogged property. + * @param builder The {@link StateManager.Builder} to add properties to. + */ + @Override + protected void appendProperties(StateManager.Builder builder) { + builder.add(SlabBlock.TYPE, SlabBlock.WATERLOGGED); + } + + /** + * Creates a {@link BlockEntityTicker} that runs the specified "tick" function from the + * given {@link BlockEntityType}. + * @param world The {@link World} instance. + * @param state The {@link BlockState} of the block. + * @param type The {@link BlockEntityType} to run the tick function from. + * @return A new {@link BlockEntityTicker} that runs the specified tick function. + * @param The {@link BlockEntity} to use. + */ + @Override + public BlockEntityTicker getTicker(World world, BlockState state, BlockEntityType type) { + return validateTicker(type, ModBlockEntityTypes.MAGICAL_CRAFTING_TABLE, MagicalCrafterBlockEntity::tick); } } diff --git a/src/main/java/com/uncreativebunch/magicmod/block/entity/MagicalCrafterBlockEntity.java b/src/main/java/com/uncreativebunch/magicmod/block/entity/MagicalCrafterBlockEntity.java new file mode 100644 index 0000000..a01f6aa --- /dev/null +++ b/src/main/java/com/uncreativebunch/magicmod/block/entity/MagicalCrafterBlockEntity.java @@ -0,0 +1,87 @@ +package com.uncreativebunch.magicmod.block.entity; + +import com.uncreativebunch.magicmod.inventory.InputInventory; +import com.uncreativebunch.magicmod.inventory.OutputInventory; +import com.uncreativebunch.magicmod.recipe.MagicShapedInput; +import com.uncreativebunch.magicmod.recipe.ModRecipeTypes; +import com.uncreativebunch.magicmod.recipe.MagicShapedRecipe; +import com.uncreativebunch.magicmod.screen.MagicalCraftingScreenHandler; +import net.minecraft.block.BlockState; +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.entity.player.PlayerInventory; +import net.minecraft.recipe.RecipeEntry; +import net.minecraft.recipe.ServerRecipeManager; +import net.minecraft.screen.NamedScreenHandlerFactory; +import net.minecraft.screen.ScreenHandler; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.text.Text; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; + +import java.util.Optional; + +public class MagicalCrafterBlockEntity extends BlockEntity implements NamedScreenHandlerFactory { + private final ServerRecipeManager.MatchGetter recipeGetter; + + /** + * The input inventory for crafting ingredients. It goes in the order core, material, and grip. + */ + private final InputInventory inputInventory = new InputInventory(3); + /** + * The output inventory for crafting ingredients. + */ + private final OutputInventory outputInventory = new OutputInventory(1); + + public MagicalCrafterBlockEntity(BlockPos pos, BlockState state) { + super(ModBlockEntityTypes.MAGICAL_CRAFTING_TABLE, pos, state); + this.recipeGetter = ServerRecipeManager.createCachedMatchGetter(ModRecipeTypes.MAGIC_SHAPED); + } + + /** + * Gets the input inventory for this block entity. + * @return A {@link InputInventory} that represents the input inventory. + */ + public InputInventory getInputInventory() { return inputInventory; } + + /** + * Gets the output inventory for this block entity. + * @return An {@link OutputInventory} that represents the output slot. + */ + public OutputInventory getOutputInventory() { return outputInventory; } + + /** + * The ticking logic. This will check if the block's inventory contains the correct items to craft + * a {@link MagicShapedRecipe}. + * @param world The {@link World} instance. + * @param pos The {@link BlockPos} positional data. + * @param state The {@link BlockState} of the block. + * @param blockEntity An instance of this type. + */ + public static void tick(World world, BlockPos pos, BlockState state, MagicalCrafterBlockEntity blockEntity) { + // Check if the recipe within the crafting inventory matches any recipes. Must happen on server side. + if (world instanceof ServerWorld serverWorld) { + Optional> matched = blockEntity.recipeGetter + .getFirstMatch( + new MagicShapedInput(blockEntity.inputInventory.getHeldStacks()), + serverWorld + ); + + if (matched.isPresent()) { + // Work with the RecipeEntry here + MagicShapedRecipe entry = matched.get().value(); + blockEntity.outputInventory.heldStacks.set(0, entry.output()); + } + } + } + + @Override + public ScreenHandler createMenu(int syncId, PlayerInventory playerInventory, PlayerEntity player) { + return new MagicalCraftingScreenHandler(syncId, playerInventory, inputInventory, outputInventory); + } + + @Override + public Text getDisplayName() { + return Text.translatable(getCachedState().getBlock().getTranslationKey()); + } +} diff --git a/src/main/java/com/uncreativebunch/magicmod/block/entity/ModBlockEntityTypes.java b/src/main/java/com/uncreativebunch/magicmod/block/entity/ModBlockEntityTypes.java new file mode 100644 index 0000000..7cd0e70 --- /dev/null +++ b/src/main/java/com/uncreativebunch/magicmod/block/entity/ModBlockEntityTypes.java @@ -0,0 +1,22 @@ +package com.uncreativebunch.magicmod.block.entity; + +import com.uncreativebunch.magicmod.block.ModBlocks; +import net.fabricmc.fabric.api.object.builder.v1.block.entity.FabricBlockEntityTypeBuilder; +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.block.entity.BlockEntityType; +import net.minecraft.registry.Registries; +import net.minecraft.registry.Registry; + +public class ModBlockEntityTypes { + + public static final BlockEntityType MAGICAL_CRAFTING_TABLE = register( + "magical_crafting_table", + FabricBlockEntityTypeBuilder.create(MagicalCrafterBlockEntity::new, ModBlocks.MAGICAL_CRAFTING_TABLE).build() + ); + + public static BlockEntityType register(String id, BlockEntityType type) { + return Registry.register(Registries.BLOCK_ENTITY_TYPE, id, type); + } + + public static void init() {} +} diff --git a/src/main/java/com/uncreativebunch/magicmod/datagen/DataGenerator.java b/src/main/java/com/uncreativebunch/magicmod/datagen/DataGenerator.java index f5e2a80..8f01978 100644 --- a/src/main/java/com/uncreativebunch/magicmod/datagen/DataGenerator.java +++ b/src/main/java/com/uncreativebunch/magicmod/datagen/DataGenerator.java @@ -13,6 +13,6 @@ public class DataGenerator implements DataGeneratorEntrypoint { MagicMod.LOGGER.info("Hello from the data generator!"); // Add providers to the pack here. - pack.addProvider(StickTagRecipeProvider::new); + pack.addProvider(MagicRecipeProvider::new); } } diff --git a/src/main/java/com/uncreativebunch/magicmod/datagen/MagicRecipeProvider.java b/src/main/java/com/uncreativebunch/magicmod/datagen/MagicRecipeProvider.java new file mode 100644 index 0000000..0a80ffd --- /dev/null +++ b/src/main/java/com/uncreativebunch/magicmod/datagen/MagicRecipeProvider.java @@ -0,0 +1,44 @@ +package com.uncreativebunch.magicmod.datagen; + +import com.uncreativebunch.magicmod.recipe.MagicShapedRecipe; +import net.fabricmc.fabric.api.datagen.v1.FabricDataOutput; +import net.fabricmc.fabric.api.datagen.v1.provider.FabricRecipeProvider; +import net.minecraft.data.recipe.RecipeExporter; +import net.minecraft.data.recipe.RecipeGenerator; +import net.minecraft.item.Item; +import net.minecraft.item.Items; +import net.minecraft.recipe.Ingredient; +import net.minecraft.registry.RegistryKeys; +import net.minecraft.registry.RegistryWrapper; +import net.minecraft.util.Identifier; + +import java.util.concurrent.CompletableFuture; + +public class MagicRecipeProvider extends FabricRecipeProvider { + public MagicRecipeProvider(FabricDataOutput output, CompletableFuture registriesFuture) { + super(output, registriesFuture); + } + + @Override + protected RecipeGenerator getRecipeGenerator(RegistryWrapper.WrapperLookup registryLookup, RecipeExporter exporter) { + return new RecipeGenerator(registryLookup, exporter) { + @Override + public void generate() { + RegistryWrapper.Impl itemLookup = registries.getOrThrow(RegistryKeys.ITEM); + + // Recipes are added here. We only want to create MagicShapedRecipes here. + MagicShapedRecipe.Builder.build( + Ingredient.ofItem(Items.STICK), + Ingredient.ofItem(Items.STICK), + Ingredient.ofItem(Items.STICK), + Items.REDSTONE.getDefaultStack() + ).offerTo(exporter, Identifier.of("magic_mod", "magic_testing_recipe")); + } + }; + } + + @Override + public String getName() { + return "MagicRecipeProvider"; + } +} diff --git a/src/main/java/com/uncreativebunch/magicmod/datagen/StickTagRecipeProvider.java b/src/main/java/com/uncreativebunch/magicmod/datagen/StickTagRecipeProvider.java deleted file mode 100644 index 7c3da6d..0000000 --- a/src/main/java/com/uncreativebunch/magicmod/datagen/StickTagRecipeProvider.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.uncreativebunch.magicmod.datagen; - -import com.uncreativebunch.magicmod.MagicMod; -import net.fabricmc.fabric.api.datagen.v1.FabricDataOutput; -import net.fabricmc.fabric.api.datagen.v1.provider.FabricRecipeProvider; -import net.minecraft.data.recipe.RecipeExporter; -import net.minecraft.data.recipe.RecipeGenerator; -import net.minecraft.registry.RegistryWrapper; - -import java.util.concurrent.CompletableFuture; - -public class StickTagRecipeProvider extends FabricRecipeProvider { - public StickTagRecipeProvider(FabricDataOutput output, CompletableFuture registriesFuture) { - super(output, registriesFuture); - } - - @Override - protected RecipeGenerator getRecipeGenerator(RegistryWrapper.WrapperLookup lookup, RecipeExporter exporter) { - return new RecipeGenerator(lookup, exporter) { - @Override - public void generate() { - MagicMod.LOGGER.info("Hello from the recipe provider!"); - MagicMod.LOGGER.info("This cannot scan recipes - apparently they don't exist in this context."); - } - }; - } - - @Override - public String getName() { - return "StickTagRecipeProvider"; - } -} diff --git a/src/main/java/com/uncreativebunch/magicmod/inventory/ImplementedInventory.java b/src/main/java/com/uncreativebunch/magicmod/inventory/ImplementedInventory.java new file mode 100644 index 0000000..77b4cfd --- /dev/null +++ b/src/main/java/com/uncreativebunch/magicmod/inventory/ImplementedInventory.java @@ -0,0 +1,77 @@ +package com.uncreativebunch.magicmod.inventory; + +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.inventory.Inventories; +import net.minecraft.inventory.Inventory; +import net.minecraft.item.ItemStack; +import net.minecraft.util.collection.DefaultedList; + +public interface ImplementedInventory extends Inventory { + DefaultedList getItems(); + + static ImplementedInventory of(DefaultedList items) { + return () -> items; + } + + static ImplementedInventory ofSize(int size) { + return of(DefaultedList.ofSize(size, ItemStack.EMPTY)); + } + + @Override + default int size() { + return getItems().size(); + } + + @Override + default boolean isEmpty() { + for (int i = 0; i < size(); i++) { + ItemStack stack = getStack(i); + if (!stack.isEmpty()) { + return false; + } + } + return true; + } + + @Override + default ItemStack getStack(int slot) { + return getItems().get(slot); + } + + @Override + default ItemStack removeStack(int slot, int count) { + ItemStack result = Inventories.splitStack(getItems(), slot, count); + if (!result.isEmpty()) { + markDirty(); + } + return result; + } + + @Override + default ItemStack removeStack(int slot) { + return Inventories.removeStack(getItems(), slot); + } + + @Override + default void setStack(int slot, ItemStack stack) { + getItems().set(slot, stack); + if (stack.getCount() > stack.getMaxCount()) { + stack.setCount(stack.getMaxCount()); + } + } + + @Override + default void clear() { + getItems().clear(); + } + + @Override + default void markDirty() { + // Doesn't need to be overridden in this case. + } + + @Override + default boolean canPlayerUse(PlayerEntity player) { + return true; + } +} diff --git a/src/main/java/com/uncreativebunch/magicmod/inventory/InputInventory.java b/src/main/java/com/uncreativebunch/magicmod/inventory/InputInventory.java new file mode 100644 index 0000000..cc00bea --- /dev/null +++ b/src/main/java/com/uncreativebunch/magicmod/inventory/InputInventory.java @@ -0,0 +1,24 @@ +package com.uncreativebunch.magicmod.inventory; + +import net.minecraft.inventory.SimpleInventory; +import net.minecraft.item.ItemStack; + +public class InputInventory extends SimpleInventory { + public InputInventory(int size) { + super(size); + } + + @Override + public boolean canInsert(ItemStack stack) { + return true; + } + + /** + * Executes basic crafting logic and decrements all input stacks. + */ + public void onCrafted() { + for (var stack : this) { + stack.decrement(1); + } + } +} diff --git a/src/main/java/com/uncreativebunch/magicmod/inventory/OutputInventory.java b/src/main/java/com/uncreativebunch/magicmod/inventory/OutputInventory.java new file mode 100644 index 0000000..4c83e2d --- /dev/null +++ b/src/main/java/com/uncreativebunch/magicmod/inventory/OutputInventory.java @@ -0,0 +1,25 @@ +package com.uncreativebunch.magicmod.inventory; + +import net.minecraft.inventory.SimpleInventory; +import net.minecraft.item.ItemStack; +import net.minecraft.util.collection.DefaultedList; + +public class OutputInventory extends SimpleInventory { + + public OutputInventory(int size) { + super(size); + } + + public OutputInventory(ItemStack... items) { + super(items); + } + + @Override + public boolean canInsert(ItemStack stack) { + return false; + } + + public boolean canAddStack(ItemStack stack) { + return super.canInsert(stack); + } +} diff --git a/src/main/java/com/uncreativebunch/magicmod/item/ModItems.java b/src/main/java/com/uncreativebunch/magicmod/item/ModItems.java index 4df44c7..8fcb99a 100644 --- a/src/main/java/com/uncreativebunch/magicmod/item/ModItems.java +++ b/src/main/java/com/uncreativebunch/magicmod/item/ModItems.java @@ -93,7 +93,7 @@ public class ModItems { Item::new, new Item.Settings() ); - + public static RegistryKey keyOf(String id) { return RegistryKey.of(RegistryKeys.ITEM, Identifier.of(MagicMod.MOD_ID, id)); } diff --git a/src/main/java/com/uncreativebunch/magicmod/recipe/MagicShapedInput.java b/src/main/java/com/uncreativebunch/magicmod/recipe/MagicShapedInput.java new file mode 100644 index 0000000..3f463ea --- /dev/null +++ b/src/main/java/com/uncreativebunch/magicmod/recipe/MagicShapedInput.java @@ -0,0 +1,23 @@ +package com.uncreativebunch.magicmod.recipe; + +import net.minecraft.item.ItemStack; +import net.minecraft.recipe.input.RecipeInput; + +import java.util.List; + +public record MagicShapedInput(List stacks) implements RecipeInput { + @Override + public ItemStack getStackInSlot(int index) { + return stacks.get(index); + } + + @Override + public int size() { + return stacks.size(); + } + + @Override + public boolean isEmpty() { + return RecipeInput.super.isEmpty(); + } +} diff --git a/src/main/java/com/uncreativebunch/magicmod/recipe/MagicShapedRecipe.java b/src/main/java/com/uncreativebunch/magicmod/recipe/MagicShapedRecipe.java new file mode 100644 index 0000000..b86dd79 --- /dev/null +++ b/src/main/java/com/uncreativebunch/magicmod/recipe/MagicShapedRecipe.java @@ -0,0 +1,164 @@ +package com.uncreativebunch.magicmod.recipe; + +import com.mojang.serialization.MapCodec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import com.uncreativebunch.magicmod.MagicMod; +import net.minecraft.data.recipe.RecipeExporter; +import net.minecraft.item.ItemStack; +import net.minecraft.network.RegistryByteBuf; +import net.minecraft.network.codec.PacketCodec; +import net.minecraft.recipe.*; +import net.minecraft.recipe.book.RecipeBookCategory; +import net.minecraft.recipe.display.RecipeDisplay; +import net.minecraft.registry.RegistryKey; +import net.minecraft.registry.RegistryKeys; +import net.minecraft.registry.RegistryWrapper; +import net.minecraft.util.Identifier; +import net.minecraft.world.World; + +import java.util.List; +import java.util.Optional; + +public record MagicShapedRecipe(Ingredient coreIngredient, Ingredient materialIngredient, Ingredient gripIngredient, + ItemStack output, Identifier id) implements Recipe { + @Override + public boolean matches(MagicShapedInput input, World world) { + if (input.stacks().size() < 2) return false; + return coreIngredient.test(input.stacks().getFirst()) + && materialIngredient.test(input.stacks().get(1)) + && gripIngredient.test(input.stacks().get(2)); + } + + @Override + public ItemStack craft(MagicShapedInput input, RegistryWrapper.WrapperLookup registries) { + return this.output.copy(); + } + + @Override + public boolean isIgnoredInRecipeBook() { + return Recipe.super.isIgnoredInRecipeBook(); + } + + @Override + public boolean showNotification() { + return Recipe.super.showNotification(); + } + + @Override + public String getGroup() { + return Recipe.super.getGroup(); + } + + @Override + public RecipeSerializer> getSerializer() { + return ModRecipeSerializers.MAGIC_SHAPED; + } + + @Override + public RecipeType> getType() { + return ModRecipeTypes.MAGIC_SHAPED; + } + + @Override + public List getDisplays() { + return Recipe.super.getDisplays(); + } + + @Override + public RecipeBookCategory getRecipeBookCategory() { + return null; + } + + @Override + public IngredientPlacement getIngredientPlacement() { + List> ingredients = List.of( + Optional.of(coreIngredient), + Optional.of(materialIngredient), + Optional.of(gripIngredient) + ); + + return IngredientPlacement.forMultipleSlots(ingredients); + } + + // Type definition + public static class Type implements RecipeType { + private Type() { } + + public static final Type INSTANCE = new Type(); + public static final String ID = "magic_crafting"; + } + + // Serializer definition + public static class Serializer implements RecipeSerializer { + public static final Serializer INSTANCE = new Serializer(); + + private static final MapCodec CODEC = RecordCodecBuilder.mapCodec( + // This function essentially creates a way to deserialize from a JSON using the constructor's + // parameters, as defined above. + instance -> instance.group( + Ingredient.CODEC.fieldOf("coreIngredient").forGetter(MagicShapedRecipe::coreIngredient), + Ingredient.CODEC.fieldOf("materialIngredient").forGetter(MagicShapedRecipe::materialIngredient), + Ingredient.CODEC.fieldOf("gripIngredient").forGetter(MagicShapedRecipe::gripIngredient), + ItemStack.CODEC.fieldOf("output").forGetter(MagicShapedRecipe::output), + Identifier.CODEC.fieldOf("id").forGetter(MagicShapedRecipe::id) + ).apply(instance, MagicShapedRecipe::new) + ); + + private static final PacketCodec PACKET_CODEC = PacketCodec.tuple( + Ingredient.PACKET_CODEC, MagicShapedRecipe::coreIngredient, + Ingredient.PACKET_CODEC, MagicShapedRecipe::materialIngredient, + Ingredient.PACKET_CODEC, MagicShapedRecipe::gripIngredient, + ItemStack.PACKET_CODEC, MagicShapedRecipe::output, + Identifier.PACKET_CODEC, MagicShapedRecipe::id, + MagicShapedRecipe::new + ); + + /** + * Gets the codec for this serializer. This determines how the recipe is parsed from JSON. + * + * @return The codec for this serializer. + */ + @Override + public MapCodec codec() { + return CODEC; + } + + @Override + @Deprecated + public PacketCodec packetCodec() { + return PACKET_CODEC; + } + } + + // Builder definition + public record Builder(Ingredient core, Ingredient material, Ingredient grip, ItemStack output) { + /** + * Creates a new {@link Builder}. + * @param core The core {@link Ingredient}. + * @param material The material {@link Ingredient}. + * @param grip The grip {@link Ingredient}. + * @param output The output {@link ItemStack} + * @return A new {@link Builder} that can be offered to a {@link RecipeExporter}. + */ + public static Builder build( + Ingredient core, + Ingredient material, + Ingredient grip, + ItemStack output + ) { + return new Builder(core, material, grip, output); + } + + /** + * Offers the contents of this builder to the given {@link RecipeExporter}. + * @param exporter The {@link RecipeExporter} to use. + * @param recipeId The {@link Identifier} of the recipe to create. + */ + public void offerTo(RecipeExporter exporter, Identifier recipeId) { + RegistryKey> key = RegistryKey.of(RegistryKeys.RECIPE, recipeId); + MagicShapedRecipe recipe = new MagicShapedRecipe(core, material, grip, output, recipeId); + exporter.accept(key, recipe, null); + MagicMod.LOGGER.info("Created recipe with id {}", recipeId.toString()); + } + } +} diff --git a/src/main/java/com/uncreativebunch/magicmod/recipe/ModRecipeSerializers.java b/src/main/java/com/uncreativebunch/magicmod/recipe/ModRecipeSerializers.java new file mode 100644 index 0000000..524055f --- /dev/null +++ b/src/main/java/com/uncreativebunch/magicmod/recipe/ModRecipeSerializers.java @@ -0,0 +1,28 @@ +package com.uncreativebunch.magicmod.recipe; + +import com.uncreativebunch.magicmod.MagicMod; +import net.minecraft.recipe.Recipe; +import net.minecraft.recipe.RecipeSerializer; +import net.minecraft.registry.Registries; +import net.minecraft.registry.Registry; +import net.minecraft.util.Identifier; + +public class ModRecipeSerializers { + public static RecipeSerializer MAGIC_SHAPED = register( + "magic_shaped", + MagicShapedRecipe.Serializer.INSTANCE + ); + + /** + * Registers a {@link RecipeSerializer} with Minecraft's internal registry. + * @param id The {@link net.minecraft.util.Identifier} of the serializer. + * @param type An instance of the {@link RecipeSerializer} to register. + * @return The registered instance of the {@link RecipeSerializer}. + * @param The type of the {@link Recipe} that is serialized. + */ + public static > RecipeSerializer register(String id, RecipeSerializer type) { + return Registry.register(Registries.RECIPE_SERIALIZER, Identifier.of(MagicMod.MOD_ID, id), type); + } + + public static void init() {} +} diff --git a/src/main/java/com/uncreativebunch/magicmod/recipe/ModRecipeTypes.java b/src/main/java/com/uncreativebunch/magicmod/recipe/ModRecipeTypes.java new file mode 100644 index 0000000..986ebe0 --- /dev/null +++ b/src/main/java/com/uncreativebunch/magicmod/recipe/ModRecipeTypes.java @@ -0,0 +1,19 @@ +package com.uncreativebunch.magicmod.recipe; + +import com.uncreativebunch.magicmod.MagicMod; +import net.minecraft.recipe.Recipe; +import net.minecraft.recipe.RecipeType; +import net.minecraft.recipe.input.RecipeInput; +import net.minecraft.registry.Registries; +import net.minecraft.registry.Registry; +import net.minecraft.util.Identifier; + +public class ModRecipeTypes { + public static final RecipeType MAGIC_SHAPED = register("magic_shaped", MagicShapedRecipe.Type.INSTANCE); + + public static > RecipeType register(String id, RecipeType type) { + return Registry.register(Registries.RECIPE_TYPE, Identifier.of(MagicMod.MOD_ID, id), type); + } + + public static void init() {} +} diff --git a/src/main/java/com/uncreativebunch/magicmod/screen/MagicResultSlot.java b/src/main/java/com/uncreativebunch/magicmod/screen/MagicResultSlot.java new file mode 100644 index 0000000..ca3ba49 --- /dev/null +++ b/src/main/java/com/uncreativebunch/magicmod/screen/MagicResultSlot.java @@ -0,0 +1,42 @@ +package com.uncreativebunch.magicmod.screen; + +import com.uncreativebunch.magicmod.inventory.InputInventory; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.inventory.Inventory; +import net.minecraft.item.ItemStack; +import net.minecraft.screen.slot.Slot; + +public class MagicResultSlot extends Slot { + /** + * The crafting input inventory. There is not a current need for custom logic, but this may come in the future. + */ + protected InputInventory inputInventory; + + public MagicResultSlot(InputInventory inputInventory, Inventory outputInventory, int index, int x, int y) { + super(outputInventory, index, x, y); + this.inputInventory = inputInventory; + } + + /** + * Runs when the player takes the item out of the slot. + * This slot will run the crafting logic of the {@link InputInventory}. + * @param player The player that took the item. + * @param stack The item that was taken. + */ + @Override + public void onTakeItem(PlayerEntity player, ItemStack stack) { + this.inputInventory.onCrafted(); + + super.onTakeItem(player, stack); + } + + /** + * Returns false, as the result slot does not allow items to be inserted. + * @param stack The item to insert. + * @return False. + */ + @Override + public boolean canInsert(ItemStack stack) { + return false; + } +} diff --git a/src/main/java/com/uncreativebunch/magicmod/screen/MagicalCraftingScreenHandler.java b/src/main/java/com/uncreativebunch/magicmod/screen/MagicalCraftingScreenHandler.java index 1ed50a7..e2ad680 100644 --- a/src/main/java/com/uncreativebunch/magicmod/screen/MagicalCraftingScreenHandler.java +++ b/src/main/java/com/uncreativebunch/magicmod/screen/MagicalCraftingScreenHandler.java @@ -1,17 +1,17 @@ package com.uncreativebunch.magicmod.screen; +import com.uncreativebunch.magicmod.inventory.InputInventory; +import com.uncreativebunch.magicmod.inventory.OutputInventory; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.player.PlayerInventory; -import net.minecraft.inventory.CraftingInventory; import net.minecraft.item.ItemStack; import net.minecraft.screen.ScreenHandler; -import net.minecraft.screen.ScreenHandlerContext; import net.minecraft.screen.slot.Slot; public class MagicalCraftingScreenHandler extends ScreenHandler { - private final ScreenHandlerContext context; - private final CraftingInventory inventory; + private final InputInventory inputInventory; + private final OutputInventory outputInventory; /** * Client-side constructor. @@ -19,7 +19,7 @@ public class MagicalCraftingScreenHandler extends ScreenHandler { * @param playerInventory The player's inventory. */ public MagicalCraftingScreenHandler(int syncId, PlayerInventory playerInventory) { - this(syncId, playerInventory, ScreenHandlerContext.EMPTY); + this(syncId, playerInventory, new InputInventory(3), new OutputInventory(1)); } /** @@ -27,30 +27,36 @@ public class MagicalCraftingScreenHandler extends ScreenHandler { * It handles creating the inventory slots and syncs all information from the server to the client. * @param syncId The sync ID that's sent from the server. * @param playerInventory The player's inventory. - * @param screenHandlerContext The context of the screen handler. Not sure if this is needed anymore. + * @param inputInventory The crafting inventory. + * @param outputInventory The output inventory. */ - public MagicalCraftingScreenHandler(int syncId, PlayerInventory playerInventory, ScreenHandlerContext screenHandlerContext) { + public MagicalCraftingScreenHandler(int syncId, PlayerInventory playerInventory, InputInventory inputInventory, + OutputInventory outputInventory) { super(ModScreenHandlers.MAGICAL_CRAFTING_SCREEN_HANDLER, syncId); - this.context = screenHandlerContext; - this.inventory = new CraftingInventory(this, 3, 3); + this.inputInventory = inputInventory; + this.outputInventory = outputInventory; // Ensure that the crafting inventory is the correct size. - checkSize(inventory, 9); + checkSize(inputInventory, 3); + checkSize(outputInventory, 1); // Ensure that the inventory's custom logic runs (if any) - inventory.onOpen(playerInventory.player); + inputInventory.onOpen(playerInventory.player); + outputInventory.onOpen(playerInventory.player); // Place all inventory slots within the screen. This piece doesn't actually render the background of the slots, // as this is handled by the client-side application (the Screen), but it does tell the screen where to render. + // We do assign locations - each slot is 18 units wide/tall. int m, l; - for (m = 0; m < 3; m++) { - for (l = 0; l < 3; l++) { - // Create the crafting slots. This is good for a 3x3 grid of crafting slots. It registers them to a - // specific inventory and location on the client's screen, relative to the screen's size and origin. - this.addSlot(new Slot(inventory, l + m * 3, 62 + l * 18, 17 + m * 18)); - } + for (l = 0; l < 3; l++) { + // Create the crafting slots. This is good for a 3x3 grid of crafting slots. It registers them to a + // specific inventory and location on the client's screen, relative to the screen's size and origin. + this.addSlot(new Slot(inputInventory, l, 62 + l * 18, 17)); } + // TODO I don't actually know where the output slot should be located. Get correct coords. + this.addSlot(new MagicResultSlot(inputInventory, outputInventory, 0, 80, 35)); + // Add all player inventory slots. Start with the main inventory, then the hotbar. for (m = 0; m < 3; m++) { for (l = 0; l < 9; l++) { @@ -68,7 +74,7 @@ public class MagicalCraftingScreenHandler extends ScreenHandler { * @return True if the slot index is within the crafting inventory, false otherwise. */ protected boolean isCraftingInventorySlot(int slotIndex) { - return slotIndex < this.inventory.size(); + return slotIndex < this.inputInventory.size(); } @Override @@ -83,7 +89,7 @@ public class MagicalCraftingScreenHandler extends ScreenHandler { newStack = stack.copy(); if (isCraftingInventorySlot(slotIndex)) { // Attempt to move the stack into the player's inventory. - if (!this.insertItem(stack, this.inventory.size(), this.slots.size(), true)) { + if (!this.insertItem(stack, this.inputInventory.size() + this.outputInventory.size(), this.slots.size(), true)) { return ItemStack.EMPTY; } } else { @@ -104,6 +110,6 @@ public class MagicalCraftingScreenHandler extends ScreenHandler { @Override public boolean canUse(PlayerEntity player) { - return inventory.canPlayerUse(player); + return inputInventory.canPlayerUse(player); } }