diff --git a/.gitignore b/.gitignore
index 54a7985..1d52742 100755
--- a/.gitignore
+++ b/.gitignore
@@ -33,3 +33,4 @@ local.properties
*-autosave.kra
*.kra~
/src/generated/
+/libs/
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/build.gradle b/build.gradle
index 5799aee..d9637fb 100755
--- a/build.gradle
+++ b/build.gradle
@@ -110,11 +110,15 @@ dependencies {
modCompileOnly "earth.terrarium.athena:athena-fabric-${project.minecraft_version}:${project.athena_version}"
modRuntimeOnly "earth.terrarium.athena:athena-fabric-${project.minecraft_version}:${project.athena_version}"
+ // Continuity for connectedTextures
+ modCompileOnly "maven.modrinth:continuity:${project.continuity_version}"
+ modRuntimeOnly "maven.modrinth:continuity:${project.continuity_version}"
+
// Chipped to test athena implementation
modRuntimeOnly "com.teamresourceful.resourcefullib:resourcefullib-fabric-${project.minecraft_version}:2.4.7"
modRuntimeOnly "earth.terrarium.chipped:Chipped-fabric-${project.minecraft_version}:3.1.2"
- // Fabric API. This is technically optional, but you probably want it anyway.
+ // Fabric API.
modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}"
}
@@ -126,6 +130,7 @@ processResources {
inputs.property "athena_version", project.athena_version
inputs.property "indium_version", project.indium_version
inputs.property "sodium_version", project.sodium_version
+ inputs.property "continuity_version", project.continuity_version
filteringCharset "UTF-8"
filesMatching("fabric.mod.json") {
diff --git a/gradle.properties b/gradle.properties
index 77a6cd7..d530df0 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.6
maven_group = fr.adrien1106
archives_base_name = ReFramed
mod_id = reframed
@@ -24,3 +24,4 @@ git_repo=ReFramed
athena_version=3.3.0
sodium_version=0.5.8
indium_version=1.0.30
+continuity_version=3.0.0-beta.4+1.20.2
diff --git a/src/main/java/fr/adrien1106/reframed/ReFramed.java b/src/main/java/fr/adrien1106/reframed/ReFramed.java
index 8886e19..691fe87 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,23 +23,26 @@ 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 Dynamic Ambient Occlusion -> for v1.6
* TODO add minecraft models like wall fence etc -> for v1.6
* TODO better connected textures -> maybe v1.6 ?
+ * TODO support continuity overlays -> not scheduled
*/
public class ReFramed implements ModInitializer {
public static final String MODID = "reframed";
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 +52,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 +90,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 +108,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/DynamicBakedModel.java b/src/main/java/fr/adrien1106/reframed/client/model/DynamicBakedModel.java
index 2f4d109..cb81daf 100644
--- a/src/main/java/fr/adrien1106/reframed/client/model/DynamicBakedModel.java
+++ b/src/main/java/fr/adrien1106/reframed/client/model/DynamicBakedModel.java
@@ -4,7 +4,8 @@ import net.minecraft.block.BlockState;
import net.minecraft.client.render.model.BakedModel;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.BlockRenderView;
+import org.jetbrains.annotations.Nullable;
public interface DynamicBakedModel {
- BakedModel computeQuads(BlockRenderView level, BlockState state, BlockPos pos, int theme_index);
+ BakedModel computeQuads(@Nullable BlockRenderView level, BlockState origin_state, @Nullable BlockPos pos, int theme_index);
}
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..e30fe39 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
@@ -6,6 +6,7 @@ import fr.adrien1106.reframed.ReFramed;
import fr.adrien1106.reframed.client.ReFramedClient;
import fr.adrien1106.reframed.client.model.DynamicBakedModel;
import fr.adrien1106.reframed.client.model.QuadPosBounds;
+import fr.adrien1106.reframed.compat.RebakedModel;
import fr.adrien1106.reframed.mixin.model.WeightedBakedModelAccessor;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
@@ -39,6 +40,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 +66,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;
}
@@ -82,14 +87,18 @@ public class CamoAppearanceManager {
public CamoAppearance getCamoAppearance(BlockRenderView world, BlockState state, BlockPos pos, int theme_index, boolean item) {
BakedModel model = MinecraftClient.getInstance().getBlockRenderManager().getModel(state);
- // add support for connected textures and more generally any compatible models injected so that they return baked quads
+ // add support for connected textures that uses dynamic baking
if (model instanceof DynamicBakedModel dynamic_model) {
// cache items as they get rendered more often
if (item && APPEARANCE_CACHE.asMap().containsKey(state)) return APPEARANCE_CACHE.getIfPresent(state);
- CamoAppearance appearance = computeAppearance(dynamic_model.computeQuads(world, state, pos, theme_index), state);
- if (item) APPEARANCE_CACHE.put(state, appearance);
- return appearance;
+ model = dynamic_model.computeQuads(world, state, pos, theme_index);
+ // if model isn't rebaked its just wrapped (i.e. not dynamic and may be cached)
+ if (model instanceof RebakedModel) {
+ CamoAppearance appearance = computeAppearance(model, state);
+ if (item) APPEARANCE_CACHE.put(state, appearance);
+ return appearance;
+ }
}
// refresh cache
diff --git a/src/main/java/fr/adrien1106/reframed/client/util/RenderHelper.java b/src/main/java/fr/adrien1106/reframed/client/util/RenderHelper.java
new file mode 100644
index 0000000..4df304b
--- /dev/null
+++ b/src/main/java/fr/adrien1106/reframed/client/util/RenderHelper.java
@@ -0,0 +1,177 @@
+package fr.adrien1106.reframed.client.util;
+
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheBuilder;
+import fr.adrien1106.reframed.block.ReFramedBlock;
+import fr.adrien1106.reframed.client.ReFramedClient;
+import fr.adrien1106.reframed.client.model.QuadPosBounds;
+import fr.adrien1106.reframed.util.blocks.ThemeableBlockEntity;
+import net.fabricmc.api.EnvType;
+import net.fabricmc.api.Environment;
+import net.fabricmc.fabric.api.renderer.v1.Renderer;
+import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial;
+import net.fabricmc.fabric.api.renderer.v1.mesh.QuadEmitter;
+import net.fabricmc.fabric.api.renderer.v1.model.ForwardingBakedModel;
+import net.minecraft.block.Block;
+import net.minecraft.block.BlockState;
+import net.minecraft.util.function.BooleanBiFunction;
+import net.minecraft.util.math.BlockPos;
+import net.minecraft.util.math.Direction;
+import net.minecraft.util.math.random.Random;
+import net.minecraft.util.shape.VoxelShape;
+import net.minecraft.util.shape.VoxelShapes;
+import net.minecraft.world.BlockRenderView;
+import net.minecraft.world.BlockView;
+
+import java.util.List;
+import java.util.Objects;
+
+import static net.minecraft.util.shape.VoxelShapes.combine;
+
+@Environment(EnvType.CLIENT)
+public class RenderHelper {
+
+
+ // self culling cache of the models not made thread local so that it is only computed once
+ private static final Cache INNER_CULL_MAP = CacheBuilder.newBuilder().maximumSize(1024).build();
+ private record CullElement(Block block, Object state_key, int model) {}
+
+ /**
+ * compute which quad might cull with another model quad
+ * @param state - the state of the model
+ * @param models - list of models on the same block
+ */
+ public static void computeInnerCull(BlockState state, List models) {
+ if (!(state.getBlock() instanceof ReFramedBlock frame_block)) return;
+ Object key = frame_block.getModelCacheKey(state);
+ if (INNER_CULL_MAP.asMap().containsKey(new CullElement(frame_block, key, 1))) return;
+
+ Renderer r = ReFramedClient.HELPER.getFabricRenderer();
+ QuadEmitter quad_emitter = r.meshBuilder().getEmitter();
+ RenderMaterial material = r.materialFinder().clear().find();
+ Random random = Random.create();
+
+ List
> model_bounds = models.stream()
+ .map(ForwardingBakedModel::getWrappedModel)
+ .filter(Objects::nonNull)
+ .map(wrapped -> wrapped.getQuads(state, null, random))
+ .map(quads -> quads.stream().map(quad -> {
+ quad_emitter.fromVanilla(quad, material, null);
+ return QuadPosBounds.read(quad_emitter, false);
+ }).toList()).toList();
+
+ Integer[] cull_array;
+ for(int self_id = 1; self_id <= model_bounds.size(); self_id++) {
+ List self_bounds = model_bounds.get(self_id - 1);
+ cull_array = new Integer[self_bounds.size()];
+ for (int self_quad = 0; self_quad < cull_array.length; self_quad++) {
+ QuadPosBounds self_bound = self_bounds.get(self_quad);
+ for(int other_id = 1; other_id <= model_bounds.size(); other_id++) {
+ if (other_id == self_id) continue;
+ if (model_bounds.get(other_id - 1).stream().anyMatch(other_bound -> other_bound.equals(self_bound))) {
+ cull_array[self_quad] = other_id;
+ break;
+ }
+ }
+ }
+ INNER_CULL_MAP.put(new CullElement(frame_block, key, self_id), cull_array);
+ }
+ }
+
+ public static boolean shouldDrawInnerFace(BlockState state, BlockRenderView view, BlockPos pos, int quad_index, int theme_index) {
+ if ( !(state.getBlock() instanceof ReFramedBlock frame_block)
+ || !(view.getBlockEntity(pos) instanceof ThemeableBlockEntity frame_entity)
+ ) return true;
+ CullElement key = new CullElement(frame_block, frame_block.getModelCacheKey(state), theme_index);
+ if (!INNER_CULL_MAP.asMap().containsKey(key)) return true;
+
+ // needs to be Integer object because array is initialized with null not 0
+ Integer cull_theme = Objects.requireNonNull(INNER_CULL_MAP.getIfPresent(key))[quad_index];
+ if (cull_theme == null) return true; // no culling possible
+
+ BlockState self_theme = frame_entity.getTheme(theme_index);
+ BlockState other_theme = frame_entity.getTheme(cull_theme);
+
+ if (self_theme.isSideInvisible(other_theme, null)) return false;
+ return !self_theme.isOpaque() || !other_theme.isOpaque();
+ }
+
+ // Doing this method from scratch as it is simpler to do than injecting everywhere
+ public static boolean shouldDrawSide(BlockState self_state, BlockView world, BlockPos pos, Direction side, BlockPos other_pos, int theme_index) {
+ ThemeableBlockEntity self = world.getBlockEntity(pos) instanceof ThemeableBlockEntity e ? e : null;
+ ThemeableBlockEntity other = world.getBlockEntity(other_pos) instanceof ThemeableBlockEntity e ? e : null;
+ BlockState other_state = world.getBlockState(other_pos);
+
+ // normal behaviour
+ if (self == null && other == null) return Block.shouldDrawSide(self_state, world, pos, side, other_pos);
+
+ // self is a normal Block
+ if (self == null && other_state.getBlock() instanceof ReFramedBlock other_block) {
+ VoxelShape self_shape = self_state.getCullingShape(world, pos);
+ if (self_shape.isEmpty()) return true;
+
+ int i = 0;
+ VoxelShape other_shape = VoxelShapes.empty();
+ for (BlockState s: other.getThemes()) {
+ i++;
+ if (self_state.isSideInvisible(s, side) || s.isOpaque())
+ other_shape = combine(
+ other_shape,
+ other_block
+ .getShape(other_state, i)
+ .getFace(side.getOpposite()),
+ BooleanBiFunction.OR
+ );
+ }
+
+ // determine if side needs to be rendered
+ return VoxelShapes.matchesAnywhere(self_shape, other_shape, BooleanBiFunction.ONLY_FIRST);
+ }
+
+ BlockState self_theme = self.getTheme(theme_index);
+ // other is normal Block
+ if (other == null && self_state.getBlock() instanceof ReFramedBlock self_block) {
+ // Transparent is simple if self and the neighbor are invisible don't render side (like default)
+ if (self_theme.isSideInvisible(other_state, side)) return false;
+
+ // Opaque is also simple as each model are rendered one by one
+ if (other_state.isOpaque()) {
+ // no cache section :( because it differs between each instance of the frame
+ VoxelShape self_shape = self_block.getShape(self_state, theme_index).getFace(side);
+ if (self_shape.isEmpty()) return true;
+ VoxelShape other_shape = other_state.getCullingFace(world, other_pos, side.getOpposite());
+
+ // determine if side needs to be rendered
+ return VoxelShapes.matchesAnywhere(self_shape, other_shape, BooleanBiFunction.ONLY_FIRST);
+ }
+
+ return true;
+ }
+
+ // Both are frames
+ // here both are computed in the same zone as there will necessarily a shape comparison
+ if (self_state.getBlock() instanceof ReFramedBlock self_block && other_state.getBlock() instanceof ReFramedBlock other_block) {
+ VoxelShape self_shape = self_block.getShape(self_state, theme_index).getFace(side);
+ if (self_shape.isEmpty()) return true;
+
+ int i = 0;
+ VoxelShape other_shape = VoxelShapes.empty();
+ for (BlockState s: other.getThemes()) {
+ i++;
+ if (self_theme.isSideInvisible(s, side) || s.isOpaque())
+ other_shape = combine(
+ other_shape,
+ other_block
+ .getShape(other_state, i)
+ .getFace(side.getOpposite()),
+ BooleanBiFunction.OR
+ );
+ }
+
+ // determine if side needs to be rendered
+ return VoxelShapes.matchesAnywhere(self_shape, other_shape, BooleanBiFunction.ONLY_FIRST);
+ }
+
+ return true;
+ }
+}
diff --git a/src/main/java/fr/adrien1106/reframed/compat/ICTMQuadTransform.java b/src/main/java/fr/adrien1106/reframed/compat/ICTMQuadTransform.java
new file mode 100644
index 0000000..9a0e24d
--- /dev/null
+++ b/src/main/java/fr/adrien1106/reframed/compat/ICTMQuadTransform.java
@@ -0,0 +1,22 @@
+package fr.adrien1106.reframed.compat;
+
+import me.pepperbell.continuity.client.model.QuadProcessors;
+import me.pepperbell.continuity.impl.client.ProcessingContextImpl;
+import net.fabricmc.fabric.api.renderer.v1.render.RenderContext;
+import net.minecraft.block.BlockState;
+import net.minecraft.client.texture.Sprite;
+import net.minecraft.util.math.BlockPos;
+import net.minecraft.util.math.random.Random;
+import net.minecraft.world.BlockRenderView;
+
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+public interface ICTMQuadTransform extends RenderContext.QuadTransform {
+
+ void invokePrepare(BlockRenderView view, BlockState state, BlockPos pos, Supplier random, boolean manual_culling, Function slice);
+
+ ProcessingContextImpl getProcessingContext();
+
+ void invokeReset();
+}
diff --git a/src/main/java/fr/adrien1106/reframed/compat/RebakedAthenaModel.java b/src/main/java/fr/adrien1106/reframed/compat/RebakedModel.java
similarity index 85%
rename from src/main/java/fr/adrien1106/reframed/compat/RebakedAthenaModel.java
rename to src/main/java/fr/adrien1106/reframed/compat/RebakedModel.java
index f0741ac..cb5c54c 100644
--- a/src/main/java/fr/adrien1106/reframed/compat/RebakedAthenaModel.java
+++ b/src/main/java/fr/adrien1106/reframed/compat/RebakedModel.java
@@ -12,10 +12,10 @@ import net.minecraft.util.math.random.Random;
import java.util.List;
import java.util.Map;
-public class RebakedAthenaModel implements BakedModel {
+public class RebakedModel implements BakedModel {
protected final Map> face_quads;
- public RebakedAthenaModel(Map> face_quads) {
+ public RebakedModel(Map> face_quads) {
this.face_quads = face_quads;
}
@@ -26,7 +26,7 @@ public class RebakedAthenaModel implements BakedModel {
@Override
public boolean useAmbientOcclusion() {
- return false;
+ return true;
}
@Override
@@ -51,11 +51,11 @@ public class RebakedAthenaModel implements BakedModel {
@Override
public ModelTransformation getTransformation() {
- return null;
+ return ModelTransformation.NONE;
}
@Override
public ModelOverrideList getOverrides() {
- return null;
+ return ModelOverrideList.EMPTY;
}
}
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/mixin/CompatMixinPlugin.java b/src/main/java/fr/adrien1106/reframed/mixin/CompatMixinPlugin.java
index a0a36a5..2530838 100644
--- a/src/main/java/fr/adrien1106/reframed/mixin/CompatMixinPlugin.java
+++ b/src/main/java/fr/adrien1106/reframed/mixin/CompatMixinPlugin.java
@@ -7,6 +7,7 @@ import org.slf4j.LoggerFactory;
import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin;
import org.spongepowered.asm.mixin.extensibility.IMixinInfo;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -16,24 +17,27 @@ public class CompatMixinPlugin implements IMixinConfigPlugin {
private static final FabricLoader LOADER = FabricLoader.getInstance();
private static final Logger LOGGER = LoggerFactory.getLogger("ReFramed MIXIN");
- private static final List COMPAT_MOD = List.of("athena", "indium", "sodium");
- private static final Map> CONDITIONS = Map.of(
- "fr.adrien1106.reframed.mixin.compat.AthenaBakedModelMixin", () -> LOADER.isModLoaded(COMPAT_MOD.get(0)),
- "fr.adrien1106.reframed.mixin.compat.AthenaWrappedGetterMixin", () -> LOADER.isModLoaded(COMPAT_MOD.get(0)),
- "fr.adrien1106.reframed.mixin.render.TerrainRenderContextMixin", () -> !LOADER.isModLoaded(COMPAT_MOD.get(1)),
- "fr.adrien1106.reframed.mixin.render.BlockRenderInfoMixin", () -> !LOADER.isModLoaded(COMPAT_MOD.get(1)),
- "fr.adrien1106.reframed.mixin.render.AbstractBlockRenderContextMixin", () -> !LOADER.isModLoaded(COMPAT_MOD.get(1)),
- "fr.adrien1106.reframed.mixin.compat.IndiumTerrainRenderContextMixin", () -> LOADER.isModLoaded(COMPAT_MOD.get(1)),
- "fr.adrien1106.reframed.mixin.compat.IndiumTerrainBlockRenderInfoMixin", () -> LOADER.isModLoaded(COMPAT_MOD.get(1)),
- "fr.adrien1106.reframed.mixin.compat.IndiumAbstractBlockRenderContextMixin", () -> LOADER.isModLoaded(COMPAT_MOD.get(1)),
- "fr.adrien1106.reframed.mixin.compat.SodiumBlockOcclusionCacheMixin", () -> LOADER.isModLoaded(COMPAT_MOD.get(2))
- );
+ private static final List COMPAT_MOD = List.of("athena", "indium", "sodium", "special-model-loader", "continuity");
+ private static final Map> CONDITIONS = new HashMap<>();
+ static {
+ CONDITIONS.put("fr.adrien1106.reframed.mixin.compat.AthenaBakedModelMixin", () -> LOADER.isModLoaded(COMPAT_MOD.get(0)));
+ CONDITIONS.put("fr.adrien1106.reframed.mixin.compat.AthenaWrappedGetterMixin", () -> LOADER.isModLoaded(COMPAT_MOD.get(0)));
+ CONDITIONS.put("fr.adrien1106.reframed.mixin.render.TerrainRenderContextMixin", () -> !LOADER.isModLoaded(COMPAT_MOD.get(1)));
+ CONDITIONS.put("fr.adrien1106.reframed.mixin.render.BlockRenderInfoMixin", () -> !LOADER.isModLoaded(COMPAT_MOD.get(1)));
+ CONDITIONS.put("fr.adrien1106.reframed.mixin.render.AbstractBlockRenderContextMixin", () -> !LOADER.isModLoaded(COMPAT_MOD.get(1)));
+ CONDITIONS.put("fr.adrien1106.reframed.mixin.compat.IndiumTerrainRenderContextMixin", () -> LOADER.isModLoaded(COMPAT_MOD.get(1)));
+ CONDITIONS.put("fr.adrien1106.reframed.mixin.compat.IndiumTerrainBlockRenderInfoMixin", () -> LOADER.isModLoaded(COMPAT_MOD.get(1)));
+ CONDITIONS.put("fr.adrien1106.reframed.mixin.compat.IndiumAbstractBlockRenderContextMixin", () -> LOADER.isModLoaded(COMPAT_MOD.get(1)));
+ CONDITIONS.put("fr.adrien1106.reframed.mixin.compat.SodiumBlockOcclusionCacheMixin", () -> LOADER.isModLoaded(COMPAT_MOD.get(2)));
+ CONDITIONS.put("fr.adrien1106.reframed.mixin.compat.ContinuityConnectionPredicateMixin", () -> LOADER.isModLoaded(COMPAT_MOD.get(4)));
+ CONDITIONS.put("fr.adrien1106.reframed.mixin.compat.ContinuityCTMBakedModelMixin", () -> LOADER.isModLoaded(COMPAT_MOD.get(4)));
+ CONDITIONS.put("fr.adrien1106.reframed.mixin.compat.ContinuityCTMQuadTransformMixin", () -> LOADER.isModLoaded(COMPAT_MOD.get(4)));
+ CONDITIONS.put("fr.adrien1106.reframed.mixin.compat.ContinuityModelWrappingHandlerMixin", () -> LOADER.isModLoaded(COMPAT_MOD.get(4)));
+ }
@Override
- public void onLoad(String mixin_package) {
-
- }
+ public void onLoad(String mixin_package) {}
@Override
public String getRefMapperConfig() {
@@ -46,9 +50,7 @@ public class CompatMixinPlugin implements IMixinConfigPlugin {
}
@Override
- public void acceptTargets(Set mine, Set others) {
-
- }
+ public void acceptTargets(Set mine, Set others) {}
@Override
public List getMixins() {
@@ -56,9 +58,7 @@ public class CompatMixinPlugin implements IMixinConfigPlugin {
}
@Override
- public void preApply(String target_class_name, ClassNode target_class, String mixin_class_name, IMixinInfo mixin_info) {
-
- }
+ public void preApply(String target_class_name, ClassNode target_class, String mixin_class_name, IMixinInfo mixin_info) {}
@Override
public void postApply(String target_class, ClassNode target, String mixin_class, IMixinInfo mixin_info) {
diff --git a/src/main/java/fr/adrien1106/reframed/mixin/compat/AthenaBakedModelMixin.java b/src/main/java/fr/adrien1106/reframed/mixin/compat/AthenaBakedModelMixin.java
index 2bbcb90..20bd2f4 100644
--- a/src/main/java/fr/adrien1106/reframed/mixin/compat/AthenaBakedModelMixin.java
+++ b/src/main/java/fr/adrien1106/reframed/mixin/compat/AthenaBakedModelMixin.java
@@ -5,7 +5,7 @@ import earth.terrarium.athena.api.client.fabric.WrappedGetter;
import earth.terrarium.athena.api.client.models.AthenaBlockModel;
import fr.adrien1106.reframed.client.ReFramedClient;
import fr.adrien1106.reframed.client.model.DynamicBakedModel;
-import fr.adrien1106.reframed.compat.RebakedAthenaModel;
+import fr.adrien1106.reframed.compat.RebakedModel;
import fr.adrien1106.reframed.util.blocks.ThemeableBlockEntity;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import net.fabricmc.fabric.api.renderer.v1.Renderer;
@@ -18,6 +18,7 @@ import net.minecraft.client.texture.Sprite;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.world.BlockRenderView;
+import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
@@ -35,27 +36,27 @@ public abstract class AthenaBakedModelMixin implements DynamicBakedModel, BakedM
* Reuses the emitQuad method to compute the quads to be used by the frame
*
* @param level - the world
- * @param state - the current block camo
+ * @param origin_state - the current block camo
* @param pos - the block position
* @return - the rebakedmodel containing the computed quads
*/
@Override
- public BakedModel computeQuads(BlockRenderView level, BlockState state, BlockPos pos, int theme_index) {
+ public BakedModel computeQuads(@Nullable BlockRenderView level, BlockState origin_state, @Nullable BlockPos pos, int theme_index) {
Map> face_quads = new HashMap<>();
Renderer r = ReFramedClient.HELPER.getFabricRenderer();
QuadEmitter emitter = r.meshBuilder().getEmitter();
WrappedGetter getter = new WrappedGetter(level);
+ BlockState state = level != null && pos != null
+ && level.getBlockEntity(pos) instanceof ThemeableBlockEntity framed_entity
+ ? framed_entity.getTheme(theme_index)
+ : origin_state;
Arrays.stream(Direction.values()).forEach(direction -> {
face_quads.put(direction, new ArrayList<>());
(level == null || pos == null
? model.getDefaultQuads(direction).get(direction)
- : model.getQuads(
- getter,
- level.getBlockEntity(pos) instanceof ThemeableBlockEntity framed_entity
- ? framed_entity.getTheme(theme_index)
- : state, pos, direction)
+ : model.getQuads(getter, state, pos, direction)
).forEach(sprite -> face_quads.computeIfPresent(direction, (d, quads) -> {
Sprite texture = textures.get(sprite.sprite());
if (texture == null) return quads;
@@ -76,6 +77,6 @@ public abstract class AthenaBakedModelMixin implements DynamicBakedModel, BakedM
}));
});
- return new RebakedAthenaModel(face_quads);
+ return new RebakedModel(face_quads);
}
}
diff --git a/src/main/java/fr/adrien1106/reframed/mixin/compat/AthenaWrappedGetterMixin.java b/src/main/java/fr/adrien1106/reframed/mixin/compat/AthenaWrappedGetterMixin.java
index bb31415..3ae0979 100644
--- a/src/main/java/fr/adrien1106/reframed/mixin/compat/AthenaWrappedGetterMixin.java
+++ b/src/main/java/fr/adrien1106/reframed/mixin/compat/AthenaWrappedGetterMixin.java
@@ -13,10 +13,13 @@ import org.spongepowered.asm.mixin.injection.Redirect;
@Mixin(WrappedGetter.class)
public class AthenaWrappedGetterMixin {
- @Redirect(method = "query",
- at = @At(value = "INVOKE", target = "Lnet/minecraft/world/BlockRenderView;" +
- "getBlockState(Lnet/minecraft/util/math/BlockPos;)" +
- "Lnet/minecraft/block/BlockState;"))
+ @Redirect(
+ method = "query",
+ at = @At(
+ value = "INVOKE",
+ target = "Lnet/minecraft/world/BlockRenderView;getBlockState(Lnet/minecraft/util/math/BlockPos;)Lnet/minecraft/block/BlockState;"
+ )
+ ) // TODO better connected textures
private BlockState queryCamoState(BlockRenderView world, BlockPos pos, @Local(argsOnly = true) BlockState reference_state) {
// get Any that will connect or return any other (/!\ isOf is an uncertain check)
if (world.getBlockEntity(pos) instanceof ThemeableBlockEntity framed_entity)
diff --git a/src/main/java/fr/adrien1106/reframed/mixin/compat/ContinuityCTMBakedModelMixin.java b/src/main/java/fr/adrien1106/reframed/mixin/compat/ContinuityCTMBakedModelMixin.java
new file mode 100644
index 0000000..4add74d
--- /dev/null
+++ b/src/main/java/fr/adrien1106/reframed/mixin/compat/ContinuityCTMBakedModelMixin.java
@@ -0,0 +1,94 @@
+package fr.adrien1106.reframed.mixin.compat;
+
+import fr.adrien1106.reframed.client.ReFramedClient;
+import fr.adrien1106.reframed.client.model.DynamicBakedModel;
+import fr.adrien1106.reframed.compat.ICTMQuadTransform;
+import fr.adrien1106.reframed.compat.RebakedModel;
+import fr.adrien1106.reframed.util.blocks.ThemeableBlockEntity;
+import me.pepperbell.continuity.client.config.ContinuityConfig;
+import me.pepperbell.continuity.client.model.CTMBakedModel;
+import me.pepperbell.continuity.client.model.ModelObjectsContainer;
+import me.pepperbell.continuity.client.model.QuadProcessors;
+import me.pepperbell.continuity.client.util.RenderUtil;
+import net.fabricmc.fabric.api.renderer.v1.Renderer;
+import net.fabricmc.fabric.api.renderer.v1.mesh.QuadEmitter;
+import net.fabricmc.fabric.api.renderer.v1.model.ForwardingBakedModel;
+import net.minecraft.block.BlockState;
+import net.minecraft.client.render.model.BakedModel;
+import net.minecraft.client.render.model.BakedQuad;
+import net.minecraft.client.texture.Sprite;
+import net.minecraft.util.math.BlockPos;
+import net.minecraft.util.math.Direction;
+import net.minecraft.util.math.random.Random;
+import net.minecraft.world.BlockRenderView;
+import org.jetbrains.annotations.Nullable;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Shadow;
+
+import java.util.*;
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+@Mixin(CTMBakedModel.class)
+public abstract class ContinuityCTMBakedModelMixin extends ForwardingBakedModel implements DynamicBakedModel {
+
+ @Shadow protected abstract Function getSliceFunc(BlockState state);
+
+ @Override
+ public BakedModel computeQuads(@Nullable BlockRenderView level, BlockState origin_state, @Nullable BlockPos pos, int theme_index) {
+ if (wrapped instanceof DynamicBakedModel wrapped_dynamic) // support wrap of dynamic models
+ return wrapped_dynamic.computeQuads(level, origin_state, pos, theme_index);
+
+
+ ModelObjectsContainer container = ModelObjectsContainer.get();
+ // normally baked model / wrapped or feature disabled or item (i.e. no need to compute quads)
+ if (level == null || pos == null
+ || !ContinuityConfig.INSTANCE.connectedTextures.get()
+ || !container.featureStates.getConnectedTexturesState().isEnabled()
+ ) return wrapped;
+
+ Map> face_quads = new HashMap<>();
+
+ Renderer r = ReFramedClient.HELPER.getFabricRenderer();
+ QuadEmitter emitter = r.meshBuilder().getEmitter();
+
+ // get applicable state
+ BlockState state = level.getBlockEntity(pos) instanceof ThemeableBlockEntity framed_entity
+ ? framed_entity.getTheme(theme_index)
+ : origin_state;
+
+ // get random supplier
+ Random random = Random.create();
+ Supplier random_supplier = () -> {
+ random.setSeed(state.getRenderingSeed(pos));
+ return random;
+ };
+
+ // get quad transform and prepare
+ ICTMQuadTransform transform = ((ICTMQuadTransform) container.ctmQuadTransform);
+ transform.invokePrepare(
+ level,
+ state,
+ pos,
+ random_supplier,
+ ContinuityConfig.INSTANCE.useManualCulling.get(),
+ getSliceFunc(state)
+ );
+ Arrays.stream(Direction.values()).forEach(direction -> {
+ face_quads.put(direction, new ArrayList<>());
+
+ wrapped.getQuads(state, direction, random_supplier.get()).forEach(quad -> face_quads.computeIfPresent(direction, (d, quads) -> {
+ emitter.fromVanilla(quad, emitter.material(), direction);
+ transform.transform(emitter);
+ quads.add(emitter.toBakedQuad(RenderUtil.getSpriteFinder().find(emitter)));
+ return quads;
+ }));
+// transform.getProcessingContext().getExtraQuadEmitter(); // TODO start here for overlay support
+ });
+
+ transform.getProcessingContext().reset(); // reset instead of outputting to emitter
+ transform.invokeReset();
+
+ return new RebakedModel(face_quads);
+ }
+}
diff --git a/src/main/java/fr/adrien1106/reframed/mixin/compat/ContinuityCTMQuadTransformMixin.java b/src/main/java/fr/adrien1106/reframed/mixin/compat/ContinuityCTMQuadTransformMixin.java
new file mode 100644
index 0000000..02ba637
--- /dev/null
+++ b/src/main/java/fr/adrien1106/reframed/mixin/compat/ContinuityCTMQuadTransformMixin.java
@@ -0,0 +1,56 @@
+package fr.adrien1106.reframed.mixin.compat;
+
+import fr.adrien1106.reframed.compat.ICTMQuadTransform;
+import fr.adrien1106.reframed.util.blocks.ThemeableBlockEntity;
+import me.pepperbell.continuity.client.model.CullingCache;
+import me.pepperbell.continuity.client.model.QuadProcessors;
+import me.pepperbell.continuity.impl.client.ProcessingContextImpl;
+import net.fabricmc.fabric.api.renderer.v1.mesh.QuadView;
+import net.minecraft.block.BlockState;
+import net.minecraft.client.texture.Sprite;
+import net.minecraft.util.math.BlockPos;
+import net.minecraft.util.math.random.Random;
+import net.minecraft.world.BlockRenderView;
+import org.spongepowered.asm.mixin.Final;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Shadow;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Redirect;
+
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+@Mixin(targets = "me.pepperbell.continuity.client.model.CTMBakedModel$CTMQuadTransform")
+public abstract class ContinuityCTMQuadTransformMixin implements ICTMQuadTransform {
+ @Shadow(remap = false) @Final protected ProcessingContextImpl processingContext;
+
+ @Shadow public abstract void prepare(BlockRenderView view, BlockState state, BlockPos pos, Supplier random, boolean manual_culling, Function slice);
+ @Shadow(remap = false) public abstract void reset();
+
+ @Redirect(
+ method = "transform",
+ at = @At(
+ value = "INVOKE",
+ target = "Lme/pepperbell/continuity/client/model/CullingCache;shouldCull(Lnet/fabricmc/fabric/api/renderer/v1/mesh/QuadView;Lnet/minecraft/world/BlockRenderView;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/block/BlockState;)Z"
+ )
+ )
+ private boolean camo_replacement(CullingCache cache, QuadView quad, BlockRenderView view, BlockPos pos, BlockState state) {
+ if (view.getBlockEntity(pos) instanceof ThemeableBlockEntity) return false;
+ return cache.shouldCull(quad, view, pos, state);
+ }
+
+ // uses this because invoker did not want to work for some reason
+ public void invokePrepare(BlockRenderView view, BlockState state, BlockPos pos, Supplier random, boolean manual_culling, Function slice) {
+ prepare(view, state, pos, random, manual_culling, slice);
+ }
+
+ @Override
+ public ProcessingContextImpl getProcessingContext() {
+ return processingContext;
+ }
+
+ @Override
+ public void invokeReset() {
+ reset();
+ }
+}
diff --git a/src/main/java/fr/adrien1106/reframed/mixin/compat/ContinuityConnectionPredicateMixin.java b/src/main/java/fr/adrien1106/reframed/mixin/compat/ContinuityConnectionPredicateMixin.java
new file mode 100644
index 0000000..364b885
--- /dev/null
+++ b/src/main/java/fr/adrien1106/reframed/mixin/compat/ContinuityConnectionPredicateMixin.java
@@ -0,0 +1,32 @@
+package fr.adrien1106.reframed.mixin.compat;
+
+import com.llamalad7.mixinextras.sugar.Local;
+import fr.adrien1106.reframed.util.blocks.ThemeableBlockEntity;
+import me.pepperbell.continuity.client.processor.ConnectionPredicate;
+import net.minecraft.block.BlockState;
+import net.minecraft.util.math.BlockPos;
+import net.minecraft.world.BlockRenderView;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Redirect;
+
+@Mixin(ConnectionPredicate.class)
+public interface ContinuityConnectionPredicateMixin {
+
+ @Redirect(
+ method = "shouldConnect(Lnet/minecraft/world/BlockRenderView;Lnet/minecraft/block/BlockState;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/util/math/Direction;Lnet/minecraft/client/texture/Sprite;)Z",
+ at = @At(
+ value = "INVOKE",
+ target = "Lnet/minecraft/world/BlockRenderView;getBlockState(Lnet/minecraft/util/math/BlockPos;)Lnet/minecraft/block/BlockState;"
+ )
+ ) // TODO better connected textures
+ private BlockState getBlockState(BlockRenderView view, BlockPos pos, @Local(argsOnly = true) BlockState state) {
+ if (!(view.getBlockEntity(pos) instanceof ThemeableBlockEntity frame_entity)) return view.getBlockState(pos);
+ return frame_entity.getThemes()
+ .stream()
+ .filter(theme -> theme.getBlock() == state.getBlock())
+ .findFirst()
+ .orElse(frame_entity.getTheme(0));
+ }
+
+}
diff --git a/src/main/java/fr/adrien1106/reframed/mixin/compat/ContinuityModelWrappingHandlerMixin.java b/src/main/java/fr/adrien1106/reframed/mixin/compat/ContinuityModelWrappingHandlerMixin.java
new file mode 100644
index 0000000..8e7791b
--- /dev/null
+++ b/src/main/java/fr/adrien1106/reframed/mixin/compat/ContinuityModelWrappingHandlerMixin.java
@@ -0,0 +1,29 @@
+package fr.adrien1106.reframed.mixin.compat;
+
+import fr.adrien1106.reframed.block.ReFramedBlock;
+import me.pepperbell.continuity.client.resource.ModelWrappingHandler;
+import net.minecraft.block.Block;
+import net.minecraft.registry.DefaultedRegistry;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Redirect;
+
+import java.util.Iterator;
+
+@Mixin(ModelWrappingHandler.class)
+public class ContinuityModelWrappingHandlerMixin {
+
+ @Redirect(
+ method = "createBlockStateModelIdMap",
+ at = @At(
+ value = "INVOKE",
+ target = "Lnet/minecraft/registry/DefaultedRegistry;iterator()Ljava/util/Iterator;"
+ )
+ )
+ private static Iterator filterFrames(DefaultedRegistry registry) {
+ return registry
+ .stream()
+ .filter(block -> !(block instanceof ReFramedBlock))
+ .iterator();
+ }
+}
diff --git a/src/main/java/fr/adrien1106/reframed/mixin/compat/IndiumAbstractBlockRenderContextMixin.java b/src/main/java/fr/adrien1106/reframed/mixin/compat/IndiumAbstractBlockRenderContextMixin.java
index e9d2f43..4490579 100644
--- a/src/main/java/fr/adrien1106/reframed/mixin/compat/IndiumAbstractBlockRenderContextMixin.java
+++ b/src/main/java/fr/adrien1106/reframed/mixin/compat/IndiumAbstractBlockRenderContextMixin.java
@@ -1,7 +1,7 @@
package fr.adrien1106.reframed.mixin.compat;
import com.llamalad7.mixinextras.sugar.Local;
-import fr.adrien1106.reframed.util.blocks.BlockHelper;
+import fr.adrien1106.reframed.client.util.RenderHelper;
import fr.adrien1106.reframed.util.mixin.IBlockRenderInfoMixin;
import link.infra.indium.renderer.mesh.MutableQuadViewImpl;
import link.infra.indium.renderer.render.AbstractBlockRenderContext;
@@ -24,6 +24,6 @@ public abstract class IndiumAbstractBlockRenderContextMixin {
private boolean shouldDrawInnerQuad(AbstractBlockRenderContext instance, Direction face, @Local(argsOnly = true) MutableQuadViewImpl quad) {
if (face != null || quad.tag() == 0 || !(blockInfo instanceof IBlockRenderInfoMixin info) || info.getThemeIndex() == 0) return isFaceCulled(face);
- return !BlockHelper.shouldDrawInnerFace(blockInfo.blockState, blockInfo.blockView, blockInfo.blockPos, quad.tag() >>> 8, info.getThemeIndex());
+ return !RenderHelper.shouldDrawInnerFace(blockInfo.blockState, blockInfo.blockView, blockInfo.blockPos, quad.tag() >>> 8, info.getThemeIndex());
}
}
diff --git a/src/main/java/fr/adrien1106/reframed/mixin/compat/IndiumTerrainBlockRenderInfoMixin.java b/src/main/java/fr/adrien1106/reframed/mixin/compat/IndiumTerrainBlockRenderInfoMixin.java
index 1fef1c3..2e95fde 100644
--- a/src/main/java/fr/adrien1106/reframed/mixin/compat/IndiumTerrainBlockRenderInfoMixin.java
+++ b/src/main/java/fr/adrien1106/reframed/mixin/compat/IndiumTerrainBlockRenderInfoMixin.java
@@ -1,6 +1,6 @@
package fr.adrien1106.reframed.mixin.compat;
-import fr.adrien1106.reframed.util.blocks.BlockHelper;
+import fr.adrien1106.reframed.client.util.RenderHelper;
import fr.adrien1106.reframed.util.mixin.IBlockRenderInfoMixin;
import fr.adrien1106.reframed.util.blocks.ThemeableBlockEntity;
import link.infra.indium.renderer.render.BlockRenderInfo;
@@ -32,7 +32,7 @@ public abstract class IndiumTerrainBlockRenderInfoMixin extends BlockRenderInfo
if (!(view.getBlockEntity(pos) instanceof ThemeableBlockEntity
|| view.getBlockEntity(other_pos) instanceof ThemeableBlockEntity))
return instance.shouldDrawSide(state, view, pos, face);
- return BlockHelper.shouldDrawSide(state, view, pos, face, other_pos, theme_index);
+ return RenderHelper.shouldDrawSide(state, view, pos, face, other_pos, theme_index);
}
@Override
diff --git a/src/main/java/fr/adrien1106/reframed/mixin/compat/IndiumTerrainRenderContextMixin.java b/src/main/java/fr/adrien1106/reframed/mixin/compat/IndiumTerrainRenderContextMixin.java
index 42c5534..5855735 100644
--- a/src/main/java/fr/adrien1106/reframed/mixin/compat/IndiumTerrainRenderContextMixin.java
+++ b/src/main/java/fr/adrien1106/reframed/mixin/compat/IndiumTerrainRenderContextMixin.java
@@ -1,7 +1,7 @@
package fr.adrien1106.reframed.mixin.compat;
import fr.adrien1106.reframed.client.model.MultiRetexturableModel;
-import fr.adrien1106.reframed.util.blocks.BlockHelper;
+import fr.adrien1106.reframed.client.util.RenderHelper;
import fr.adrien1106.reframed.util.mixin.IBlockRenderInfoMixin;
import fr.adrien1106.reframed.util.mixin.IMultipartBakedModelMixin;
import link.infra.indium.renderer.render.AbstractBlockRenderContext;
@@ -32,7 +32,7 @@ public abstract class IndiumTerrainRenderContextMixin extends AbstractBlockRende
|| !(wrapped.getModel(ctx.state()) instanceof MultiRetexturableModel retexturing_model)) return;
List models = retexturing_model.models();
- BlockHelper.computeInnerCull(ctx.state(), models);
+ RenderHelper.computeInnerCull(ctx.state(), models);
int i = 0;
for (BakedModel model : models) {
i++;
diff --git a/src/main/java/fr/adrien1106/reframed/mixin/compat/SodiumBlockOcclusionCacheMixin.java b/src/main/java/fr/adrien1106/reframed/mixin/compat/SodiumBlockOcclusionCacheMixin.java
index 3e6c28d..9d622a1 100644
--- a/src/main/java/fr/adrien1106/reframed/mixin/compat/SodiumBlockOcclusionCacheMixin.java
+++ b/src/main/java/fr/adrien1106/reframed/mixin/compat/SodiumBlockOcclusionCacheMixin.java
@@ -1,7 +1,7 @@
package fr.adrien1106.reframed.mixin.compat;
import com.llamalad7.mixinextras.sugar.Local;
-import fr.adrien1106.reframed.util.blocks.BlockHelper;
+import fr.adrien1106.reframed.client.util.RenderHelper;
import fr.adrien1106.reframed.util.blocks.ThemeableBlockEntity;
import me.jellysquid.mods.sodium.client.render.chunk.compile.pipeline.BlockOcclusionCache;
import net.minecraft.block.BlockState;
@@ -19,12 +19,12 @@ public class SodiumBlockOcclusionCacheMixin {
@Inject(
method = "shouldDrawSide",
at = @At(
- value = "INVOKE_ASSIGN",
- target = "Lnet/minecraft/world/BlockView;getBlockState(Lnet/minecraft/util/math/BlockPos;)Lnet/minecraft/block/BlockState;",
+ value = "INVOKE",
+ target = "Lnet/minecraft/util/math/BlockPos$Mutable;set(III)Lnet/minecraft/util/math/BlockPos$Mutable;",
shift = At.Shift.AFTER
), cancellable = true)
private void shouldDrawFrameNeighborSide(BlockState self_state, BlockView view, BlockPos self_pos, Direction face, CallbackInfoReturnable cir, @Local BlockPos.Mutable other_pos) {
if (!(view.getBlockEntity(other_pos) instanceof ThemeableBlockEntity)) return;
- cir.setReturnValue(BlockHelper.shouldDrawSide(self_state, view, self_pos, face, other_pos, 0));
+ cir.setReturnValue(RenderHelper.shouldDrawSide(self_state, view, self_pos, face, other_pos, 0));
}
}
diff --git a/src/main/java/fr/adrien1106/reframed/mixin/render/AbstractBlockRenderContextMixin.java b/src/main/java/fr/adrien1106/reframed/mixin/render/AbstractBlockRenderContextMixin.java
index 6d02c28..388ea4a 100644
--- a/src/main/java/fr/adrien1106/reframed/mixin/render/AbstractBlockRenderContextMixin.java
+++ b/src/main/java/fr/adrien1106/reframed/mixin/render/AbstractBlockRenderContextMixin.java
@@ -1,7 +1,7 @@
package fr.adrien1106.reframed.mixin.render;
import com.llamalad7.mixinextras.sugar.Local;
-import fr.adrien1106.reframed.util.blocks.BlockHelper;
+import fr.adrien1106.reframed.client.util.RenderHelper;
import fr.adrien1106.reframed.util.mixin.IBlockRenderInfoMixin;
import net.fabricmc.fabric.impl.client.indigo.renderer.mesh.MutableQuadViewImpl;
import net.fabricmc.fabric.impl.client.indigo.renderer.render.AbstractBlockRenderContext;
@@ -31,6 +31,6 @@ public abstract class AbstractBlockRenderContextMixin {
private boolean shouldDrawInnerQuad(AbstractBlockRenderContext instance, Direction face, @Local(argsOnly = true) MutableQuadViewImpl quad) {
if (face != null || quad.tag() == 0 || !(blockInfo instanceof IBlockRenderInfoMixin info) || info.getThemeIndex() == 0) return isFaceCulled(face);
- return !BlockHelper.shouldDrawInnerFace(blockInfo.blockState, blockInfo.blockView, blockInfo.blockPos, quad.tag() >>> 8, info.getThemeIndex());
+ return !RenderHelper.shouldDrawInnerFace(blockInfo.blockState, blockInfo.blockView, blockInfo.blockPos, quad.tag() >>> 8, info.getThemeIndex());
}
}
diff --git a/src/main/java/fr/adrien1106/reframed/mixin/render/BlockRenderInfoMixin.java b/src/main/java/fr/adrien1106/reframed/mixin/render/BlockRenderInfoMixin.java
index 9a5485c..122e62a 100644
--- a/src/main/java/fr/adrien1106/reframed/mixin/render/BlockRenderInfoMixin.java
+++ b/src/main/java/fr/adrien1106/reframed/mixin/render/BlockRenderInfoMixin.java
@@ -1,7 +1,7 @@
package fr.adrien1106.reframed.mixin.render;
import com.llamalad7.mixinextras.sugar.Local;
-import fr.adrien1106.reframed.util.blocks.BlockHelper;
+import fr.adrien1106.reframed.client.util.RenderHelper;
import fr.adrien1106.reframed.util.mixin.IBlockRenderInfoMixin;
import fr.adrien1106.reframed.util.blocks.ThemeableBlockEntity;
import net.fabricmc.fabric.impl.client.indigo.renderer.render.BlockRenderInfo;
@@ -37,7 +37,7 @@ public abstract class BlockRenderInfoMixin implements IBlockRenderInfoMixin {
@Redirect(method = "shouldDrawFace", at = @At(value = "INVOKE", target = "Lnet/minecraft/block/Block;shouldDrawSide(Lnet/minecraft/block/BlockState;Lnet/minecraft/world/BlockView;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/util/math/Direction;Lnet/minecraft/util/math/BlockPos;)Z"))
private boolean shouldDrawAdjacentCamoSide(BlockState state, BlockView world, BlockPos pos, Direction side, BlockPos other_pos) {
- return BlockHelper.shouldDrawSide(state, world, pos, side, other_pos, theme_index);
+ return RenderHelper.shouldDrawSide(state, world, pos, side, other_pos, theme_index);
}
@Override
diff --git a/src/main/java/fr/adrien1106/reframed/mixin/render/TerrainRenderContextMixin.java b/src/main/java/fr/adrien1106/reframed/mixin/render/TerrainRenderContextMixin.java
index ddb93ef..41e1813 100644
--- a/src/main/java/fr/adrien1106/reframed/mixin/render/TerrainRenderContextMixin.java
+++ b/src/main/java/fr/adrien1106/reframed/mixin/render/TerrainRenderContextMixin.java
@@ -1,7 +1,7 @@
package fr.adrien1106.reframed.mixin.render;
import fr.adrien1106.reframed.client.model.MultiRetexturableModel;
-import fr.adrien1106.reframed.util.blocks.BlockHelper;
+import fr.adrien1106.reframed.client.util.RenderHelper;
import fr.adrien1106.reframed.util.mixin.IBlockRenderInfoMixin;
import fr.adrien1106.reframed.util.mixin.IMultipartBakedModelMixin;
import net.fabricmc.fabric.api.renderer.v1.model.ForwardingBakedModel;
@@ -31,7 +31,7 @@ public abstract class TerrainRenderContextMixin extends AbstractBlockRenderConte
|| !(wrapped.getModel(state) instanceof MultiRetexturableModel retexturing_model)) return;
List models = retexturing_model.models();
- BlockHelper.computeInnerCull(state, models);
+ RenderHelper.computeInnerCull(state, models);
int i = 0;
for (BakedModel model : models) {
i++;
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..cef30ab 100644
--- a/src/main/java/fr/adrien1106/reframed/util/blocks/BlockHelper.java
+++ b/src/main/java/fr/adrien1106/reframed/util/blocks/BlockHelper.java
@@ -1,19 +1,8 @@
package fr.adrien1106.reframed.util.blocks;
-import com.google.common.cache.Cache;
-import com.google.common.cache.CacheBuilder;
-import fr.adrien1106.reframed.block.ReFramedBlock;
import fr.adrien1106.reframed.block.ReFramedEntity;
import fr.adrien1106.reframed.block.ReFramedStairBlock;
import fr.adrien1106.reframed.block.ReFramedStairsCubeBlock;
-import fr.adrien1106.reframed.client.ReFramedClient;
-import fr.adrien1106.reframed.client.model.QuadPosBounds;
-import net.fabricmc.api.EnvType;
-import net.fabricmc.api.Environment;
-import net.fabricmc.fabric.api.renderer.v1.Renderer;
-import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial;
-import net.fabricmc.fabric.api.renderer.v1.mesh.QuadEmitter;
-import net.fabricmc.fabric.api.renderer.v1.model.ForwardingBakedModel;
import net.minecraft.block.Block;
import net.minecraft.block.BlockEntityProvider;
import net.minecraft.block.BlockState;
@@ -25,33 +14,27 @@ import net.minecraft.sound.SoundEvents;
import net.minecraft.util.ActionResult;
import net.minecraft.util.Hand;
import net.minecraft.util.Pair;
-import net.minecraft.util.function.BooleanBiFunction;
import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.util.math.Vec3d;
-import net.minecraft.util.math.random.Random;
import net.minecraft.util.shape.VoxelShape;
-import net.minecraft.util.shape.VoxelShapes;
-import net.minecraft.world.BlockRenderView;
import net.minecraft.world.BlockView;
import net.minecraft.world.World;
-import java.util.*;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static fr.adrien1106.reframed.util.blocks.BlockProperties.EDGE;
import static fr.adrien1106.reframed.util.blocks.BlockProperties.LIGHT;
import static fr.adrien1106.reframed.util.blocks.StairShape.*;
-import static net.minecraft.util.shape.VoxelShapes.combine;
public class BlockHelper {
- // self culling cache of the models not made thread local so that it is only computed once
- private static final Cache INNER_CULL_MAP = CacheBuilder.newBuilder().maximumSize(1024).build();
- private record CullElement(Block block, Object state_key, int model) {}
-
public static Corner getPlacementCorner(ItemPlacementContext ctx) {
Direction side = ctx.getSide().getOpposite();
Vec3d pos = getHitPos(ctx.getHitPos(), ctx.getBlockPos());
@@ -167,8 +150,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));
@@ -232,148 +215,6 @@ public class BlockHelper {
return ActionResult.PASS;
}
- /**
- * compute which quad might cull with another model quad
- * @param state - the state of the model
- * @param models - list of models on the same block
- */
- @Environment(EnvType.CLIENT)
- public static void computeInnerCull(BlockState state, List models) {
- if (!(state.getBlock() instanceof ReFramedBlock frame_block)) return;
- Object key = frame_block.getModelCacheKey(state);
- if (INNER_CULL_MAP.asMap().containsKey(new CullElement(frame_block, key, 1))) return;
-
- Renderer r = ReFramedClient.HELPER.getFabricRenderer();
- QuadEmitter quad_emitter = r.meshBuilder().getEmitter();
- RenderMaterial material = r.materialFinder().clear().find();
- Random random = Random.create();
-
- List> model_bounds = models.stream()
- .map(ForwardingBakedModel::getWrappedModel)
- .filter(Objects::nonNull)
- .map(wrapped -> wrapped.getQuads(state, null, random))
- .map(quads -> quads.stream().map(quad -> {
- quad_emitter.fromVanilla(quad, material, null);
- return QuadPosBounds.read(quad_emitter, false);
- }).toList()).toList();
-
- Integer[] cull_array;
- for(int self_id = 1; self_id <= model_bounds.size(); self_id++) {
- List self_bounds = model_bounds.get(self_id - 1);
- cull_array = new Integer[self_bounds.size()];
- for (int self_quad = 0; self_quad < cull_array.length; self_quad++) {
- QuadPosBounds self_bound = self_bounds.get(self_quad);
- for(int other_id = 1; other_id <= model_bounds.size(); other_id++) {
- if (other_id == self_id) continue;
- if (model_bounds.get(other_id - 1).stream().anyMatch(other_bound -> other_bound.equals(self_bound))) {
- cull_array[self_quad] = other_id;
- break;
- }
- }
- }
- INNER_CULL_MAP.put(new CullElement(frame_block, key, self_id), cull_array);
- }
- }
-
- @Environment(EnvType.CLIENT)
- public static boolean shouldDrawInnerFace(BlockState state, BlockRenderView view, BlockPos pos, int quad_index, int theme_index) {
- if ( !(state.getBlock() instanceof ReFramedBlock frame_block)
- || !(view.getBlockEntity(pos) instanceof ThemeableBlockEntity frame_entity)
- ) return true;
- CullElement key = new CullElement(frame_block, frame_block.getModelCacheKey(state), theme_index);
- if (!INNER_CULL_MAP.asMap().containsKey(key)) return true;
-
- // needs to be Integer object because array is initialized with null not 0
- Integer cull_theme = Objects.requireNonNull(INNER_CULL_MAP.getIfPresent(key))[quad_index];
- if (cull_theme == null) return true; // no culling possible
-
- BlockState self_theme = frame_entity.getTheme(theme_index);
- BlockState other_theme = frame_entity.getTheme(cull_theme);
-
- if (self_theme.isSideInvisible(other_theme, null)) return false;
- return !self_theme.isOpaque() || !other_theme.isOpaque();
- }
-
- // Doing this method from scratch as it is simpler to do than injecting everywhere
- @Environment(EnvType.CLIENT)
- public static boolean shouldDrawSide(BlockState self_state, BlockView world, BlockPos pos, Direction side, BlockPos other_pos, int theme_index) {
- ThemeableBlockEntity self = world.getBlockEntity(pos) instanceof ThemeableBlockEntity e ? e : null;
- ThemeableBlockEntity other = world.getBlockEntity(other_pos) instanceof ThemeableBlockEntity e ? e : null;
- BlockState other_state = world.getBlockState(other_pos);
-
- // normal behaviour
- if (self == null && other == null) return Block.shouldDrawSide(self_state, world, pos, side, other_pos);
-
- // self is a normal Block
- if (self == null && other_state.getBlock() instanceof ReFramedBlock other_block) {
- VoxelShape self_shape = self_state.getCullingShape(world, pos);
- if (self_shape.isEmpty()) return true;
-
- int i = 0;
- VoxelShape other_shape = VoxelShapes.empty();
- for (BlockState s: other.getThemes()) {
- i++;
- if (self_state.isSideInvisible(s, side) || s.isOpaque())
- other_shape = combine(
- other_shape,
- other_block
- .getShape(other_state, i)
- .getFace(side.getOpposite()),
- BooleanBiFunction.OR
- );
- }
-
- // determine if side needs to be rendered
- return VoxelShapes.matchesAnywhere(self_shape, other_shape, BooleanBiFunction.ONLY_FIRST);
- }
-
- BlockState self_theme = self.getTheme(theme_index);
- // other is normal Block
- if (other == null && self_state.getBlock() instanceof ReFramedBlock self_block) {
- // Transparent is simple if self and the neighbor are invisible don't render side (like default)
- if (self_theme.isSideInvisible(other_state, side)) return false;
-
- // Opaque is also simple as each model are rendered one by one
- if (other_state.isOpaque()) {
- // no cache section :( because it differs between each instance of the frame
- VoxelShape self_shape = self_block.getShape(self_state, theme_index).getFace(side);
- if (self_shape.isEmpty()) return true;
- VoxelShape other_shape = other_state.getCullingFace(world, other_pos, side.getOpposite());
-
- // determine if side needs to be rendered
- return VoxelShapes.matchesAnywhere(self_shape, other_shape, BooleanBiFunction.ONLY_FIRST);
- }
-
- return true;
- }
-
- // Both are frames
- // here both are computed in the same zone as there will necessarily a shape comparison
- if (self_state.getBlock() instanceof ReFramedBlock self_block && other_state.getBlock() instanceof ReFramedBlock other_block) {
- VoxelShape self_shape = self_block.getShape(self_state, theme_index).getFace(side);
- if (self_shape.isEmpty()) return true;
-
- int i = 0;
- VoxelShape other_shape = VoxelShapes.empty();
- for (BlockState s: other.getThemes()) {
- i++;
- if (self_theme.isSideInvisible(s, side) || s.isOpaque())
- other_shape = combine(
- other_shape,
- other_block
- .getShape(other_state, i)
- .getFace(side.getOpposite()),
- BooleanBiFunction.OR
- );
- }
-
- // determine if side needs to be rendered
- return VoxelShapes.matchesAnywhere(self_shape, other_shape, BooleanBiFunction.ONLY_FIRST);
- }
-
- return true;
- }
-
public static boolean cursorMatchesFace(VoxelShape shape, Vec3d pos) {
Map axes = Arrays.stream(Direction.Axis.values())
.collect(Collectors.toMap(
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 0000000..2e15ac4
Binary files /dev/null and b/src/main/resources/assets/reframed/textures/item/blueprint.png differ
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 0000000..1d57634
Binary files /dev/null and b/src/main/resources/assets/reframed/textures/item/blueprint_written.png differ
diff --git a/src/main/resources/assets/reframed/textures/item/hammer.png b/src/main/resources/assets/reframed/textures/item/hammer.png
new file mode 100644
index 0000000..8687e8e
Binary files /dev/null and b/src/main/resources/assets/reframed/textures/item/hammer.png differ
diff --git a/src/main/resources/assets/reframed/textures/item/screwdriver.png b/src/main/resources/assets/reframed/textures/item/screwdriver.png
new file mode 100644
index 0000000..85c40bf
Binary files /dev/null and b/src/main/resources/assets/reframed/textures/item/screwdriver.png differ
diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json
index ce39b5b..a7e1264 100755
--- a/src/main/resources/fabric.mod.json
+++ b/src/main/resources/fabric.mod.json
@@ -8,7 +8,7 @@
"Adrien1106"
],
"contact": {},
- "license": "MIT",
+ "license": "MIT AND LGPL-3.0",
"icon": "assets/reframed-icon.png",
"environment": "*",
"entrypoints": {
@@ -33,6 +33,7 @@
"suggests": {
"athena": "^${athena_version}",
"sodium": "^${sodium_version}",
- "indium": "^${indium_version}"
+ "indium": "^${indium_version}",
+ "continuity": "^${continuity_version}"
}
}
\ No newline at end of file
diff --git a/src/main/resources/reframed.mixins.json b/src/main/resources/reframed.mixins.json
index 059e59b..de74135 100644
--- a/src/main/resources/reframed.mixins.json
+++ b/src/main/resources/reframed.mixins.json
@@ -14,6 +14,10 @@
"compat.AthenaBakedModelMixin",
"compat.AthenaConnectedBlockModelMixin",
"compat.AthenaWrappedGetterMixin",
+ "compat.ContinuityConnectionPredicateMixin",
+ "compat.ContinuityCTMBakedModelMixin",
+ "compat.ContinuityCTMQuadTransformMixin",
+ "compat.ContinuityModelWrappingHandlerMixin",
"compat.IndiumAbstractBlockRenderContextMixin",
"compat.IndiumTerrainBlockRenderInfoMixin",
"compat.IndiumTerrainRenderContextMixin",