From 5c064176b2b1b33d416f179c3975e2f7941c9b4e Mon Sep 17 00:00:00 2001 From: Adrien1106 Date: Fri, 15 Mar 2024 23:30:10 +0100 Subject: [PATCH] complied with FramedBlock assets License by making this project Multi-Licensed. In addition, wrote a README.md + cache reload fix on resource reload + items for hammer, blueprint and screwdriver + updated to v1.5.5 --- LICENSE | 16 +- LICENSE.LGPL-3.0 | 165 ++++++++++++++++++ LICENSE.MIT | 15 ++ README.md | 43 +++++ gradle.properties | 2 +- .../java/fr/adrien1106/reframed/ReFramed.java | 65 ++++--- .../reframed/block/ReFramedDoubleBlock.java | 14 +- .../reframed/client/ReFramedClient.java | 2 +- .../client/ReFramedModelProvider.java | 1 + .../apperance/CamoAppearanceManager.java | 18 +- .../reframed/generator/GBlockstate.java | 4 +- .../reframed/generator/GLanguage.java | 1 + .../reframed/generator/GRecipe.java | 3 + .../reframed/item/ReFramedBlueprintItem.java | 55 ++++++ .../item/ReFramedBlueprintWrittenItem.java | 113 ++++++++++++ .../reframed/item/ReFramedHammerItem.java | 70 ++++++++ .../item/ReFramedScrewdriverItem.java | 78 +++++++++ .../reframed/util/blocks/BlockHelper.java | 2 +- .../reframed/textures/item/blueprint.png | Bin 0 -> 5188 bytes .../textures/item/blueprint_written.png | Bin 0 -> 5475 bytes .../assets/reframed/textures/item/hammer.png | Bin 0 -> 320 bytes .../reframed/textures/item/screwdriver.png | Bin 0 -> 226 bytes src/main/resources/fabric.mod.json | 2 +- 23 files changed, 613 insertions(+), 56 deletions(-) create mode 100644 LICENSE.LGPL-3.0 create mode 100644 LICENSE.MIT create mode 100644 README.md create mode 100644 src/main/java/fr/adrien1106/reframed/item/ReFramedBlueprintItem.java create mode 100644 src/main/java/fr/adrien1106/reframed/item/ReFramedBlueprintWrittenItem.java create mode 100644 src/main/java/fr/adrien1106/reframed/item/ReFramedHammerItem.java create mode 100644 src/main/java/fr/adrien1106/reframed/item/ReFramedScrewdriverItem.java create mode 100644 src/main/resources/assets/reframed/textures/item/blueprint.png create mode 100644 src/main/resources/assets/reframed/textures/item/blueprint_written.png create mode 100644 src/main/resources/assets/reframed/textures/item/hammer.png create mode 100644 src/main/resources/assets/reframed/textures/item/screwdriver.png diff --git a/LICENSE b/LICENSE index dd3d80a..fd473a9 100644 --- a/LICENSE +++ b/LICENSE @@ -1,15 +1 @@ -Copyright (c) 2019 B0undarybreaker (Meredith Espinosa) -Copyright (c) 2024 Adrien1106 - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +MIT AND LGPL-3.0 \ No newline at end of file diff --git a/LICENSE.LGPL-3.0 b/LICENSE.LGPL-3.0 new file mode 100644 index 0000000..0e4ab77 --- /dev/null +++ b/LICENSE.LGPL-3.0 @@ -0,0 +1,165 @@ +GNU LESSER GENERAL PUBLIC LICENSE +Version 3, 29 June 2007 + +Copyright (C) 2007 Free Software Foundation, Inc. +Everyone is permitted to copy and distribute verbatim copies +of this license document, but changing it is not allowed. + + +This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + +0. Additional Definitions. + +As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + +"The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + +An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + +A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + +The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + +The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + +1. Exception to Section 3 of the GNU GPL. + +You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + +2. Conveying Modified Versions. + +If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + +a) under this License, provided that you make a good faith effort to +ensure that, in the event an Application does not supply the +function or data, the facility still operates, and performs +whatever part of its purpose remains meaningful, or + +b) under the GNU GPL, with none of the additional permissions of +this License applicable to that copy. + +3. Object Code Incorporating Material from Library Header Files. + +The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + +a) Give prominent notice with each copy of the object code that the +Library is used in it and that the Library and its use are +covered by this License. + +b) Accompany the object code with a copy of the GNU GPL and this license +document. + +4. Combined Works. + +You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + +a) Give prominent notice with each copy of the Combined Work that +the Library is used in it and that the Library and its use are +covered by this License. + +b) Accompany the Combined Work with a copy of the GNU GPL and this license +document. + +c) For a Combined Work that displays copyright notices during +execution, include the copyright notice for the Library among +these notices, as well as a reference directing the user to the +copies of the GNU GPL and this license document. + +d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + +e) Provide Installation Information, but only if you would otherwise +be required to provide such information under section 6 of the +GNU GPL, and only to the extent that such information is +necessary to install and execute a modified version of the +Combined Work produced by recombining or relinking the +Application with a modified version of the Linked Version. (If +you use option 4d0, the Installation Information must accompany +the Minimal Corresponding Source and Corresponding Application +Code. If you use option 4d1, you must provide the Installation +Information in the manner specified by section 6 of the GNU GPL +for conveying Corresponding Source.) + +5. Combined Libraries. + +You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + +a) Accompany the combined library with a copy of the same work based +on the Library, uncombined with any other library facilities, +conveyed under the terms of this License. + +b) Give prominent notice with the combined library that part of it +is a work based on the Library, and explaining where to find the +accompanying uncombined form of the same work. + +6. Revised Versions of the GNU Lesser General Public License. + +The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + +If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. \ No newline at end of file diff --git a/LICENSE.MIT b/LICENSE.MIT new file mode 100644 index 0000000..5a5f963 --- /dev/null +++ b/LICENSE.MIT @@ -0,0 +1,15 @@ +Copyright (c) 2019 B0undarybreaker (Meredith Espinosa)\ +Copyright (c) 2024 Adrien1106 + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..7b670aa --- /dev/null +++ b/README.md @@ -0,0 +1,43 @@ +# ReFramed +## Overview +This Project is an unofficial port of [FramedBlocks](https://github.com/XFactHD/FramedBlocks) +forked from [Templates 2](https://github.com/quat1024/templates-mod). +It aims to add the same mechanics as [FramedBlocks](https://github.com/XFactHD/FramedBlocks) into Fabric. + +For a complete Feature list please have a look at the [Modrinth](https://modrinth.com/mod/reframed) page. + +## Information +### Where to get it? +The mod can be downloaded on [Modrinth](https://modrinth.com/mod/reframed). + +### Issues/requests +Any issues/requests may be addressed on this repository in the [Issues](https://github.com/DriHut/ReFramed/issues) section. + +### Out of date? +I will be adding new shapes as time passes, but I am currently not planing on keeping up with the newer versions of the game. +That said this mod is under permissive licenses and make it easy for anyone to create their own fork. +If anyone in the future plans to do so, I will gladly either link it from here and the modrinth page, +or based on preferences add the person(s) to the project + +### What Shapes are planed to be added +Currently, the list of shapes to be added is pretty simple as the mod is still under development: +- Wall +- Fence +- Pane +- Button +- Pressure Plate +- Trapdoor +- Door +- Carpet +- Post +- Half Slab (maybe redundant with Layer) +- Slabs Stair (a stair with one end being of a second theme, might be done in multiple blocks) + +Any Ideas feel free to make a suggestion [here](https://github.com/DriHut/ReFramed/issues). + +## License +This work is dual-licensed under MIT and LGPL 3.0 both applying to different part of this project: +- The LGPL 3.0 license applies to all the textures that can be found in [`assets/reframed/textures`](src/main/resources/assets/reframed/textures) +- The MIT license applied to everything else so including all the code present within this project + +`SPDX-License-Identifier: MIT AND LGPL-3.0` \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 77a6cd7..6a153bd 100644 --- a/gradle.properties +++ b/gradle.properties @@ -9,7 +9,7 @@ loader_version=0.15.6 # Mod Properties modrinth_id = jCpoCBpn -mod_version = 1.5 +mod_version = 1.5.5 maven_group = fr.adrien1106 archives_base_name = ReFramed mod_id = reframed diff --git a/src/main/java/fr/adrien1106/reframed/ReFramed.java b/src/main/java/fr/adrien1106/reframed/ReFramed.java index 8886e19..ee970b2 100644 --- a/src/main/java/fr/adrien1106/reframed/ReFramed.java +++ b/src/main/java/fr/adrien1106/reframed/ReFramed.java @@ -1,6 +1,10 @@ package fr.adrien1106.reframed; import fr.adrien1106.reframed.block.*; +import fr.adrien1106.reframed.item.ReFramedHammerItem; +import fr.adrien1106.reframed.item.ReFramedBlueprintItem; +import fr.adrien1106.reframed.item.ReFramedBlueprintWrittenItem; +import fr.adrien1106.reframed.item.ReFramedScrewdriverItem; import net.fabricmc.api.ModInitializer; import net.fabricmc.fabric.api.itemgroup.v1.FabricItemGroup; import net.fabricmc.fabric.api.object.builder.v1.block.entity.FabricBlockEntityTypeBuilder; @@ -8,10 +12,7 @@ import net.minecraft.block.AbstractBlock; import net.minecraft.block.Block; import net.minecraft.block.Blocks; import net.minecraft.block.entity.BlockEntityType; -import net.minecraft.item.BlockItem; -import net.minecraft.item.Item; -import net.minecraft.item.ItemGroup; -import net.minecraft.item.ItemStack; +import net.minecraft.item.*; import net.minecraft.registry.Registries; import net.minecraft.registry.Registry; import net.minecraft.sound.BlockSoundGroup; @@ -22,15 +23,12 @@ import net.minecraft.world.World; import java.util.ArrayList; import java.util.function.BiConsumer; -import java.util.stream.Collectors; +import java.util.stream.Stream; import static fr.adrien1106.reframed.util.blocks.BlockProperties.LIGHT; /** * TODO make block pairable by right click -> for v1.6 - * TODO add Hammer from framed ( removes theme ) -> for v1.5.5 - * TODO add screwdriver ( iterate over theme states ) ? - * TODO add blueprint for survival friendly copy paste of a theme. -> for v1.5.5 * TODO add minecraft models like wall fence etc -> for v1.6 * TODO better connected textures -> maybe v1.6 ? */ @@ -39,6 +37,10 @@ public class ReFramed implements ModInitializer { public static final ArrayList BLOCKS = new ArrayList<>(); public static Block CUBE, SMALL_CUBE, SMALL_CUBES_STEP, STAIR, HALF_STAIR, STAIRS_CUBE, HALF_STAIRS_SLAB, HALF_STAIRS_STAIR, SLAB, SLABS_CUBE, STEP, STEPS_SLAB, LAYER; + + public static final ArrayList ITEMS = new ArrayList<>(); + public static Item HAMMER, SCREWDRIVER, BLUEPRINT, BLUEPRINT_WRITTEN; + public static ItemGroup ITEM_GROUP; public static BlockEntityType REFRAMED_BLOCK_ENTITY; @@ -48,19 +50,24 @@ public class ReFramed implements ModInitializer { @Override public void onInitialize() { - CUBE = registerReFramed("cube" , new ReFramedBlock(cp(Blocks.OAK_PLANKS))); - SMALL_CUBE = registerReFramed("small_cube" , new ReFramedSmallCubeBlock(cp(Blocks.OAK_PLANKS))); - SMALL_CUBES_STEP = registerReFramed("small_cubes_step" , new ReFramedSmallCubesStepBlock(cp(Blocks.OAK_PLANKS))); - STAIR = registerReFramed("stair" , new ReFramedStairBlock(cp(Blocks.OAK_STAIRS))); - STAIRS_CUBE = registerReFramed("stairs_cube" , new ReFramedStairsCubeBlock(cp(Blocks.OAK_STAIRS))); - HALF_STAIR = registerReFramed("half_stair" , new ReFramedHalfStairBlock(cp(Blocks.OAK_STAIRS))); - HALF_STAIRS_SLAB = registerReFramed("half_stairs_slab" , new ReFramedHalfStairsSlabBlock(cp(Blocks.OAK_STAIRS))); - HALF_STAIRS_STAIR = registerReFramed("half_stairs_stair" , new ReFramedHalfStairsStairBlock(cp(Blocks.OAK_STAIRS))); - LAYER = registerReFramed("layer" , new ReFramedLayerBlock(cp(Blocks.OAK_SLAB))); - SLAB = registerReFramed("slab" , new ReFramedSlabBlock(cp(Blocks.OAK_SLAB))); - SLABS_CUBE = registerReFramed("slabs_cube" , new ReFramedSlabsCubeBlock(cp(Blocks.OAK_SLAB))); - STEP = registerReFramed("step" , new ReFramedStepBlock(cp(Blocks.OAK_SLAB))); - STEPS_SLAB = registerReFramed("steps_slab" , new ReFramedStepsSlabBlock(cp(Blocks.OAK_SLAB))); + CUBE = registerBlock("cube" , new ReFramedBlock(cp(Blocks.OAK_PLANKS))); + SMALL_CUBE = registerBlock("small_cube" , new ReFramedSmallCubeBlock(cp(Blocks.OAK_PLANKS))); + SMALL_CUBES_STEP = registerBlock("small_cubes_step" , new ReFramedSmallCubesStepBlock(cp(Blocks.OAK_PLANKS))); + STAIR = registerBlock("stair" , new ReFramedStairBlock(cp(Blocks.OAK_STAIRS))); + STAIRS_CUBE = registerBlock("stairs_cube" , new ReFramedStairsCubeBlock(cp(Blocks.OAK_STAIRS))); + HALF_STAIR = registerBlock("half_stair" , new ReFramedHalfStairBlock(cp(Blocks.OAK_STAIRS))); + HALF_STAIRS_SLAB = registerBlock("half_stairs_slab" , new ReFramedHalfStairsSlabBlock(cp(Blocks.OAK_STAIRS))); + HALF_STAIRS_STAIR = registerBlock("half_stairs_stair" , new ReFramedHalfStairsStairBlock(cp(Blocks.OAK_STAIRS))); + LAYER = registerBlock("layer" , new ReFramedLayerBlock(cp(Blocks.OAK_SLAB))); + SLAB = registerBlock("slab" , new ReFramedSlabBlock(cp(Blocks.OAK_SLAB))); + SLABS_CUBE = registerBlock("slabs_cube" , new ReFramedSlabsCubeBlock(cp(Blocks.OAK_SLAB))); + STEP = registerBlock("step" , new ReFramedStepBlock(cp(Blocks.OAK_SLAB))); + STEPS_SLAB = registerBlock("steps_slab" , new ReFramedStepsSlabBlock(cp(Blocks.OAK_SLAB))); + + HAMMER = registerItem("hammer" , new ReFramedHammerItem(new Item.Settings().maxCount(1))); + SCREWDRIVER = registerItem("screwdriver" , new ReFramedScrewdriverItem(new Item.Settings().maxCount(1))); + BLUEPRINT = registerItem("blueprint" , new ReFramedBlueprintItem(new Item.Settings())); + BLUEPRINT_WRITTEN = registerItem("blueprint_written" , new ReFramedBlueprintWrittenItem(new Item.Settings().maxCount(1))); REFRAMED_BLOCK_ENTITY = Registry.register(Registries.BLOCK_ENTITY_TYPE, id("camo"), FabricBlockEntityTypeBuilder.create( @@ -81,7 +88,12 @@ public class ReFramed implements ModInitializer { ITEM_GROUP = Registry.register(Registries.ITEM_GROUP, id("tab"), FabricItemGroup.builder() .displayName(Text.translatable("itemGroup.reframed.tab")) .icon(() -> new ItemStack(SLAB)) - .entries((ctx, e) -> e.addAll(BLOCKS.stream().map(ItemStack::new).collect(Collectors.toList()))).build() + .entries((ctx, e) -> e.addAll( + Stream.concat( + ITEMS.stream().filter(item -> item != BLUEPRINT_WRITTEN), + BLOCKS.stream().map(Block::asItem) + ).map(Item::getDefaultStack).toList()) + ).build() ); } @@ -94,8 +106,15 @@ public class ReFramed implements ModInitializer { .suffocates((a,b,c) -> false) .blockVision((a,b,c) -> false); } + + private static I registerItem(String path, I item) { + Identifier id = id(path); + Registry.register(Registries.ITEM, id, item); + ITEMS.add(item); + return item; + } - private static B registerReFramed(String path, B block) { + private static B registerBlock(String path, B block) { Identifier id = id(path); Registry.register(Registries.BLOCK, id, block); diff --git a/src/main/java/fr/adrien1106/reframed/block/ReFramedDoubleBlock.java b/src/main/java/fr/adrien1106/reframed/block/ReFramedDoubleBlock.java index cb708b0..143b48c 100644 --- a/src/main/java/fr/adrien1106/reframed/block/ReFramedDoubleBlock.java +++ b/src/main/java/fr/adrien1106/reframed/block/ReFramedDoubleBlock.java @@ -31,8 +31,11 @@ public abstract class ReFramedDoubleBlock extends ReFramedBlock { return ReFramed.REFRAMED_DOUBLE_BLOCK_ENTITY.instantiate(pos, state); } - protected int getHitShape(BlockState state, BlockHitResult hit) { - Direction side = hit.getSide(); + public int getHitShape(BlockState state, BlockHitResult hit) { + return getHitShape(state, hit.getPos(), hit.getBlockPos(), hit.getSide()); + } + + public int getHitShape(BlockState state, Vec3d hit, BlockPos pos, Direction side) { VoxelShape first_shape = getShape(state, 1); VoxelShape second_shape = getShape(state, 2); @@ -40,10 +43,9 @@ public abstract class ReFramedDoubleBlock extends ReFramedBlock { if (isFaceFullSquare(first_shape, side)) return 1; if (isFaceFullSquare(second_shape, side)) return 2; - Vec3d pos = BlockHelper.getRelativePos(hit.getPos(), hit.getBlockPos()); -// System.out.println(side.getAxis().choose(hit.getPos().x, hit.getPos().y, hit.getPos().z)); - if (BlockHelper.cursorMatchesFace(first_shape, pos)) return 1; - if (BlockHelper.cursorMatchesFace(second_shape, pos)) return 2; + Vec3d rel = BlockHelper.getRelativePos(hit, pos); + if (BlockHelper.cursorMatchesFace(first_shape, rel)) return 1; + if (BlockHelper.cursorMatchesFace(second_shape, rel)) return 2; return 0; } diff --git a/src/main/java/fr/adrien1106/reframed/client/ReFramedClient.java b/src/main/java/fr/adrien1106/reframed/client/ReFramedClient.java index 59e08fb..355cce5 100644 --- a/src/main/java/fr/adrien1106/reframed/client/ReFramedClient.java +++ b/src/main/java/fr/adrien1106/reframed/client/ReFramedClient.java @@ -105,7 +105,7 @@ public class ReFramedClient implements ClientModInitializer { //supporting code for the TemplatesModelProvider ModelLoadingRegistry.INSTANCE.registerResourceProvider(rm -> PROVIDER); //block models ModelLoadingRegistry.INSTANCE.registerVariantProvider(rm -> PROVIDER); //item models - + ResourceManagerHelper.get(ResourceType.CLIENT_RESOURCES).registerReloadListener(new SimpleSynchronousResourceReloadListener() { @Override public Identifier getFabricId() { return ReFramed.id("dump-caches"); } @Override public void reload(ResourceManager blah) { PROVIDER.dumpCache(); } diff --git a/src/main/java/fr/adrien1106/reframed/client/ReFramedModelProvider.java b/src/main/java/fr/adrien1106/reframed/client/ReFramedModelProvider.java index 86b673c..ab6b946 100644 --- a/src/main/java/fr/adrien1106/reframed/client/ReFramedModelProvider.java +++ b/src/main/java/fr/adrien1106/reframed/client/ReFramedModelProvider.java @@ -70,6 +70,7 @@ public class ReFramedModelProvider implements ModelResourceProvider, ModelVarian } public void dumpCache() { + CamoAppearanceManager.dumpCahe(); appearanceManager = null; //volatile write } diff --git a/src/main/java/fr/adrien1106/reframed/client/model/apperance/CamoAppearanceManager.java b/src/main/java/fr/adrien1106/reframed/client/model/apperance/CamoAppearanceManager.java index f6a5c64..63ab5b2 100644 --- a/src/main/java/fr/adrien1106/reframed/client/model/apperance/CamoAppearanceManager.java +++ b/src/main/java/fr/adrien1106/reframed/client/model/apperance/CamoAppearanceManager.java @@ -39,6 +39,12 @@ import java.util.function.Function; @Environment(EnvType.CLIENT) public class CamoAppearanceManager { + + protected static final SpriteIdentifier DEFAULT_SPRITE_MAIN = new SpriteIdentifier(PlayerScreenHandler.BLOCK_ATLAS_TEXTURE, new Identifier(ReFramed.MODID, "block/framed_block")); + protected static final SpriteIdentifier DEFAULT_SPRITE_SECONDARY = new SpriteIdentifier(PlayerScreenHandler.BLOCK_ATLAS_TEXTURE, new Identifier(ReFramed.MODID, "block/framed_accent_block")); + private static final SpriteIdentifier BARRIER_SPRITE_ID = new SpriteIdentifier(PlayerScreenHandler.BLOCK_ATLAS_TEXTURE, new Identifier("minecraft:item/barrier")); + private static final Cache APPEARANCE_CACHE = CacheBuilder.newBuilder().maximumSize(2048).build(); + public CamoAppearanceManager(Function spriteLookup) { MaterialFinder finder = ReFramedClient.HELPER.getFabricRenderer().materialFinder(); for(BlendMode blend : BlendMode.values()) { @@ -59,22 +65,20 @@ public class CamoAppearanceManager { sprite = spriteLookup.apply(BARRIER_SPRITE_ID); this.barrierItemAppearance = new SingleSpriteAppearance(sprite, materials.get(BlendMode.CUTOUT), serial_number.getAndIncrement()); } - - protected static final SpriteIdentifier DEFAULT_SPRITE_MAIN = new SpriteIdentifier(PlayerScreenHandler.BLOCK_ATLAS_TEXTURE, new Identifier(ReFramed.MODID, "block/framed_block")); - protected static final SpriteIdentifier DEFAULT_SPRITE_SECONDARY = new SpriteIdentifier(PlayerScreenHandler.BLOCK_ATLAS_TEXTURE, new Identifier(ReFramed.MODID, "block/framed_accent_block")); - private static final SpriteIdentifier BARRIER_SPRITE_ID = new SpriteIdentifier(PlayerScreenHandler.BLOCK_ATLAS_TEXTURE, new Identifier("minecraft:item/barrier")); private final CamoAppearance default_appearance; private final CamoAppearance accent_appearance; private final CamoAppearance barrierItemAppearance; - private static final Cache APPEARANCE_CACHE = CacheBuilder.newBuilder().maximumSize(2048).build(); - private final AtomicInteger serial_number = new AtomicInteger(0); //Mutable private final EnumMap ao_materials = new EnumMap<>(BlendMode.class); private final EnumMap materials = new EnumMap<>(BlendMode.class); //Immutable contents - + + public static void dumpCahe() { + APPEARANCE_CACHE.invalidateAll(); + } + public CamoAppearance getDefaultAppearance(int appearance) { return appearance == 2 ? accent_appearance: default_appearance; } diff --git a/src/main/java/fr/adrien1106/reframed/generator/GBlockstate.java b/src/main/java/fr/adrien1106/reframed/generator/GBlockstate.java index eaab778..f3711ed 100644 --- a/src/main/java/fr/adrien1106/reframed/generator/GBlockstate.java +++ b/src/main/java/fr/adrien1106/reframed/generator/GBlockstate.java @@ -38,7 +38,9 @@ public class GBlockstate extends FabricModelProvider { } @Override - public void generateItemModels(ItemModelGenerator itemModelGenerator) {} + public void generateItemModels(ItemModelGenerator model_generator) { + ReFramed.ITEMS.forEach(item -> model_generator.register(item, Models.GENERATED)); + } public static BlockStateVariant variant(Identifier model, boolean uv_lock, VariantSettings.Rotation x, VariantSettings.Rotation y) { BlockStateVariant variant = BlockStateVariant.create().put(VariantSettings.MODEL, model); diff --git a/src/main/java/fr/adrien1106/reframed/generator/GLanguage.java b/src/main/java/fr/adrien1106/reframed/generator/GLanguage.java index d9270fb..6ec0ca0 100644 --- a/src/main/java/fr/adrien1106/reframed/generator/GLanguage.java +++ b/src/main/java/fr/adrien1106/reframed/generator/GLanguage.java @@ -18,6 +18,7 @@ public class GLanguage extends FabricLanguageProvider { builder.add(Registries.ITEM_GROUP.getKey(ReFramed.ITEM_GROUP).get(), "Frames"); builder.add("advancements.reframed.description", "Get all the frame types."); ReFramed.BLOCKS.forEach(block -> builder.add(block, beautify(Registries.BLOCK.getId(block).getPath()) + " Frame")); + ReFramed.ITEMS.forEach(block -> builder.add(block, beautify(Registries.ITEM.getId(block).getPath()))); } private static String beautify(String name) { diff --git a/src/main/java/fr/adrien1106/reframed/generator/GRecipe.java b/src/main/java/fr/adrien1106/reframed/generator/GRecipe.java index 0e2b8c7..db5181e 100644 --- a/src/main/java/fr/adrien1106/reframed/generator/GRecipe.java +++ b/src/main/java/fr/adrien1106/reframed/generator/GRecipe.java @@ -15,5 +15,8 @@ public class GRecipe extends FabricRecipeProvider { ReFramed.BLOCKS.forEach(block -> { if (block instanceof RecipeSetter provider) provider.setRecipe(exporter); }); + ReFramed.ITEMS.forEach(item -> { + if (item instanceof RecipeSetter provider) provider.setRecipe(exporter); + }); } } diff --git a/src/main/java/fr/adrien1106/reframed/item/ReFramedBlueprintItem.java b/src/main/java/fr/adrien1106/reframed/item/ReFramedBlueprintItem.java new file mode 100644 index 0000000..735f13b --- /dev/null +++ b/src/main/java/fr/adrien1106/reframed/item/ReFramedBlueprintItem.java @@ -0,0 +1,55 @@ +package fr.adrien1106.reframed.item; + +import fr.adrien1106.reframed.ReFramed; +import fr.adrien1106.reframed.block.ReFramedEntity; +import fr.adrien1106.reframed.generator.RecipeSetter; +import net.fabricmc.fabric.api.datagen.v1.provider.FabricRecipeProvider; +import net.minecraft.block.Blocks; +import net.minecraft.data.server.recipe.RecipeExporter; +import net.minecraft.data.server.recipe.ShapedRecipeJsonBuilder; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.item.ItemUsageContext; +import net.minecraft.item.Items; +import net.minecraft.recipe.book.RecipeCategory; +import net.minecraft.sound.SoundCategory; +import net.minecraft.sound.SoundEvents; +import net.minecraft.util.ActionResult; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; + +public class ReFramedBlueprintItem extends Item implements RecipeSetter { + public ReFramedBlueprintItem(Settings settings) { + super(settings); + } + + @Override + public ActionResult useOnBlock(ItemUsageContext context) { + BlockPos pos = context.getBlockPos(); + World world = context.getWorld(); + if (!(world.getBlockEntity(pos) instanceof ReFramedEntity frame_entity) + || frame_entity.getThemes().stream().noneMatch(state -> state.getBlock() != Blocks.AIR) + ) return ActionResult.PASS; + + context.getStack().decrement(1); + ItemStack stack = ReFramed.BLUEPRINT_WRITTEN.getDefaultStack(); + frame_entity.setStackNbt(stack); + context.getPlayer().giveItemStack(stack); + world.playSound(context.getPlayer(), context.getPlayer().getBlockPos(), SoundEvents.ITEM_BOOK_PUT, SoundCategory.PLAYERS); + + return ActionResult.SUCCESS; + } + + @Override + public void setRecipe(RecipeExporter exporter) { + ShapedRecipeJsonBuilder + .create(RecipeCategory.BUILDING_BLOCKS, this, 3) + .pattern("PI") + .pattern("PP") + .input('P', Items.PAPER) + .input('I', Items.INK_SAC) + .criterion(FabricRecipeProvider.hasItem(Items.PAPER), FabricRecipeProvider.conditionsFromItem(Items.PAPER)) + .criterion(FabricRecipeProvider.hasItem(this), FabricRecipeProvider.conditionsFromItem(this)) + .offerTo(exporter); + } +} diff --git a/src/main/java/fr/adrien1106/reframed/item/ReFramedBlueprintWrittenItem.java b/src/main/java/fr/adrien1106/reframed/item/ReFramedBlueprintWrittenItem.java new file mode 100644 index 0000000..0736a50 --- /dev/null +++ b/src/main/java/fr/adrien1106/reframed/item/ReFramedBlueprintWrittenItem.java @@ -0,0 +1,113 @@ +package fr.adrien1106.reframed.item; + +import fr.adrien1106.reframed.ReFramed; +import fr.adrien1106.reframed.block.ReFramedEntity; +import net.minecraft.block.AbstractBlock; +import net.minecraft.block.Block; +import net.minecraft.block.BlockState; +import net.minecraft.block.Blocks; +import net.minecraft.client.item.TooltipContext; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.entity.player.PlayerInventory; +import net.minecraft.item.BlockItem; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.item.ItemUsageContext; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.nbt.NbtHelper; +import net.minecraft.registry.Registries; +import net.minecraft.sound.SoundCategory; +import net.minecraft.sound.SoundEvents; +import net.minecraft.text.Text; +import net.minecraft.util.ActionResult; +import net.minecraft.util.Formatting; +import net.minecraft.util.Hand; +import net.minecraft.util.TypedActionResult; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import static fr.adrien1106.reframed.block.ReFramedEntity.BLOCKSTATE_KEY; + +public class ReFramedBlueprintWrittenItem extends Item { + public ReFramedBlueprintWrittenItem(Settings settings) { + super(settings); + } + + @Override + public TypedActionResult use(World world, PlayerEntity player, Hand hand) { + ItemStack stack = player.getStackInHand(hand); + if (!player.isSneaking() || !stack.hasNbt()) return super.use(world, player, hand); + stack.decrement(1); + player.giveItemStack(ReFramed.BLUEPRINT.getDefaultStack()); + world.playSound(player, player.getBlockPos(), SoundEvents.ITEM_BOOK_PUT, SoundCategory.PLAYERS); + + return TypedActionResult.success(stack); + } + + @Override + public ActionResult useOnBlock(ItemUsageContext context) { + BlockPos pos = context.getBlockPos(); + World world = context.getWorld(); + if (!(world.getBlockEntity(pos) instanceof ReFramedEntity frame_entity) + || frame_entity.getThemes().stream().anyMatch(state -> state.getBlock() != Blocks.AIR) + || !context.getStack().hasNbt() + ) return ActionResult.PASS; + + NbtCompound tag = BlockItem.getBlockEntityNbt(context.getStack()); + if(tag == null) return ActionResult.FAIL; + + PlayerEntity player = context.getPlayer(); + if (!player.isCreative()) { // verify player has blocks and remove them + PlayerInventory inventory = player.getInventory(); + List stacks = getBlockStates(tag).values().stream() + .map(AbstractBlock.AbstractBlockState::getBlock) + .map(Block::asItem) + .map(Item::getDefaultStack) + .toList(); + if (stacks.stream().anyMatch(stack -> !inventory.contains(stack))) + return ActionResult.FAIL; + stacks.stream().map(inventory::getSlotWithStack).forEach(index -> inventory.removeStack(index, 1)); + player.playSound(SoundEvents.ENTITY_ITEM_PICKUP, 0.5f, 0.5f); + } + frame_entity.readNbt(tag); + world.playSound(player, player.getBlockPos(), SoundEvents.ITEM_BOOK_PAGE_TURN, SoundCategory.PLAYERS); + + return ActionResult.SUCCESS; + } + + @Override + public void appendTooltip(ItemStack stack, @Nullable World world, List tooltip, TooltipContext context) { + NbtCompound tag = BlockItem.getBlockEntityNbt(stack); + if(tag == null) return; + + Map states = getBlockStates(tag); + states.forEach((index, state) -> tooltip.add( + Text.literal("Theme " + index + ": ") + .append( + Text.translatable(state.getBlock().getTranslationKey()) + .formatted(Formatting.GRAY) + ) + )); + super.appendTooltip(stack, world, tooltip, context); + } + + private static Map getBlockStates(NbtCompound tag) { + return tag.getKeys().stream() + .filter(key -> + key.startsWith(BLOCKSTATE_KEY) + && key.replace(BLOCKSTATE_KEY,"").chars().allMatch(Character::isDigit) + ) + .collect(Collectors.toMap( + key -> Integer.parseInt(key.substring(BLOCKSTATE_KEY.length())), + key -> NbtHelper.toBlockState( + Registries.BLOCK.getReadOnlyWrapper(), + tag.getCompound(key) + ) + )); + } +} diff --git a/src/main/java/fr/adrien1106/reframed/item/ReFramedHammerItem.java b/src/main/java/fr/adrien1106/reframed/item/ReFramedHammerItem.java new file mode 100644 index 0000000..9facbd0 --- /dev/null +++ b/src/main/java/fr/adrien1106/reframed/item/ReFramedHammerItem.java @@ -0,0 +1,70 @@ +package fr.adrien1106.reframed.item; + +import fr.adrien1106.reframed.ReFramed; +import fr.adrien1106.reframed.block.ReFramedDoubleBlock; +import fr.adrien1106.reframed.generator.RecipeSetter; +import fr.adrien1106.reframed.util.blocks.ThemeableBlockEntity; +import net.fabricmc.fabric.api.datagen.v1.provider.FabricRecipeProvider; +import net.minecraft.block.BlockState; +import net.minecraft.block.Blocks; +import net.minecraft.data.server.recipe.RecipeExporter; +import net.minecraft.data.server.recipe.ShapedRecipeJsonBuilder; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.item.ItemUsageContext; +import net.minecraft.item.Items; +import net.minecraft.recipe.book.RecipeCategory; +import net.minecraft.sound.SoundCategory; +import net.minecraft.sound.SoundEvents; +import net.minecraft.util.ActionResult; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; + +public class ReFramedHammerItem extends Item implements RecipeSetter { + public ReFramedHammerItem(Settings settings) { + super(settings); + } + + @Override + public ActionResult useOnBlock(ItemUsageContext context) { + World world = context.getWorld(); + BlockPos pos = context.getBlockPos(); + if (!(world.getBlockEntity(pos) instanceof ThemeableBlockEntity frame_entity)) return ActionResult.PASS; + BlockState state = world.getBlockState(pos); + PlayerEntity player = context.getPlayer(); + int theme_index = state.getBlock() instanceof ReFramedDoubleBlock b + ? b.getHitShape( + state, + context.getHitPos(), + context.getBlockPos(), + context.getSide() + ) + : 1; + + if (frame_entity.getTheme(theme_index).getBlock() == Blocks.AIR) return ActionResult.PASS; + + if (!player.isCreative()) { + player.giveItemStack(new ItemStack(frame_entity.getTheme(theme_index).getBlock())); + world.playSound(player, pos, SoundEvents.ENTITY_ITEM_PICKUP, SoundCategory.BLOCKS, 1f, 1.1f); + } + frame_entity.setTheme(Blocks.AIR.getDefaultState(), theme_index); + ReFramed.chunkRerenderProxy.accept(world, pos); + return ActionResult.SUCCESS; + } + + @Override + public void setRecipe(RecipeExporter exporter) { + ShapedRecipeJsonBuilder + .create(RecipeCategory.BUILDING_BLOCKS, this) + .pattern(" CI") + .pattern(" ~C") + .pattern("~ ") + .input('I', Items.IRON_INGOT) + .input('C', ReFramed.CUBE) + .input('~', Items.STICK) + .criterion(FabricRecipeProvider.hasItem(ReFramed.CUBE), FabricRecipeProvider.conditionsFromItem(ReFramed.CUBE)) + .criterion(FabricRecipeProvider.hasItem(this), FabricRecipeProvider.conditionsFromItem(this)) + .offerTo(exporter); + } +} diff --git a/src/main/java/fr/adrien1106/reframed/item/ReFramedScrewdriverItem.java b/src/main/java/fr/adrien1106/reframed/item/ReFramedScrewdriverItem.java new file mode 100644 index 0000000..b820578 --- /dev/null +++ b/src/main/java/fr/adrien1106/reframed/item/ReFramedScrewdriverItem.java @@ -0,0 +1,78 @@ +package fr.adrien1106.reframed.item; + +import fr.adrien1106.reframed.ReFramed; +import fr.adrien1106.reframed.block.ReFramedDoubleBlock; +import fr.adrien1106.reframed.generator.RecipeSetter; +import fr.adrien1106.reframed.util.blocks.ThemeableBlockEntity; +import net.fabricmc.fabric.api.datagen.v1.provider.FabricRecipeProvider; +import net.minecraft.block.BlockState; +import net.minecraft.data.server.recipe.RecipeExporter; +import net.minecraft.data.server.recipe.ShapedRecipeJsonBuilder; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.Item; +import net.minecraft.item.ItemUsageContext; +import net.minecraft.item.Items; +import net.minecraft.recipe.book.RecipeCategory; +import net.minecraft.sound.BlockSoundGroup; +import net.minecraft.sound.SoundCategory; +import net.minecraft.state.property.Properties; +import net.minecraft.util.ActionResult; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Direction; +import net.minecraft.world.World; + +public class ReFramedScrewdriverItem extends Item implements RecipeSetter { + + public ReFramedScrewdriverItem(Settings settings) { + super(settings); + } + + @Override + public ActionResult useOnBlock(ItemUsageContext context) { + World world = context.getWorld(); + BlockPos pos = context.getBlockPos(); + if (!(world.getBlockEntity(pos) instanceof ThemeableBlockEntity frame_entity)) return ActionResult.PASS; + BlockState state = world.getBlockState(pos); + PlayerEntity player = context.getPlayer(); + int theme_index = state.getBlock() instanceof ReFramedDoubleBlock b + ? b.getHitShape( + state, + context.getHitPos(), + context.getBlockPos(), + context.getSide() + ) + : 1; + + + BlockState theme = frame_entity.getTheme(theme_index); + if (!theme.contains(Properties.AXIS)) return ActionResult.PASS; + + Direction.Axis axis = theme.get(Properties.AXIS); + BlockSoundGroup group = theme.getSoundGroup(); + world.playSound(player, pos, group.getPlaceSound(), SoundCategory.BLOCKS, group.getVolume(), group.getPitch()); + frame_entity.setTheme(theme.with( + Properties.AXIS, + switch (axis) { + case X -> Direction.Axis.Y; + case Y -> Direction.Axis.Z; + case Z -> Direction.Axis.X; + } + ), theme_index); + ReFramed.chunkRerenderProxy.accept(world, pos); + return ActionResult.SUCCESS; + } + + @Override + public void setRecipe(RecipeExporter exporter) { + ShapedRecipeJsonBuilder + .create(RecipeCategory.BUILDING_BLOCKS, this) + .pattern(" I") + .pattern(" I ") + .pattern("C ") + .input('I', Items.IRON_INGOT) + .input('C', ReFramed.CUBE) + .criterion(FabricRecipeProvider.hasItem(ReFramed.CUBE), FabricRecipeProvider.conditionsFromItem(ReFramed.CUBE)) + .criterion(FabricRecipeProvider.hasItem(this), FabricRecipeProvider.conditionsFromItem(this)) + .offerTo(exporter); + } +} diff --git a/src/main/java/fr/adrien1106/reframed/util/blocks/BlockHelper.java b/src/main/java/fr/adrien1106/reframed/util/blocks/BlockHelper.java index df7037d..daaf5ad 100644 --- a/src/main/java/fr/adrien1106/reframed/util/blocks/BlockHelper.java +++ b/src/main/java/fr/adrien1106/reframed/util/blocks/BlockHelper.java @@ -167,8 +167,8 @@ public class BlockHelper { public static ActionResult useCamo(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockHitResult hit, int theme_index) { if(!(world.getBlockEntity(pos) instanceof ReFramedEntity block_entity)) return ActionResult.PASS; - // Changing the theme ItemStack held = player.getStackInHand(hand); + // Changing the theme if(held.getItem() instanceof BlockItem block_item && block_entity.getTheme(theme_index).getBlock() == Blocks.AIR) { Block block = block_item.getBlock(); ItemPlacementContext ctx = new ItemPlacementContext(new ItemUsageContext(player, hand, hit)); diff --git a/src/main/resources/assets/reframed/textures/item/blueprint.png b/src/main/resources/assets/reframed/textures/item/blueprint.png new file mode 100644 index 0000000000000000000000000000000000000000..2e15ac47a499f5578c4432e6a80d6d54e755ad57 GIT binary patch literal 5188 zcmeHKc~}!?6OYPaK@=>PiX{XQ6gSyi3%2Bc2S|}8XCt?Lf)KXMLM2_Np^KBwt&)0u^p0EFzWRu-_XMQvDn|WumnSuTs z6Qk)y7!1aQ>&p&Ce@(T=L<98O_Team!RYalLc&zRkQysj$b{lp1glDvBUnTu7Gf}( zYlBOcZ@TX^>9xq>px&$z({neMQs-~G)7sFjDjBNdR=kuohhU6NIn1f_^3jJ+!L>Ib zqu7$%>A@2%bN%pfKAgd%2SEk#oZ6KGoySkNNK)*k9{PC&b>R8lk^%3pkw>pean-AK zh8u3W{^GHR*?yndyE|pegRW)$rvoF=Pb@;bE@@<*_bMfO9Zn<*L;d&HxYu$t`x6K3 z&qw&qi?%$9IsGa>Kii)TkQ~R8UaYuOag;>wadz*VAJUt@WqEF0zIz@HI%A@@M3lb# zWm(Cq)YZ5xA9D-w-^KID1vy%=vcQ_rX#^8VJ`XBEPM0*-*RL z!aw8x^2&`^)t`uMPoG44HPL?ZOmWYQ7=dN=j23Q$^OCn+q#o=|+*f;}SZ(!ciMa{qU?ZX- zv)y|x9km%3(Z4*prB%Mjx^b(Q+18;$mroe_c~?E?Kbp9;J%5V+o{GW7Z|skm+b)0j zXFaW&GNZ7|GG^4&s_jZ|{0f=Fkd@`y?ViSg!vh82~rFV<}HaXs5ztW(z$8pIH z(>K#<64yUN>U*B-ExH=`Mv!RYCYlvfV4 z(eY12_Ln>5eM}5yT$~1V8#)vb|LW zb1fS@#mQa~8y9CDZag`8Ug*s5lK6PJg`8c%gsL?k@lsBD1NiPE!GiX1w7 zp5?MIDHKWxxy8Lzvpae-K@c8Fm&JwpSzI~hxE2EU0(+17D@}>>a5;MH(?3Q|a0tt| zSdq`iabE^a8BMtzkmonM!@!!+8*|> zra-V1JOvK|-WqWN3Fl^nbyWz2%wV?97zBF7!bPc6awdVGR;%%9GG3+-5r_;1g8-5U zBocsX0LnzE3eo^lrM(v7Ee0D=!V0lmC6-CCT1<#9i&wF5ICLHRE*GDTTrZiF61CV30%yD0~+_08xknfJ_91 z5Q#!3lS%xKptw?{3X;Nz777K&i%}dpLP4lhG6aA^A{C$rUUAPAsQ=u`$Dq`Hux zk0AmSVzeuv*pH*qLJ3eP1{o&OXbc3PK_CQBU;!DRyHH60Sx6Gn5gJ6Mf@EDZ0+{I| zQ%E4RoMH(iLI`rHNVlLRoaqtBW#LG8@Pi~U7E%dO19T6Fr2?5+`C%zUEI}5lAT6In z7ZS|{B%}RFaUr_6&^{=IA_^tii&|77h$m5WE84;^(R5I1A#JCk06ICE4bw}3Kq{Fc zL?(-6;j}?vwU)XD$GW~Pi?3LTY9wlVeyr;kBXMu9-fn_ev2F>A)io{?g5Nq(LJ5dK z=LofXy97r;QW1jg@UfP9Cl~)ixyV!!NkAhC0R~?v04QWW8DO|D(3;WcX#GeuP(Y`D zimsFiRcc6qc!J&a20;t_v89H3hSw(n1tj74#Qur^v#_I4ddO)GSIQcAoztHuCuFqoN zvy{Jd*B81zi-FHl{?c9lH@b{IJnkS;^dFEKeV92v!*2k65HjHVaoCtq?R#hK-U3we zmE3o!5`!`RT6>Jc6qi_|LPHgo=WTe$aHiG#`IEcq=c1xUF54r7e(v(71171UsrL>u zzZACD-8naoL1s)N;V#&0mSr3VK9e0c$7WVwJ=Y0$M_u>u<&ohTZ#J;oO54y z-Lg=BK@+3w$(k+W$5rXW{FYVLoU7Y=_ARdL58Gc6)faB+WU(OONodTG+m`!Sk=!x;Sw`e*9W?1q$5{j;yxsj7&4%7|Mzagv!E_vW>xRediXAD{azu6a-9 yQ?M1}-5rQ_5(O7`%|p7n`+9foW6XlqWxZ7`RZr1$FkEkc_D`OX>;DTRAJG2* literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/reframed/textures/item/blueprint_written.png b/src/main/resources/assets/reframed/textures/item/blueprint_written.png new file mode 100644 index 0000000000000000000000000000000000000000..1d57634c73c6918ac13d64706c97e51e3969a80f GIT binary patch literal 5475 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 SV~ueD0000