From 4606c536a6260477870426234f748067240de3d1 Mon Sep 17 00:00:00 2001 From: Uko Kokņevičs Date: Tue, 9 Jan 2024 19:37:28 +0100 Subject: Added Alchemical Chest. --- .../lv/enes/mc/eris_alchemy/CovalenceRepair.java | 172 ------------------ src/main/java/lv/enes/mc/eris_alchemy/EMC.java | 6 +- .../java/lv/enes/mc/eris_alchemy/ErisAlchemy.java | 41 ++--- .../lv/enes/mc/eris_alchemy/ErisAlchemyItems.java | 44 +++++ src/main/java/lv/enes/mc/eris_alchemy/SubAxe.java | 18 -- .../eris_alchemy/block/AlchemicalChestBlock.java | 162 +++++++++++++++++ .../mc/eris_alchemy/block/ErisAlchemyBlocks.java | 29 +++ .../block/entity/AlchemicalChestBlockEntity.java | 198 +++++++++++++++++++++ .../block/entity/ErisAlchemyBlockEntities.java | 33 ++++ .../eris_alchemy/client/AlchemicalChestScreen.java | 43 +++++ .../mc/eris_alchemy/client/ChestItemRenderer.java | 40 +++++ .../mc/eris_alchemy/client/ErisAlchemyClient.java | 41 +++++ .../eris_alchemy/client/ErisAlchemyMaterials.java | 15 ++ .../enes/mc/eris_alchemy/client/package-info.java | 4 + .../mc/eris_alchemy/menu/AlchemicalChestMenu.java | 91 ++++++++++ .../mc/eris_alchemy/menu/ErisAlchemyMenus.java | 29 +++ .../mc/eris_alchemy/mixin/client/SheetsMixin.java | 22 +++ .../mc/eris_alchemy/mixin/client/package-info.java | 4 + .../mc/eris_alchemy/recipe/CovalenceRepair.java | 169 ++++++++++++++++++ .../recipe/ErisAlchemyRecipeSerializers.java | 27 +++ .../lv/enes/mc/eris_alchemy/utils/AxeUtils.java | 18 ++ .../lv/enes/mc/eris_alchemy/utils/BlockUtils.java | 4 +- .../lv/enes/mc/eris_alchemy/utils/CoralUtils.java | 4 +- .../lv/enes/mc/eris_alchemy/utils/DyeUtils.java | 4 +- .../lv/enes/mc/eris_alchemy/utils/ItemUtils.java | 4 +- .../lv/enes/mc/eris_alchemy/utils/RecipeUtils.java | 4 +- .../lv/enes/mc/eris_alchemy/utils/TagUtils.java | 4 +- .../eris_alchemy/blockstates/alchemical_chest.json | 7 + src/main/resources/assets/eris_alchemy/icon.png | Bin 391 -> 687 bytes .../resources/assets/eris_alchemy/lang/en_us.json | 4 + .../models/block/alchemical_chest.json | 5 + .../eris_alchemy/models/item/alchemical_chest.json | 6 + .../en_us/entries/root/alchemical_chest.json | 14 ++ .../textures/entity/chest/alchemical_chest.png | Bin 0 -> 1996 bytes .../textures/gui/container/alchemical_chest.png | Bin 0 -> 1298 bytes .../loot_tables/blocks/alchemical_chest.json | 13 ++ .../eris_alchemy/recipes/alchemical_chest.json | 34 ++++ .../minecraft/tags/blocks/mineable/pickaxe.json | 6 + src/main/resources/eris_alchemy.mixins.json | 3 + src/main/resources/quilt.mod.json | 3 +- 40 files changed, 1094 insertions(+), 231 deletions(-) delete mode 100644 src/main/java/lv/enes/mc/eris_alchemy/CovalenceRepair.java create mode 100644 src/main/java/lv/enes/mc/eris_alchemy/ErisAlchemyItems.java delete mode 100644 src/main/java/lv/enes/mc/eris_alchemy/SubAxe.java create mode 100644 src/main/java/lv/enes/mc/eris_alchemy/block/AlchemicalChestBlock.java create mode 100644 src/main/java/lv/enes/mc/eris_alchemy/block/ErisAlchemyBlocks.java create mode 100644 src/main/java/lv/enes/mc/eris_alchemy/block/entity/AlchemicalChestBlockEntity.java create mode 100644 src/main/java/lv/enes/mc/eris_alchemy/block/entity/ErisAlchemyBlockEntities.java create mode 100644 src/main/java/lv/enes/mc/eris_alchemy/client/AlchemicalChestScreen.java create mode 100644 src/main/java/lv/enes/mc/eris_alchemy/client/ChestItemRenderer.java create mode 100644 src/main/java/lv/enes/mc/eris_alchemy/client/ErisAlchemyClient.java create mode 100644 src/main/java/lv/enes/mc/eris_alchemy/client/ErisAlchemyMaterials.java create mode 100644 src/main/java/lv/enes/mc/eris_alchemy/client/package-info.java create mode 100644 src/main/java/lv/enes/mc/eris_alchemy/menu/AlchemicalChestMenu.java create mode 100644 src/main/java/lv/enes/mc/eris_alchemy/menu/ErisAlchemyMenus.java create mode 100644 src/main/java/lv/enes/mc/eris_alchemy/mixin/client/SheetsMixin.java create mode 100644 src/main/java/lv/enes/mc/eris_alchemy/mixin/client/package-info.java create mode 100644 src/main/java/lv/enes/mc/eris_alchemy/recipe/CovalenceRepair.java create mode 100644 src/main/java/lv/enes/mc/eris_alchemy/recipe/ErisAlchemyRecipeSerializers.java create mode 100644 src/main/java/lv/enes/mc/eris_alchemy/utils/AxeUtils.java create mode 100644 src/main/resources/assets/eris_alchemy/blockstates/alchemical_chest.json create mode 100644 src/main/resources/assets/eris_alchemy/models/block/alchemical_chest.json create mode 100644 src/main/resources/assets/eris_alchemy/models/item/alchemical_chest.json create mode 100644 src/main/resources/assets/eris_alchemy/patchouli_books/guide_book/en_us/entries/root/alchemical_chest.json create mode 100644 src/main/resources/assets/eris_alchemy/textures/entity/chest/alchemical_chest.png create mode 100644 src/main/resources/assets/eris_alchemy/textures/gui/container/alchemical_chest.png create mode 100644 src/main/resources/data/eris_alchemy/loot_tables/blocks/alchemical_chest.json create mode 100644 src/main/resources/data/eris_alchemy/recipes/alchemical_chest.json create mode 100644 src/main/resources/data/minecraft/tags/blocks/mineable/pickaxe.json diff --git a/src/main/java/lv/enes/mc/eris_alchemy/CovalenceRepair.java b/src/main/java/lv/enes/mc/eris_alchemy/CovalenceRepair.java deleted file mode 100644 index f094342..0000000 --- a/src/main/java/lv/enes/mc/eris_alchemy/CovalenceRepair.java +++ /dev/null @@ -1,172 +0,0 @@ -package lv.enes.mc.eris_alchemy; - -import com.google.gson.Gson; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonSyntaxException; -import jakarta.annotation.Nonnull; -import net.minecraft.core.RegistryAccess; -import net.minecraft.network.FriendlyByteBuf; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.inventory.CraftingContainer; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.crafting.CraftingBookCategory; -import net.minecraft.world.item.crafting.CustomRecipe; -import net.minecraft.world.item.crafting.Ingredient; -import net.minecraft.world.item.crafting.RecipeSerializer; -import net.minecraft.world.level.Level; -import org.quiltmc.qsl.recipe.api.serializer.QuiltRecipeSerializer; - -import java.util.ArrayList; -import java.util.Arrays; - -public class CovalenceRepair extends CustomRecipe { - static class Serializer implements QuiltRecipeSerializer { - private static class Json { - CraftingBookCategory category = CraftingBookCategory.MISC; - JsonElement dust; - JsonElement materials = new JsonObject(); - JsonElement tools = new JsonObject(); - } - - private Serializer() {} - - public static final Serializer INSTANCE = new Serializer(); - public static final ResourceLocation ID = new ResourceLocation(ErisAlchemy.ID, "covalence_repair"); - - @Override - public JsonObject toJson(CovalenceRepair recipe) { - var res = new JsonObject(); - res.addProperty("category", recipe.category().toString()); - res.add("dust", recipe.dust.toJson()); - res.add("materials", recipe.materials.toJson()); - res.add("tools", recipe.tools.toJson()); - return res; - } - - @Nonnull - @Override - public CovalenceRepair fromJson(ResourceLocation id, JsonObject json) { - var recipeJson = new Gson().fromJson(json, Json.class); - if (recipeJson.dust == null) { - throw new JsonSyntaxException("A required attribute is missing"); - } - - var dust = Ingredient.fromJson(recipeJson.dust); - var materials = Ingredient.fromJson(recipeJson.materials); - var tools = Ingredient.fromJson(recipeJson.tools); - - return new CovalenceRepair(id, recipeJson.category, dust, materials, tools); - } - - @Override - public void toNetwork(FriendlyByteBuf buf, CovalenceRepair recipe) { - buf.writeEnum(recipe.category()); - recipe.dust.toNetwork(buf); - recipe.materials.toNetwork(buf); - recipe.tools.toNetwork(buf); - } - - @Nonnull - @Override - public CovalenceRepair fromNetwork(ResourceLocation id, FriendlyByteBuf buf) { - var category = buf.readEnum(CraftingBookCategory.class); - var dust = Ingredient.fromNetwork(buf); - var materials = Ingredient.fromNetwork(buf); - var tools = Ingredient.fromNetwork(buf); - return new CovalenceRepair(id, category, dust, materials, tools); - } - } - - private record Inputs(ItemStack toolStack, int dustCount) {} - - private final static int DUSTS_TO_FIX = 8; - - /** What dust do we use to repair. */ - private final Ingredient dust; - /** What materials this dust can repair. */ - private final Ingredient materials; - /** What tools can this dust repair. */ - private final Ingredient tools; - - public CovalenceRepair(ResourceLocation id, CraftingBookCategory category, Ingredient dust, Ingredient materials, Ingredient tools) { - super(id, category); - - this.dust = dust; - this.materials = materials; - this.tools = tools; - } - - @Override - public boolean canCraftInDimensions(int width, int height) { - return width * height > 2; - } - - @Override - public boolean matches(CraftingContainer inventory, Level world) { - return getInputs(inventory) != null; - } - - @Nonnull - @Override - public ItemStack assemble(CraftingContainer inventory, RegistryAccess registryManager) { - var inputs = getInputs(inventory); - if (inputs == null) { - return ItemStack.EMPTY; - } - - var newToolStack = inputs.toolStack.copy(); - var repairedAmount = inputs.toolStack.getItem().getMaxDamage() * inputs.dustCount / DUSTS_TO_FIX; - newToolStack.setDamageValue(inputs.toolStack.getDamageValue() - repairedAmount); - return newToolStack; - } - - @Nonnull - @Override - public RecipeSerializer getSerializer() { - return Serializer.INSTANCE; - } - - private boolean isTool(ItemStack stack) { - if (!stack.isDamageableItem() || !stack.isDamaged() || stack.getCount() != 1) { - return false; - } - - if (tools.test(stack)) { - return true; - } - - var item = stack.getItem(); - return Arrays.stream(materials.getItems()).anyMatch(material -> item.isValidRepairItem(stack, material)); - } - - private boolean isDust(ItemStack stack) { - return dust.test(stack); - } - - /** @return null if recipe isn't correct. */ - private Inputs getInputs(CraftingContainer inventory) { - ItemStack toolStack = null; - var dustStacks = new ArrayList(); - for (var i = 0; i < inventory.getContainerSize(); i++) { - var stack = inventory.getItem(i); - if (stack.isEmpty()) { - continue; - } - - if (isDust(stack) && (dustStacks.isEmpty() || ItemStack.isSameItemSameTags(dustStacks.get(0), stack))) { - dustStacks.add(stack); - } else if (toolStack == null) { - toolStack = stack; - } else { - return null; - } - } - - if (toolStack == null || dustStacks.isEmpty() || !isTool(toolStack)) { - return null; - } - - return new Inputs(toolStack, dustStacks.size()); - } -} diff --git a/src/main/java/lv/enes/mc/eris_alchemy/EMC.java b/src/main/java/lv/enes/mc/eris_alchemy/EMC.java index 4f2c5e4..b8d5557 100644 --- a/src/main/java/lv/enes/mc/eris_alchemy/EMC.java +++ b/src/main/java/lv/enes/mc/eris_alchemy/EMC.java @@ -1,10 +1,8 @@ package lv.enes.mc.eris_alchemy; import jakarta.annotation.Nullable; -import lv.enes.mc.eris_alchemy.utils.CoralUtils; -import lv.enes.mc.eris_alchemy.utils.DyeUtils; +import lv.enes.mc.eris_alchemy.utils.*; import lv.enes.mc.eris_alchemy.utils.ItemUtils; -import lv.enes.mc.eris_alchemy.utils.RecipeUtils; import net.minecraft.core.RegistryAccess; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.tags.BlockTags; @@ -116,7 +114,7 @@ public class EMC { private List getRecipes(@Nullable Level world) { Stream recipes = Stream.concat( FAKE_RECIPES.stream(), - SubAxe.getStrippables() + AxeUtils.getStrippables() .entrySet() .stream() .map(entry -> new SimplifiedRecipe(entry.getValue(), Ingredient.of(entry.getKey()))) diff --git a/src/main/java/lv/enes/mc/eris_alchemy/ErisAlchemy.java b/src/main/java/lv/enes/mc/eris_alchemy/ErisAlchemy.java index fe7e40d..83cef8c 100644 --- a/src/main/java/lv/enes/mc/eris_alchemy/ErisAlchemy.java +++ b/src/main/java/lv/enes/mc/eris_alchemy/ErisAlchemy.java @@ -1,39 +1,29 @@ package lv.enes.mc.eris_alchemy; +import lv.enes.mc.eris_alchemy.block.ErisAlchemyBlocks; +import lv.enes.mc.eris_alchemy.block.entity.ErisAlchemyBlockEntities; +import lv.enes.mc.eris_alchemy.menu.ErisAlchemyMenus; +import lv.enes.mc.eris_alchemy.recipe.ErisAlchemyRecipeSerializers; import net.fabricmc.fabric.api.itemgroup.v1.FabricItemGroup; import net.minecraft.core.Registry; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.item.CreativeModeTab; -import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.Rarity; import org.quiltmc.loader.api.ModContainer; import org.quiltmc.qsl.base.api.entrypoint.ModInitializer; -import org.quiltmc.qsl.item.setting.api.QuiltItemSettings; -import org.quiltmc.qsl.tooltip.api.client.ItemTooltipCallback; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.text.DecimalFormat; - public class ErisAlchemy implements ModInitializer { public static final String ID = "eris_alchemy"; public static final Logger LOGGER = LoggerFactory.getLogger(ID); - public static final Item LOW_COVALENCE_DUST = new Item(new QuiltItemSettings().rarity(Rarity.COMMON)); - public static final Item MEDIUM_COVALENCE_DUST = new Item(new QuiltItemSettings().rarity(Rarity.UNCOMMON)); - public static final Item HIGH_COVALENCE_DUST = new Item(new QuiltItemSettings().rarity(Rarity.RARE)); - public static final CreativeModeTab ITEM_GROUP = FabricItemGroup.builder() - .icon(() -> new ItemStack(LOW_COVALENCE_DUST)) + .icon(() -> new ItemStack(ErisAlchemyItems.LOW_COVALENCE_DUST)) .title(Component.translatable("itemGroup.eris_alchemy.item_group")) - .displayItems((context, entries) -> { - entries.accept(LOW_COVALENCE_DUST); - entries.accept(MEDIUM_COVALENCE_DUST); - entries.accept(HIGH_COVALENCE_DUST); - }) + .displayItems((context, entries) -> ErisAlchemyItems.consumeItems((id, item) -> entries.accept(item))) .build(); @Override @@ -45,19 +35,10 @@ public class ErisAlchemy implements ModInitializer { Registry.register(BuiltInRegistries.CREATIVE_MODE_TAB, new ResourceLocation(ID, "item_group"), ITEM_GROUP); - Registry.register(BuiltInRegistries.ITEM, new ResourceLocation(ID, "low_covalence_dust"), LOW_COVALENCE_DUST); - Registry.register(BuiltInRegistries.ITEM, new ResourceLocation(ID, "medium_covalence_dust"), MEDIUM_COVALENCE_DUST); - Registry.register(BuiltInRegistries.ITEM, new ResourceLocation(ID, "high_covalence_dust"), HIGH_COVALENCE_DUST); - - Registry.register(BuiltInRegistries.RECIPE_SERIALIZER, CovalenceRepair.Serializer.ID, CovalenceRepair.Serializer.INSTANCE); - - var doubleFormat = new DecimalFormat("0"); - doubleFormat.setMaximumFractionDigits(1); - - ItemTooltipCallback.EVENT.register((stack, player, context, tooltip) -> { - var world = player == null ? null : player.level(); - var emc = EMC.getInstance(world).get(stack.getItem()); - emc.ifPresent(value -> tooltip.add(Component.literal("EMC %s".formatted(doubleFormat.format(value))))); - }); + ErisAlchemyBlocks.consumeBlocks((id, block) -> Registry.register(BuiltInRegistries.BLOCK, id, block)); + ErisAlchemyBlockEntities.consumeBlockEntities((id, block) -> Registry.register(BuiltInRegistries.BLOCK_ENTITY_TYPE, id, block)); + ErisAlchemyItems.consumeItems((id, item) -> Registry.register(BuiltInRegistries.ITEM, id, item)); + ErisAlchemyMenus.consumeMenus((id, menu) -> Registry.register(BuiltInRegistries.MENU, id, menu)); + ErisAlchemyRecipeSerializers.consumeSerializers((id, serializer) -> Registry.register(BuiltInRegistries.RECIPE_SERIALIZER, id, serializer)); } } diff --git a/src/main/java/lv/enes/mc/eris_alchemy/ErisAlchemyItems.java b/src/main/java/lv/enes/mc/eris_alchemy/ErisAlchemyItems.java new file mode 100644 index 0000000..21bcc7f --- /dev/null +++ b/src/main/java/lv/enes/mc/eris_alchemy/ErisAlchemyItems.java @@ -0,0 +1,44 @@ +package lv.enes.mc.eris_alchemy; + +import lv.enes.mc.eris_alchemy.block.ErisAlchemyBlocks; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.BlockItem; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.Rarity; +import net.minecraft.world.level.block.Block; +import org.quiltmc.qsl.item.setting.api.QuiltItemSettings; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.function.BiConsumer; + +public final class ErisAlchemyItems { + private static final Map items = new LinkedHashMap<>(); + + public static final Item ALCHEMICAL_CHEST = register(ErisAlchemyBlocks.ALCHEMICAL_CHEST, new QuiltItemSettings().rarity(Rarity.RARE)); + public static final Item LOW_COVALENCE_DUST = register("low_covalence_dust", new Item(new QuiltItemSettings().rarity(Rarity.COMMON))); + @SuppressWarnings("unused") + public static final Item MEDIUM_COVALENCE_DUST = register("medium_covalence_dust", new Item(new QuiltItemSettings().rarity(Rarity.UNCOMMON))); + @SuppressWarnings("unused") + public static final Item HIGH_COVALENCE_DUST = register("high_covalence_dust", new Item(new QuiltItemSettings().rarity(Rarity.RARE))); + + public static void consumeItems(BiConsumer consumer) { + items.forEach(consumer); + } + + private static BlockItem register(Block block, QuiltItemSettings settings) { + return register(BuiltInRegistries.BLOCK.getKey(block), new BlockItem(block, settings)); + } + + private static T register(String id, T item) { + return register(new ResourceLocation(ErisAlchemy.ID, id), item); + } + + private static T register(ResourceLocation id, T item) { + items.putIfAbsent(id, item); + return item; + } + + private ErisAlchemyItems() {} +} diff --git a/src/main/java/lv/enes/mc/eris_alchemy/SubAxe.java b/src/main/java/lv/enes/mc/eris_alchemy/SubAxe.java deleted file mode 100644 index 045a5a1..0000000 --- a/src/main/java/lv/enes/mc/eris_alchemy/SubAxe.java +++ /dev/null @@ -1,18 +0,0 @@ -package lv.enes.mc.eris_alchemy; - -import net.minecraft.world.item.AxeItem; -import net.minecraft.world.item.Tier; -import net.minecraft.world.level.block.Block; - -import java.util.Map; - -/** The only reason this exists is to read STRIPPABLES property from AxeItem :3 */ -public abstract class SubAxe extends AxeItem { - public static Map getStrippables() { - return STRIPPABLES; - } - - private SubAxe(Tier material, float attackDamage, float attackSpeed, Properties settings) { - super(material, attackDamage, attackSpeed, settings); - } -} diff --git a/src/main/java/lv/enes/mc/eris_alchemy/block/AlchemicalChestBlock.java b/src/main/java/lv/enes/mc/eris_alchemy/block/AlchemicalChestBlock.java new file mode 100644 index 0000000..c013df6 --- /dev/null +++ b/src/main/java/lv/enes/mc/eris_alchemy/block/AlchemicalChestBlock.java @@ -0,0 +1,162 @@ +package lv.enes.mc.eris_alchemy.block; + +import jakarta.annotation.Nonnull; +import jakarta.annotation.Nullable; +import lv.enes.mc.eris_alchemy.block.entity.AlchemicalChestBlockEntity; +import lv.enes.mc.eris_alchemy.block.entity.ErisAlchemyBlockEntities; +import lv.enes.mc.eris_alchemy.menu.AlchemicalChestMenu; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.network.chat.Component; +import net.minecraft.world.*; +import net.minecraft.world.entity.monster.piglin.PiglinAi; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.context.BlockPlaceContext; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.*; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.entity.BlockEntityTicker; +import net.minecraft.world.level.block.entity.BlockEntityType; +import net.minecraft.world.level.block.entity.ChestBlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.StateDefinition; +import net.minecraft.world.level.block.state.properties.BlockStateProperties; +import net.minecraft.world.level.block.state.properties.DirectionProperty; +import net.minecraft.world.level.pathfinder.PathComputationType; +import net.minecraft.world.phys.BlockHitResult; +import net.minecraft.world.phys.shapes.CollisionContext; +import net.minecraft.world.phys.shapes.VoxelShape; + +public class AlchemicalChestBlock extends AbstractChestBlock { + public static final Component CONTAINER_TITLE = Component.translatable("container.eris_alchemy.alchemical_chest"); + public static final DirectionProperty FACING = BlockStateProperties.HORIZONTAL_FACING; + public static final VoxelShape SHAPE = Block.box(1.0, 0.0, 1.0, 15.0, 14.0, 15.0); + + public static Container getContainer(Level world, BlockPos pos) { + if (world.getBlockEntity(pos) instanceof AlchemicalChestBlockEntity container) { + return container; + } + return null; + } + + public AlchemicalChestBlock(Properties properties) { + super(properties, () -> ErisAlchemyBlockEntities.ALCHEMICAL_CHEST); + registerDefaultState(getStateDefinition().any() + .setValue(FACING, Direction.NORTH) + ); + } + + @Nonnull + @Override + public DoubleBlockCombiner.NeighborCombineResult combine(BlockState state, Level world, BlockPos pos, boolean ignoreBlocked) { + return DoubleBlockCombiner.Combiner::acceptNone; + } + + @Override + protected void createBlockStateDefinition(StateDefinition.Builder builder) { + builder.add(FACING); + } + + @SuppressWarnings("deprecation") + @Override + public int getAnalogOutputSignal(BlockState state, Level world, BlockPos pos) { + return AlchemicalChestMenu.getRedstoneSignalFromContainer(getContainer(world, pos)); + } + + @Override + public MenuProvider getMenuProvider(BlockState state, Level world, BlockPos pos) { + if (world.getBlockEntity(pos) instanceof AlchemicalChestBlockEntity entity) { + return new SimpleMenuProvider(entity, CONTAINER_TITLE); + } + return null; + } + + @Nonnull + @Override + public RenderShape getRenderShape(BlockState state) { + return RenderShape.ENTITYBLOCK_ANIMATED; + } + + @SuppressWarnings("deprecation") + @Nonnull + @Override + public VoxelShape getShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext ctx) { + return SHAPE; + } + + @Override + public BlockState getStateForPlacement(BlockPlaceContext ctx) { + return defaultBlockState() + .setValue(FACING, ctx.getHorizontalDirection().getOpposite()); + } + + @Override + public BlockEntityTicker getTicker(Level ignoredWorld, BlockState ignoredState, BlockEntityType type) { + return createTickerHelper(type, blockEntityType.get(), (world, pos, state, entity) -> entity.tick(world, pos, state)); + } + + @SuppressWarnings("deprecation") + @Override + public boolean hasAnalogOutputSignal(BlockState state) { + return true; + } + + @SuppressWarnings("deprecation") + @Override + public boolean isPathfindable(BlockState state, BlockGetter world, BlockPos pos, PathComputationType type) { + return false; + } + + @SuppressWarnings("deprecation") + @Nonnull + @Override + public BlockState mirror(BlockState state, Mirror mirror) { + return state.rotate(mirror.getRotation(state.getValue(FACING))); + } + + @Nullable + @Override + public BlockEntity newBlockEntity(BlockPos pos, BlockState state) { + return new AlchemicalChestBlockEntity(pos, state); + } + + @SuppressWarnings("deprecation") + @Override + public void onRemove(BlockState state, Level world, BlockPos pos, BlockState newState, boolean moved) { + if (!state.is(newState.getBlock())) { + var entity = world.getBlockEntity(pos); + if (entity instanceof Container container) { + Containers.dropContents(world, pos, container); + world.updateNeighbourForOutputSignal(pos, this); + } + + super.onRemove(state, world, pos, newState, moved); + } + } + + @SuppressWarnings("deprecation") + @Nonnull + @Override + public BlockState rotate(BlockState state, Rotation rotation) { + return state.setValue(FACING, rotation.rotate(state.getValue(FACING))); + } + + @SuppressWarnings("deprecation") + @Nonnull + @Override + public InteractionResult use(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit) { + if (world.isClientSide) { + return InteractionResult.SUCCESS; + } + + var provider = getMenuProvider(state, world, pos); + if (provider != null) { + player.openMenu(provider); + // player.awardStat(getOpenChestStat); + PiglinAi.angerNearbyPiglins(player, true); + } + + return InteractionResult.CONSUME; + } +} diff --git a/src/main/java/lv/enes/mc/eris_alchemy/block/ErisAlchemyBlocks.java b/src/main/java/lv/enes/mc/eris_alchemy/block/ErisAlchemyBlocks.java new file mode 100644 index 0000000..6966681 --- /dev/null +++ b/src/main/java/lv/enes/mc/eris_alchemy/block/ErisAlchemyBlocks.java @@ -0,0 +1,29 @@ +package lv.enes.mc.eris_alchemy.block; + +import lv.enes.mc.eris_alchemy.ErisAlchemy; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Blocks; +import org.quiltmc.qsl.block.extensions.api.QuiltBlockSettings; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.function.BiConsumer; + +public final class ErisAlchemyBlocks { + private static final Map blocks = new LinkedHashMap<>(); + + public static final AlchemicalChestBlock ALCHEMICAL_CHEST = register("alchemical_chest", new AlchemicalChestBlock(QuiltBlockSettings.copy(Blocks.ENDER_CHEST))); + + public static void consumeBlocks(BiConsumer consumer) { + blocks.forEach(consumer); + } + + private static T register(String id, T block) { + blocks.putIfAbsent(new ResourceLocation(ErisAlchemy.ID, id), block); + return block; + } + + private ErisAlchemyBlocks() {} + +} diff --git a/src/main/java/lv/enes/mc/eris_alchemy/block/entity/AlchemicalChestBlockEntity.java b/src/main/java/lv/enes/mc/eris_alchemy/block/entity/AlchemicalChestBlockEntity.java new file mode 100644 index 0000000..9c9942f --- /dev/null +++ b/src/main/java/lv/enes/mc/eris_alchemy/block/entity/AlchemicalChestBlockEntity.java @@ -0,0 +1,198 @@ +package lv.enes.mc.eris_alchemy.block.entity; + +import jakarta.annotation.Nonnull; +import lv.enes.mc.eris_alchemy.block.ErisAlchemyBlocks; +import lv.enes.mc.eris_alchemy.menu.AlchemicalChestMenu; +import net.minecraft.core.BlockPos; +import net.minecraft.core.NonNullList; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.chat.Component; +import net.minecraft.sounds.SoundEvents; +import net.minecraft.sounds.SoundSource; +import net.minecraft.world.Container; +import net.minecraft.world.ContainerHelper; +import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.entity.BaseContainerBlockEntity; +import net.minecraft.world.level.block.entity.ChestLidController; +import net.minecraft.world.level.block.entity.ContainerOpenersCounter; +import net.minecraft.world.level.block.entity.LidBlockEntity; +import net.minecraft.world.level.block.state.BlockState; + +public class AlchemicalChestBlockEntity extends BaseContainerBlockEntity implements LidBlockEntity { + private final static int WIDTH = 13; + private final static int HEIGHT = 8; + + private final static int EVENT = 1; + + private final NonNullList items = NonNullList.withSize(WIDTH * HEIGHT, ItemStack.EMPTY); + private final ChestLidController lidController = new ChestLidController(); + private final ContainerOpenersCounter openersCounter = new ContainerOpenersCounter() { + @Override + protected void onOpen(Level world, BlockPos pos, BlockState state) { + // TODO: Sound effects? + world.playSound( + null, + pos.getX() + 0.5, + pos.getY() + 0.5, + pos.getZ() + 0.5, + SoundEvents.ENDER_CHEST_OPEN, + SoundSource.BLOCKS, + 0.5f, + world.random.nextFloat() * 0.1f + 0.9f + ); + } + + @Override + protected void onClose(Level world, BlockPos pos, BlockState state) { + // TODO: Sound effects? + world.playSound( + null, + pos.getX() + 0.5, + pos.getY() + 0.5, + pos.getZ() + 0.5, + SoundEvents.ENDER_CHEST_CLOSE, + SoundSource.BLOCKS, + 0.5f, + world.random.nextFloat() * 0.1f + 0.9f + ); + } + + @Override + protected void openerCountChanged(Level world, BlockPos pos, BlockState state, int oldViewerCount, int newViewerCount) { + world.blockEvent(worldPosition, ErisAlchemyBlocks.ALCHEMICAL_CHEST, EVENT, newViewerCount); + } + + @Override + protected boolean isOwnContainer(Player player) { + if (player.containerMenu instanceof AlchemicalChestMenu menu) { + return menu.getContainer() == AlchemicalChestBlockEntity.this; + } + return false; + } + }; + + public AlchemicalChestBlockEntity(BlockPos pos, BlockState state) { + super(ErisAlchemyBlockEntities.ALCHEMICAL_CHEST, pos, state); + } + + @Override + public void clearContent() { + items.clear(); + } + + @Nonnull + @Override + protected AbstractContainerMenu createMenu(int syncId, Inventory playerInventory) { + return new AlchemicalChestMenu(syncId, playerInventory, this); + } + + @Override + public int getContainerSize() { + return items.size(); + } + + @Nonnull + @Override + protected Component getDefaultName() { + return Component.translatable("container.eris_alchemy.alchemical_chest"); + } + + @Nonnull + @Override + public ItemStack getItem(int slot) { + return items.get(slot); + } + + @Override + public float getOpenNess(float tickDelta) { + return lidController.getOpenness(tickDelta); + } + + @Override + public boolean isEmpty() { + return items.stream().allMatch(ItemStack::isEmpty); + } + + @Override + public void load(CompoundTag nbt) { + super.load(nbt); + ContainerHelper.loadAllItems(nbt, items); + } + + private void recheckOpen() { + if (!remove) { + openersCounter.recheckOpeners(getLevel(), getBlockPos(), getBlockState()); + } + } + + @Nonnull + @Override + public ItemStack removeItem(int slot, int amount) { + var stack = ContainerHelper.removeItem(items, slot, amount); + if (!stack.isEmpty()) { + this.setChanged(); + } + return stack; + } + + @Nonnull + @Override + public ItemStack removeItemNoUpdate(int slot) { + return ContainerHelper.takeItem(items, slot); + } + + @Override + protected void saveAdditional(CompoundTag nbt) { + super.saveAdditional(nbt); + ContainerHelper.saveAllItems(nbt, items); + } + + @Override + public void setItem(int slot, ItemStack stack) { + items.set(slot, stack); + if (stack.getCount() > getMaxStackSize()) { + stack.setCount(getMaxStackSize()); + } + this.setChanged(); + } + + @Override + public void startOpen(Player player) { + if (!remove && !player.isSpectator()) { + openersCounter.incrementOpeners(player, getLevel(), getBlockPos(), getBlockState()); + } + } + + @Override + public boolean stillValid(Player player) { + return Container.stillValidBlockEntity(this, player); + } + + @Override + public void stopOpen(Player player) { + if (!remove && !player.isSpectator()) { + openersCounter.decrementOpeners(player, getLevel(), getBlockPos(), getBlockState()); + } + } + + public void tick(Level world, BlockPos ignoredPos, BlockState ignoredState) { + if (world.isClientSide) { + lidController.tickLid(); + } + recheckOpen(); + } + + @Override + public boolean triggerEvent(int type, int data) { + if (type == EVENT) { + lidController.shouldBeOpen(data > 0); + return true; + } + + return super.triggerEvent(type, data); + } +} diff --git a/src/main/java/lv/enes/mc/eris_alchemy/block/entity/ErisAlchemyBlockEntities.java b/src/main/java/lv/enes/mc/eris_alchemy/block/entity/ErisAlchemyBlockEntities.java new file mode 100644 index 0000000..a95ac58 --- /dev/null +++ b/src/main/java/lv/enes/mc/eris_alchemy/block/entity/ErisAlchemyBlockEntities.java @@ -0,0 +1,33 @@ +package lv.enes.mc.eris_alchemy.block.entity; + +import lv.enes.mc.eris_alchemy.ErisAlchemy; +import lv.enes.mc.eris_alchemy.block.ErisAlchemyBlocks; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.entity.BlockEntityType; +import org.quiltmc.qsl.block.entity.api.QuiltBlockEntityTypeBuilder; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.function.BiConsumer; + +public final class ErisAlchemyBlockEntities { + private static final Map> entities = new LinkedHashMap<>(); + + public static final BlockEntityType ALCHEMICAL_CHEST = register( + "alchemical_chest", + QuiltBlockEntityTypeBuilder.create(AlchemicalChestBlockEntity::new, ErisAlchemyBlocks.ALCHEMICAL_CHEST) + .build() + ); + + public static void consumeBlockEntities(BiConsumer> consumer) { + entities.forEach(consumer); + } + + private static BlockEntityType register(String id, BlockEntityType type) { + entities.putIfAbsent(new ResourceLocation(ErisAlchemy.ID, id), type); + return type; + } + + private ErisAlchemyBlockEntities() {} +} diff --git a/src/main/java/lv/enes/mc/eris_alchemy/client/AlchemicalChestScreen.java b/src/main/java/lv/enes/mc/eris_alchemy/client/AlchemicalChestScreen.java new file mode 100644 index 0000000..319dea8 --- /dev/null +++ b/src/main/java/lv/enes/mc/eris_alchemy/client/AlchemicalChestScreen.java @@ -0,0 +1,43 @@ +package lv.enes.mc.eris_alchemy.client; + +import lv.enes.mc.eris_alchemy.ErisAlchemy; +import lv.enes.mc.eris_alchemy.menu.AlchemicalChestMenu; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.player.Inventory; + +public class AlchemicalChestScreen extends AbstractContainerScreen { + public static final ResourceLocation TEXTURE = + new ResourceLocation(ErisAlchemy.ID, "textures/gui/container/alchemical_chest.png"); + + public static final int TEXTURE_WIDTH = 248; + public static final int TEXTURE_HEIGHT = 237; + + public AlchemicalChestScreen(AlchemicalChestMenu menu, Inventory inventory, Component title) { + super(menu, inventory, title); + + imageWidth = TEXTURE_WIDTH; + imageHeight = TEXTURE_HEIGHT; + } + + @Override + public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { + renderBackground(graphics); + super.render(graphics, mouseX, mouseY, delta); + renderTooltip(graphics, mouseX, mouseY); + } + + @Override + protected void renderBg(GuiGraphics graphics, float delta, int mouseX, int mouseY) { + int x = (width - imageWidth) / 2; + int y = (height - imageHeight) / 2; + graphics.blit(TEXTURE, x, y, 0, 0, imageWidth, imageHeight); + } + + @Override + protected void renderLabels(GuiGraphics graphics, int mouseX, int mouseY) { + // Don't render any labels, there's no space for them lol + } +} diff --git a/src/main/java/lv/enes/mc/eris_alchemy/client/ChestItemRenderer.java b/src/main/java/lv/enes/mc/eris_alchemy/client/ChestItemRenderer.java new file mode 100644 index 0000000..d49cc28 --- /dev/null +++ b/src/main/java/lv/enes/mc/eris_alchemy/client/ChestItemRenderer.java @@ -0,0 +1,40 @@ +package lv.enes.mc.eris_alchemy.client; + +import com.mojang.blaze3d.vertex.PoseStack; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.BlockEntityWithoutLevelRenderer; +import net.minecraft.client.renderer.MultiBufferSource; +import net.minecraft.core.BlockPos; +import net.minecraft.world.item.ItemDisplayContext; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.block.AbstractChestBlock; +import net.minecraft.world.level.block.entity.BaseContainerBlockEntity; +import net.minecraft.world.level.block.state.BlockState; + +import java.util.function.BiFunction; + +public class ChestItemRenderer extends BlockEntityWithoutLevelRenderer { + private final T chest; + + public ChestItemRenderer( + AbstractChestBlock block, + BiFunction creator + ) { + super(Minecraft.getInstance().getBlockEntityRenderDispatcher(), Minecraft.getInstance().getEntityModels()); + chest = creator.apply(BlockPos.ZERO, block.defaultBlockState()); + } + + @Override + public void renderByItem( + ItemStack stack, + ItemDisplayContext transformationMode, + PoseStack matrices, + MultiBufferSource vertexConsumers, + int light, + int overlay + ) { + Minecraft.getInstance() + .getBlockEntityRenderDispatcher() + .renderItem(chest, matrices, vertexConsumers, light, overlay); + } +} diff --git a/src/main/java/lv/enes/mc/eris_alchemy/client/ErisAlchemyClient.java b/src/main/java/lv/enes/mc/eris_alchemy/client/ErisAlchemyClient.java new file mode 100644 index 0000000..bb5de4f --- /dev/null +++ b/src/main/java/lv/enes/mc/eris_alchemy/client/ErisAlchemyClient.java @@ -0,0 +1,41 @@ +package lv.enes.mc.eris_alchemy.client; + +import lv.enes.mc.eris_alchemy.EMC; +import lv.enes.mc.eris_alchemy.ErisAlchemyItems; +import lv.enes.mc.eris_alchemy.block.ErisAlchemyBlocks; +import lv.enes.mc.eris_alchemy.block.entity.AlchemicalChestBlockEntity; +import lv.enes.mc.eris_alchemy.block.entity.ErisAlchemyBlockEntities; +import lv.enes.mc.eris_alchemy.menu.ErisAlchemyMenus; +import net.fabricmc.fabric.api.client.rendering.v1.BuiltinItemRendererRegistry; +import net.minecraft.client.gui.screens.MenuScreens; +import net.minecraft.client.renderer.blockentity.BlockEntityRenderers; +import net.minecraft.client.renderer.blockentity.ChestRenderer; +import net.minecraft.network.chat.Component; +import org.quiltmc.loader.api.ModContainer; +import org.quiltmc.qsl.base.api.entrypoint.client.ClientModInitializer; +import org.quiltmc.qsl.tooltip.api.client.ItemTooltipCallback; + +import java.text.DecimalFormat; + +@SuppressWarnings("unused") +public class ErisAlchemyClient implements ClientModInitializer { + @Override + public void onInitializeClient(ModContainer mod) { + BlockEntityRenderers.register(ErisAlchemyBlockEntities.ALCHEMICAL_CHEST, ChestRenderer::new); + + BuiltinItemRendererRegistry.INSTANCE.register( + ErisAlchemyItems.ALCHEMICAL_CHEST, + new ChestItemRenderer<>(ErisAlchemyBlocks.ALCHEMICAL_CHEST, AlchemicalChestBlockEntity::new)::renderByItem + ); + + MenuScreens.register(ErisAlchemyMenus.ALCHEMICAL_CHEST, AlchemicalChestScreen::new); + + var doubleFormat = new DecimalFormat("0"); + doubleFormat.setMaximumFractionDigits(1); + ItemTooltipCallback.EVENT.register((stack, player, context, tooltip) -> { + var world = player == null ? null : player.level(); + var emc = EMC.getInstance(world).get(stack.getItem()); + emc.ifPresent(value -> tooltip.add(Component.literal("EMC %s".formatted(doubleFormat.format(value))))); + }); + } +} diff --git a/src/main/java/lv/enes/mc/eris_alchemy/client/ErisAlchemyMaterials.java b/src/main/java/lv/enes/mc/eris_alchemy/client/ErisAlchemyMaterials.java new file mode 100644 index 0000000..15d9209 --- /dev/null +++ b/src/main/java/lv/enes/mc/eris_alchemy/client/ErisAlchemyMaterials.java @@ -0,0 +1,15 @@ +package lv.enes.mc.eris_alchemy.client; + +import lv.enes.mc.eris_alchemy.ErisAlchemy; +import net.minecraft.client.renderer.Sheets; +import net.minecraft.client.resources.model.Material; +import net.minecraft.resources.ResourceLocation; + +public final class ErisAlchemyMaterials { + public static final Material ALCHEMICAL_CHEST = new Material( + Sheets.CHEST_SHEET, + new ResourceLocation(ErisAlchemy.ID, "entity/chest/alchemical_chest") + ); + + private ErisAlchemyMaterials() {} +} diff --git a/src/main/java/lv/enes/mc/eris_alchemy/client/package-info.java b/src/main/java/lv/enes/mc/eris_alchemy/client/package-info.java new file mode 100644 index 0000000..7b1c99a --- /dev/null +++ b/src/main/java/lv/enes/mc/eris_alchemy/client/package-info.java @@ -0,0 +1,4 @@ +@ClientOnly +package lv.enes.mc.eris_alchemy.client; + +import org.quiltmc.loader.api.minecraft.ClientOnly; \ No newline at end of file diff --git a/src/main/java/lv/enes/mc/eris_alchemy/menu/AlchemicalChestMenu.java b/src/main/java/lv/enes/mc/eris_alchemy/menu/AlchemicalChestMenu.java new file mode 100644 index 0000000..e4135c9 --- /dev/null +++ b/src/main/java/lv/enes/mc/eris_alchemy/menu/AlchemicalChestMenu.java @@ -0,0 +1,91 @@ +package lv.enes.mc.eris_alchemy.menu; + +import jakarta.annotation.Nonnull; +import net.minecraft.world.Container; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.ItemStack; + +public class AlchemicalChestMenu extends AbstractContainerMenu { + private static final int WIDTH = 13; + private static final int HEIGHT = 8; + + private final Container container; + + public AlchemicalChestMenu(int syncId, Inventory playerInventory) { + this(syncId, playerInventory, new SimpleContainer(WIDTH * HEIGHT)); + } + + public AlchemicalChestMenu(int syncId, Inventory playerInventory, Container inventory) { + super(ErisAlchemyMenus.ALCHEMICAL_CHEST, syncId); + checkContainerSize(inventory, WIDTH * HEIGHT); + + this.container = inventory; + inventory.startOpen(playerInventory.player); + + var x_off = 8; + var y_off = 8; + + for (var y = 0; y < HEIGHT; y++) { + for (var x = 0; x < WIDTH; x++ ) { + addSlot(new Slot(inventory, y * WIDTH + x, x_off + x * 18, y_off + y * 18)); + } + } + + x_off = 44; + y_off = 155; + + for (var y = 0; y < 3; y++) { + for (var x = 0; x < 9; x++) { + addSlot(new Slot(playerInventory, y * 9 + x + 9, x_off + x * 18, y_off + y * 18)); + } + } + + y_off = 213; + + for (var x = 0; x < 9; x++) { + addSlot(new Slot(playerInventory, x, x_off + x * 18, y_off)); + } + } + + public Container getContainer() { + return container; + } + + @Nonnull + @Override + public ItemStack quickMoveStack(Player player, int fromIndex) { + var newStack = ItemStack.EMPTY; + var slot = slots.get(fromIndex); + if (!slot.hasItem()) { + return newStack; + } + + var originalStack = slot.getItem(); + newStack = originalStack.copy(); + + if (fromIndex < container.getContainerSize()) { + if (!moveItemStackTo(originalStack, container.getContainerSize(), slots.size(), true)) { + return ItemStack.EMPTY; + } + } else if (!moveItemStackTo(originalStack, 0, container.getContainerSize(), false)) { + return ItemStack.EMPTY; + } + + if (originalStack.isEmpty()) { + slot.setByPlayer(ItemStack.EMPTY); + } else { + slot.setChanged(); + } + + return newStack; + } + + @Override + public boolean stillValid(Player player) { + return container.stillValid(player); + } +} diff --git a/src/main/java/lv/enes/mc/eris_alchemy/menu/ErisAlchemyMenus.java b/src/main/java/lv/enes/mc/eris_alchemy/menu/ErisAlchemyMenus.java new file mode 100644 index 0000000..5e3b54a --- /dev/null +++ b/src/main/java/lv/enes/mc/eris_alchemy/menu/ErisAlchemyMenus.java @@ -0,0 +1,29 @@ +package lv.enes.mc.eris_alchemy.menu; + +import lv.enes.mc.eris_alchemy.ErisAlchemy; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.flag.FeatureFlags; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.MenuType; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.function.BiConsumer; + +public final class ErisAlchemyMenus { + private static final Map> menus = new LinkedHashMap<>(); + + public static final MenuType ALCHEMICAL_CHEST = register("alchemy_chest", AlchemicalChestMenu::new); + + public static void consumeMenus(BiConsumer> consumer) { + menus.forEach(consumer); + } + + private static MenuType register(String id, MenuType.MenuSupplier supplier) { + var menuType = new MenuType<>(supplier, FeatureFlags.VANILLA_SET); + menus.putIfAbsent(new ResourceLocation(ErisAlchemy.ID, id), menuType); + return menuType; + } + + private ErisAlchemyMenus() {} +} diff --git a/src/main/java/lv/enes/mc/eris_alchemy/mixin/client/SheetsMixin.java b/src/main/java/lv/enes/mc/eris_alchemy/mixin/client/SheetsMixin.java new file mode 100644 index 0000000..544dcef --- /dev/null +++ b/src/main/java/lv/enes/mc/eris_alchemy/mixin/client/SheetsMixin.java @@ -0,0 +1,22 @@ +package lv.enes.mc.eris_alchemy.mixin.client; + +import lv.enes.mc.eris_alchemy.client.ErisAlchemyMaterials; +import lv.enes.mc.eris_alchemy.block.entity.AlchemicalChestBlockEntity; +import net.minecraft.client.renderer.Sheets; +import net.minecraft.client.resources.model.Material; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.properties.ChestType; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(Sheets.class) +public abstract class SheetsMixin { + @Inject(method = "chooseMaterial(Lnet/minecraft/world/level/block/entity/BlockEntity;Lnet/minecraft/world/level/block/state/properties/ChestType;Z)Lnet/minecraft/client/resources/model/Material;", at = @At("RETURN"), cancellable = true) + private static void chooseMaterial(BlockEntity entity, ChestType type, boolean christmas, CallbackInfoReturnable cir) { + if (entity instanceof AlchemicalChestBlockEntity) { + cir.setReturnValue(ErisAlchemyMaterials.ALCHEMICAL_CHEST); + } + } +} diff --git a/src/main/java/lv/enes/mc/eris_alchemy/mixin/client/package-info.java b/src/main/java/lv/enes/mc/eris_alchemy/mixin/client/package-info.java new file mode 100644 index 0000000..f7d5ada --- /dev/null +++ b/src/main/java/lv/enes/mc/eris_alchemy/mixin/client/package-info.java @@ -0,0 +1,4 @@ +@ClientOnly +package lv.enes.mc.eris_alchemy.mixin.client; + +import org.quiltmc.loader.api.minecraft.ClientOnly; \ No newline at end of file diff --git a/src/main/java/lv/enes/mc/eris_alchemy/recipe/CovalenceRepair.java b/src/main/java/lv/enes/mc/eris_alchemy/recipe/CovalenceRepair.java new file mode 100644 index 0000000..dca464a --- /dev/null +++ b/src/main/java/lv/enes/mc/eris_alchemy/recipe/CovalenceRepair.java @@ -0,0 +1,169 @@ +package lv.enes.mc.eris_alchemy.recipe; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonSyntaxException; +import jakarta.annotation.Nonnull; +import net.minecraft.core.RegistryAccess; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.inventory.CraftingContainer; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.crafting.CraftingBookCategory; +import net.minecraft.world.item.crafting.CustomRecipe; +import net.minecraft.world.item.crafting.Ingredient; +import net.minecraft.world.item.crafting.RecipeSerializer; +import net.minecraft.world.level.Level; +import org.quiltmc.qsl.recipe.api.serializer.QuiltRecipeSerializer; + +import java.util.ArrayList; +import java.util.Arrays; + +public class CovalenceRepair extends CustomRecipe { + static class Serializer implements QuiltRecipeSerializer { + private static class Json { + CraftingBookCategory category = CraftingBookCategory.MISC; + JsonElement dust; + JsonElement materials = new JsonObject(); + JsonElement tools = new JsonObject(); + } + + Serializer() {} + + @Override + public JsonObject toJson(CovalenceRepair recipe) { + var res = new JsonObject(); + res.addProperty("category", recipe.category().toString()); + res.add("dust", recipe.dust.toJson()); + res.add("materials", recipe.materials.toJson()); + res.add("tools", recipe.tools.toJson()); + return res; + } + + @Nonnull + @Override + public CovalenceRepair fromJson(ResourceLocation id, JsonObject json) { + var recipeJson = new Gson().fromJson(json, Json.class); + if (recipeJson.dust == null) { + throw new JsonSyntaxException("A required attribute is missing"); + } + + var dust = Ingredient.fromJson(recipeJson.dust); + var materials = Ingredient.fromJson(recipeJson.materials); + var tools = Ingredient.fromJson(recipeJson.tools); + + return new CovalenceRepair(id, recipeJson.category, dust, materials, tools); + } + + @Override + public void toNetwork(FriendlyByteBuf buf, CovalenceRepair recipe) { + buf.writeEnum(recipe.category()); + recipe.dust.toNetwork(buf); + recipe.materials.toNetwork(buf); + recipe.tools.toNetwork(buf); + } + + @Nonnull + @Override + public CovalenceRepair fromNetwork(ResourceLocation id, FriendlyByteBuf buf) { + var category = buf.readEnum(CraftingBookCategory.class); + var dust = Ingredient.fromNetwork(buf); + var materials = Ingredient.fromNetwork(buf); + var tools = Ingredient.fromNetwork(buf); + return new CovalenceRepair(id, category, dust, materials, tools); + } + } + + private record Inputs(ItemStack toolStack, int dustCount) {} + + private final static int DUSTS_TO_FIX = 8; + + /** What dust do we use to repair. */ + private final Ingredient dust; + /** What materials this dust can repair. */ + private final Ingredient materials; + /** What tools can this dust repair. */ + private final Ingredient tools; + + public CovalenceRepair(ResourceLocation id, CraftingBookCategory category, Ingredient dust, Ingredient materials, Ingredient tools) { + super(id, category); + + this.dust = dust; + this.materials = materials; + this.tools = tools; + } + + @Override + public boolean canCraftInDimensions(int width, int height) { + return width * height > 2; + } + + @Override + public boolean matches(CraftingContainer inventory, Level world) { + return getInputs(inventory) != null; + } + + @Nonnull + @Override + public ItemStack assemble(CraftingContainer inventory, RegistryAccess registryManager) { + var inputs = getInputs(inventory); + if (inputs == null) { + return ItemStack.EMPTY; + } + + var newToolStack = inputs.toolStack.copy(); + var repairedAmount = inputs.toolStack.getItem().getMaxDamage() * inputs.dustCount / DUSTS_TO_FIX; + newToolStack.setDamageValue(inputs.toolStack.getDamageValue() - repairedAmount); + return newToolStack; + } + + @Nonnull + @Override + public RecipeSerializer getSerializer() { + return ErisAlchemyRecipeSerializers.COVALENCE_REPAIR; + } + + private boolean isTool(ItemStack stack) { + if (!stack.isDamageableItem() || !stack.isDamaged() || stack.getCount() != 1) { + return false; + } + + if (tools.test(stack)) { + return true; + } + + var item = stack.getItem(); + return Arrays.stream(materials.getItems()).anyMatch(material -> item.isValidRepairItem(stack, material)); + } + + private boolean isDust(ItemStack stack) { + return dust.test(stack); + } + + /** @return null if recipe isn't correct. */ + private Inputs getInputs(CraftingContainer inventory) { + ItemStack toolStack = null; + var dustStacks = new ArrayList(); + for (var i = 0; i < inventory.getContainerSize(); i++) { + var stack = inventory.getItem(i); + if (stack.isEmpty()) { + continue; + } + + if (isDust(stack) && (dustStacks.isEmpty() || ItemStack.isSameItemSameTags(dustStacks.get(0), stack))) { + dustStacks.add(stack); + } else if (toolStack == null) { + toolStack = stack; + } else { + return null; + } + } + + if (toolStack == null || dustStacks.isEmpty() || !isTool(toolStack)) { + return null; + } + + return new Inputs(toolStack, dustStacks.size()); + } +} diff --git a/src/main/java/lv/enes/mc/eris_alchemy/recipe/ErisAlchemyRecipeSerializers.java b/src/main/java/lv/enes/mc/eris_alchemy/recipe/ErisAlchemyRecipeSerializers.java new file mode 100644 index 0000000..c5a6676 --- /dev/null +++ b/src/main/java/lv/enes/mc/eris_alchemy/recipe/ErisAlchemyRecipeSerializers.java @@ -0,0 +1,27 @@ +package lv.enes.mc.eris_alchemy.recipe; + +import lv.enes.mc.eris_alchemy.ErisAlchemy; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.crafting.Recipe; +import net.minecraft.world.item.crafting.RecipeSerializer; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.function.BiConsumer; + +public final class ErisAlchemyRecipeSerializers { + private static final Map> serializers = new LinkedHashMap<>(); + + public static final RecipeSerializer COVALENCE_REPAIR = register("covalence_repair", new CovalenceRepair.Serializer()); + + public static void consumeSerializers(BiConsumer> consumer) { + serializers.forEach(consumer); + } + + private static > RecipeSerializer register(String id, RecipeSerializer serializer) { + serializers.putIfAbsent(new ResourceLocation(ErisAlchemy.ID, id), serializer); + return serializer; + } + + private ErisAlchemyRecipeSerializers() {} +} diff --git a/src/main/java/lv/enes/mc/eris_alchemy/utils/AxeUtils.java b/src/main/java/lv/enes/mc/eris_alchemy/utils/AxeUtils.java new file mode 100644 index 0000000..22c0bad --- /dev/null +++ b/src/main/java/lv/enes/mc/eris_alchemy/utils/AxeUtils.java @@ -0,0 +1,18 @@ +package lv.enes.mc.eris_alchemy.utils; + +import net.minecraft.world.item.AxeItem; +import net.minecraft.world.item.Tier; +import net.minecraft.world.level.block.Block; + +import java.util.Map; + +/** This extends AxeItem only to read the STRIPPABLES variable :3 */ +public final class AxeUtils extends AxeItem { + public static Map getStrippables() { + return STRIPPABLES; + } + + private AxeUtils(Tier material, float attackDamage, float attackSpeed, Properties settings) { + super(material, attackDamage, attackSpeed, settings); + } +} diff --git a/src/main/java/lv/enes/mc/eris_alchemy/utils/BlockUtils.java b/src/main/java/lv/enes/mc/eris_alchemy/utils/BlockUtils.java index 74b6069..88b6231 100644 --- a/src/main/java/lv/enes/mc/eris_alchemy/utils/BlockUtils.java +++ b/src/main/java/lv/enes/mc/eris_alchemy/utils/BlockUtils.java @@ -6,8 +6,10 @@ import net.minecraft.world.level.block.Block; import java.util.stream.Stream; -public class BlockUtils { +public final class BlockUtils { public static Stream streamTag(TagKey tag) { return TagUtils.stream(BuiltInRegistries.BLOCK, tag); } + + private BlockUtils() {} } diff --git a/src/main/java/lv/enes/mc/eris_alchemy/utils/CoralUtils.java b/src/main/java/lv/enes/mc/eris_alchemy/utils/CoralUtils.java index c9203c6..1136c9e 100644 --- a/src/main/java/lv/enes/mc/eris_alchemy/utils/CoralUtils.java +++ b/src/main/java/lv/enes/mc/eris_alchemy/utils/CoralUtils.java @@ -5,7 +5,7 @@ import net.minecraft.world.level.block.*; import java.util.stream.Stream; -public class CoralUtils { +public final class CoralUtils { /** * @see lv.enes.mc.eris_alchemy.mixin.CoralBlockMixin * @see lv.enes.mc.eris_alchemy.mixin.CoralFanBlockMixin @@ -39,4 +39,6 @@ public class CoralUtils { public static Block getDeadCoralBlock(CoralBlock live) { return ((CoralSuper)live).lv_enes_mc$getDead(); } + + private CoralUtils() {} } diff --git a/src/main/java/lv/enes/mc/eris_alchemy/utils/DyeUtils.java b/src/main/java/lv/enes/mc/eris_alchemy/utils/DyeUtils.java index 83c8e04..3ceb965 100644 --- a/src/main/java/lv/enes/mc/eris_alchemy/utils/DyeUtils.java +++ b/src/main/java/lv/enes/mc/eris_alchemy/utils/DyeUtils.java @@ -7,7 +7,7 @@ import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.ConcretePowderBlock; import net.minecraft.world.level.block.ShulkerBoxBlock; -public class DyeUtils { +public final class DyeUtils { public static Block getConcrete(DyeColor color) { return switch (color) { case BLACK -> Blocks.BLACK_CONCRETE; @@ -57,4 +57,6 @@ public class DyeUtils { public static ShulkerBoxBlock getShulkerBox(DyeColor color) { return (ShulkerBoxBlock)ShulkerBoxBlock.getBlockByColor(color); } + + private DyeUtils() {} } diff --git a/src/main/java/lv/enes/mc/eris_alchemy/utils/ItemUtils.java b/src/main/java/lv/enes/mc/eris_alchemy/utils/ItemUtils.java index 2f7c8b2..26d4405 100644 --- a/src/main/java/lv/enes/mc/eris_alchemy/utils/ItemUtils.java +++ b/src/main/java/lv/enes/mc/eris_alchemy/utils/ItemUtils.java @@ -6,8 +6,10 @@ import net.minecraft.world.item.Item; import java.util.stream.Stream; -public class ItemUtils { +public final class ItemUtils { public static Stream streamTag(TagKey tag) { return TagUtils.stream(BuiltInRegistries.ITEM, tag); } + + private ItemUtils() {} } diff --git a/src/main/java/lv/enes/mc/eris_alchemy/utils/RecipeUtils.java b/src/main/java/lv/enes/mc/eris_alchemy/utils/RecipeUtils.java index 53ceaab..0eb6a2c 100644 --- a/src/main/java/lv/enes/mc/eris_alchemy/utils/RecipeUtils.java +++ b/src/main/java/lv/enes/mc/eris_alchemy/utils/RecipeUtils.java @@ -7,7 +7,7 @@ import net.minecraft.world.item.crafting.Recipe; import java.util.List; -public class RecipeUtils { +public final class RecipeUtils { public interface RecipeSuper { List lv_enes_mc$getIngredients(); ItemStack lv_enes_mc$getOutput(RegistryAccess registryAccess); @@ -20,4 +20,6 @@ public class RecipeUtils { public static ItemStack getOutput(Recipe recipe, RegistryAccess registryAccess) { return ((RecipeSuper)recipe).lv_enes_mc$getOutput(registryAccess); } + + private RecipeUtils() {} } diff --git a/src/main/java/lv/enes/mc/eris_alchemy/utils/TagUtils.java b/src/main/java/lv/enes/mc/eris_alchemy/utils/TagUtils.java index bb923ba..93e577f 100644 --- a/src/main/java/lv/enes/mc/eris_alchemy/utils/TagUtils.java +++ b/src/main/java/lv/enes/mc/eris_alchemy/utils/TagUtils.java @@ -7,8 +7,10 @@ import net.minecraft.tags.TagKey; import java.util.stream.Stream; import java.util.stream.StreamSupport; -public class TagUtils { +public final class TagUtils { public static Stream stream(Registry registry, TagKey tag) { return StreamSupport.stream(registry.getTagOrEmpty(tag).spliterator(), false).map(Holder::value); } + + private TagUtils() {} } diff --git a/src/main/resources/assets/eris_alchemy/blockstates/alchemical_chest.json b/src/main/resources/assets/eris_alchemy/blockstates/alchemical_chest.json new file mode 100644 index 0000000..e04b5f5 --- /dev/null +++ b/src/main/resources/assets/eris_alchemy/blockstates/alchemical_chest.json @@ -0,0 +1,7 @@ +{ + "variants": { + "": { + "model": "eris_alchemy:block/alchemical_chest" + } + } +} \ No newline at end of file diff --git a/src/main/resources/assets/eris_alchemy/icon.png b/src/main/resources/assets/eris_alchemy/icon.png index 09015e4..89c12c2 100644 Binary files a/src/main/resources/assets/eris_alchemy/icon.png and b/src/main/resources/assets/eris_alchemy/icon.png differ diff --git a/src/main/resources/assets/eris_alchemy/lang/en_us.json b/src/main/resources/assets/eris_alchemy/lang/en_us.json index c001018..ed007dd 100644 --- a/src/main/resources/assets/eris_alchemy/lang/en_us.json +++ b/src/main/resources/assets/eris_alchemy/lang/en_us.json @@ -1,8 +1,12 @@ { + "block.eris_alchemy.alchemical_chest": "Alchemical Chest", + "book.eris_alchemy.title": "Eris Alchemy", "book.eris_alchemy.subtitle": "the Official Guide", "book.eris_alchemy.landing_text": "Welcome to Alchemy!", + "container.eris_alchemy.alchemical_chest": "Alchemical Chest", + "item.eris_alchemy.low_covalence_dust": "Low Covalence Dust", "item.eris_alchemy.medium_covalence_dust": "Medium Covalence Dust", "item.eris_alchemy.high_covalence_dust": "High Covalence Dust", diff --git a/src/main/resources/assets/eris_alchemy/models/block/alchemical_chest.json b/src/main/resources/assets/eris_alchemy/models/block/alchemical_chest.json new file mode 100644 index 0000000..84acbec --- /dev/null +++ b/src/main/resources/assets/eris_alchemy/models/block/alchemical_chest.json @@ -0,0 +1,5 @@ +{ + "textures": { + "particle": "minecraft:block/stone" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/eris_alchemy/models/item/alchemical_chest.json b/src/main/resources/assets/eris_alchemy/models/item/alchemical_chest.json new file mode 100644 index 0000000..62b3156 --- /dev/null +++ b/src/main/resources/assets/eris_alchemy/models/item/alchemical_chest.json @@ -0,0 +1,6 @@ +{ + "parent": "item/chest", + "textures": { + "particle": "block/stone" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/eris_alchemy/patchouli_books/guide_book/en_us/entries/root/alchemical_chest.json b/src/main/resources/assets/eris_alchemy/patchouli_books/guide_book/en_us/entries/root/alchemical_chest.json new file mode 100644 index 0000000..bd594f6 --- /dev/null +++ b/src/main/resources/assets/eris_alchemy/patchouli_books/guide_book/en_us/entries/root/alchemical_chest.json @@ -0,0 +1,14 @@ +{ + "name": "Alchemical Chest", + "icon": "eris_alchemy:alchemical_chest", + "category": "eris_alchemy:root", + "pages": [{ + "type": "patchouli:spotlight", + "item": "eris_alchemy:alchemical_chest", + "link_recipe": true, + "text": "$(item)Alchemical Chest$() is a chest with a giant inventory space (104 slots)." + }, { + "type": "patchouli:crafting", + "recipe": "eris_alchemy:alchemical_chest" + }] +} \ No newline at end of file diff --git a/src/main/resources/assets/eris_alchemy/textures/entity/chest/alchemical_chest.png b/src/main/resources/assets/eris_alchemy/textures/entity/chest/alchemical_chest.png new file mode 100644 index 0000000..cc75ed1 Binary files /dev/null and b/src/main/resources/assets/eris_alchemy/textures/entity/chest/alchemical_chest.png differ diff --git a/src/main/resources/assets/eris_alchemy/textures/gui/container/alchemical_chest.png b/src/main/resources/assets/eris_alchemy/textures/gui/container/alchemical_chest.png new file mode 100644 index 0000000..fe06cf6 Binary files /dev/null and b/src/main/resources/assets/eris_alchemy/textures/gui/container/alchemical_chest.png differ diff --git a/src/main/resources/data/eris_alchemy/loot_tables/blocks/alchemical_chest.json b/src/main/resources/data/eris_alchemy/loot_tables/blocks/alchemical_chest.json new file mode 100644 index 0000000..a552e23 --- /dev/null +++ b/src/main/resources/data/eris_alchemy/loot_tables/blocks/alchemical_chest.json @@ -0,0 +1,13 @@ +{ + "type": "minecraft:block", + "pools": [{ + "rolls": 1, + "entries": [{ + "type": "minecraft:item", + "name": "eris_alchemy:alchemical_chest" + }], + "conditions": [{ + "condition": "minecraft:survives_explosion" + }] + }] +} \ No newline at end of file diff --git a/src/main/resources/data/eris_alchemy/recipes/alchemical_chest.json b/src/main/resources/data/eris_alchemy/recipes/alchemical_chest.json new file mode 100644 index 0000000..7c97900 --- /dev/null +++ b/src/main/resources/data/eris_alchemy/recipes/alchemical_chest.json @@ -0,0 +1,34 @@ +{ + "type": "crafting_shaped", + "pattern": [ + "123", + "SDS", + "ICI" + ], + "key": { + "1": { + "item": "eris_alchemy:low_covalence_dust" + }, + "2": { + "item": "eris_alchemy:medium_covalence_dust" + }, + "3": { + "item": "eris_alchemy:high_covalence_dust" + }, + "S": { + "item": "minecraft:stone" + }, + "D": { + "item": "minecraft:diamond" + }, + "I": { + "item": "minecraft:iron_ingot" + }, + "C": { + "item": "minecraft:chest" + } + }, + "result": { + "item": "eris_alchemy:alchemical_chest" + } +} \ No newline at end of file diff --git a/src/main/resources/data/minecraft/tags/blocks/mineable/pickaxe.json b/src/main/resources/data/minecraft/tags/blocks/mineable/pickaxe.json new file mode 100644 index 0000000..27c0b6d --- /dev/null +++ b/src/main/resources/data/minecraft/tags/blocks/mineable/pickaxe.json @@ -0,0 +1,6 @@ +{ + "replace": false, + "values": [ + "eris_alchemy:alchemical_chest" + ] +} \ No newline at end of file diff --git a/src/main/resources/eris_alchemy.mixins.json b/src/main/resources/eris_alchemy.mixins.json index 56177c0..36cb47f 100644 --- a/src/main/resources/eris_alchemy.mixins.json +++ b/src/main/resources/eris_alchemy.mixins.json @@ -3,6 +3,9 @@ "minVersion": "0.8", "package": "lv.enes.mc.eris_alchemy.mixin", "compatibilityLevel": "JAVA_17", + "client": [ + "client.SheetsMixin" + ], "mixins": [ "CoralBlockMixin", "CoralFanBlockMixin", diff --git a/src/main/resources/quilt.mod.json b/src/main/resources/quilt.mod.json index 873c25f..5b6373a 100644 --- a/src/main/resources/quilt.mod.json +++ b/src/main/resources/quilt.mod.json @@ -19,7 +19,8 @@ }, "intermediate_mappings": "net.fabricmc:intermediary", "entrypoints": { - "init": "lv.enes.mc.eris_alchemy.ErisAlchemy" + "init": "lv.enes.mc.eris_alchemy.ErisAlchemy", + "client_init": "lv.enes.mc.eris_alchemy.client.ErisAlchemyClient" }, "depends": [{ "id": "quilt_loader", -- cgit v1.2.3